OpenGL-基础光照
冯氏光照模型
现实世界中的光照是极其复杂,难以计算的,因此OpenGL的光照使用的是简化的模型,其中一个模型被称为冯氏光照模型。
冯氏光照模型的主要结构由三个分量组成:
1、环境(Ambient)光照
2、漫反射(Diffuse)光照
3、镜面(Specular)光照
环境(Ambient)光照
环境光通常都不是来自于同一个光源,而是来自于我们周围分散的很多光源,即使它们可能并不是那么显而易见。它可以向很多方向发散并反弹,所以现实环境周围通常总会有些光亮,对应的物体通常都会反射些微弱的光。lightColor为光源的颜色,ambientStrength为强度,objectColor为物体颜色。
//环境光的计算
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
漫反射(Diffuse)光照
环境光和漫反射光最大的区别在于:漫反射光依赖于光源的方向,而环境光和光源方向完全无关,环境光在场景中是均匀分布的,对场景中的所有物体都有效,而漫反射光在物体朝向光源的一面才有光照效果,在背面则没有光照效果。除了光源方向,漫反射光还和物体表面的法向有关。
上图中2个光束的强度都是一样的,唯一不同的只是方向,则在左边的图中,物体表面光照要亮一些,因为光线是直射,而右边的图中,物体表面光照要弱一些,因为有因为有一个角度。
有四道光A,B,C,D,注意:物体表面法向和A重合,但方向相反。光线A和法向的夹角是0,余弦值就是1,所以A光速光照的强度最大,B次之,C和法向夹角是90度,所以光照强度是0,而D作用与物体的背面,余弦值是个负值,光照强度不可能是个负值,所以D的光照强度也是0,也就是说,光源要在物体表面有光照效果,光源方向和物体表面法向夹角必须是0-90度之间。
如果物体表面是个平面,那么法向就是一个固定的向量,但真实物体表面往往并不是平面,所以物体表面的法向是不断变化的,如下图中的两个法向。
对三角形面来说,可以取它的面法向计算光照,此时三角形面上的光照是均匀的,但这种光照效果并不好,我们通常都是对每个顶点定义法向,而三角形面光栅化后的每个像素,它的法向是由顶点法向插值得到。这种光照模型称作 Phong Shading, 下面就是顶点法向插值后的效果:
attribute vec4 aPosition;
attribute vec2 aCoord;
attribute vec3 aNormal;//每个顶点定义法向
uniform mat4 uMatrix; // 物体的矩阵
uniform vec4 uBaseColor; // 物体颜色 物体有a,所以是4个颜色
uniform vec3 uLightColor; // 光源颜色 光源是没有a,所以是3个颜色
uniform float uAmbientStrength;//默认0.3 光强度
uniform float uDiffuseStrength;//默认0.5
uniform float uSpecularStrength;//默认0.8
uniform vec3 uLightPosition; // 光源位置
varying vec4 vColor;
//在片元着色器中计算光照会获得更好更真实的光照效果,但是会比较耗性能
//在片元着色器里面只需要赋值颜色就可以了
//漫反射的计算
vec4 diffuseColor(){
//模型变换后的位置
vec3 fragPos=(uMatrix*aPosition).xyz;
//光照方向
vec3 direction=normalize(uLightPosition-fragPos);
//模型变换后的法线向量
vec3 normal=normalize(mat3(uMatrix)*aNormal);
//max(cos(入射角),0)
float diff = max(dot(normal,direction), 0.0);
//材质的漫反射系数*max(cos(入射角),0)*光照颜色
vec3 diffuse=uDiffuseStrength * diff * uLightColor;
return vec4(diffuse,1.0);
}
vec3 specular=uSpecularStrength*diff*uLightColor;
return vec4(specular,1.0);
}
void main(){
gl_Position=uMatrix*aPosition;
vColor=diffuseColor()* uBaseColor;
}
镜面(Specular)光照
和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。你可以在下图中看到效果:
为此我们还需要一个观察者坐标(即摄像机)。光的反射向量与观察方向之间夹角越小,镜面光照效果越强。
物体片段着色器部分代码:
attribute vec4 aPosition;
attribute vec2 aCoord;
attribute vec3 aNormal;//每个顶点定义法向
uniform mat4 uMatrix; // 物体的矩阵
uniform vec4 uBaseColor; // 物体颜色 物体有a,所以是4个颜色
uniform vec3 uLightColor; // 光源颜色 光源是没有a,所以是3个颜色
uniform float uAmbientStrength;//默认0.3 光强度
uniform float uDiffuseStrength;//默认0.5
uniform float uSpecularStrength;//默认0.8
uniform vec3 uLightPosition; // 光源位置
varying vec4 vColor;
//在片元着色器中计算光照会获得更好更真实的光照效果,但是会比较耗性能
//在片元着色器里面只需要赋值颜色就可以了
//镜面光计算,镜面光计算有两种方式,一种是冯氏模型,一种是Blinn改进的冯氏模型
//这里使用的是改进的冯氏模型,基于Half-Vector的计算方式
vec4 specularColor(){
//模型变换后的位置
vec3 fragPos=(uMatrix*aPosition).xyz;
//光照方向
vec3 lightDirection=normalize(uLightPosition-fragPos);
//模型变换后的法线向量
vec3 normal=normalize(mat3(uMatrix)*aNormal);
//观察方向,这里将观察点固定在(0,0,uLightPosition.z)处
vec3 viewDirection=normalize(vec3(0,0,uLightPosition.z)-fragPos);
//观察向量与光照向量的半向量
vec3 hafVector=normalize(lightDirection+viewDirection);
//max(0,cos(半向量与法向量的夹角)^粗糙度
float diff=pow(max(dot(normal,hafVector),0.0),4.0);
//材质的镜面反射系数*max(0,cos(反射向量与观察向量夹角)^粗糙度*光照颜色
//材质的镜面反射系数*max(0,cos(半向量与法向量的夹角)^粗糙度*光照颜色
vec3 specular=uSpecularStrength*diff*uLightColor;
return vec4(specular,1.0);
}
void main(){
gl_Position=uMatrix*aPosition;
vColor=specularColor()* uBaseColor;
}
具体使用
整个冯氏光照模型的代码如下:
attribute vec4 aPosition;
attribute vec2 aCoord;
attribute vec3 aNormal;
uniform mat4 uMatrix; // 物体的矩阵
uniform vec4 uBaseColor; // 物体颜色 物体有a,所以是4个颜色
uniform vec3 uLightColor; // 光源颜色 光源是没有a,所以是3个颜色
uniform float uAmbientStrength;//默认0.3 光强度
uniform float uDiffuseStrength;//默认0.5
uniform float uSpecularStrength;//默认0.8
uniform vec3 uLightPosition; // 光源位置
varying vec4 vColor;
//在片元着色器中计算光照会获得更好更真实的光照效果,但是会比较耗性能
//在片元着色器里面只需要赋值颜色就可以了
//环境光的计算
vec4 ambientColor(){
vec3 ambient = uAmbientStrength * uLightColor;
return vec4(ambient,1.0);
}
//漫反射的计算
vec4 diffuseColor(){
//模型变换后的位置
vec3 fragPos=(uMatrix*aPosition).xyz;
//光照方向
vec3 direction=normalize(uLightPosition-fragPos);
//模型变换后的法线向量
vec3 normal=normalize(mat3(uMatrix)*aNormal);
//max(cos(入射角),0)
float diff = max(dot(normal,direction), 0.0);
//材质的漫反射系数*max(cos(入射角),0)*光照颜色
vec3 diffuse=uDiffuseStrength * diff * uLightColor;
return vec4(diffuse,1.0);
}
//镜面光计算,镜面光计算有两种方式,一种是冯氏模型,一种是Blinn改进的冯氏模型
//这里使用的是改进的冯氏模型,基于Half-Vector的计算方式
vec4 specularColor(){
//模型变换后的位置
vec3 fragPos=(uMatrix*aPosition).xyz;
//光照方向
vec3 lightDirection=normalize(uLightPosition-fragPos);
//模型变换后的法线向量
vec3 normal=normalize(mat3(uMatrix)*aNormal);
//观察方向,这里将观察点固定在(0,0,uLightPosition.z)处
vec3 viewDirection=normalize(vec3(0,0,uLightPosition.z)-fragPos);
//观察向量与光照向量的半向量
vec3 hafVector=normalize(lightDirection+viewDirection);
//max(0,cos(半向量与法向量的夹角)^粗糙度
float diff=pow(max(dot(normal,hafVector),0.0),4.0);
//材质的镜面反射系数*max(0,cos(反射向量与观察向量夹角)^粗糙度*光照颜色
//材质的镜面反射系数*max(0,cos(半向量与法向量的夹角)^粗糙度*光照颜色
vec3 specular=uSpecularStrength*diff*uLightColor;
return vec4(specular,1.0);
}
void main(){
gl_Position=uMatrix*aPosition;
vColor=(ambientColor() + diffuseColor() + specularColor())* uBaseColor;
}
precision highp float;
//uniform sampler2D uTexture;
varying vec4 vColor;
void main(){
gl_FragColor=vColor;
}
package com.opengles.book.es2_0.light;
import android.content.res.Resources;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import com.opengles.book.es2_0.utils.BufferUtils;
import com.opengles.book.es2_0.utils.MatrixUtils;
import com.opengles.book.es2_0.utils.ShaderUtils;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class LightRenderer implements GLSurfaceView.Renderer {
private Resources res;
private int glProgramId;
private int glAPosition;
private int glACoord;
private int glANormal;
private int glUMatrix;
private int glUBaseColor;
private int glULightColor;
private int glUAmbientStrength;
private int glUDiffuseStrength;
private int glUSpecularStrength;
private int glULightPosition;
private float[] mvpMatrix; // 物体的矩阵
private float[] lambMatrix; // 光源的矩阵
private FloatBuffer vertexBuffer;
private float lx = 0f, ly = 0.8f, lz = -1f; // 光源位置
private final float DEFAULT_AMBIENT = 0.3f;
private final float DEFAULT_DIFFUSE = 0.5f;
private final float DEFAULT_SPECULAR = 0.8f;
private float ambientStrength = 0;
private float diffuseStrength = 0;
private float specularStrength = 0;
// 包含了顶点数据,法线数据
private final float[] data = new float[]{
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
};
public LightRenderer(Resources res) {
this.res = res;
vertexBuffer = BufferUtils.arr2FloatBuffer(data);
}
public void setAmbientStrength(boolean isChecked) {
this.ambientStrength = isChecked ? DEFAULT_AMBIENT : 0;
}
public void setDiffuseStrength(boolean isChecked) {
this.diffuseStrength = isChecked ? DEFAULT_DIFFUSE : 0;
}
public void setSpecularStrength(boolean isChecked) {
this.specularStrength = isChecked ? DEFAULT_SPECULAR : 0;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glCullFace(GLES20.GL_FRONT);
GLES20.glFrontFace(GLES20.GL_CW);
glProgramId = ShaderUtils.createProgram(res, "light/light.vert", "light/light.frag");
glAPosition = GLES20.glGetAttribLocation(glProgramId, "aPosition");
glACoord = GLES20.glGetAttribLocation(glProgramId, "aCoord");
glANormal = GLES20.glGetAttribLocation(glProgramId, "aNormal");
glUMatrix = GLES20.glGetUniformLocation(glProgramId, "uMatrix");
glUBaseColor = GLES20.glGetUniformLocation(glProgramId, "uBaseColor");
glULightColor = GLES20.glGetUniformLocation(glProgramId, "uLightColor");
glUAmbientStrength = GLES20.glGetUniformLocation(glProgramId, "uAmbientStrength");
glUDiffuseStrength = GLES20.glGetUniformLocation(glProgramId, "uDiffuseStrength");
glUSpecularStrength = GLES20.glGetUniformLocation(glProgramId, "uSpecularStrength");
glULightPosition = GLES20.glGetUniformLocation(glProgramId, "uLightPosition");
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 整体缩小0.5f,相当于置于正中心
mvpMatrix = MatrixUtils.getOriginalMatrix();
Matrix.scaleM(mvpMatrix, 0, 0.5f, 0.5f * width / (float) height, 0.5f);
// 平移指定距离,确定光源位置,并整体缩小0.09
lambMatrix = MatrixUtils.getOriginalMatrix();
Matrix.translateM(lambMatrix, 0, lx, ly, lz);
Matrix.scaleM(lambMatrix, 0, 0.09f, 0.09f * width / (float) height, 0.09f);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
GLES20.glUseProgram(glProgramId);
Matrix.rotateM(mvpMatrix, 0, 2, -1, -1, 1); // 绕xy旋转
GLES20.glUniformMatrix4fv(glUMatrix, 1, false, mvpMatrix, 0);
// ---------------- 设置光 start -----------------
//环境光强度
GLES20.glUniform1f(glUAmbientStrength, ambientStrength);
//漫反射光强度
GLES20.glUniform1f(glUDiffuseStrength, diffuseStrength);
//镜面光强度
GLES20.glUniform1f(glUSpecularStrength, specularStrength);
//光源颜色
GLES20.glUniform3f(glULightColor, 0.0f, 0.0f, 1.0f); // 物体默认颜色是blue
//物体颜色
GLES20.glUniform4f(glUBaseColor, 1.0f, 1.0f, 1.0f, 1.0f); // 物体默认颜色是黑色
//光源位置
GLES20.glUniform3f(glULightPosition, lx, ly, lz);
// ---------------- 设置光 end -----------------
//传入顶点信息
vertexBuffer.position(0);
GLES20.glEnableVertexAttribArray(glAPosition);
GLES20.glVertexAttribPointer(glAPosition, 3, GLES20.GL_FLOAT, false,
6*4, // 跨度,一般情况下写0系统会自动识别。识别方式为size*sizeof(数组定义时报类型) 6*4,此处不传跨度会绘制不出来图形,不知为何???
vertexBuffer);
//传入法线信息
vertexBuffer.position(3);
GLES20.glEnableVertexAttribArray(glANormal);
GLES20.glVertexAttribPointer(glANormal, 3, GLES20.GL_FLOAT, false,
6*4, // 跨度,一般情况下写0系统会自动识别。识别方式为size*sizeof(数组定义时报类型) 6*4,此处不传跨度会绘制不出来图形,不知为何???
vertexBuffer);
// 绘制缓冲区的图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,data.length/6);
// 再绘制一个立方体,标记光源位置
GLES20.glUniformMatrix4fv(glUMatrix,1,false,lambMatrix,0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,data.length/6);
//释放资源
// GLES20.glDisable(GLES20.GL_CULL_FACE);
// GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glDisableVertexAttribArray(glAPosition);
GLES20.glDisableVertexAttribArray(glANormal);
}
}