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. 【MicroPython】用 c 添加接口 -- 框架介绍

    [来源]https://www.eemaker.com/micropython-c-kuangjia.html

  2. [转帖]fullgc问题解决:Full GC (Metadata GC Threshold)

    #问题描述 在工作过程中,遇到一个问题:Tomcat在重启或者发布的时候,会有多次的full GC. 笔者使用的版本说明: Tomcat7.0.25 JDK8 首先排查JVM的问题,就要把GC日志打开 ...

  3. blackbox的简单学习-监控web服务是否正常以及证书过期时间

    blackbox的简单学习-监控web服务是否正常以及证书过期时间 下载blackbox https://github.com/prometheus/blackbox_exporter 可以在rele ...

  4. [转帖]decimal and numeric (Transact-SQL)

    https://learn.microsoft.com/en-us/sql/t-sql/data-types/decimal-and-numeric-transact-sql?view=sql-ser ...

  5. [转帖]【Kafka】(二)Kafka去Zookeeper化,kraft模式搭建

    1.简介 由于zookeeper慢慢的成了kafka的瓶颈,kafka提出了去zookeeper化的概念,并在2.8版本之后版本都包含了kraft模式,也就是不需要使用zookeeper了,目前这种模 ...

  6. [转帖]一行Python代码实现同一局域网内的文件共享

    在不同的设备之间传输文件除了数据线,网盘传输外是否还有其他优雅的方法?我们可以使用一行Python代码使局域网内的所有设备都可以访问并下载文件夹内的文件. 要求: 电脑中安装配置好python 访问的 ...

  7. [转帖]【JVM】字节码执行引擎

    引入 class文件就是字节码文件,是由虚拟机执行的文件.也就是java语言和C & C++语言的区别就是,整个编译执行过程多了一个虚拟机这一步.这个在 类文件结构 中已经解释.上一节讲了虚拟 ...

  8. jmaps

    #!/bin/bash # # jmaps - creates java /tmp/perf-PID.map symbol maps for all java processes. # # This ...

  9. 【k哥爬虫普法】爬虫第一案,侵犯个人隐私,“入侵”短视频服务器!

    我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K 哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识, ...

  10. JavaScript获取浏览器的显示区域大小测试

    JavaScript获取浏览器的显示区域大小测试 Now we get the screen size about this browser 网页可见区域宽 document.body.clientW ...