1、在GLSL中运用光照,首先需考虑的是光照强度,有了光强,有就明暗和层次感,那么光照强度如何判断?我们可以通过计算顶点的法线向量与光照向量的夹角余弦值(cosine值)来得到。所以,当法线离光照方向很接近的情况下,将会出现亮的色调;当光照方向与法线方向的夹角很大时,渲染的地方就会很暗,换句话而言就是,夹角的cosine值影响色调的强度,Cosine值的计算可以利用下面这个公式,也即是点积公式:
cos(lightDir,normal) = lightDir . normal / ( |lightDir| * |normal| )
当法线向量和光照都被归一化,这个公式将被简化成:
cos(lightDir,normal) = lightDir . normal
2、所以在GLSL编程时经常会看到如下代码:
Vec3 normal = gl_NormalMatrix * gl_Normal;
Vec3 tnorm = normalize( normal );
float intensity = dot( lightdir, n );
这里如果没有进行 normalize( normal );那显然dot是不成行的,即得不到光的强度。
3、光线向量的计算
光线向量即从光源点发出,到顶点结束的向量,所以豪无疑问的是我们首先必须得到光源位置以及顶点位置(以观察坐标为基):
(1)光源位置
我们自己设置一个光源位置,比如:uniform vec3 LightPostion;在应用程序中对其赋值。也可以通过GLSL内置的光照属性结构体来获取,结构命名为gl_LightSourceParameters,大概如下:
struct gl_LightSourceParameters {
vec4 ambient;
vec4 diffuse;
vec4 specular;
vec4 position;
...
};
uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];
这样光源的位置为:gl_LightSource[0].postion
(2)顶点位置
经过模型视图变换的顶点位置可以这样计算:
vec3 ecPosition = vec3(gl_ModelViewMatrix * gl_Vertex);
注意这里是模型视图变换矩阵,不是模型视图投影变换矩阵。
接下来求光线方向就非常容易了:vec3 lightVec = normalize(LightPosition - ecPosition);
4、例子1:
varying vec3 normal;
void main()
{
float intensity;
vec4 color;
vec3 n = normalize(normal);
intensity = dot(vec3(gl_LightSource[0].position),n);
if (intensity > 0.95)
color = vec4(1.0,0.5,0.5,1.0);
else if (intensity > 0.5)
color = vec4(0.6,0.3,0.3,1.0);
else if (intensity > 0.25)
color = vec4(0.4,0.2,0.2,1.0);
else
color = vec4(0.2,0.1,0.1,1.0);
gl_FragColor = color;
}
此处通过光强来设置不同的颜色。
例子2:
/// CH06-brick.vert(取自GLSL橙皮书例子)
uniform vec3 LightPosition;
const float SpecularContribution = 0.3;
const float DiffuseContribution = 1.0 - SpecularContribution;
varying float LightIntensity;
varying vec2 MCposition;
void main()
{
vec3 ecPosition = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 tnorm = normalize(gl_NormalMatrix * gl_Normal);
vec3 lightVec = normalize(LightPosition - ecPosition);
vec3 reflectVec = reflect(-lightVec, tnorm);
vec3 viewVec = normalize(-ecPosition);
float diffuse = max(dot(lightVec, tnorm), 0.0);
float spec = 0.0;
if (diffuse > 0.0)
{
spec = max(dot(reflectVec, viewVec), 0.0);
spec = pow(spec, 16.0);
}
LightIntensity = DiffuseContribution * diffuse +
SpecularContribution * spec;
MCposition = gl_Vertex.xy;
gl_Position = ftransform();
}
在该顶点着色器代码中,需注意几点:
(1)通过顶点的坐标位置(ecPosition ),及顶点的单位法线向量(tnorm),并由此计算出光线在顶点平面上的反射向量(reflectVec )。注意该函数TYPE reflect( TYPE I, TYPE N ) 它根据规范化的表面方向向量N,返回入射向量I的反射方向:result = I - 2*dot(N, I) * N
(2)float diffuse = max(dot(lightVec, tnorm), 0.0);
这里dot(lightVec, tnorm)实质是计算光的强度,由于dot实质是计算两向量的夹角余弦,所以当角度>90度时为负值,当处于0~90度时为正值。
max(dot(lightVec, tnorm), 0.0);的意思也即当光线方向的逆方向(注意lightVec的定义)与法向量的夹角大于90度时,取0值(即就是说完全没有漫射光了,比如想像一下点法向量朝Z轴正上,而光线方向是(1, 1, 1) )。
(3)if (diffuse > 0.0) 即如果存在漫射光,那必然是有反射光的。
spec = max(dot(reflectVec, viewVec), 0.0);
spec = pow(spec, 16.0);
这里dot(reflectVec, viewVec)表示反射光强,前面讲过很多了。
pow也是一个GLSL的内置函数,TYPE pow( TYPE x, TYPE y )
表x的y次方。注意spec是个float型数,所以必须为16.0
此处用16次方是随意的,你可以用任意一个数,它只是为了使反射光更加明显,当光线向量与点法微量非常接近的时候,不至于看不见。
/// CH06-brick.frag(取自GLSL橙皮书例子)
uniform vec3 BrickColor, MortarColor;
uniform vec2 BrickSize;
uniform vec2 BrickPct;
varying vec2 MCposition;
varying float LightIntensity;
void main()
{
vec3 color;
vec2 position, useBrick;
position = MCposition / BrickSize;
if (fract(position.y * 0.5) > 0.5)
position.x += 0.5;
position = fract(position);
useBrick = step(position, BrickPct);
color = mix(MortarColor, BrickColor, useBrick.x * useBrick.y);
color *= LightIntensity;
gl_FragColor = vec4(color, 1.0);
}