原帖地址:
本篇教程中,我们通过shadowmap来实现阴影渲染。
我们知道shadow mapping是一个两趟渲染技术,在第一趟渲染过程中,光源位置作为视点,下面我们回顾一下第一趟渲染时候,顶点的z分量是如何变化的。
- 局部坐标空间中的顶点位置被传入vertex shader。
- 在vertex shader中,局部坐标的顶点值被转化为clip空间表示的顶点值。
- 执行透视除法,顶点被转化到归一化的clip空间,这个空间中x,y,z值的范围都是[-1,1],这个范围之外的顶点值都被clip掉。
- 光栅化时候映射x,y值到屏幕空间坐标系。(例如: 800x600, 1024x768, etc)。
- 光栅化阶段在屏幕空间中对三角形的顶点进行差值,得到该三角形覆盖的每个像素的x,y值。顶点的z值(仍在[-1,1]范围内)也被差值,结果放在深度缓冲中。
- 由于第一趟渲染中,禁止颜色写,所以fs(PS)不会被执行,但深度测试仍会进行,深度buffer也会更新。
在第二趟渲染中,视点在摄像机的位置,所以我们会得到一个不同的深度缓冲。两个缓冲都是需要的,一个用来保证物体渲染顺序,一个用来判断顶点是否在阴影内。 我们可以通过在第二趟渲染的vertex shader中传入两个WVP矩阵来做到这个。
- 内建的 gl_Position是摄像机WVP矩阵转化后得到的顶点位置。
- 另一个传输到下一个阶段的向量则是光源位置WVP矩阵转化后的顶点值。
向量gl_Position用作通常的渲染,另一个向量在光栅化阶段也被差值,每个fragment也有自己相应的值。 该向量的z值即为光源到该点的距离。该z值可以和shadow map中的深度值比较,决定该fragment是否在阴影内。
- 硬件会自动对gl_Position进行透视除法,但是对于我们传入的第二个向量,我们必须在fs(ps)中手工进行透视除法,以便把该向量转化为归一化clip空间中。
- 把[-1,1]范围的x,y坐标转为到[0,1]范围,以便能够作为纹理坐标,来采样shadow map值。
- u = 0.5 * X + 0.5
- v = 0.5 * Y + 0.5
源码:
lighting_technique.h
class LightingTechnique : public Technique { public: ... void SetLightWVP(const Matrix4f& LightWVP); void SetShadowMapTextureUnit(unsigned int TextureUnit); ... private: GLuint m_LightWVPLocation; GLuint m_shadowMapLocation; ...
增加一个新的属性,用来表示光源位置作为视点的WVP矩阵。我们用texture unit 0表示通常的纹理,texture unit 1表示shadow map。
lighting.vs
#version 400 layout (location = 0) in vec3 Position; layout (location = 1) in vec2 TexCoord; layout (location = 2) in vec3 Normal; uniform mat4 gWVP; uniform mat4 gLightWVP; uniform mat4 gWorld; out vec4 LightSpacePos; out vec2 TexCoord0; out vec3 Normal0; out vec3 WorldPos0; void main() { gl_Position = gWVP * vec4(Position, 1.0); LightSpacePos = gLightWVP * vec4(Position, 1.0); TexCoord0 = TexCoord; Normal0 = (gWorld * vec4(Normal, 0.0)).xyz; WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz; }
lighting.fs:58
float CalcShadowFactor(vec4 LightSpacePos) { vec3 ProjCoords = LightSpacePos.xyz / LightSpacePos.w; vec2 UVCoords; UVCoords.x = 0.5 * ProjCoords.x + 0.5; UVCoords.y = 0.5 * ProjCoords.y + 0.5; float z = 0.5 * ProjCoords.z + 0.5; float Depth = texture(gShadowMap, UVCoords).x; if (Depth < (z + 0.00001)) return 0.5; else return 1.0; }
上面这个shader函数用来计算阴影因子。