方向光的漫反射

立方体 drawElements 中得到这样的一个立方体,虽然是立体的,但在色彩上没有明暗变化, 整个平面中的美一个点的颜色值都一样。那是因为在 Fragment Shader 中 gl_FragColor = v_Color,而 r、g、b、a 在4个顶点中都是相同的。这相当于每个面都有白色的平行光垂直照射到表面上。但现实中不能有6束平行光单独影响6个表面中的一个, 对其他表面都没有影响。

现在假设只有一束垂直于上表面的白色光,拿可想象只能看到上表面,其他的面全黑。怎么套入下面的公式呢?

漫反射颜色 = 入射光颜色 × 表面基底色 × cosθ

1. 光的信息:在 Vertex Shader 中声明两个类型是 vec3uniform 变量,存入光的颜色和方向。在 JavaScript 中要把 lightDirection 向量归一化。

// GLSL ES uniform vec3 u_LightColor; uniform vec3 u_LightDirection; // JavaScript var lightColor = new Vector3([ 1.0, 1.0, 1.0 ]); var u_LightColor = gl.getUniformLocation( gl.program, "u_LightColor" ); gl.uniform3fv( u_LightColor, lightColor.elements ); var lightDirection = new Vector3([ 0.0, 1.0, 0.0 ]); lightDirection.normalize(); // 归一化 var u_LightDirection = gl.getUniformLocation( gl.program, "u_LightDirection" ); gl.uniform3fv( u_LightDirection, lightDirection.elements );

2. 顶点法向:在 Vertex Shader 中声明一个类型是 vec4 的 attribute 变量,存入物体的顶点的法线的数据。 因为法线数据是 Float32Array([ ]),不确定数据是否已经归一化,在 GLSL ES 中可用 normalize() 来实现。 公共顶点在三个面都要用到,法线到底朝向哪?每个面都有唯一一个垂直于表面的法线方向, 在这个面上的每个点的法向都一样,所以一个顶点在这个面有一个法向数据,在另一个面就另写一个。

vec3 normal = normalize( a_Normal );

3. 计算cosθ:在 GLSL ES 中计算光和法线的夹角余弦。用 dot() 函数来求夹角余弦,注意要把入射角大于90°的光线去掉,因为这时算出来的是负值。

float cosAngle = max( dot( u_LightDirection, normal ), 0.0 );

4. 最后代入公式:漫反射颜色 = 入射光颜色 × 表面基底色 × cosθ 算出颜色值,这时我们默认物体都不透明的。

vec3 diffuse = u_LightColor * vec3( a_Color ) * cosAngle; v_Color = vec4( diffuse, a_Color.a );

方向光只有方向,没有强弱

下面分别是光沿着x轴、y轴、z轴和从原点到[ 1.0, 1.0, 1.0 ]的方向的光照效果。果然,只有一束垂直于上表面照射时,只能看到上表面, 其他的面全黑。换成其他方向的也一样。方向光只有方向,没有强弱变化,所以对于同一个面依然没有明暗变化。

<script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute vec4 a_Color; attribute vec3 a_Normal; uniform mat4 u_mvpMatrix; uniform vec3 u_LightColor; uniform vec3 u_LightDirection; varying vec4 v_Color; void main(){ gl_Position = u_mvpMatrix * a_Position; vec3 normal = normalize( a_Normal ); float cosAngle = max( dot( u_LightDirection, normal ), 0.0 ); vec3 diffuse = u_LightColor * vec3( a_Color ) * cosAngle; v_Color = vec4( diffuse, a_Color.a ); } </script> <script id="fragmentShader" type="x-shader/x-fragment"> precision mediump float; varying vec4 v_Color; void main(){ gl_FragColor = v_Color; } </script> function init(){ var canvas = document.getElementById('canvas'), gl = canvas.getContext('webgl'); if(!gl){ console.log('Failed to get WebGL.'); return; } if(!initShaders(gl, "vertexShader", "fragmentShader")){ console.log('Failed to initShaders.'); return; } gl.clearColor( 0.0, 0.0, 0.0, 1.0 ); gl.enable( gl.DEPTH_TEST ); var viewProjMatrix = new Matrix4(); viewProjMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100); viewProjMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0); var u_mvpMatrix = gl.getUniformLocation(gl.program, 'u_mvpMatrix'); gl.uniformMatrix4fv(u_mvpMatrix, false, viewProjMatrix.elements); var lightColor = new Vector3([ 1.0, 1.0, 1.0 ]); var u_LightColor = gl.getUniformLocation( gl.program, "u_LightColor" ); gl.uniform3fv( u_LightColor, lightColor.elements ); var lightDirection = new Vector3([ 1.0, 1.0, 1.0 ]); lightDirection.normalize(); var u_LightDirection = gl.getUniformLocation( gl.program, "u_LightDirection" ); gl.uniform3fv( u_LightDirection, lightDirection.elements ); // 立方体 gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); var n = initVertexBuffers(gl); gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0); } function initVertexBuffers(gl){ var vertices = new Float32Array([ 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0 ]), colors = new Float32Array([ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 ]), normals = new Float32Array([ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0 ]) , indices = new Uint8Array([ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 ]); initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT); initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT); initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT); var indicesBuffer = gl.createBuffer(); gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indicesBuffer ); gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW ); return indices.length; } function initArrayBuffer(gl, attribute, data, num, type){ var buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); var a_location = gl.getAttribLocation(gl.program, attribute); gl.vertexAttribPointer(a_location, num, type, false, 0, 0); gl.enableVertexAttribArray(a_location); return true; }