opengl 教程(18) 漫反射光
原帖地址:http://ogldev.atspace.co.uk/www/tutorial18/tutorial18.html
环境光和漫反射光最大的区别在于:漫反射光依赖于光源的方向,而环境光和光源方向完全无关,环境光在场景中是均匀分布的,对场景中的所有物体都有效,而漫反射光在物体朝向光源的一面才有光照效果,在背面则没有光照效果。
如下图所示,除了光源方向,漫反射光还和物体表面的法向有关。
假设上图中2个光束的强度都是一样的,唯一不同的只是方向,则在左边的图中,物体表面光照要亮一些,因为光线是直射,而右边的图中,物体表面光照要弱一些,因为有一个角度。
漫反射光照模型实际上基于Lambert's cosine law,也就是说物体表面反射的光照强度和与光源方向和物体表面法向有关,光源方向和法向的夹角的越小,则物体表面反射的光强越大。 用公式表示就是光源的强度乘以光源和物体表面法向夹角的余弦值。
在上图中,有四道光A,B,C,D,注意:物体表面法向和A重合,但方向相反。光线A和法向的夹角是0,余弦值就是1,所以A光速光照的强度最大,B次之,C和法向夹角是90度,所以光照强度是0,而D作用与物体的背面,余弦值是个负值,光照强度不可能是个负值,所以D的光照强度也是0,也就是说,光源要在物体表面有光照效果,光源方向和物体表面法向夹角必须是0-90度之间。
如果物体表面是个平面,那么法向就是一个固定的向量,但真实物体表面往往并不是平面,所以物体表面的法向是不断变化的,如下图中的两个法向。
对三角形面来说,可以取它的面法向计算光照,此时三角形面上的光照是均匀的,但这种光照效果并不好,我们通常都是对每个顶点定义法向,而三角形面光栅化后的每个像素,它的法向是由顶点法向插值得到。这种光照模型称作 Phong Shading, 下面就是顶点法向插值后的效果:
注意在片元shader中计算光照前,我们要把法向转化到世界坐标系中去,这样得到的光照效果才真实的光照模型。
主要代码:
lighting_technique.h
struct DirectionalLight
{
Vector3f Color;
float AmbientIntensity;
Vector3f Direction;
float DiffuseIntensity;
};
新的方向光结构,增加了2个成员变量:一个是光源在世界坐标系中的方向,一个是漫反射的光照强度。
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
uniform mat4 gWVP;
uniform mat4 gWorld;
out vec2 TexCoord0;
out vec3 Normal0;
void main()
{
gl_Position = gWVP * vec4(Position, 1.0);
TexCoord0 = TexCoord;
Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
}
这是更新后的顶点shader代码,我们增加了一个新的顶点属性--法向,法向需要从物体的局部坐标系转化到世界坐标系中,所以我们增加了一个uniform变量,用来传入世界矩阵,该矩阵乘以法向向量,得到世界坐标系中的法向向量,最后把该向量传入到片元shader中去。注意:该代码中一些glsl语法,vec4表示四维向量,.xyz表示取向量的前三维。
in vec2 TexCoord0;
in vec3 Normal0;
out vec4 FragColor;
struct DirectionalLight
{
vec3 Color;
float AmbientIntensity;
float DiffuseIntensity;
vec3 Direction;
};
片元shader中,首先会接受插值的纹理坐标和顶点法向,同时定义了一个法向光的结构,该结构和c++代码中的结构是一致的。
void main()
{
vec4 AmbientColor = vec4(gDirectionalLight.Color, 1.0f) *
gDirectionalLight.AmbientIntensity;
float DiffuseFactor = dot(normalize(Normal0), -gDirectionalLight.Direction);
首先我们会计算环境光的强度,接着计算漫反射光的强度。我们使用光源方向向量的负值点乘像素的法向向量,得到光源和法向夹角的余弦值,最后用光源强度乘以余弦值得到最终的漫反射光强,如果余弦值是负的,那就说明光照在物体的背面,此时漫反射光强为0。
vec4 DiffuseColor;
if (DiffuseFactor > 0) {
DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.DiffuseIntensity * DiffuseFactor;
}
else {
DiffuseColor = vec4(0, 0, 0, 0);
}
最后把纹理采样的结果和光照的结果相乘,就是把它们混合起来,得到最终像素的输出颜色。
FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor);
}
lighting_technique.cpp
void LightingTechnique::SetDirectionalLight(const DirectionalLight& Light)
{
glUniform3f(m_dirLightLocation.Color, Light.Color.x, Light.Color.y, Light.Color.z);
glUniform1f(m_dirLightLocation.AmbientIntensity, Light.AmbientIntensity);
Vector3f Direction = Light.Direction;
Direction.Normalize();
glUniform3f(m_dirLightLocation.Direction, Direction.x, Direction.y, Direction.z);
glUniform1f(m_dirLightLocation.DiffuseIntensity, Light.DiffuseIntensity);
}
上面这个函数设置传入shader的方向光参数,注意方向光方向会被归一化。另外也会增加一个设置世界矩阵的函数。
tutorial18.cpp
struct Vertex
{
Vector3f m_pos;
Vector2f m_tex;
Vector3f m_normal;
Vertex() {}
Vertex(Vector3f pos, Vector2f tex)
{
m_pos = pos;
m_tex = tex;
m_normal = Vector3f(0.0f, 0.0f, 0.0f);
}
};
更新顶点结构,增加了法向,法向初始化为(0.0f, 0.0f, 0.0f)。
void CalcNormals(const unsigned int* pIndices, unsigned int IndexCount, Vertex* pVertices, unsigned int VertexCount)
{
for (unsigned int i = 0 ; i < IndexCount ; i += 3) {
unsigned int Index0 = pIndices[i];
unsigned int Index1 = pIndices[i + 1];
unsigned int Index2 = pIndices[i + 2];
Vector3f v1 = pVertices[Index1].m_pos - pVertices[Index0].m_pos;
Vector3f v2 = pVertices[Index2].m_pos - pVertices[Index0].m_pos;
Vector3f Normal = v1.Cross(v2);
Normal.Normalize();
pVertices[Index0].m_normal += Normal;
pVertices[Index1].m_normal += Normal;
pVertices[Index2].m_normal += Normal;
}
for (unsigned int i = 0 ; i < VertexCount ; i++) {
pVertices[i].m_normal.Normalize();
}
}
上面这个函数计算三角形面的面法向,然后把三个顶点的法向都赋值面法向的值。
const Matrix4f& WorldTransformation = p.GetWorldTrans();
m_pEffect->SetWorldMatrix(WorldTransformation);
...
glEnableVertexAttribArray(2);
...
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);
...
glDisableVertexAttribArray(2);
pipeline类中增加了世界矩阵,它是缩放,旋转,平移坐标的乘积。同时,我还使用了第三个顶点属性,那就是法向。
程序执行后界面如下,可以通过x和z键控制漫反射强度。