项目中使用了《DirectX 12 3D游戏开发实战》也就是龙书所给出的代码框架. 并且担心不同功能间相互影响, 整合在一起恐怕复杂度剧增, 自己水平不够难以掌握, 因此各个功能都是独立开来分别完成的.
在多数实现中可以用 WASD 进行移动, 按住鼠标左键转动视角.
另外在较低版本的的VS中打开项目可能会出现MSB8020错误.
MSBuild 8020错误 : 项目代码都使用Visual Studio 2019构建, 若使用低于2019的版本则会出现这个错误.
解决方法 : 项目 >> 属性 >> 常规 >> 平台工具集 >> 切换到对应版本工具集后确认即可.
几何着色器擅长创建与销毁几何图形. 在看过的示例中, 公告牌技术与粒子系统均使用几何着色器进行实现. 具体实现中, 为了便于观察细分将PSO设置线框模式, 并由数字键1,2,3(位于QWER键上面的数字键)来变更细分等级.
索贝尔算子部分利用计算着色器进行输出, 计算着色器独立于渲染流水线之外, 因此其调用(不是 Draw Call 而是 Dispatch Call), 格式(UAV), PSO设置, 输出设置(RWTexture2D"<"Type">")等等均与平时有所不同, 需要留意. 另外, Dispatch 分派调用时, 需指定线程数, 而N卡与A卡对线程数的对齐要求不同.
法线与深度部分曾尝试使用 Roberts 算子进行边缘检测, 但效果并不好, 边缘会断断续续. 因此使用较为粗糙的办法, 对3x3范围内的点进行采样比较来检测边缘.
按住鼠标左键可转动视角, 按住鼠标右键可控制距离.
左上为源图像, 右上为索贝尔算子的逆图像
左下为法线边缘, 右下为深度边缘.
在波浪模拟中因为顶点位移只涉及Y轴(即高度)一个维度上的位移, 因此利用法线贴图的A通道, 将两者整合到了一起.
波浪的几何用了曲面细分来做的.
在DX12中, 顶点着色器与几何着色器中间有一个由三部分组成的曲面细分阶段:
- 外壳着色器: (1.1) 常量外壳着色器, 用于控制边与内部的细分因子、 (1.2) 控制点外壳着色器
- 镶嵌器阶段, 根据细分因子进行曲面细分, 无法控制
- 域着色器, 相当于细分后新增顶点的顶点着色器
另外, 在域着色器中似乎只能使用 SampleLevel 方法对贴图采样(即必须指定 mipmap 等级). 后面查了后才知道 sample 方法GPU会自动指定 mipmap 等级, 通过 ddx 和 ddy 来看相邻像素间的uv坐标差距, 差的越多证明物体距离摄像机更远, mipmap 等级也要更高.
泛光效果的步骤如下:
- 正常渲染
- 提取出高亮部分
- 对高亮部分进行模糊
- 对原图像以及模糊后的高亮部分图像进行叠加
其中模糊部分使用了高斯模糊, 并利用其可分离性, 将 N² 次采样降低为 2N. 对于其他不具备分离性的卷积操作, 也可以这样操作来减少采样, 优化性能.
在具体实现中, 问题主要出现在最后一步上, 直接叠加的话, 高亮部分画面一片白, 观感很差.
给高亮部分加个权值或者使用 Reinhard 色调映射(其公式为 A / (A + 1))的话, 效果也不是很好.
后面参考了 LearnOpenGL 上 HDR 部分的曝光色调映射.
https://learnopengl-cn.github.io/05%20Advanced%20Lighting/06%20HDR/
曝光色调映射公式为: 1.0 - exp(-Color * exposure), 其中 exposure 可理解为曝光度, 其值越高, 暗部细节越多, 反之则亮部细节越多.
其中左下角小窗口为高亮部分模糊后的结果.
相较于PCF, PCSS主要改善在可以动态调整filter大小, 其核心主要有三步:
- 搜索一定范围内的遮挡物平均深度
- 根据平均深度来计算PCF的filter大小
- PCF部分
计算filter大小的公式为:
在具体实现中, 第一步搜索平均深度的范围定为了固定大小. 而第三步中过多的采样会对帧率影响极大.
因此使用16个点的泊松分布采样. 这一部分参考了 Nvidia 的实现:
https://developer.download.nvidia.com/whitepapers/2008/PCSS_Integration.pdf
但是因为采样分布固定且数量太少会导致阴影分层,特别是filter较大时分层很严重. 效果如下:
1. 阴影分层
如果生成伪随机数, 对泊松分布进行旋转, 可以改善分层, 但又会导致噪点问题. 而比噪点更影响观感的是近距离时阴影会有类似于摩尔纹的部分(红圈处), 效果如下:
2. 阴影噪点与摩尔纹
对于这样的噪点与摩尔纹, 在虎书上看到过高斯模糊对其效果很好. 但这要把阴影部分单独渲染到一张纹理上进行模糊再叠加回去, 一是很耗性能, 二是很麻烦.
后面参考了 Games 202 作业1的框架, 其中对于每个纹理坐标都重新生成一次随机的泊松分布盘, 只需增大一些泊松分布盘以及采样数量即可得到较好的软阴影, 效果如下:
3. 每次重新生成泊松分布盘
虽然PCSS软阴影的实现流程很容易理解, 但想要高效实现却很难.
延迟渲染需要两个pass, 第一个pass中将计算光照所需的各种数据写入到渲染目标, 第二个pass中使用这些数据来计算光照.
在具体的实现中, pass1用到了格式不同的三个渲染目标, 分别储存1.位置、2.法线、3.颜色, 并在位置、法线渲染目标空余的A通道储存粗糙度等数据. pass2绘制一个全屏四边形,并对对应纹理坐标的三种数据进行采样用以计算光照.
其中三个小窗从左到右分别对应1.颜色2.法线3.位置
复用前面延迟渲染的部分来做的实现, 主要问题出在步长和深度容差, 步长稍大时会分层, 容差稍大时则会糊成一片. 面多加水, 水多加面, 调了半天效果都不是很满意.
PBR核心为渲染方程, 其公式如下
可将渲染方程拆分为两部分: 漫反射 + 镜面反射.
其中漫反射有兰伯特模型以及迪士尼的模型, 而镜面反射为Cook-Torrance模型.
需要利用此方程计算的光源也可以分为两种: 一是数量已知、位置固定的直接光, 可以直接套公式计算; 二是位于法线半球上的环境光, 实时渲染中中用 IBL 来近似实现.
具体实现参考了LearnOpenGL以及知乎上的众多文章. https://learnopengl-cn.github.io/07%20PBR/03%20IBL/01%20Diffuse%20irradiance/
着色器文件均位于 Shaders 文件夹下, 其中 Common.hlsl 包含了DGF 项的实现以及IBL中所需要的辐照度、BRDF积分等计算. 而PBR主体的实现位于 PBR.hlsl 文件当中.
在程序运行中可用WASD进行移动, 按住鼠标左键可转动视角.
其中对DGF三项的实现代码为:
G: 几何遮蔽, 分为两部分, 一是因微表面不平整而产生的对于入射光的几何阴影, 二是对于出射光的几何遮蔽