【DirectX】42-阴影映射

参考教程

Tutorial 40: Shadow Mapping

阴影技术:Shadow Map 初探

Tutorial 16 : Shadow mapping

学习记录

  这篇文章中我们介绍在 DX11 中实现阴影映射。

  阴影的示意图如下:

1

  The basic shadowmap algorithm consists in two passes. First, the scene is rendered from the point of view of the light. Only the depth of each fragment is computed. Next, the scene is rendered as usual, but with an extra test to see it the current fragment is in the shadow.

  The “being in the shadow” test is actually quite simple. If the current sample is further from the light than the shadowmap at the same point, this means that the scene contains an object that is closer to the light. In other words, the current fragment is in the shadow.

  我们对于动态阴影的渲染可以分为以下两个部分:1. 以光的视角来渲染一张纹理,这张纹理上存储了像素点的深度,我们称它为深度图。2. 正常渲染,不同的是我们在像素着色器中判断当前像素的深度,如果它大于深度图中存储的深度,则它处于阴影中。

  这么想的话其实并不复杂,我们首先使用 RenderToTexture 技术将像素的深度存入我们的深度图,我们需要一个 DepthShaderClass 以及对应着色器。由于只是存入深度信息,所以比较简单。

  着色器代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
////////////////////////////////////////////////////////////////////////////////
// Filename: depth.vs
////////////////////////////////////////////////////////////////////////////////

/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};

//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
float4 position : POSITION;
};

struct PixelInputType
{
float4 position : SV_POSITION;
float4 depthPosition : TEXTURE0;
};

////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType DepthVertexShader(VertexInputType input)
{
PixelInputType output;
// Change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;
// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
// Store the position value in a second input value for depth value calculations.
output.depthPosition = output.position;
return output;
}

////////////////////////////////////////////////////////////////////////////////
// Filename: depth.ps
////////////////////////////////////////////////////////////////////////////////

//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float4 depthPosition : TEXTURE0;
};

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 DepthPixelShader(PixelInputType input) : SV_TARGET
{
float depthValue;
float4 color;
// Get the depth value of the pixel by dividing the Z pixel depth by the homogeneous W coordinate.
depthValue = input.depthPosition.z / input.depthPosition.w;
color = float4(depthValue, depthValue, depthValue, 1.0f);
return color;
}

  其对应的 DepthShaderClass 类也比较简单,在此我们贴出声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
////////////////////////////////////////////////////////////////////////////////
// Class name: DepthShaderClass
////////////////////////////////////////////////////////////////////////////////
class DepthShaderClass
{
private:
struct MatrixBufferType
{
DirectX::XMMATRIX world;
DirectX::XMMATRIX view;
DirectX::XMMATRIX projection;
};

public:
DepthShaderClass();
DepthShaderClass(const DepthShaderClass&);
~DepthShaderClass();

bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX);

private:
bool InitializeShader(ID3D11Device*, HWND, CHAR*, CHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, CHAR*);

bool SetShaderParameters(ID3D11DeviceContext*, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX);
void RenderShader(ID3D11DeviceContext*, int);

private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11Buffer* m_matrixBuffer;
};

  相比较而言,我们需要注意的是 shadow 着色器的内容,如我们上边所言,我们需要使用深度图中的深度进行比较,所以需要计算当前像素相对于光源的深度,在 vertex 中我们完成计算光源坐标以及方向,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
////////////////////////////////////////////////////////////////////////////////
// Filename: shadow.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
matrix lightViewMatrix;
matrix lightProjectionMatrix;
};


//////////////////////
// CONSTANT BUFFERS //
//////////////////////
cbuffer LightBuffer2
{
float3 lightPosition;
float padding;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
float4 position : POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
};

struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
float4 lightViewPosition : TEXCOORD1;
float3 lightPos : TEXCOORD2;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ShadowVertexShader(VertexInputType input)
{
PixelInputType output;
float4 worldPosition;


// Change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;

// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);

// Calculate the position of the vertice as viewed by the light source.
output.lightViewPosition = mul(input.position, worldMatrix);
output.lightViewPosition = mul(output.lightViewPosition, lightViewMatrix);
output.lightViewPosition = mul(output.lightViewPosition, lightProjectionMatrix);

// Store the texture coordinates for the pixel shader.
output.tex = input.tex;

// Calculate the normal vector against the world matrix only.
output.normal = mul(input.normal, (float3x3)worldMatrix);

// Normalize the normal vector.
output.normal = normalize(output.normal);

// Calculate the position of the vertex in the world.
worldPosition = mul(input.position, worldMatrix);

// Determine the light position based on the position of the light and the position of the vertex in the world.
output.lightPos = lightPosition.xyz - worldPosition.xyz;

// Normalize the light position vector.
output.lightPos = normalize(output.lightPos);

return output;
}

  在像素着色器中我们读出投影到当前像素的深度图中的深度,然后使用变换后的相对于光源的坐标来得到相对于光源的深度,然后进行比较,当深度小于深度图中深度的时候我们对他进行正常光照,相反则只进行环境光计算,达到阴影效果。像素着色器中代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
////////////////////////////////////////////////////////////////////////////////
// Filename: shadow.ps
////////////////////////////////////////////////////////////////////////////////


//////////////
// TEXTURES //
//////////////
Texture2D shaderTexture : register(t0);
Texture2D depthMapTexture : register(t1);


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleTypeClamp : register(s0);
SamplerState SampleTypeWrap : register(s1);


//////////////////////
// CONSTANT BUFFERS //
//////////////////////
cbuffer LightBuffer
{
float4 ambientColor;
float4 diffuseColor;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
float4 lightViewPosition : TEXCOORD1;
float3 lightPos : TEXCOORD2;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ShadowPixelShader(PixelInputType input) : SV_TARGET
{
float bias;
float4 color;
float2 projectTexCoord;
float depthValue;
float lightDepthValue;
float lightIntensity;
float4 textureColor;


// Set the bias value for fixing the floating point precision issues.
bias = 0.001f;

// Set the default output color to the ambient light value for all pixels.
color = ambientColor;

// Calculate the projected texture coordinates.
projectTexCoord.x = input.lightViewPosition.x / input.lightViewPosition.w / 2.0f + 0.5f;
projectTexCoord.y = -input.lightViewPosition.y / input.lightViewPosition.w / 2.0f + 0.5f;

// Determine if the projected coordinates are in the 0 to 1 range. If so then this pixel is in the view of the light.
if((saturate(projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y))
{
// Sample the shadow map depth value from the depth texture using the sampler at the projected texture coordinate location.
depthValue = depthMapTexture.Sample(SampleTypeClamp, projectTexCoord).r;

// Calculate the depth of the light.
lightDepthValue = input.lightViewPosition.z / input.lightViewPosition.w;

// Subtract the bias from the lightDepthValue.
lightDepthValue = lightDepthValue - bias;

// Compare the depth of the shadow map value and the depth of the light to determine whether to shadow or to light this pixel.
// If the light is in front of the object then light the pixel, if not then shadow this pixel since an object (occluder) is casting a shadow on it.
if(lightDepthValue < depthValue)
{
// Calculate the amount of light on this pixel.
lightIntensity = saturate(dot(input.normal, input.lightPos));

if(lightIntensity > 0.0f)
{
// Determine the final diffuse color based on the diffuse color and the amount of light intensity.
color += (diffuseColor * lightIntensity);

// Saturate the final light color.
color = saturate(color);
}
}
}

// Sample the pixel color from the texture using the sampler at this texture coordinate location.
textureColor = shaderTexture.Sample(SampleTypeWrap, input.tex);

// Combine the light and texture color.
color = color * textureColor;

return color;
}

  关于光照,投影映射我们之前的文章中都有介绍。

  Shadow shader 对应的着色器类也仅仅是缓冲的不同,我们依旧只列出其类声明,具体实现可以参考源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
////////////////////////////////////////////////////////////////////////////////
// Class name: ShadowShaderClass
////////////////////////////////////////////////////////////////////////////////
class ShadowShaderClass
{
private:
struct MatrixBufferType
{
DirectX::XMMATRIX world;
DirectX::XMMATRIX view;
DirectX::XMMATRIX projection;
DirectX::XMMATRIX lightView;
DirectX::XMMATRIX lightProjection;
};

struct LightBufferType
{
DirectX::XMFLOAT4 ambientColor;
DirectX::XMFLOAT4 diffuseColor;
};

struct LightBufferType2
{
DirectX::XMFLOAT3 lightPosition;
float padding;
};

public:
ShadowShaderClass();
ShadowShaderClass(const ShadowShaderClass&);
~ShadowShaderClass();

bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX, ID3D11ShaderResourceView*,
ID3D11ShaderResourceView*, DirectX::XMFLOAT3, DirectX::XMFLOAT4, DirectX::XMFLOAT4);

private:
bool InitializeShader(ID3D11Device*, HWND, CHAR*, CHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, CHAR*);

bool SetShaderParameters(ID3D11DeviceContext*, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX, DirectX::XMMATRIX, ID3D11ShaderResourceView*,
ID3D11ShaderResourceView*, DirectX::XMFLOAT3, DirectX::XMFLOAT4, DirectX::XMFLOAT4);
void RenderShader(ID3D11DeviceContext*, int);

private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11SamplerState* m_sampleStateWrap;
ID3D11SamplerState* m_sampleStateClamp;
ID3D11Buffer* m_matrixBuffer;
ID3D11Buffer* m_lightBuffer;
ID3D11Buffer* m_lightBuffer2;
};

  在 GraphicsClass 中我们首先通过 DepthShaderClass 对象将深度信息绘制进纹理中,然后再使用 ShadowShaderClass 对象进行渲染,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
bool GraphicsClass::Frame(float posX, float posY, float posZ, float rotX, float rotY, float rotZ)
{
bool result;
static float lightPositionX = -5.0f;

// Set the position of the camera.
m_Camera->SetPosition(posX, posY, posZ);
m_Camera->SetRotation(rotX, rotY, rotZ);

// Update the position of the light each frame.
lightPositionX += 0.05f;
if(lightPositionX > 5.0f)
{
lightPositionX = -5.0f;
}

// Update the position of the light.
m_Light->SetPosition(lightPositionX, 8.0f, -5.0f);

// Render the graphics scene.
result = Render();
if(!result)
{
return false;
}

return true;
}

  我们设计了一个在场景上方缓缓移动的光源,这样我们可以看到阴影的变化,某一刻的效果如下:

2

  源代码:DX11Tutorial-ShadowMapping

0%