目录
- 光照相关基本知识
- 实践
- 资料
- 收获
效果如下:
一、光照相关基本知识
经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到材质表面上特定点。这些成分包括环境光(Ambient Lighting)、漫反射光(Diffuse Lighting)和镜面反射光(Specular Lighting)。
1.1 环境光(Ambient Lightiing)
环境光(Ambient Lightiing)不来自任何特定方向的光,在经典光照模型中会用一个常量来表示 使用时只需要对其片源着色器添加一个环境光常量,作为gl_Fragcolor的调制即可_
代码语言:javascript复制uniform vec4 ambient;
varying vec4 color;
void main{
vec3 ambientLight = vec3(ambient);
vec3 rgb = min(color.rgb * ambientLight,vec3(1.0));
gl_FragColor = vec4(rgb,color.a);
}
1.2 漫反射光(Diffuse Lighting)
漫反射光(Diffuse Lighting)和镜面反射光都涉及到光源方向和眼睛观察方向。为此引入法向量的概念。
法向量是垂直于顶点表面的单位向量。
由于顶点本身并没有表面,它只是一个独立的点,我们可以利用它周围的顶点来计算出这个顶点的表面 就像顶点坐标一样,顶点方向量也作为一个location传给着色器使用。
漫反射光照是一种简单的光照模型,它的公式是:
N是顶点的单位法线,L是表示从顶点到光源的单位向量方向。Cmat是表面材料的颜色,Cli是光线的颜色,Cdiff是最终的散射颜色。
对应简化的着色器代码如下
代码语言:javascript复制 // Diffuse
float diffuseStrength = 0.5; //漫反射强度
vec3 unitNormal = normalize(vec3(u_ModelMatrix * vec4(a_normal, 1.0)));//顶点的单位法线
vec3 lightDir = normalize(lightPos - fragPos);//从顶点到光源的单位向量方向
float diff = max(dot(unitNormal, lightDir), 0.0);
diffuse = diffuseStrength * diff * lightColor;
1.3 镜面反射
只有漫反射,再漂亮的模型也会失去光泽,我们必须找出一个方法来显示模型的高光,这时应采用镜面反射光照模型。镜面反射光照模型的公式是:
H表示光线向量和视图向量(可通过视图矩阵转换)之间的夹角正中的方向。称为半角向量。Sexp是最终产生的镜面颜色。N、L、Cmat和Cli的值与散射光方程式相同
对应简化的着色器代码如下:
代码语言:javascript复制 // Specular
float specularStrength = 0.9;//镜面反射强度
vec3 viewDir = normalize(viewPos - fragPos);//归一化的
vec3 reflectDir = reflect(-lightDir, unitNormal);//光线向量和视图向量(可通过视图矩阵转换)之间的夹角正中的方向。称为半角向量
float spec = pow(max(dot(unitNormal, reflectDir), 0.0), 16.0);
specular = specularStrength * spec * lightColor;
二、 实践
为了更好的展示效果,我们立方体的基础上进行环境光照、漫反射光照、镜面反射光照。下面我们依次进行实践。
- 画一个立方体 加上图片纹理
- 加上环境光
- 加上漫反射光
- 加上镜面反射光
2.1 画个立方体并且渲染图片纹理
立方体的绘画我们可以采用画六个面的方式,也可以采用画一个面然后采用投影的方式。本篇我们才有后者实现
先来看下着色器, 比较简单,传入顶点坐标、纹理坐标、MVP矩阵以及纹理
代码语言:javascript复制//cube_vertex.glsl
precision mediump float;
attribute vec3 aPosition;
attribute vec2 aTexCoord;
uniform mat4 uMatrix;
varying vec2 v_texCoord;
void main()
{
gl_Position = uMatrix * vec4(aPosition,1.0);
v_texCoord = aTexCoord;
}
代码语言:javascript复制//cube_fragment.glsl
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D uTexture;
void main()
{
gl_FragColor = texture2D(uTexture, v_texCoord);
}
接着可以先画一个面,设置每个面上的顶点坐标和纹理坐标,然后根据透视投影变换投影出每个面上的画面。 关键代码如下: 首先确定每个面的顶点坐标和纹理坐标
代码语言:javascript复制 val vertexData = floatArrayOf(
//position //texture coord
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
)
var vertexArrayBuffer = ByteBuffer
.allocateDirect(vertexData.size * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData)
vertexArrayBuffer.position(0)
图片来自:OPENGL ES 案例03:COREANIMATION绘制立方体 旋转,其中图片中有个环节位移方向反了
下面来看下Render的实现
代码语言:javascript复制//在onSurfaceChanged 确定好透视投影矩阵和视图投影矩阵。
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
val whRadio = width / (height * 1.0f)
Matrix.setIdentityM(projectionMatrix, 0)
Matrix.perspectiveM(projectionMatrix, 0, 60f, whRadio, 1f, 100f)
Matrix.setIdentityM(viewMatrix,0);
Matrix.setLookAtM(viewMatrix,0,
2f,0f,3f,
0f,0f,0f,
0f,1f,0f)
}
//绘制
override fun onDrawFrame(gl: GL10?) {
//这里由于用到深度测试需要清除GL_DEPTH_BUFFER_BIT
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
GLES20.glClearColor(0f, 0f, 0f, 1f)
//必须要加深度测试
GLES20.glEnable(GLES20.GL_DEPTH_TEST)
Matrix.setIdentityM(modeMatrix, 0)
//采用旋转的方式,只能采用旋转的方式,进行实现视角变换,达到移动的效果
Matrix.rotateM(modeMatrix, 0, xRotation, 1f, 0f, 0f)
Matrix.rotateM(modeMatrix, 0, yRotation, 0f, 1f, 0f)
Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modeMatrix, 0)
Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0)
GLES20.glUseProgram(mProgram)
//传mvp矩阵数据
GLES20.glUniformMatrix4fv(uMatrixLoc, 1, false, mvpMatrix, 0)
//传纹理数据
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, skyBoxTexture)
GLES20.glUniform1i(uTextureLoc, 0)
GLES20.glEnableVertexAttribArray(aPositionLoc)
cubeLight.vertexArrayBuffer.position(0);
GLES20.glVertexAttribPointer(aPositionLoc, CubeLight.POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, CubeLight.STRIDE, cubeLight.vertexArrayBuffer)
GLES20.glEnableVertexAttribArray(aTextureCoorLoc)
cubeLight.vertexArrayBuffer.position(CubeLight.POSITION_COMPONENT_COUNT);
GLES20.glVertexAttribPointer(aTextureCoorLoc, CubeLight.POSITION_TEXTURE_COUNT, GLES20.GL_FLOAT, false, CubeLight.STRIDE, cubeLight.vertexArrayBuffer)
cubeLight.vertexArrayBuffer.position(0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,36)
}
具体介绍请看代码注释。完整代码已上传至 github https://github.com/ayyb1988/mediajourney
资料
《OpenGL编程指南》
基础光照 探究OpenGL光照模型的着色器实现 OpenGL_ES-光照(光照基础,漫反射,镜面反射) NDK OpenGL ES 3.0 开发(九):光照基础 OPENGL ES 案例03:COREANIMATION绘制立方体 旋转
收获
- 了解了经典的冯氏光照模型
- 了解环境光照、漫反射光照、镜面反射光照的原理
- 拆分成多个环节逐步实现
- 代码先实现立方的绘制
由于在绘制立方体时,有涉及到内容较多,光照部分的具体实践我们留在下一篇学习。在学习实践过程中地图非常关键,当不知道往哪走的时候,打开地图,想一想目的地,很快就可以梳理清楚要走的路。一起加油。
感谢你的阅读 下一篇我们继续学习实践光照部分内容,欢迎关注公众号“音视频开发之旅”,一起学习成长。
欢迎交流