[DirectX12学习笔记] 阴影

  • 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。

阴影贴图


阴影贴图简介

要渲染场景的阴影,我们可以在渲染场景之前,先从每个光源出发渲染一遍深度缓冲,记录下被光照射的深度,再正常渲染,渲染的时候计算到光源的深度,深度大于记录下来的深度的话,就说明没有被照到,就是在阴影范围内。

下面介绍基础知识,首先是正射投影。正射投影的视锥是一个长方体,所以不会有透视效果,投影矩阵如下
在这里插入图片描述
在c++里可以这样获取正射投影矩阵

    // Ortho frustum in light space encloses scene.
    float l = sphereCenterLS.x - mSceneBounds.Radius;
    float b = sphereCenterLS.y - mSceneBounds.Radius;
    float n = sphereCenterLS.z - mSceneBounds.Radius;
    float r = sphereCenterLS.x + mSceneBounds.Radius;
    float t = sphereCenterLS.y + mSceneBounds.Radius;
    float f = sphereCenterLS.z + mSceneBounds.Radius;

    mLightNearZ = n;
    mLightFarZ = f;
    XMMATRIX lightProj = XMMatrixOrthographicOffCenterLH(l, r, b, t, n, f);

这个变换是线性的,也不需要再作齐次除法,不过除一下也不影响,反正w除的是1。

以往我们通过view和proj矩阵把坐标变换到ndc,现在介绍怎么把NDC变换到贴图的坐标系,贴图坐标系的xy∈[0,1],而ndc则是[-1,1],而且y是反的,所以这里我们要乘以下矩阵来变换。
在这里插入图片描述
这个矩阵我们称为纹理矩阵。

接下来介绍贴图投影技术,类似于cs里的喷图,如果我们要将一张贴图投射到一个平面上绘制出来,我们该怎么做呢?首先我们需要知道出发点的view矩阵和投影矩阵,从世界坐标出发,经过view和投影矩阵,就可以变换成ndc里的坐标,然后再乘一个贴图矩阵,就可以把当前渲染的坐标变换到贴图的uv坐标系,然后用这个uv去采样,就可以得到颜色了。
然后有这么一个小问题,用这种方法渲染的时候,我们是没有作裁剪的,我们做了三次投影,到了uv坐标系里面采样,但是如果点在视锥外,也会被采样到,所以我们应该在采样的时候把address mode设置成border,让超出范围的采样结果都返回0。

渲染阴影的原理也和这个差不多,我们渲染一个点的时候,想知道这个点在不在阴影范围内,首先投影到光的坐标系里,用前两个维度去采样这个光源的阴影贴图,得到深度记录的 s ( p ) s(p) s(p),再取第三个维度,也就是渲染的这个点的深度 d ( p ) d(p) d(p),比较 s ( p ) s(p) s(p) d ( p ) d(p) d(p)就可以知道这个点在不在阴影范围内。
然而这样做还是有问题,我们会在地上看到一条一条的黑斑,这个问题被称为shadow acne,效果以及出现原因如下图。
在这里插入图片描述
在这里插入图片描述
一个解决办法是我们设置一个bias。
在这里插入图片描述
这样就不会有条纹了,注意过大的bias可能导致影子与物体分离,这种问题叫做peter panning。而且固定的bias可能达不到要求,因为在相对摄像头坡度大的地方,需要的bias会更多,如图。
在这里插入图片描述
幸运的是dx里面已经支持了带斜率缩放的bias,这个设置在PSO的D3D12_RASTERIZER_DESC里

    D3D12_GRAPHICS_PIPELINE_STATE_DESC smapPsoDesc = opaquePsoDesc;
    smapPsoDesc.RasterizerState.DepthBias = 100000;
    smapPsoDesc.RasterizerState.DepthBiasClamp = 0.0f;
    smapPsoDesc.RasterizerState.SlopeScaledDepthBias = 1.0f;

这个bias的大小设置是根据设备支持的最小单位缩放到32位里的,比如24位的深度缓冲,那么1对应的就是 1 / 2 24 1/2^{24} 1/224,这里设置成100000那么实际的bias就是 100000 / 2 24 = 0.006 100000/2^{24}=0.006 100000/224=0.006。这里因为是在PSO里设置的,所以应该是深度缓冲在写入的时候就加上这个值再写进去,我们读的时候就不用再去加bias了。

PCF(Percentage Closer Filtering)
直接采样比大小的话,阴影的边缘会非常的硬,所以我们希望多采样一个范围内的一些点,然后取平均,然后点不是只能在或者不在阴影范围内,可以有中间状态,比如0.5表示一半的状态,这样的话阴影边缘就会平滑很多。为了达到这个效果,我们可以采样四个点,x和y的增量都取Δx=1/SHADOW_MAP_SIZE,分别检查是否在阴影范围内,然后结果取平均,如下图所示
在这里插入图片描述
代码如下

static const float SMAP_SIZE = 2048.0f;
static const float SMAP_DX = 1.0f / SMAP_SIZE;// Sample shadow map to get nearest depth to light.
float s0 = gShadowMap.Sample(gShadowSam,
projTexC.xy).r;
float s1 = gShadowMap.Sample(gShadowSam,
projTexC.xy + float2(SMAP_DX, 0)).r;
float s2 = gShadowMap.Sample(gShadowSam,
projTexC.xy + float2(0, SMAP_DX)).r;
float s3 = gShadowMap.Sample(gShadowSam,
projTexC.xy + float2(SMAP_DX, SMAP_DX)).r;
// Is the pixel depth <= shadow map value?
float result0 = depth <= s0;
float result1 = depth <= s1;
float result2 = depth <= s2;
float result3 = depth <= s3;
// Transform to texel space.
float2 texelPos = SMAP_SIZE*projTexC.xy;
// Determine the interpolation amounts.
float2 t = frac( texelPos );
// Interpolate results.
return lerp( lerp(result0, result1, t.x),
lerp(result2, result3, t.x), t.y);

但是这样的话采样点就是原来的四倍,效率就低了很多,好在dx支持硬件的4点PCF采样,代码如下

Texture2D gShadowMap : register(t1);
SamplerComparisonState gsamShadow : register(s6);
// Complete projection by doing division by w.
shadowPosH.xyz /= shadowPosH.w;
// Depth in NDC space.
float depth = shadowPosH.z;
// Automatically does a 4-tap PCF.
gShadowMap.SampleCmpLevelZero(gsamShadow, shadowPosH.xy, depth).r;

这里我们用的采样函数不再是Sample,而是SampleCmpLevelZero,cmp表示要拿来和cmp比较,level zero表示采样mipmap的第0级,实际上阴影贴图我们是不要生成一系列mipmap的,depth就是拿来比较的值,采样结果大于这个就返回1,小于就返回0,然后返回的结果是四个采样结果的平均,其中1是指不在阴影里,0是指在阴影范围里。
一般来说我们不会只用2x2的PCF核,例如下面实现的demo里就用了3x3次采样,(每次都是2x2,有重叠的部分)。

还有一种方法是先判断好阴影和被照亮区域的边界,在这个边界上用开销更大的PCF核,不在边缘范围上的就用0和1。

较大的PCF核
较大的PCF核会导致acne问题重新出现,原因如图
在这里插入图片描述
可以看到,p点不应该被阴影遮蔽,但是采样的三个点中,有两个点是没有遮蔽的,还有一个是在阴影范围内的,均值就是0.33,这样就出问题了,因为我们的p的深度是不变的,但是采样的时候采样的点不在同一个texel上,还拿p点的深度去比,当然会小于采样结果。这里可行的一种解决方案是用一个大一点的bias,但是如果PCF核更大点的话,就不适用了,就需要新的解决方法。
这里我们用不到这种方法,因为我们不会用特别大的PCF核,这里简单地提一下,HLSL里有个DDX函数和DDY函数,可以用来求一些量对x和y的偏导,这里的x和y指的是屏幕空间,假如(u,v,z)是光源坐标系下的点,根据链式法则
在这里插入图片描述
所以可以算出
在这里插入图片描述
现在我们知道了在光源坐标系下坐标变化(Δu,Δv)的话,屏幕坐标会变化(Δx,Δy),在pcf采样的时候,我们知道我们的采样间距(Δu,Δv),可以算出Δx和Δy,然后根据下式求得深度变化。
在这里插入图片描述
还有一种方法就是,根据链式法则有
在这里插入图片描述
我们直接求出z对u和v的偏导然后按下式计算深度偏移
在这里插入图片描述

阴影demo

接下来实现一个阴影demo,并且给出关键的代码

先封装了一个ShadowMap类,有点类似于之前的CubeMapping里的CubeRenderTarget类

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
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 《DirectX 12 3D游戏开发实战》是一本介绍DirectX 12游戏开发技术的电子书籍。DirectX 12是微软公司推出的一种图形API,用于开发Windows平台上的高性能3D图形应用程序和游戏。 这本书主要面向有一定游戏开发基础的读者,希望能通过实战案例来深入学习DirectX 12技术。书中首先详细介绍了DirectX 12的基础知识,包括API的架构、渲染管线、着色器编程等。然后,通过一系列实际案例,教授读者如何使用DirectX 12来构建完整的3D游戏。 在实战部分,书中涵盖了很多方面的内容,例如几何体的创建和管理、材质和纹理的处理、光照和阴影的实现、相机和观察矩阵的应用等等。通过这些案例,读者可以了解并掌握DirectX 12的重要概念和技巧,从而能够自己开发出优秀的3D游戏。 此外,书中还介绍了一些高级主题,如多线程渲染、延迟渲染、GPU计算等,帮助读者进一步提高游戏的性能和效果。同时,书中还讨论了一些常见的问题和挑战,如内存管理、资源加载、性能优化等,帮助读者更好地应对实际开发中的各种情况。 总之,《DirectX 12 3D游戏开发实战》是一本实用性很强的教程,对于想要深入了解和掌握DirectX 12技术的游戏开发者来说,是一本值得阅读和实践的书籍。 ### 回答2: 《DirectX 12 3D游戏开发实战PDF》是一本介绍使用DirectX 12进行游戏开发的实用资料。DirectX是由微软开发的一套多媒体技术,包括与计算机图形和声音相关的API(应用程序接口),用于游戏和多媒体应用程序的开发。 本书主要介绍了DirectX 12的基础知识和技术,并提供了大量实战案例和代码示例,帮助读者理解和掌握DirectX 12游戏开发的要点。其中,3D游戏的开发是该书的重要内容之一。 在这本书中,读者将学习到如何使用DirectX 12创建具有逼真图形效果的3D游戏。内容涵盖了3D图形的基本概念、着色器编程、模型加载与渲染、纹理贴图、光照和阴影效果等方面。通过学习这些内容,读者将能够掌握构建高质量和流畅的3D游戏所需的核心技术。 此外,本书还介绍了实时渲染技术、物理引擎、碰撞检测和基于DirectX的图形优化等高级主题。这些内容将帮助读者进一步提升游戏性能和开发效率。 《DirectX 12 3D游戏开发实战PDF》适合对游戏开发和计算机图形学感兴趣的读者阅读。不仅能够帮助初学者入门,还适用于有一定编程基础的读者进一步提升自己的技能。无论是学生、游戏开发者还是爱好者,都可以通过这本书了解和掌握现代游戏开发所需的技术和工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值