本文是OpenGL 4.0 Shading Language Cookbook的学习笔记
在本文我们将介绍下面这些内容:
- 使用多个点光源进行着色
- 使用方向光源进行着色
- 使用逐像素着色来提高真实感
- 使用半角向量提高性能
- 模拟聚光灯
- 模拟卡通风格
- 模拟雾的效果
使用多个点光源进行着色
使用多个光源进行着色时,我们需要对每个光源进行计算,然后把它们的结果相加,最后得到表面反射的光的强度。很自然的,我们可以通过uniform数组来存储光源的位置和光的强度。在这里我们使用一个uniform结构体数组来存储所有光源信息。
设置OpenGL程序的顶点位置属性的location为0,法线location为1。
创建使用 ADS(Phong)光照模型的多光源着色器程序的步骤如下:
1. 使用下面的顶点着色器:
#version 400
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;
out vec3 Color;
struct LightInfo {
vec4 Position; // Light position in eye coords.
vec3 Intensity; // Light intensity
};
uniform LightInfo lights[5];
// Material parameters
uniform vec3 Kd; // Diffuse reflectivity
uniform vec3 Ka; // Ambient reflectivity
uniform vec3 Ks; // Specular reflectivity
uniform float Shininess; // Specular shininess factor
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 MVP;
vec3 ads( int lightIndex, vec4 position, vec3 norm )
{
vec3 s = normalize( vec3(
lights[lightIndex].Position –
position) );
vec3 v = normalize(vec3(-position));
vec3 r = reflect( -s, norm );
vec3 I = lights[lightIndex].Intensity;
return
I * ( Ka +
Kd * max( dot(s, norm), 0.0 ) +
Ks * pow( max( dot(r,v), 0.0 ),
Shininess ) );
}
void main()
{
vec3 eyeNorm = normalize( NormalMatrix *
VertexNormal);
vec4 eyePosition = ModelViewMatrix *
vec4(VertexPosition,1.0);
//Evaluate the lighting equation for each light
Color = vec3(0.0);
for( int i = 0; i < 5; i++ )
Color +=ads( i, eyePosition, eyeNorm);
gl_Position = MVP * vec4(VertexPosition,1.0);
}
2. 使用下面的片段着色器:
#version 400
in vec3 Color;
layout( location = 0 ) out vec4 FragColor;
void main() {
FragColor = vec4(Color, 1.0);
}
3. 在OpenGL程序中设置光源数组,代码类似下面:
prog.setUniform("lights[0].Intensity", vec3(0.0f,0.8f,0.8f) );
prog.setUniform("lights[0].Position", position );
光源信息存储在lights数组中,示例使用了5个光源。光强存储在Intensiy域中,观察坐标系下的位置信息存储在Position域中。
函数ads负责光照计算。lightIndex表示光源信息数组索引。
main函数里的for循环计算每一个光源,并将结果相加写入Color变量。
片段着色器直接输出了插值后Color变量。
使用方向光源进行着色
着色公式的核心组成部分是从表面到光源的向量(也就是前面示例中的s)。对于距离非常远的光源,整个物体表面到光源的向量变化并不大。也就是说,对于非常遥远的光源,表面上的点到光源的向量是相同的。(也可以认为此时光源发出的光线是平行的。)这一光照模型可以用来模拟远距离光源,比如太阳。通常这样的光源被叫做方向光源,它没有位置信息,只有方向。我们实际上忽略掉了光强随距离平方衰减,然而,对于方向光源,这是常见做法。
对于方向光源,场景中的所有点到光源的方向都是相同的,所以,就不需要重复计算表面上的点到光源的方向,从而提高着色性能。
当然,点光源和方向光源的光照效果会有视觉上的差异。下面左图使用点光源渲染,右图使用方向光源渲染。左图的点光源在距离圆环较近的某个地方,由于平行光的原因右图相比左图有更多位置被照亮。
OpenGL的传统管线,使用光源位置的第四个成分来确定它是否为方向光源。如果第四个成分为0,则它为方向光源,位置信息是光源的方向向量。如果为1,位置信息被作为光源位置。在本例,我们使用同样的处理。
设置OpenGL程序的顶点位置的location为0,法线location为1。
使用下面的代码创建方向光源的ADS着色程序:
1. 使用下面的顶点着色器:
#version 400
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;
out vec3 Color;
uniform vec4 LightPosition;
uniform vec3 LightIntensity;
uniform vec3 Kd; // Diffuse reflectivity
uniform vec3 Ka; // Ambient reflectivity
uniform vec3 Ks; // Specular reflectivity
uniform float Shininess; // Specular shininess factor
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;
vec3 ads( vec4 position, vec3 norm )
{
vec3 s;
if( LightPosition.w == 0.0 )
s = normalize(vec3(LightPosition));
else
s = n