Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十章:阴影贴图

本章介绍一种在游戏和应用中,模拟动态阴影的基本阴影贴图算法;还有一些更复杂和效果更好的阴影算法,比如cascading shadow maps[Engel06],都是基于基本阴影算法扩展出来的。



学习目标

  1. 熟悉基本阴影贴图算法;
  2. 学习投射纹理如何工作;
  3. 找到正交投射;
  4. 理解阴影贴图锯齿问题和一些常用的修复它们的策略。


1 渲染场景深度

阴影贴图算法依赖于从灯光的视角渲染场景的深度(渲染到纹理)。渲染完深度后,我们知道了距离灯光最近的像素片段(这些像素片段不会在阴影中)。我们实现一个ShadowMap类来帮助我们保存灯光透视视角的场景深度。它简单的封装了一个深度/模板缓冲,需要的views,和viewport。

class ShadowMap
{
public:
	ShadowMap(ID3D12Device* device, UINT width, UINT height);
	ShadowMap(const ShadowMap& rhs)=delete;
	ShadowMap& operator=(const ShadowMap& rhs)=delete;
	˜ShadowMap()=default;
	
	UINT Width()const;
	UINT Height()const;
	
	ID3D12Resource* Resource();
	CD3DX12_GPU_DESCRIPTOR_HANDLE Srv()const;
	CD3DX12_CPU_DESCRIPTOR_HANDLE Dsv()const;
	
	D3D12_VIEWPORT Viewport()const;
	D3D12_RECT ScissorRect()const;
	
	void BuildDescriptors(
		CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuSrv,
		CD3DX12_GPU_DESCRIPTOR_HANDLE hGpuSrv,
		CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuDsv);
		
	void OnResize(UINT newWidth, UINT newHeight);
	
private:
	void BuildDescriptors();
	void BuildResource();
	
private:
	ID3D12Device* md3dDevice = nullptr;
	D3D12_VIEWPORT mViewport;
	D3D12_RECT mScissorRect;
	UINT mWidth = 0;
	UINT mHeight = 0;
	DXGI_FORMAT mFormat = DXGI_FORMAT_R24G8_TYPELESS;
	CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuSrv;
	CD3DX12_GPU_DESCRIPTOR_HANDLE mhGpuSrv;
	CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuDsv;
	Microsoft::WRL::ComPtr<ID3D12Resource> mShadowMap = nullptr;
};

构造函数通过分辨率和viewport来创建纹理。分辨率影响了阴影的效果,高分辨率会需要更多性能开销和内存。

ShadowMap::ShadowMap(ID3D12Device* device, UINT width, UINT height)
{
	md3dDevice = device;
	mWidth = width;
	mHeight = height;
	mViewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f };
	mScissorRect = { 0, 0, (int)width, (int)height };
	
	BuildResource();
}

void ShadowMap::BuildResource()
{
	D3D12_RESOURCE_DESC texDesc;
	ZeroMemory(&texDesc, sizeof(D3D12_RESOURCE_DESC));
	texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	texDesc.Alignment = 0;
	texDesc.Width = mWidth;
	texDesc.Height = mHeight;
	texDesc.DepthOrArraySize = 1;
	texDesc.MipLevels = 1;
	texDesc.Format = mFormat;
	texDesc.SampleDesc.Count = 1;
	texDesc.SampleDesc.Quality = 0;
	texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	texDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
	
	D3D12_CLEAR_VALUE optClear;
	optClear.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	optClear.DepthStencil.Depth = 1.0f;
	optClear.DepthStencil.Stencil = 0;
	ThrowIfFailed(md3dDevice->CreateCommittedResource(
		&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
		D3D12_HEAP_FLAG_NONE,
		&texDesc,
		D3D12_RESOURCE_STATE_GENERIC_READ,
		&optClear,
		IID_PPV_ARGS(&mShadowMap)));
}

从上面可以看出,我们的阴影贴图算法需要两个渲染调用:第一个调用用来从灯光视角渲染场景深度;第二个调用正常渲染场景,但是要使用阴影贴图实现阴影算法。我们提供了下面的方法来访问着色器资源:

ID3D12Resource* ShadowMap::Resource()
{
	return mShadowMap.Get();
}

CD3DX12_GPU_DESCRIPTOR_HANDLE ShadowMap::Srv()const
{
	return mhGpuSrv;
}

CD3DX12_CPU_DESCRIPTOR_HANDLE ShadowMap::Dsv()const
{
	return mhCpuDsv;
}


2 正交投影

正交投影主要用以3D科学和工程应用。
在这里插入图片描述
在正交投影中,线都是平行于Z轴。
在这里插入图片描述
正交投影矩阵为(推导过程这里省略,可以查看原书):
在这里插入图片描述
相比于透视投影就是w值不同。



3 投影纹理坐标

之所以叫投影纹理,是因为它可以让我们投影一个纹理到任意几何体上,比较像一个滑动的投影机,比如下图:
在这里插入图片描述
它可以是阴影纹理的一个中间步骤。投影纹理主要是对每一个像素创建一个纹理坐标,从下图中可以看出,纹理坐标(u, v)定义了要被投影到的3D点P。创建纹理坐标的策略是:
1、映射点P到灯光的投影窗口并且转换到NDC坐标系;
2、将投影坐标从NDC坐标系转换到纹理坐标系,然后有效的准换它们到纹理坐标。
在这里插入图片描述
第一步可以将灯光认为是一个摄像机,然后定义它的视图矩阵V和投影矩阵P;
第二步可以通过下面的变换从NDC转换到纹理坐标:
在这里插入图片描述
它的变换矩阵:
在这里插入图片描述
我们称上面的矩阵T(纹理矩阵),也可以直接乘以VPT从世界空间转换到纹理空间。变换过后我们还是要做透视分割来完成变换(查看第五章练习8)。


3.1 实现代码

创建透视纹理坐标代码如下:

struct VertexOut
{
	float4 PosH : SV_POSITI
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值