效果图
先放一张最终效果图,实现了给场景添加光照、纹理,模型的独立运动、层级运动,场景漫游的效果,右上角为绘制的光源。完整源码已给出:传送门
本篇仅对给场景添加光照进行讲解。
给场景添加光照
整体思路
采用逐片元光照的方式,每个模型维护一个顶点数组和法向量数组,在向顶点数组添加顶点坐标的同时,对应向法向量数组中添加每个顶点对应的法向量。每个模型绘制前,传入该模型的材质参数,绑定其对应的顶点和法向量缓冲区并激活数组。
需要注意的是,法向量方向必须指向模型外部,若法向量方向错误将导致光照效果错误。
代码部分
着色器
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform vec4 lightPosition;
uniform mat4 viewMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 normalMatrix;
void main()
{
vec3 pos = (modelViewMatrix * vPosition).xyz;
// check for directional light
if(lightPosition.w == 0.0) L = normalize(lightPosition.xyz);
else L = normalize( lightPosition.xyz - pos );
E = -normalize(pos);
N = normalize(vec3(normalMatrix*vNormal));
gl_Position = projectionMatrix * viewMatrix * modelViewMatrix * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;
void main()
{
vec4 fColor;
vec3 H = normalize( L + E );
vec4 ambient = ambientProduct;
float Kd = max( dot(L, N), 0.0 );
vec4 diffuse = Kd*diffuseProduct;
float Ks = pow( max(dot(N, H), 0.0), shininess );
vec4 specular = Ks * specularProduct;
if( dot(L, N) < 0.0 ) specular = vec4(0.0, 0.0, 0.0, 1.0);
fColor = ambient + diffuse +specular;
fColor.a = 1.0;
gl_FragColor = fColor;
}
</script>
顶点着色器内,首先判断根据LightPosition的w分量判断光源是点光源还是平行光,由此计算得出归一化的L向量(顶点到光源的距离),N向量为实际法向量(初始法向量 * 法向量矩阵,这里的法向量矩阵实际就等于模型的变换矩阵,是为了计算出模型在经过一系列变换之后的法向量)。片元着色器内接收js传递的ambientProduct(环境光材质)、diffuseProduct(漫反射材质)、specularProduct(镜面反射材质)、shininess(光强)。
js代码:
首先在全局定义了材质参数,依次为光源位置、光源环境光参数、光源漫反射参数、光源镜面反射参数、模型材质的环境光参数、模型材质的漫反射参数、模型材质的镜面反射参数、发光强度
var lightPosition = vec4(0.8, 0.8, 0.8, 0.0 );
var lightAmbient = vec4(0.2, 0.2, 0.2, 1.0 );
var lightDiffuse = vec4( 1.0, 1.0, 1.0, 1.0 );
var lightSpecular = vec4( 1.0, 1.0, 1.0, 1.0 );
var materialAmbient = vec4( 1.0, 0.0, 1.0, 1.0 );
var materialDiffuse = vec4( 1.0, 0.8, 0.0, 1.0);
var materialSpecular = vec4( 1.0, 0.8, 0.0, 1.0 );
var materialShininess = 100.0;
最终传入片元着色器的材质参数实际为光源和模型本身材质参数的乘积,即
var ambientProduct = mult(lightAmbient, materialAmbient);
var diffuseProduct = mult(lightDiffuse, materialDiffuse);
var specularProduct = mult(lightSpecular, materialSpecular);
添加立方体的法向量:
这里在为立方体设置顶点的同时,设置了法向量
// 顶点编号图示
// v5----- v6
// /| /|
// v1------v2|
// | | | |
// | |v4---|-|v7
// |/ |/
// v0------v3
function drawBody(pointArray, normalArray) {
// 身体的八个顶点(x,y,z,a)
var bodyVertices = [
vec4(-body[0]/2, -body[1]/3, body[2]/2, 1.0),
vec4(-body[0]/2, body[1]*2/3, body[2]/2, 1.0),
vec4(body[0]/2, body[1]*2/3, body[2]/2, 1.0),
vec4(body[0]/2, -body[1]/3, body[2]/2, 1.0),
vec4(-body[0]/2, -body[1]/3, -body[2]/2, 1.0),
vec4(-body[0]/2, body[1]*2/3, -body[2]/2, 1.0),
vec4(body[0]/2, body[1]*2/