OpenGL固定管线中默认的光照类似于将三种光照分量组合起来:环境光+漫反射+镜面反射。
其中漫反射之前就说过了。环境光和光源之于顶点的入射角度无关,仅仅与物体本身的反射系数相关。本身也非常好理解,给出公式如下
Ambient公式
镜面反射模型如下图所示
怎么理解这个模型呢。镜面反射模型中,最终我们看到的光照强度是光源向量与顶点法线的反射向量的点积。想象一下拿着光束照镜子,与光束的反射角越大,则越不刺眼,就能理解这个模型了。模型的公式为
镜面反射公式
公式本身是很好理解的,其中系数f为镜面反射系数。类似物体的光滑程度吧,相同角度的光源照射在石块上的反射光强度和玻璃上的反射光强度是不同的。这里就通过这个镜面反射系数f来决定。
这个模型称为Phong反射模型。最终公式如下:
有了公式之后剩下的就是代码实现了。这里给出关键的shader代码——
先上vertex shader的
attribute vec3 vertPosition;
attribute vec3 vertNormal;
varying vec3 lightIntensity;
struct LightInfo {
vec3 Position; // Light position in eye - coordinate
vec3 La; // Ambient light intensity
vec3 Ld; // Diffuse light intensity
vec3 Ls; // Sepcular light intesity
};
uniform LightInfo light;
struct MaterialInfo {
vec3 Ka; // Ambient reflectivity
vec3 Kd; // Diffuse reflectivity
vec3 Ks; // Specular reflectivity
float Shininess;// Specular shininess factor
};
uniform MaterialInfo material;
uniform mat3 normMat;
uniform mat4 modelview;
uniform mat4 projection;
void main() {
vec3 tnorm = normalize(normMat * vertNormal);
vec3 eyecoord = vec3(modelview * vec4(vertPosition, 1.0));
vec3 s = normalize(light.Position - eyecoord);
vec3 v = normalize(-eyecoord.xyz);
vec3 r = reflect(-s, tnorm);
vec3 ambient = light.La * material.Ka;
float sDotN = max(dot(s, tnorm), 0.0);
vec3 diffuse = light.Ld * material.Kd * sDotN;
vec3 spec = vec3(0.0);
if (sDotN > 0.0)
spec = light.Ls * material.Ks * pow(max(dot(r, v), 0.0), material.Shininess);
lightIntensity = ambient + diffuse + spec;
gl_Position = projection * modelview * vec4(vertPosition, 1.0);
}
OpenGL ES shading 语法类似C的语法。结构体定义是一样的。首先定义了表示光源的结构体LightInfo以及相关的光源坐标、三种光的反射强度,然后是材质结构MaterialInfo以及相关三种光的材质反射系数以及镜面光反射系数Shininess,然后是变换矩阵。
其他的语句不过多解释了,和之前的处理差不多。
vec3 v = normalize(-eyecoord.xyz);
这里取顶点坐标的反向向量是因为在观察坐标下,要计算反射光和观察点的角度,换句话来说,就是从顶点发射出去的光和从顶点到观察点的向量之间的夹角的余弦值才是我们要的值。
接下来给出Fragment shader的代码
precision mediump float;
varying vec3 lightIntensity;
void main() {
gl_FragColor = vec4(lightIntensity, 1.0);
}
这个代码和OpenGL ES 光照模型(一)是一样的。
猛戳代码