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. Oracle官网下载软件需要登录Oracle账户问题

    问题描述 当我们在Oracle官网上下载JDK时,(JDK下载地址)系统会提示需要登录Oracle账户.对于没有Oracle账户的人来说,注册账户太繁琐. 没有账户怎么办??? 此处推荐一个靠谱的网站 ...

  2. 宝塔部署 springboot 项目遇到的 一些bug处理方案

    1,上传的项目(jar包)的数据库用户名 .密码 , 和服务器的数据库用户名.密码不一致 2,数据库的表结构没有创建 3, 宝塔 phpmyadmin 进不去 原因: 服务器没有放行888端口, 宝塔 ...

  3. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2024.01.21)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  4. CPU信息查看的工具

    CPU信息查看的工具 背景 信创国产化如火如荼. CPU的型号其实越来越多 lscpu出来的结果其实太抽象, 对CPU的缓存架构显示不充分 今天在看大佬的文章是看到了一个工具: hwloc 感觉非常优 ...

  5. [转帖]容器环境的JVM内存设置最佳实践

    https://cloud.tencent.com/developer/article/1585288 Docker和K8S的兴起,很多服务已经运行在容器环境,对于java程序,JVM设置是一个重要的 ...

  6. [转帖]elasticsearch 8.0 linux安装部署

    1. 下载安装包 https://www.elastic.co/cn/downloads/elasticsearch 选择下载linux版本,elasticsearch-8.0.0-linux-x86 ...

  7. [转帖]【存储测试】vdbench存储性能测试工具

    一.前言 1.介绍  vdbench是一个I/O工作负载生成器,通常用于验证数据完整性和度量直接附加(或网络连接)存储性能.它可以运行在windows.linux环境,可用于测试文件系统或块设备基准性 ...

  8. [转帖]GC Ergonomics间接引发的锁等待超时问题排查分析

    https://www.cnblogs.com/micrari/p/8831834.html 1. 问题背景 上周线上某模块出现锁等待超时,如下图所示:我虽然不是该模块负责人,但出于好奇,也一起帮忙排 ...

  9. Springboot 使用nacos鉴权的简单步骤

    Springboot 使用nacos鉴权的简单步骤 背景 前端时间nacos爆出了漏洞. 因为他的默认token固定,容易被利用. 具体的问题为: QVD-2023-6271 漏洞描述:开源服务管理平 ...

  10. 境内下载nodejs二进制文件

    下载 nodejs 安装包的方法 找到一个境内的淘宝源 可以直接使用 下载速度还比较快 但是没有 龙芯的.. http://npm.taobao.org/mirrors/node/v10.20.0/