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. Qt5.9 UI设计(二)——最简Qt工程搭建

    前言 前面一章已经介绍了QT的开发环境的安装,这里介绍一下一个最简工程的搭建 操作步骤 新建项目 选择带界面的Qt Widgets Application 设置项目位置 注意这里的目录不能有中文路径 ...

  2. Net Core中使用EF Core连接Mysql数据库

    Entity Framework Core的前身是微软提供并主推的ORM框架,简称EF,其底层是对ADO.NET的封装.EF支持SQLServer.MYSQL.Oracle.Sqlite等所有主流数据 ...

  3. [转帖]CoreDNS loop 插件异常问题

    https://zhuanlan.zhihu.com/p/476611162   背景 最近有遇到一个客户集群,发现集群中的 CoreDNS 老是异常 (loop 插件检测到有回路后进行 panic) ...

  4. TiDB恢复部分表的方式方法

    TiDB恢复部分表的方式方法 背景 今天同事告知误删了部分表. 因为是UAT准生产的环境, 所以仅有每天晚上11点的备份处理. 同时告知 昨天的数据也可以. 得到认可后进行了 TiDB的单表备份恢复. ...

  5. 【转帖】nginx变量使用方法详解-8

    https://www.diewufeiyang.com/post/582.html 与 $arg_XXX 类似,我们在 (二) 中提到过的内建变量 $cookie_XXX 变量也会在名为 XXX 的 ...

  6. [转帖]技术派-epoll和IOCP之比较

    直入正题 Epoll 用于Linux系统: IOCP 是用于 Windows: Epoll 是当事件资源满足时发出可处理通知消息: IOCP 则是当事件完成时发出完成通知消息. 从应用程序的角度来看, ...

  7. 京东金融APP-新交互技术“虚拟数字人”赋能世界杯主题营销

    作者:平台研发部,智能服务与产品部 距离加文·伍德提出web3.0已经过去8年时间,这8年加文·伍德创建的以太坊大放异彩,同时由web3.0引出的数字人.元宇宙也生根发芽,茁壮成长,带来了非凡的用户体 ...

  8. Seata配置参考

    注意:mysql.redis等连接密码需修改为相应值 Seata-Server 环境 版本:1.4.2 OS: CentOS Linux release 7.5.1804 (Core) ip:192. ...

  9. 清空elementui让计数器input-number的默认值

    <el-form-item label="考试时长:" prop="testTimeLong"> <el-input-number style ...

  10. vue混入mixin

    <div id="app"> --{{nick11}} </div> <script> // 全局混入 不需要注册 var m1 = Vue.m ...