OpenGL教程翻译 第十七课 环境光(Ambient Lighting)
环境光和漫反射光的主要不同是,漫反射光的计算需要依靠光线方向而环境光完全忽略了它!当只有环境光时整个场景被均等照亮。漫反射光会使物体面对光的部分比背对光的部分更加明亮。
此外漫反射光还增加了一点新的计算,光线的入射角决定了表面的亮度。通过下面的图片来演示这个概念:(lighting_technique.h:25) struct DirectionalLight { Vector3f Color; float AmbientIntensity; Vector3f Direction; float DiffuseIntensity; };这是新的平行光结构体。这里有两个新成员:方向是一个在世界空间中指定的三维向量,强度是一个浮点数(使用方式与环境光相同)。
(lighting.vs) #version 330 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; }这是更新后的顶点着色器。我们有一个新的顶点属性,法线,需要由应用程序提供。除此之外,世界坐标系的变换有它自己的一致变量,所以除了WVP矩阵,我们还需要提供这个一致变量。顶点着色器用世界矩阵将法线转变到世界空间中,然后传递给片元着色器。请注意三维的法线是如何拓展成为一个四维向量的:先与一个4*4的世界矩阵相乘,然后用符号(...).xyz又减为三维。GLSL语言的这个能力被称为“交叉混合”,给予向量操纵很大的灵活性。例如,如果你有一个三维的向量v(1,2,3)你可以这样写:vec4 n = v.zzyy然后向量n将包含(3,3,2,2)。记得当我们将法线从三维扩展成四维时,第四组分必须放0。这可以消除在世界矩阵中转变时候带来的影响。原因是向量不能像点那样被移动。它们只能被缩放或旋转。
#version 330 in vec2 TexCoord0; in vec3 Normal0; out vec4 FragColor; struct DirectionalLight { vec3 Color; float AmbientIntensity; float DiffuseIntensity; vec3 Direction; };这是片元着色器的开始。它接收了通过顶点着色器转换到世界空间的经过插值的顶点法线。平行光结构体被扩展以与c++代码中的相匹配,包含了法线这一新的光线的属性。
(lighting.fs:19)
void main()
{
vec4 AmbientColor =vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.AmbientIntensity;
环境光的计算没有改变。我们计算、存储并在下面最后的公式中使用它.
Float DiffuseFactor = dot(normalize(Normal0),-gDirectionalLight.Direction);
这是漫反射光计算的核心。我们通过点积来计算光向量和法线夹角的余弦值。这里有三个要注意的事情:
1,顶点着色器传来的法线在使用前需要标准化。这是因为向量经过插值后其长度可能会被改变而不再是一个单位向量。
2,光的方向被反转。如果你仔细想一会,你将发现以一定角度照射物体表面的光实际上远离表面法线(指回光源)180度。在这里通过反转定向光,我们得到一个和法线相等的向量。因此,它们之间的角度正是我们希望的0.
3,光向量没有被标准化。为所有的像素重复标准化相同向量将是对GPU资源的浪费。相反,我们只用确保应用程序传来向量在绘制命令调用之前已被标准化。
vec4 DiffuseColor;
if (DiffuseFactor > 0) {
DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.DiffuseIntensity * DiffuseFactor;
}
else {
DiffuseColor = vec4(0, 0, 0, 0);
}
这里我们计算了漫反射光,它由光颜色、漫反射光强度和光的方向共同决定。如果漫反射光因子小于或等于0意味着光以钝角照射物体表面(或者是从边或后面照射),在此情况下漫反射光对物体没有影响,而DiffuseColor向量也初始化为0,如果角度大于0,我们通过将基本的光颜色和不变的漫射强度相乘,然后通过漫射因子缩放来计算漫反射光的颜色。如果光线与法线间的角度是0,漫射因子将是1,进而提供最大光强。
FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor);
}
这是最后的光计算。我们把环境光和漫反射光相叠加并将结果与从纹理中提取的颜色相乘。现在你明白了即使漫反射光由于方向对表面没有影响,如果环境光存在仍然会照亮物体。
(lighting_technique.cpp:144) 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); }这个函数将平行光的参数定向到着色器。它被扩展以覆盖方向向量和漫射强度。注意方向向量在设置前应先标准化。LightingTechnique类还从着色器中提取方向和漫射强度一致变量的位置,以及世界矩阵一致变量的位置。也有一个设置世界转变矩阵的函数。这都是一些常规的代码,请仔细查看代码以获取更多细节。
(tutorial18.cpp:35) 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,我们有一个专用功能可以扫描所有的顶点并计算法线。
(tutorial18.cpp:197) 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(); } }这个函数接收顶点和索引数组为参数,根据索引获取每个三角形的顶点,并计算出其法线。在第一个循环我们只是给每个三角形三个顶点累加了法线。每个三角形的法线由从第一个顶点射出的两边缘叉乘计算得出。在累加法线之前,我们确保法线被标准化,原因是叉乘的结果无法保证它是单位长度。在第二个循环我们直接扫描顶点(因为我们不再关心索引)并且标准化每个顶点的法线。这个操作等同于得到累加法线和的平均数,并且留给我们一个单位长度的顶点法线。这个函数在顶点缓存器创立之前被调用,以使计算后的顶点法线以及其他顶点属性进入缓冲器中。
</pre><pre name="code" class="cpp">(tutorial18.cpp:131) 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);这是渲染循环的主要改变。管线类有一个新的函数来提供世界转变矩阵(当然也提供WVP矩阵)。世界矩阵是缩放、旋转和平移矩阵相乘的结果。我们启用和禁用第三个顶点属性数组,指明顶点缓冲器中每个顶点的法线的偏移量。这个偏移是20,因为法线是在位置(12位)和纹理坐标(8位)之后的。
OpenGL教程翻译 第十八课 漫反射光(Diffuse Lighting)
原文地址:http://blog.csdn.net/vcube/article/details/46011591