1 前言

​ 光照元素主要有环境光(ambient)、漫反射光(diffuse)、镜面反射光(specular),光照模型主要有冯氏模型Blinn 改进的冯氏模型,两者区别在与镜面反射光的计算,冯氏模型根据反向量和观察向量计算镜面反射光,Blinn 改进的冯氏模型根据半向量和法向量计算镜面反射光。

​ 模型合成颜色:finalColor = (ambient + diffuse + specular) · modelColor

  • 环境光:ambient = ambientStrength · ambientColor
  • 漫反射光:diffuse = cos(α)· diffuseStrength · lightColor
  • 镜面反射光:specular = pow(cos(β), μ)· specularStrength · lightColor

​ 读者如果对 OpenGL ES 不太熟悉,请回顾以下内容:

​ 项目目录如下:

2 案例

​ MainActivity.java

package com.zhyan8.light.activity;

import android.opengl.GLSurfaceView;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.zhyan8.light.opengl.MyGLSurfaceView;
import com.zhyan8.light.opengl.MyRender; public class MainActivity extends AppCompatActivity {
private GLSurfaceView mGlSurfaceView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGlSurfaceView = new MyGLSurfaceView(this);
setContentView(mGlSurfaceView);
mGlSurfaceView.setRenderer(new MyRender(getResources()));
} @Override
protected void onResume() {
super.onResume();
mGlSurfaceView.onResume();
} @Override
protected void onPause() {
super.onPause();
mGlSurfaceView.onPause();
}
}

​ MyGLSurfaceView.java

package com.zhyan8.light.opengl;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet; public class MyGLSurfaceView extends GLSurfaceView {
public MyGLSurfaceView(Context context) {
super(context);
setEGLContextClientVersion(3);
} public MyGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(3);
}
}

​ MyRender.java

package com.zhyan8.light.opengl;

import android.content.res.Resources;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import com.zhyan8.light.model.Model;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10; public class MyRender implements GLSurfaceView.Renderer {
private Model mModel; public MyRender(Resources resources) {
mModel = new Model(resources);
} @Override
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
//设置背景颜色
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//启动深度测试
GLES30.glEnable(GLES30.GL_DEPTH_TEST);
//创建程序id
mModel.onModelCreate();
} @Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置视图窗口
GLES30.glViewport(0, 0, width, height);
mModel.onModelChange(width, height);
} @Override
public void onDrawFrame(GL10 gl) {
//将颜色缓冲区设置为预设的颜色
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
//启用顶点的数组句柄
GLES30.glEnableVertexAttribArray(0);
GLES30.glEnableVertexAttribArray(1);
//绘制模型
mModel.onModelDraw();
//禁止顶点数组句柄
GLES30.glDisableVertexAttribArray(0);
GLES30.glDisableVertexAttribArray(1);
}
}

​ Model.java

package com.zhyan8.light.model;

import android.content.res.Resources;
import android.opengl.GLES30;
import com.zhyan8.light.R;
import com.zhyan8.light.utils.ArraysUtils;
import com.zhyan8.light.utils.ShaderUtils;
import java.nio.FloatBuffer; public class Model {
private static final float BALL_RADIUS = 0.5f; // 球半径
private static final int RING_NUM = 400; // 环数(纬度)
private static final int RAW_NUM = 200; // 射线数(经度)
private static final float RING_WIDTH = (float) (Math.PI / RING_NUM); // 环宽度(维度刻度)
private static final float RAW_GAP_ANGLE = (float) (2 * Math.PI / RAW_NUM); // 两条射线间最小夹角(经度刻度)
private static final int VERTEX_DIMENSION = 3; // 顶点坐标维度
private Resources mResources;
private MyTransform mTransform;
private Light mLight;
private float[][] mVertices;
private FloatBuffer[] mVerticesBuffers;
private FloatBuffer[] mNormsBuffers;
private int mProgramId;
private int mPointNumPerRing; public Model(Resources resources) {
mResources = resources;
mPointNumPerRing = (RAW_NUM + 1) * 2;
mVertices = new float[RING_NUM][mPointNumPerRing * VERTEX_DIMENSION];
mVerticesBuffers = new FloatBuffer[RING_NUM];
mNormsBuffers = new FloatBuffer[RING_NUM];
mTransform = new MyTransform();
mLight = new Light();
} // 模型创建
public void onModelCreate() {
computeVertexAndNorm();
mProgramId = ShaderUtils.createProgram(mResources, R.raw.vertex_shader, R.raw.fragment_shader);
mLight.onLightCreate(mProgramId);
mTransform.onTransformCreate(mProgramId);
} // 模型参数变化
public void onModelChange(int width, int height) {
mTransform.onTransformChange(width, height);
} // 模型绘制
public void onModelDraw() {
GLES30.glUseProgram(mProgramId);
mLight.openLight();
mTransform.onTransformExecute();
for (int i = 0; i < RING_NUM; i++) { // 一环一环绘制纹理
//准备顶点坐标和纹理坐标
GLES30.glVertexAttribPointer(0, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mVerticesBuffers[i]);
GLES30.glVertexAttribPointer(1, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mNormsBuffers[i]);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mPointNumPerRing);
}
} // 计算顶点坐标与法线坐标
private void computeVertexAndNorm() {
for (int i = 0; i < RING_NUM; i++) {
getRingVertex(i);
mVerticesBuffers[i] = ArraysUtils.getFloatBuffer(mVertices[i]);
mNormsBuffers[i] = ArraysUtils.getFloatBuffer(mVertices[i]);
}
} // 计算顶点坐标
private void getRingVertex(int ring) {
float phi1 = ring * RING_WIDTH;
float phi2 = phi1 + RING_WIDTH;
float theta = 0f;
int index = 0;
for (int i = 0; i <= RAW_NUM; i++) {
mVertices[ring][index++] = (float) (BALL_RADIUS * Math.sin(phi1) * Math.cos(theta));
mVertices[ring][index++] = (float) (BALL_RADIUS * Math.sin(phi1) * Math.sin(theta));
mVertices[ring][index++] = (float) (BALL_RADIUS * Math.cos(phi1));
mVertices[ring][index++] = (float) (BALL_RADIUS * Math.sin(phi2) * Math.cos(theta));
mVertices[ring][index++] = (float) (BALL_RADIUS * Math.sin(phi2) * Math.sin(theta));
mVertices[ring][index++] = (float) (BALL_RADIUS * Math.cos(phi2));
theta += RAW_GAP_ANGLE;
}
}
}

​ Light.java

package com.zhyan8.light.model;

import android.opengl.GLES30;

public class Light {
private int mProgramId;
private int mLightPosHandle;
private int mModelColorHandle;
private int mAmbientLightColorHandle;
private int mLightColorHandle;
private int mMaterialHandle;
private float[] mLightPos = new float[] {0f, 2f, 0f};
private float[] mModelColor = new float[] {0.8f, 0.3f, 0.2f, 1.0f}; // 模型颜色(红色)
private float[] mAmbientLightColor = new float[] {0.7f, 0.7f, 0.7f}; // 环境光颜色(白光)
private float[] mLightColor = new float[] {1f, 1f, 1f, 1.0f}; // 光源颜色(白光)
private float[] mMaterial = new float[] {0.2f, 0.9f, 0.6f}; // 材质对环境光、漫反射光、镜面光的反射系数 public void onLightCreate(int programId) {
mProgramId = programId;
mLightPosHandle = GLES30.glGetUniformLocation(mProgramId, "uLightPos");
mModelColorHandle = GLES30.glGetUniformLocation(mProgramId, "uModelColor");
mAmbientLightColorHandle = GLES30.glGetUniformLocation(mProgramId, "uAmbientLightColor");
mLightColorHandle = GLES30.glGetUniformLocation(mProgramId, "uLightColor");
mMaterialHandle = GLES30.glGetUniformLocation(mProgramId, "uMaterial");
} public void openLight() {
// 光源位置
GLES30.glUniform3f(mLightPosHandle, mLightPos[0], mLightPos[1], mLightPos[2]);
// 模型颜色
GLES30.glUniform4f(mModelColorHandle, mModelColor[0], mModelColor[1], mModelColor[2], mModelColor[3]);
// 环境光颜色
GLES30.glUniform3f(mAmbientLightColorHandle, mAmbientLightColor[0], mAmbientLightColor[1], mAmbientLightColor[2]);
// 光源颜色
GLES30.glUniform3f(mLightColorHandle, mLightColor[0], mLightColor[1], mLightColor[2]);
// 设置材质系数(材质对环境光、漫反射光、镜面光的反射系数)
GLES30.glUniform3f(mMaterialHandle, mMaterial[0], mMaterial[1], mMaterial[2]);
}
}

​ MyTransform.java

package com.zhyan8.light.model;

import android.opengl.GLES30;
import android.opengl.Matrix; public class MyTransform {
private int mProgramId;
private float mViewportRatio;
private int mViewPosHandle;
private int mModelMatrixHandle;
private int mMvpMatrixHandle;
private float[] mViewPos = new float[] {0.0f, 0.0f, 6.0f}; // 相机位置
private float[] mModelMatrix;
private float[] mViewMatrix;
private float[] mProjectionMatrix;
private float[] mMvpMatrix;
private float mTheta = 0;
private float mThetaGap = 0.03f;
private float mRadius = 1f;
private float[] mTranslate = new float[] {0f, 0f, 0f}; // 变换创建
public void onTransformCreate(int programId) {
mProgramId = programId;
mViewPosHandle = GLES30.glGetUniformLocation(mProgramId, "uViewPos");
mModelMatrixHandle = GLES30.glGetUniformLocation(mProgramId, "modelMatrix");
mMvpMatrixHandle = GLES30.glGetUniformLocation(mProgramId, "mvpMatrix");
mViewMatrix = getIdentityMatrix(16, 0);
mMvpMatrix = getIdentityMatrix(16, 0);
Matrix.setLookAtM(mViewMatrix, 0, mViewPos[0], mViewPos[1], mViewPos[2], 0, 0, 0, 0, 1, 0);
} // 变换参数变换
public void onTransformChange(int width, int height) {
mViewportRatio = 1.0f * width / height;
mProjectionMatrix = getIdentityMatrix(16, 0);
Matrix.frustumM(mProjectionMatrix, 0, -mViewportRatio, mViewportRatio, -1, 1, 3, 10);
} // 变换执行
public void onTransformExecute() {
mModelMatrix = getIdentityMatrix(16, 0);
mTheta = mTheta > 360 ? mTheta - 360 + mThetaGap : mTheta + mThetaGap;
mTranslate[0] = (float) (mRadius * Math.cos(mTheta));
mTranslate[2] = (float) (mRadius * Math.sin(mTheta));
Matrix.translateM(mModelMatrix, 0, mTranslate[0], mTranslate[1], mTranslate[2]);
// 计算MVP变换矩阵: mvpMatrix = projectionMatrix * viewMatrix * modelMatrix
float[] tempMatrix = new float[16];
Matrix.multiplyMM(tempMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
Matrix.multiplyMM(mMvpMatrix, 0, mProjectionMatrix, 0, tempMatrix, 0);
GLES30.glUniformMatrix4fv(mModelMatrixHandle, 1, false, mModelMatrix, 0);
GLES30.glUniformMatrix4fv(mMvpMatrixHandle, 1, false, mMvpMatrix, 0);
GLES30.glUniform3f(mViewPosHandle, mViewPos[0], mViewPos[1], mViewPos[2]);
} private float[] getIdentityMatrix(int size, int offset) {
float[] matrix = new float[size];
Matrix.setIdentityM(matrix, offset);
return matrix;
}
}

​ ShaderUtils.java

package com.zhyan8.light.utils;

import android.content.res.Resources;
import android.opengl.GLES30;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader; public class ShaderUtils {
//创建程序id
public static int createProgram(Resources resources, int vertexShaderResId, int fragmentShaderResId) {
final int vertexShaderId = compileShader(resources, GLES30.GL_VERTEX_SHADER, vertexShaderResId);
final int fragmentShaderId = compileShader(resources, GLES30.GL_FRAGMENT_SHADER, fragmentShaderResId);
return linkProgram(vertexShaderId, fragmentShaderId);
} //通过外部资源编译着色器
private static int compileShader(Resources resources, int type, int shaderId){
String shaderCode = readShaderFromResource(resources, shaderId);
return compileShader(type, shaderCode);
} //通过代码片段编译着色器
private static int compileShader(int type, String shaderCode){
int shader = GLES30.glCreateShader(type);
GLES30.glShaderSource(shader, shaderCode);
GLES30.glCompileShader(shader);
return shader;
} //链接到着色器
private static int linkProgram(int vertexShaderId, int fragmentShaderId) {
final int programId = GLES30.glCreateProgram();
//将顶点着色器加入到程序
GLES30.glAttachShader(programId, vertexShaderId);
//将片元着色器加入到程序
GLES30.glAttachShader(programId, fragmentShaderId);
//链接着色器程序
GLES30.glLinkProgram(programId);
return programId;
} //从shader文件读出字符串
private static String readShaderFromResource(Resources resources, int shaderId) {
InputStream is = resources.openRawResource(shaderId);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
StringBuilder sb = new StringBuilder();
try {
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
}

​ ArraysUtils.java

package com.zhyan8.light.utils;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer; public class ArraysUtils {
public static FloatBuffer getFloatBuffer(float[] floatArr) {
FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
fb.put(floatArr);
fb.position(0);
return fb;
}
}

​ vertex_shader.glsl

attribute vec4 aPosition; // 顶点坐标
attribute vec3 aNormal; // 法线向量
uniform vec3 uViewPos; // 相机坐标
uniform vec3 uLightPos; // 光源坐标
uniform vec4 uModelColor; // 模型颜色
uniform vec3 uAmbientLightColor; // 环境光颜色
uniform vec3 uLightColor; // 光源颜色(漫反射、镜面反射)
uniform vec3 uMaterial; // 材质对环境光、漫反射光、镜面光的反射系数
uniform mat4 modelMatrix; // 模型变换
uniform mat4 mvpMatrix; // mvp矩阵变换
varying vec4 vColor; // 合成颜色 // 在片元着色器中计算光照会获得更好更真实的光照效果,但是会比较耗性能 // 环境光的计算
vec4 ambientColor() {
vec3 ambient = uMaterial.x * uAmbientLightColor;
return vec4(ambient, 1.0);
} // 漫反射的计算
vec4 diffuseColor() {
// 模型变换后的位置
vec3 fragPos = (modelMatrix * aPosition).xyz;
// 光照方向
vec3 lightDirection = normalize(uLightPos - fragPos);
// 模型变换后的法线向量
vec3 normal = normalize(mat3(modelMatrix) * aNormal);
// max(cos(入射角),0)
float diff = max(dot(normal, lightDirection), 0.0);
// 材质的漫反射系数*max(cos(入射角),0)*光照颜色
vec3 diffuse = uMaterial.y * diff * uLightColor;
return vec4(diffuse, 1.0);
} // 镜面光计算,镜面光计算有两种方式,一种是冯氏模型,一种是Blinn改进的冯氏模型
// 冯氏模型: 材质的镜面反射系数*max(0,cos(反射向量与观察向量夹角)^粗糙度*光照颜色
// Blinn改进的冯氏模型: 材质的镜面反射系数*max(0,cos(半向量与法向量的夹角)^粗糙度*光照颜色
// 这里使用的是改进的冯氏模型,基于Half-Vector的计算方式
vec4 specularColor() {
// 模型变换后的位置
vec3 fragPos = (modelMatrix * aPosition).xyz;
// 光照方向
vec3 lightDirection = normalize(uLightPos - fragPos);
// 模型变换后的法线向量
vec3 normal = normalize(mat3(modelMatrix) * aNormal);
// 观察方向
vec3 viewDirection = normalize(uViewPos - fragPos);
// 半向量(观察向量与光照向量的半向量)
vec3 hafVector = normalize(lightDirection + viewDirection);
// max(0,cos(半向量与法向量的夹角)^粗糙度
float diff = pow(max(dot(normal, hafVector), 0.0), 4.0);
vec3 specular = uMaterial.z * diff * uLightColor;
return vec4(specular, 1.0);
} void main() {
gl_Position = mvpMatrix * aPosition;
vColor = (ambientColor() + diffuseColor() + specularColor()) * uModelColor;
}

​ fragment_shader.glsl

precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}

3 运行效果

​ 声明:本文转自【OpenGL ES】Blinn改进的冯氏光照模型

【OpenGL ES】Blinn改进的冯氏光照模型的更多相关文章

  1. ARM(哈弗、冯氏结构、总线和IO访问、处理器状态和处理机模式)

    1.哈弗结构与冯氏结构 (1)区别: 是否有独立的存储架构和信号通道. (2)举例: 8086:冯氏结构(相同的存储相同的通道) STM32F103:哈弗结构(不同的存储.通道) 8051:改进的哈弗 ...

  2. OpenGL ES 光照模型之——漫反射光(RenderMonkey测试,地球日出效果)

    概述及目录(版权所有,请勿转载 http://www.cnblogs.com/feng-sc) 本文在上一篇(OpenGL ES 光照模型之——环境光照(RenderMonkey测试))环境光基础上, ...

  3. OpenGL ES 光照模型之——环境光照(RenderMonkey测试)

    概述及目录(版权所有,请勿转载 www.cnblogs.com/feng-sc/) 本文总结如何在RenderMonkey下做简单的OpenGL ES环境光光照模型测试. 主要包括如下内容: 1.使用 ...

  4. 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

    在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...

  5. Android OpenGL ES 开发教程 从入门到精通

    感谢,摘自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ...

  6. OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型

    OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型 目录 背景介绍 请参考前文OpenGL ES 2.0 Shader 调试新思路(一): 改变提问方式 优化 ledCha ...

  7. OpenGL ES 3.0 帧缓冲区对象基础知识

    最近在帧缓冲区对象这里卡了一下,不过前面已经了解了相关的OpenGL ES的知识,现在再去了解就感觉轻松多了.现在就进行总结. 基础知识 我们知道,在应用程序调用任何的OpenGL ES命令之前,需要 ...

  8. 基于Cocos2d-x学习OpenGL ES 2.0系列——使用VBO索引(4)

    在上一篇文章中,我们介绍了uniform和模型-视图-投影变换,相信大家对于OpenGL ES 2.0应该有一点感觉了.在这篇文章中,我们不再画三角形了,改为画四边形.下篇教程,我们就可以画立方体了, ...

  9. OpenGL ES 简单教程

    什么是OpenGL ES? OpenGL ES (为OpenGL for Embedded System的缩写) 为适用于嵌入式系统的一个免费二维和三维图形库. 为桌面版本号OpenGL 的一个子集. ...

  10. 3D OpenGL ES

    什么是OpenGL ES? OpenGL ES (为OpenGL for Embedded System的缩写) 为适用于嵌入式系统的一个免费二维和三维图形库. 为桌面版本OpenGL 的一个子集. ...

随机推荐

  1. STM32 芯片锁死解决方法

    芯片锁死原因: 1.烧进去的工程对应器件与目标器件不一致: 2.烧进去的工程HSE_VALUE与目标板上晶振频率不一致: 3.... 解决方法: 1.工程设置 2.按住复位按键,或短接复位脚电容,点击 ...

  2. js - body的滚动事件的坑

    文章来源 : https://www.cnblogs.com/Zting00/p/7497629.html 踩过些坑,得到的结论,不一定精确 1. body的滚动条,刷新页面的时候不会回到顶部.其他d ...

  3. 海思Hi35xx 实现本地和远程升级程序的方法

    前言 嵌入式linux设备要进行软件升级有很种多方式方法,总的来说可以分为本地升级和远程升级. 本地升级包括升级工具升级,存储介质升级等,远程升级是指通过网络进行程序升级. 这里介绍一种同时至此本地和 ...

  4. 【转帖】MySQL InnoDB存储原理深入剖析与技术分析

    一.MySQL记录存储: MySQL InnoDB的数据由B+树来组织,数据记录存储在B+树数据页(page)中,每个数据页16kb,数据页 包括页头.虚记录.记录堆.自由空间链表.未分配空间.slo ...

  5. CPU实际频率查看

    cat /sys/devices/system/cpu/cpu3/cpufreq/cpuinfo_cur_freq

  6. [转帖]Redis 使用指南:深度解析 info 命令

    https://www.cnblogs.com/hwpaas/p/9442410.html Redis 是一个使用  ANSI C 编写的开源.基于内存.可选持久性的键值对存储数据库,被广泛应用于大型 ...

  7. Raid卡在Write back 与Write through 时的性能差异

    还是读姜老师的 mysql技术内核innodb存储引擎这本书里面的内容. 之前知道raid卡的设置会影响性能, 预计也是十几倍的性能差距, 但是从来没有用数据库进行过验证 书中有针对不通raid卡的设 ...

  8. TypeScript中typeof的简单介绍

    简单介绍typeof 我们都知道js提供了typeof,用来获取基本数据的类型. 实际上,TS也提供了 typeof 操作符. 可以在 [类型上下文]中进行类型查询. 只能够进行变量或者属性查询. 定 ...

  9. js文件下载blob

    使用axios文件下载 if (tableDataSource.selectedRowKeys.length > 0) { //本次请求你携带token axios.defaults.heade ...

  10. 蘑菇街大三Java后端暑期实习面经

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识.准备 Java 面试,首选 JavaGuide! 分享一位热心读者分享的实习面经给博客园的小伙伴们看看. 一面 1.自我 ...