This commit is contained in:
2023-06-29 11:55:02 +08:00
commit 36e95249b1
1236 changed files with 464197 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
## 前言
本文仅为使用Ornatrix制作角色头发的流程笔记只为记录制作流程。但因为本人非专职美术没有分析过面片头发也没有制作头发经验所以本文仅供参考。
## Ornatrix的优点
插件目前支持max、maya、c4d。拥有大部分xGen的功能我无法保证所有功能都有除了制作头发之外它还可以制作羽毛与编织物。以下是官方介绍视频
https://www.bilibili.com/video/av80061009
个人觉得优点有下:
### 节点式的流程
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/OrnatrixOutline.png)
节点式流程就代表着流程是非线性的,这意味着你可以任意拖动节点位移或者可以随时对节点树中的节点进行修改,并预览效果。同时你可以把做成完成的头发保存为预设,下次再遇到相似的发型就可以直接使用了,以此加快制作进程。
### 各种使用节点
梳理节点:
你可以通过画几个箭头就完成对发型的大致梳理。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/SurfaceOnCombTool.png)
旋转节点这个节点对于游戏面片头发的制作相当不错。另外Ornatrix可以设置生成面片的段数、UV等其他属性这也是我放弃xGen转而使用Ornatrix的原因。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/CardGenerate1.png)
使用旋转节点前
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/CardGenerate2.png)
使用旋转节点后
另外还有Frizz、Curl等节点出个大形的速度非常快。
## EPIC模型分析
这里本人使用虚幻争霸中的一个角色作为参考因为本人能力有限所以使用官方提供的头发材质进行渲染。这样只需要制作出符合高度贴图、id贴图即可。这里我为了方便分析贴图所以给不同的头发进行填色以此来判断头发组成。因为本人不知道如何在Maya中解决透明排序的问题所以结果就凑合地看吧
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/HairColorVisual.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/HairColor.png)
- 红色为最底层的头发,形状和头皮一样为了防止穿帮。
- 蓝色为主要头发,头发相对于红色部分会稍微稀疏一些。
- 剩下的黄色、绿色、亮红色为点缀部分。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/HairColorVisualLayer.png)
## 制作流程
### 设计发型与分块
尽管Ornaterix可以通过各种分组手段对不同类型头发进行分组使得可以将所有头发效果都做到一个Ornaterix节点中。但是我还是选择了使用多个Ornaterix物体来制作不同类型的头发。我这么做的原因有以下几点
1. 方便分组,可以随时隐藏头发以便于观察其他头发。
2. 如果其中一个崩溃报错了,不至于所有的工作都白费了。
### 制作头发
以下我分享以下我所知道的制作思路首先我本人采用了导入外部引导曲线的方式来制作头发因为之前学习了一段时间的xGen所以可以直接将之前制作的引导曲线导入Ornatrix中。
- 对于一些短发可以直接生成通过生成几根引导曲线,生成头发。之后再用这些头发生成更多的引导曲线来生成头发模型。
- 对于一些有着具体形状或是沿着路径生成的头发可以使用Ornatrix的发片add hair from mesh strips功能快速生成适用于睫毛、打底层头发等。
上述这些曲线以及面片生成都是带了历时的,你可以随时通过修改面片与曲线来调整头发生成结果。
模型替代:
https://www.bilibili.com/video/av56489034
这个视频中可以看出,作者使用了三棱锥模型,而非普通面片,这个可以使用(add Mesh from stands)中的proxy mesh stands来实现。从视频中可以看得出使用这个方法增强了头发的立体感。
长发不适合用随机生成的方法来制作,且需要精准控制遮盖、效果与面数,所以对于长发我推荐使用官方的方法来做:
https://www.bilibili.com/video/av79618784
### 导出模型
直接导出貌似是无效的可能是我Maya的问题我搜索了油管与文档都没有发现导出方法。所以以下的方法我不能保证一定是正确的
在加入Mesh Frome Stand节点后将Maya设置为多边形选择模式选中需要导出的面之后选择复制面命令。之后多边形就会出现在大纲视图的Or节点下面了。
### 烘焙id贴图与高度贴图
我因为对Vray、Arnold烘焙流程不熟且不会烘焙分组的id贴图所以还是选择简单易懂的xNormal流程
https://www.bilibili.com/video/av56489034
使用这个流程在烘焙id贴图前还需要把毛发模型进行分组并导出。作者提供的插件是根据百分比随机从当前物体中选择物体所以如果你要分成5份就需要依次使用20%、25%、33%、50%分离物体并导出。因此对于这个流程,插件还有很大的改进空间。
如果你对Arnold或者Vray以及maya节点系统熟悉的话可以选择官方流程这个流程的优点是制作完马上就可以预览效果
https://www.bilibili.com/video/av79619146
### 是先制作模型还是先制作贴图?
个人还是倾向于先制作贴图,因为制作贴图本身不会受制于模型,而且在之后的模型制作中就可以随时预览制作效果了。如果先制作模型,很可能会出现因为效果没有达到预期,而造成返工修改模型的情况。
## 官方网站与国内购买网站
官方网站:
http://www.ephere.com/
官方油管频道:
https://www.youtube.com/channel/UCzzR4qt--4OcXgvyUTsgW6w
文档地址:
https://ephere.com/plugins/autodesk/maya/ornatrix/docs/2/
官方频道上的教程挺多的再加上这个插件使用不难很多基础功能光看英文就能知道如何使用再看看文档与视频就差不多。但如果你想快速入门我推荐aboutcg的教程。
国内有aboutcg代理了以下是购买网址
https://tool.aboutcg.com/tool/ornatrix-maya/
## 学习过的资料
https://new.80.lv/articles/tips-tricks-on-hair-for-games/
视频的话我已上传到B站
https://www.bilibili.com/video/av56489034
Ornatrix官方视频
https://www.bilibili.com/video/av79618784
https://www.bilibili.com/video/av79619146
https://www.bilibili.com/video/av79618545
## 使用插件
我使用了《Tips & Tricks on Hair for Games》作者提供的Maya插件。
```
https://pan.baidu.com/s/1DDAj9bdMTMzvpU9ZnFevrw w6ar
```

View File

@@ -0,0 +1,67 @@
## 前言
4.24出了头发渲染与模拟功能,这真是一件令人兴奋的事,所以我稍微花了点时间进行测试,在此分享一些经验。
### 使用步骤
使用步骤大致如下具体的可以参考文档https://docs.unrealengine.com/en-US/Engine/HairRendering/QuickStart/index.html
1. 首先在项目设置启用Support Compute Skincache,并禁用Tick Animation on Skeletal Mesh Init。
2. 启用Alembic Groom Importer与Groom插件之后重启引擎。此时大概会编译大概5000多的Shader
![image](https://pic3.zhimg.com/v2-05069ca0849d59afee7d02e289edbf26_r.jpg)
1. 导入带有Groom信息的abc缓存文件。
![image](https://pic3.zhimg.com/v2-3c65d36047e05d909b39106e5fce1e7a_r.jpg)
4. 设置Groom Asset属性。主要是设置头发宽度
5. 设置Groom材质。ShadingModel需要设置hairGroom对象的材质有两种设置方式1、从ContentBrowser中设置GroomAsset的属性 2、在场景中设置Groom对象的属性
![image](https://pic3.zhimg.com/v2-a60f943d7d801da07a7834949e7c9786_r.jpg)
6. 将GroomAsset拖入场景中并且attach到场景中的骨骼模型上并调整位置。直接放入蓝图的骨骼模型引擎会崩溃
![image](https://pic1.zhimg.com/v2-c9b7ed1a85e63911c8430e809488e420_r.jpg)
7. 设置场景中的Groom物体属性给它挂载Niagara Particle System并且设置GroomAssetSystem。
![image](https://pic4.zhimg.com/v2-5877574508ff6a77038ea770e36d1793_r.jpg)
![image](https://pic2.zhimg.com/v2-ba1db289e98e5478476fb7bb43071b51_r.jpg)
8. 设置Niagara物理参数。
**最终结果:**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/hair.gif)
**关于头发对齐问题与Bind Groom to Skeletal Mesh选项**
我是复制了头皮模型并以此为基础制作头发的。所以理论上头发是不需要对齐。但导入后头发还是需要手动位移可能是我导出的时候没有勾选Unreal Engine Export的关系
**Bind Groom to Skeletal Mesh**这个选项应该让Groom根据蒙皮数据进行运动但我用下来感觉这个选项没什么卵用如有正确思路还请告知而且头发会发生错误的位移。然后在你勾选这个选项后将GroomAsset清空或者将Groom对象从骨骼物体上解除Attacked都会触发一个LOD的断言从而导致引擎直接关闭。这明显是因为容错逻辑没有写完所造成的的但也可以理解毕竟4.24.1出的时候都要过年了。
**Hair to Guide Density**
导入的头发与Groom和Ornatrix渲染的头发量Hair From Guides中的Render Count有关和Ornatrix的引导曲线无关。因为本人没有看过源代码所以以下是个人的无责任猜测
Ue4在导入头发时(abc里的数据还是类似曲线一样的东西),会根据头发分布重新生成"引导曲线"以此来进行物理模拟。因为我在进行测试的时候发现3000个引导线与9000个引导线在静态环境下渲染结果与帧数都是相同。而在设置Niagara Particle System与GroomAssetSystem时会耗费较长的时间。
所以Hair to Guide Density的默认参数0.1,这个就不要更改了。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/Hair.png)
从这个图中可以看出一些靠近边缘的区域需要增加头发密度不然就有可能出现图中这种25岁程序员的头发。
### Ornatrix导出abc
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Character/OrnatrixExport.png)
导出abc的时候需要在Ornatrix的大纲视图选中最上面的节点实际上就是选中Ornatrix的形状节点你可以Maya的关系编辑器中通过查看上下游节点进行查看。
之后点击“导出当前选择”选择OrnatrixAlembic文件类型。勾选
文件类型特定选项-Export Components-Unreal Engine Export选项之后就可以导出了。
### Groom ID
Ornatrix中可以通过StandGroup对头发进行分组编辑。Ue4会通过这些StandGroup作为Groom ID数据。你可以对Groom ID的头发使用不同的参数。这样可以实现一个GroomAssetabc文件存储头发、睫毛、眉毛多种类型毛发不同的宽度与材质
Strand Groups一般是通过Edit Guides节点进行指定的。指定步骤如下
1. 在Edit Guides,点击Edit Stands按钮进行编辑Stands模式。
2. 选中需要添加组的头发。
3. 之后在StandsGroups选项卡中勾选Use Stands Groups并在调整Stands Group Index后点击Assign。
4. 此时取消头发选择,如果之前选中过的头发变色了,就代表分组已经成功。
**Ornatrix导出文档**
https://ephere.com/plugins/autodesk/maya/ornatrix/docs/2/Exporting_Hair_to_Alembic.html
**Strand Groups文档**
https://ephere.com/plugins/autodesk/maya/ornatrix/docs/2/Strand_Groups.html
### 个人评价
因为本人电脑存在问题,打开一个测试场景帧数都是不稳定的,所以本人没有进行性能方面的测试。但是我感觉目前渲染效率还不错(帧数没有下降特别多)。目前存在问题,容易崩。再过几个版本迭代就可以直接拿来做项目了吧。
### 官方文档中所说的限制
1、帧数受Groom大小、分辨率与硬件影响。
2、目前不支持多屏显示与VR。

View File

@@ -0,0 +1,163 @@
---
title: RenderDoc使用技巧
date: 2022-09-30 11:17:29
excerpt:
tags: RenderDoc
rating: ⭐⭐
---
## 前言
参考https://zhuanlan.zhihu.com/p/568990608
## UE相关设置
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20220930121129.png)
### UE5中的改动
UE5中这些名称有了一定的变化
| 旧名称 | 新名称 | 注解 |
| --------------------------------------- | ------------------------ | -------------------------------------------------------------------------- |
| r.Shaders.KeepDebugInfo | r.Shaders.Symbols | 通过生成符号并将其写入主机的磁盘来启用着色器调试PC符号仍以内联方式存储。 |
| r.Shaders.KeepDebugInfo(被划分为两部分) | r.Shaders.ExtraData | 生成着色器名称和其他"额外"着色器数据。 |
| r.Shaders.PrepareExportedDebugInfo | r.Shaders.GenerateSymbol | 生成符号,但不将其写入磁盘(备注符号存储在DDC中) |
| r.Shaders.ExportDebugInfo | r.Shaders.WriteSymbols | 如果符号已生成,则将其写入磁盘。 |
## 其他设置
renderdoc.BinaryPath  // 查看RenderDoc的安装路径
renderdoc.BinaryPath "C:\Program Files\RenderDoc"  // 设置RenderDoc的安装路径为C:\Program Files\RenderDoc
renderdoc.CaptureAllActivity 1   // 勾选CaptureAllActivity获取编辑器所有viewport和窗口的渲染数据而不仅仅是当前viewport  获取UI的Draw需要开启该开关
renderdoc.CaptureCallstacks // 查看CaptureCallstacks获取图形API的调用堆栈是否勾选
renderdoc.EnableCrashHandler 0 // 取消勾选EnableCrashHandler截帧崩溃时是否使用RenderDoc的crash handler
renderdoc.ReferenceAllResources  // 查看ReferenceAllResources获取所有mesh、材质纹理等渲染资源开启该选项会导致帧文件很大是否勾选
renderdoc.SaveAllInitials 1 // 勾选SaveAllInitials获取所有mesh、材质纹理等渲染资源的初始状态开启该选项会导致帧文件很大
renderdoc.ShowHelpOnStartup  // 查看ShowHelpOnStartup启动编辑器时是否弹出RenderDoc的帮助对话框是否勾选
>调试Slate所需命令renderdoc.CaptureAllActivity 1
## 截帧命令
- renderdoc.CaptureFrameCount  10  // 连续截10帧并保存到一个rdc文件中
- renderdoc.CapturePIE 12 // 在编辑器中将当前地图Play然后连续截取12帧
- renderdoc.CaptureDelayInSeconds 0   // 设置Delay的单位为帧
- renderdoc.CaptureDelayInSeconds 1   // 设置Delay的单位为秒
- renderdoc.CaptureDelay 15  // 当Delay的单位为帧时表示设置延迟15帧当Delay的单位为秒时表示设置延迟15秒
//  这个只是设置仍然需要调用renderdoc.CaptureFrame等来发起截帧动作
## 重新编译Shader
- r.RecompileRenderer重新编译所有RenderModule。
- recompileshaders位于`bool RecompileShaders(const TCHAR* Cmd, FOutputDevice& Ar);`可以编译指定的Material。命令默认编译所有Shader快捷键是Ctrl+Shift+.。`recompileshaders material <material name>`
- Changed编译修改过文件
- Global编译GlobalShader
- Material **MaterialName**:附带一个参数材质名称
- All所有文件
- **ShaderFileName**编译指定的Shader
有人说`recompileshaders <USF FileName>`可以只编译指定的USF文件但实际测试会报错。或许可以试试Material版本比如`recompileshaders material M_SGSSS`
## 截取非管线Shader的方法
在你EnqueueRenderCommand前加一个FScopedCapture 跑到你的Cmd的时候就可以自动Renderdoc截帧 ,类为FcopedCapture。
## 使用技巧
### 修改ms显示耗时
![](https://pic1.zhimg.com/80/v2-804879d0e02dbd0622f6480b9dd5040c_720w.webp)
### 过滤高耗时DrawCall
使用$action(duration > 0.8ms) 进行过滤
![](https://pic4.zhimg.com/80/v2-6598ae8de61ecf7256aa2b2860fa622b_720w.webp)
### **调试VS**
Mesh Viewer中vs input选中或在preview窗口中鼠标右键选中顶点在选中顶点行上右键debug thie vertices
![](https://pic3.zhimg.com/80/v2-3001ac53f0fa085e47df236c0430d2ea_720w.webp)
### **调试PS**
Texture Viewer中右键选择像素在Pixel Context中心就是选中的像素选择需要调试的历史时间点击“Debug”调试
![](https://pic1.zhimg.com/80/v2-c7ab8ffce562b57fe12234a1a39952c4_720w.webp)
### 修改VS
![](https://pic4.zhimg.com/80/v2-b7acd2f64495b688274e6bb5ca6ea06b_720w.webp)
选中dc高亮绘制
![](https://pic1.zhimg.com/80/v2-246cc1577f0a61d68926299a30e488d8_720w.webp)
Pipeline state进入VS
![](https://pic2.zhimg.com/80/v2-37211dff3c6e57067255d6e91d08c6a1_720w.webp)
修改坐标
![](https://pic1.zhimg.com/80/v2-16f647910f37dc54ba7e09c199813a60_720w.webp)
Texture View中预览位置变化
### 修改PS
![](https://pic1.zhimg.com/80/v2-ba7d6b23e94fcf541c5815beacfbf674_720w.webp)
进入pipeline state 编辑ps
![](https://pic3.zhimg.com/80/v2-7442e83ed016b30a3bdd1745912c9c6e_720w.webp)
修改前颜色
![](https://pic2.zhimg.com/80/v2-4878401ef7cae939c76912135106500d_720w.webp)
修改base color为红色
![](https://pic3.zhimg.com/80/v2-cb6a39670c6d94c8703314239f00ebd2_720w.webp)
修改后效果预览
### 查看深度模板测试结果
![](https://pic1.zhimg.com/80/v2-10b497754fb1ed7ba0e12ced038e6fd4_720w.webp)
红色测试不同多,绿色测试通过
### 查看纹理在那些事件引用
![](https://pic4.zhimg.com/80/v2-ee8e0720527ca27045b8cefdeb483433_720w.webp)
![](https://pic2.zhimg.com/80/v2-8c3ed3a794c7b2fca04d8fda4ade6195_720w.webp)
PS 资源种点击链接
![](https://pic1.zhimg.com/80/v2-c70907dd5c33daf560f1be6405bc2a34_720w.webp)
Resource Inspector中右侧查看那些事件使用了此资源
### 纹理太暗
![](https://pic3.zhimg.com/80/v2-d7bdb13af09c789d374523b5df1e6b7e_720w.webp)
![](https://pic2.zhimg.com/80/v2-07d9e1d97230e47a22d641aae5cbbaa1_720w.webp)
### **查看DrawCall耗时**
![](https://pic2.zhimg.com/80/v2-9fc83fdaadf6a453e9dbccefb83ff65d_720w.webp)
### 查看纹理输入输出
![](https://pic2.zhimg.com/80/v2-f464f36300516d1c4c613820d2779fbd_720w.webp)
![](https://pic4.zhimg.com/80/v2-3e224c777f579d5bdcc8752a06231217_720w.webp)
### 重名名纹理
![](https://pic4.zhimg.com/80/v2-a08a7d270a4c8de3c952298ba209bcfb_720w.webp)
### 如何对比数据
将过滤后的数据导出为文本使用对比工具进行对比。用于发现dc耗时问题
![](https://pic3.zhimg.com/80/v2-358566c0e1500019840fc914d7394fc2_720w.webp)
![](https://pic4.zhimg.com/80/v2-84f8f79ac5d241946aeabaa59576a947_720w.webp)
![](https://pic4.zhimg.com/80/v2-6dca8e57407f84970125b9114b9bde2f_720w.webp)

View File

@@ -0,0 +1,134 @@
---
title: UE5RayTracing篇-1-——NSight-Graphics
date: 2022-11-11 17:51:43
excerpt:
tags:
rating: ⭐
---
最近打算开始研究UE的RayTracing但欲先利其事必先利器必要的Debug手段还是需要会用。在发现RenderDoc不支持RayTracing后花了些时间学习了一下NSight Graphics。一般的流程就是使用NSight启动要调试的游戏项目进程之后按F11截帧并使用所需工具查看。下面的章节将会简单介绍一下它的功能。
<!--more-->
## UE RayTracing与Shader开发相关
UE的RayTracing Debug界面需要输入命令后才会显示
```
show raytracingdebug 1
r.RayTracing.DebugVisualizationMode Performance
r.RayTracing.DebugVisualizationMode Traversal Node
```
UE5.0.1这些命令无效5.02虽然可以显示Debug界面了但里面不会渲染任何东西。
为了能够截取到Shader的源码并使用RenderDoc的断点调试需要开启Shader开发变量。位于Engine/Config/ConsoleVariables.ini中
```
//删除前面的//即可开启
r.ShaderDevelopmentMode=1
r.Shaders.Optimize=0
```
## NSight
介绍视频:
- https://youtu.be/yBIKsjd2dJk
- https://youtu.be/LKR5XIW1lgs
文档:
- https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/index.html
- https://docs.nvidia.com/nsight-graphics/AdvancedLearning/index.html
个人觉得相比RenderDocNSight的优势在于
- 更加注重性能统计与优化,适合图形程序使用。
- 拥有Rtx、VR相关工具可以让你debug Rtx、VR程序。
- 2个截帧文件比较功能。
PS.2020.6.1 附加进程到UE5会直接崩溃UE4.27虽然可以启动但依然会有问题。2021.2~2022.3的Shader Profiler的Soucre功能会因为SM不是SM6而只能查看Shader的汇编指令。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_SM6.png)
只能等UE5完全支持SM6后才能使用该功能。测试版本5.02开启项目设置里的DirectX12 (SM6,Experimental)功能也不行)
### 连接到进程
NSight有3种
1. 启动进程并附加设置ApplicationExcutable后点击Launch即可。调试UE4的话还需要设置CommandLineArguments为项目路径比如D:/UnrealEngine/Project/RtxRendering/RtxRendering.uproject
![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/frame_debugger_connect.01.png)
2. 手动连接已启动的进程是由通过NSight Graphics启动的进程才能连接选择启动的进程之后点Attach即可。
![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/frame_debugger_attach.02.png)
3. 远程启动在远程机器上运行Nsight Remote Monitor程序之后再本机上输入远程机器的IP即可。
进入界面后点击Capture for Live Analysis即可截取帧信息默认的截取按键为F11
### NSight工具
NSight有5种工具需要在Connect to process界面中的左下角选择
- Frame Debugger基本用这个 ![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/range_profiler.01.png)
- Frame Profiler ![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/shaderprofiler_summarypage.png)
- Generate C++ Capture
- GPU Trace Profiler
- System Trace
在选择完工具连接进程之后就会显示对应的界面。部分功能需要开启设备访问权限,没开启会显示:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_Error.png)
需要在Nvidia控制面板里进行开启步骤如下
![](https://developer.nvidia.com/sites/default/files/akamai/tools/Common/SupportSolutions/ERR_NVGPUCTRPERM_NvCtrlPanel-EnableDeveloperSettings.PNG)
![](https://developer.nvidia.com/sites/default/files/akamai/tools/Common/SupportSolutions/ERR_NVGPUCTRPERM_NvCtrlPanel-AllowAccessToPerfCtrs.PNG)
其他平台的步骤可以参考https://developer.nvidia.com/nvidia-development-tools-solutions-err_nvgpuctrperm-permission-issue-performance-counters
#### Frame Debugger与Frame Profiler
Frame Debugger工具包含了Frame Profiler这些工具里Shader Profiler工具最为重要。
- Shader Profiler
- Summary![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/shaderprofiler_summarypage.png)
- Function Summary显示Shader函数的GPU性能占用分布。
- Hot Spots显示Shader代码的GPU性能占用分布。
- Source 可以打开特定Shader文件并且显示每行代码的GPU性能占用分布。NSight2022.3版本无法显示UE5的Shader源码![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/shaderprofiler_sourcecorrelation.png)
- All Resource可以查看渲染说用到的贴图、Buffer以及场景模型的BLAS![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_AllResource.png)![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_BLAS.png)
- Geometry该功能需要选中绘制命令才能显示对应的多边形。具体操作是Scrubber中选中Event右键选择Open Event List再从Event窗口选择绘制事件即可Filter中输入Draw就可以找到绘制事件
- PixelHistory查看一个像素绘制过程的工具。可以通过在Resource中选择绘制的RT之后使用该工具点击想要查看的像素。 ![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_PixelHistory.png) ![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_PixelHistory2.png)
- Shader Timeing HeatingRtx相关工具可以查看Rtx Shader在屏幕上的资源占用分布图。但有bug。分析UE5的某个分发Ray事件必然会崩。![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_ShaderTimingHeatmap.png)
PS.Ctrl+滚轮放大与缩小Event时间轴在左上角Frame Debugger里可以打开其他工具所有的UI介绍可以参考https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/index.html#frame_debugging_profiling_ui
#### Generate C++ Capture
进入工具,点击**Generate C++ Capture**后会捕获并且生成一个VS工程。并且显示以下界面
![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/generated_capture_numbered.01.png)
该功能与RenderDoc保存截帧文件类似但NSight是截取当前帧的渲染情况并且生成一个不断渲染当前帧的程序以便后续复现与Debug。之后依次点击Build=>Execute=>Connect即可进入Frame Debugger界面。该工具还支持嵌入式Linux、桌面Linux。
#### GPU Trace
NSight的截帧功能在连接界面可以设置截帧范围截取的帧数另一个相比RenderDoc的优势就是比较2个帧文件。启动进程后点击**Generate GPU Trace Capture**就可以截取帧文件。并进入类似Frame Debugger的界面。
![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/gpu_trace_launch.01.png)
![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/gpu_trace_capture.01.png)
同时可以设置为高级模式,以截取更多的信息:
![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/gpu_trace_options_advanced_mode_config.png)
![](https://docs.nvidia.com/nsight-tools/nsight-graphics/UserGuide/graphics/gpu_trace_capture_advanced_mode_warning.png)
#### System Trace
需要额外下载Nsight Systems工具才能使用。该工具主要分析CPU与GPU直接交互情况。
#### 其他Rt Shader相关的功能
APIInspector的Rt Shader会显示额外所需的HitTable等数据
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_APIInspector_RtxShader.png)
##### Acceleration Structure View
首先可以看一下UE中的相关文档https://docs.unrealengine.com/5.0/en-US/ray-tracing-performance-guide-in-unreal-engine/
- BLASBottom Level Acceleration Structure Updates。静态模型的BLAS会在模型载入时构建。动态物体与骨骼物体会每帧构建。
- TLASTop Level Acceleration Structure。TLAS会每帧构建。
在NSigh中可以从API Inspector或All Resource只能显示BLAS中打开并查看。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/NSight_ShowTLAS.png)
视角控制:
- WASD — 向前、向后、向左或向右移动相机
- 箭头键- 向前、向后、向左或向右移动相机
- E/Q - 向上/向下移动相机
- Shift/Ctrl - 更快/更慢地移动相机
- 鼠标滚轮——放大/缩小
- 鼠标左键 + 拖动- 向前或向后移动相机并向左或向右旋转
- 鼠标右键 + 拖动- 旋转相机
- 鼠标中间 + 拖动- 跟踪相机(向上、向下、向左、向右移动)
- 鼠标左键 + 鼠标右键 + Drag — 跟踪相机(上、下、左、右)
- ALT + 鼠标左键 + 拖动- 围绕选定几何体旋转相机
- ALT + 鼠标右键 + 拖动- 放大/缩小
- 双击或 F - 将相机聚焦在选定的几何体上

View File

@@ -0,0 +1,57 @@
---
title: UE5RayTracing篇-2-——Pix与NSight-Graphics补充
date: 2022-11-11 17:53:44
excerpt:
tags:
rating: ⭐
---
## 前言
最近继续学习Rtx所以就总结一下相关debug工具的使用方法。当然如果你是A卡用户大概就需要[【新鲜资讯】Radeon™光线追踪分析器RRA1.0正式上线](https://zhuanlan.zhihu.com/p/549772737)。
## Pix
下载地址位于https://devblogs.microsoft.com/pix/download/
1. 首先启用`Pix for UnrealEngine`插件。
2.`ConsoleVariables.ini`中添加`r.D3D12.AutoAttachPIX=1`,或者在启动命令也就是`CommandLineArguments`最后添加`-attachPIX`
3. 在Pix中设置启动参数点击启动就可以截帧了。
>注意Pix与RenderDoc插件冲突需要关闭RenderDoc插件才能正常运行。
可以通过点击GUI中的照相机图标或者按下键盘上的PrintScreen截图键键来截帧。直接点击摄像机图标是没办法截到场景信息的所以只能用鼠标点击场景窗口让窗口获得焦点之后再按截图键才能截到。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114134346.png)
查看渲染过程只需要GPUCapture如果要调试性能就需要使用TimingCapture。具体操作可以参考PIX的操作视频教程位于https://www.youtube.com/watch?v=rLClOkrE47w&list=PLeHvwXyqearWuPPxh6T03iwX-McPG5LkB&index=2
PS.Collect Timing Data需要在win10上开启`开发人员模式`,具体操作为
1. 打开开始菜单,输入`开发者`
2. 进入`开发者选项`,勾选开发人员模式。
### 4个功能Tab
Pix的主要功能集中在这4个Tab中。
Overview主要是展示一下EventList在点击`CollectTimingData`下方会显示时间轴。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114155739.png)
Pipeline显示当前Event的Shader、管线状态以及其他相关数据。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114152359.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114151354.png)
想要DebugShader可以在Pipeline找到对应Shader并在Shader文件上右键点击`Open In Debugger`后进行调试。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114154405.png)
当然也可以在输入的RT上双击鼠标左键之后点击`DebugPixel`进行debug。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114154555.png)
Debugdebug时需要点击左上方Overview下面的运行按钮这里我已经点了。这里我很好奇为啥没有类似Renderdoc的断点功能。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114155712.png)
Tools大概是一个测试性能的工具可以测试Basic Information、Depth/Stencil、Primitives and Rasterization、Bandwidth、TDR Analysis、ExecuteIndirect。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221114155948.png)
## NSight For VisualStudio
之后测试了一下NSight的VS插件虽然本质上就是一个帮你填写启动参数的工具但的确方便。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221111175647.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221111175715.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221111182344.png)
测试完之后就能查看光追的一些参数。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221111183638.png)

View File

@@ -0,0 +1,77 @@
---
title: CAD可视化与MovieRenderQueue笔记
date: 2021-09-16 13:55:15
tags: Film
rating: ⭐️
---
## 前言
本文为一些个人经验总结,仅供参考。
## DataSmith
### ReTessellation
对DataSmith导入的物体进行重新细分增加或者减少面数这样就不用重新导入了。
## DataPrep Tools
可以代替python来实现Asset处理自动化。当然会Python就可以定制自动化命令了。
## 初始化
1. 将曝光单位设置成EV100可选
2. 使用SM_ColorCalibrator调整场景曝光度。推荐使用Automotive Materials中BP_Calibration
3. 仅使用天光作为照明源。环境球使用引擎的SM_SkySphere。
4. 启用r.RayTracing.Reflections.ReflectionCaptures使用ReflectionCaptures作为最后一次反射反弹结果。
## 解决临时轴心问题
1. 创一个空Actor作为轴心并设置成Moveable之后将其移动到对应位置。
2. 将CAD模型以及其根物体都改成Moveable之后将之前的空Actor作为root节点。
## 使用Automotive Materials的问题
在Raytracing下使用Automotive Materials会出现第二次反射物体变黑的问题这是官方做的优化措施但CAD产品可视化会注重反射效果所以需要把这个措施给去掉。具体做法是把父材质中最后的MF_Bounce_Rough去掉就可以了。
## MovieRenderQueue
### 输出Alpha
Movie Render Queue中有.exr和.png两种图片格式可输出带Alp通道但仅勾选“output Alpha"还不行。须在项目设置中找到”Enable alpha channel support in post processing“(在后期处理中启用透明度通道支持改为“Linear color space only”仅限线性空间颜色
>勾选“output Alpha"已经被替换为Accumulate Alpha选项在Output选项卡中。
同时还需要将天空球设置为Hidden In Game。
### 抗锯齿:
不管怎么设置画面还是会出现一些噪点如果是制作视频可以使用MovieRenderQueue来解决本人使用的参数为
- Temporal Sample Count16
- Render Warm Up Count64
- Engine Warm Up Count64
本人只需要好的反射效果所以TAA采样数不会设置太高。其他有动态模糊以及其他与TAA相关的效果就需要适当把Temporal Sample Count提高了。
在抗锯齿中
关闭TAA
测试结果:渲染结果没有锯齿,但部分反射效果会有微小锯齿
### 控制台变量:
- r.RayTracing.Reflections.MaxBounce 7
- r.RayTracing.Reflections.SamplesPerPixel 64
7次光线反弹以及64spp效果已经达到要求了。
### 关闭降噪器
使用MovieRenderQueue时因为不太关注实时性能所以可以关闭降噪器来提高渲染效果精度。
- r.AmbientOcclusion.Denoiser: 0
- r.DiffuseIndirect.Denoiser: 0
- r.RayTracing.SkyLight.Denoiser: 0
- r.Reflections.Denoiser: 0
- r.Shadow.Denoiser: 0
- r.RayTracing.GlobalIllumination.Denoiser: 0
经过测试,在关闭反射降噪器之后,对于拉丝金属表面这种有微小细节的表面,细节会锐利很多。
## 修复渲染Slot第一帧时的问题
在使用Sequence以及MovieRenderQueue渲染的时候经常发现Slot的第一帧会出现渲染错误。解决方法是将CameraCut中的轨道往前拉1~2帧即可。
## 增加物体运动的连贯感
简单的方法就是使用动态模糊或是提高帧数。但我制作的视频可能会随时暂停查看具体细节,所以我选择提高帧数并且关闭运动模糊。
## DLSS
DLSS可以在同一画质下提高帧率。因此你可以在使用DLSS的情况下适当提高采样以减少噪点。
需要注意目前版本不会对MovieRenderQueue产生效果4.27已经可以了)。

View File

@@ -0,0 +1,32 @@
---
title: 使用MovieRenderQueue输出视频格式
date: 2023-04-28 11:05:46
excerpt:
tags:
rating: ⭐
---
# 前言
具体可以参考**Command Line Encoder**章节官方文档https://docs.unrealengine.com/5.1/en-US/cinematic-rendering-export-formats-in-unreal-engine/
# 下载编码器
首先我们需要下载编码器这里使用ffmpeg。下载地址
https://ffmpeg.org/download.html#build-windows
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230428111356.png)
这里我选择第一个网址之后选择一个FullBuild版本即可。
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230428111637.png)
# 设置编码器
`ProjectSettings - Plugins - Movie Pipeline CLI Encode`中进行设置。
- Executable Path设置ffmpeg.exe的路径。
- Video Codec填写视频编码格式我这里填写libx264。
- Audio Codec填写音频编码格式我这里填写aac。
- Output File Extension填写视频后缀名我这里填写mp4。
具体可以参考doc/ffmpeg-codecs.html的**Video-Encoders**与**Audio-Encoders**以及doc/ffmpeg-formats.html。
# 输出用于剪辑的更高质量视频
具体可以参考官方文档的**Apple ProRes Video Codecs**或**Avid DNx Video Codecs**章节。
大致步骤是启用**Apple ProRes Media**或**Avid DNxHR/DNxMXF Media Plugin**插件。之后就可以在MRQ中添加输出**Apple ProRes**或**Avid DNx [8bit]**,最终输出的格式为**mov**与**mxf**。

View File

@@ -0,0 +1,81 @@
---
title: 提高光线追踪反射效果
date: 2021-02-07 13:55:15
tags: Film
rating: ⭐️
---
## 关闭与光追冲突的选项
## 让反射效果柔和
r.Reflections.Denoiser 1
## 解决低精度法线造成的反射效果不佳的问题
- 项目设置-Engine-Rendering-Optimizations-Support depth only index=>HeightPrecisionNormals
- 针对个别物体可以通过勾选StaticMesh的Use High Precision Tangent Basis,之后电解Apply Changes
## 对最后一次光线反弹后对环境球进行采样
r.RayTracing.Reflection.ReflectionCaptures 1
## 调整天光的Raytracing采样数目
默认是4可以适当提高
## MovieRenderQueue
输出设置
- Anti-Aliasing:SpatialSampleCount:5
- TemporalSampleCount:3
- Override AntioAliasing:true
文档中的参数rtx2080ti
- 空间采样数量Spatial Sample Count1
- 临时采样数量Temporal Sample Count64
- 覆盖抗锯齿模式Override Anti Aliasing Mode已启用Enabled
- 抗锯齿方法Anti Aliasing MethodNone
- 渲染预热计数Render Warm Up Count120
- 引擎预热计数Engine Warm Up Count120
### Console Variable
![MRQ_Configure_Console](https://docs.unrealengine.com/Images/RenderingAndGraphics/RayTracing/MovieRenderQueue/MRQ_Configure_ConsoleFull.webp)
- r.MotionBlurQuality: 4
- r.MotionBlurSeparable: 1
- r.DepthOfFieldQuality: 4
- r.BloomQuality: 5
- r.Tonemapper.Quality: 5
- r.RayTracing.GlobalIllumination: 1
- r.RayTracing.GlobalIllumination.MaxBounces: 2
- r.RayTracing.Reflections.MaxRoughness: 1
- r.RayTracing.Reflections.MaxBounces: 2
- r.RayTracing.Reflections.Shadows: 2
- r.RayTracing.GlobalIllumination.FinalGatherDistance [number of units]
### 其他光线追踪控制台命令
许多光线追踪特征值已针对实时使用进行了优化。这意味着它们通过减少样本数量,限制最大反射数量或其他措施,从而牺牲质量以换取性能。
下面是你可以在影片渲染队列中使用的更多控制台变量,以质量换取性能。这一点特别有用,因为仅当从队列运行渲染时,此功能才执行这些命令,并且对于你可能已在编辑器中的后期处理体积中设置的任何实时设置,该设置不会永久覆盖。
逐像素采样: 每个光线追踪功能都可以使用很少或很多样本生成最终结果。去噪器使用像素较少,通常用于计算量繁重的任务。借助影片渲染队列,你可以选择禁用降噪器,并增加逐像素样本,以便提高质量。
部分示例为:
- r.RayTracing.Reflections.SamplesPerPixel
- r.RayTracing.Shadow.SamplesPerPixel
- r.RayTracing.GlobalIllumination.SamplesPerPixel
最大反射数Maximum Number of Bounces在场景中进行多次反射或光线反射生成更自然、更高质量的效果从而让光线跟踪功能例如反射、全局光照和透明涂层从中受益。这些设置对于实时渲染来说开销很大。
- r.RayTracing.GlobalIllumination.MaxBounces
- r.RayTracing.Reflections.MaxBounces
- r.RayTracing.Reflections.MaxUnderCoatBounces
天空光照Sky Light 在实时光线追踪中,为反射和全局光照等功能计算每帧时,由于距离无限,天空光照可能造成额外的开销。
使用影片渲染队列工作时以下CVAR可以在光线跟踪中启用其他天空光照选项
- r.RayTracing.GlobalIllumination.EvalSkyLight
- r.RayTracing.SkyLight.EnableTwoSidedGeometry
- r.RayTracing.Reflections.RayTraceSkyLightContribution
- r.RayTracing.SkyLight.EnableMaterials
## 视频笔记
地址https://www.bilibili.com/video/BV1dZ4y1H7f2
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/MovieRenderQueue.png)
### 需要调节的参数
- 灯光的采样数目
- 后处理里搜索Sample 反射设置为16 AO 10

View File

@@ -0,0 +1,206 @@
# Ue4照明技术引导视频笔记(静态光照部分)
https://www.youtube.com/watch?v=jCsrWzt9F28&list=WL&index=7&t=0s
https://forums.unrealengine.com/unreal-engine/events/107078-training-livestream-lighting-techniques-and-guides-jan-24-live-from-epic-hq?134388=
https://cdn2.unrealengine.com/CommunityTab%2FEnglish%2F2017%2FJan+23%2FLiveStream_Lighting-11f85d1762b463154b5f53f7468e135f65955bed.zip
https://wiki.unrealengine.com/LightingTroubleshootingGuide
## Volume
### Lightmass Importance Volume
用于提高指定区域内的LightMap烘焙效果主要是间接照明区域外的默认为低质量效果。
### LightMass Character Indirect Detail Volume
提高区域内的间接光照缓存的密度(缓存点会更加密集)。
## 间接光照缓存
### 模型中光照选项
## LightMass设置
### 解决LightMass漏光问题
1、提高间接光照的质量WorldSetting-》Indirect Lighting Quality<br>
2、降低WorldSetting-》Indirect Lighting Smoothness的值(但是构建时间会增加)
3、尽量使用整块模型
## 地形阴影
### 远距离阴影(Far Shadow)
一般是方向光作用在地形上的阴影使用的是Cascaded Shadow Maps。<br>
相关选项有:
方向光源-》Cascaded Shadow Maps-》 Far Shadow Cascade Count阴影精细程度与Far ShadowDistance显示距离
可以通过show=》advanced=》ShadowFrustums显示阴影调试只对方向光有效。以此可以观察Cascaded的级数切换距离。
地形是默认开启Far Shadow的Actor需要手动开启。
## 自发光物体
后处理空间中除了设置GI倍率还可以自定义GI的颜色。
讲了自发光材质的GI这里有了个取巧的方式通过PostProcessVolume绑定自发光物体来调整Gi好尴尬
2017年的版本还是通过LPV来实现的GI。
## LightMass Debug
在构建选项里有一个Use Error Coloring选项可以用来标记UV不合格的物题但在产品级质量构建中则不会显示这些Debug标记。
## Lighting Info位于构建按钮菜单中
里面可以调整LightMap渲染密度选项、空间占用大小以及分辨率调整。
# Ue4照明技术引导视频笔记(动态光照部分)
https://www.youtube.com/watch?v=nm1slxtF_qA
https://forums.unrealengine.com/unreal-engine/events/113380-training-livestream-lighting-techniques-and-guides-2-dynamic-light-april-4-live-from-epic-hq?140547=
## 距离场光线追踪技术
首先需要开启距离场生成,在项目设置——渲染——灯光——勾选 生成模型距离场 选项。
级联阴影在显示远阴影时为了效果会增加阴影级数这样会增加消耗而距离场阴影的消耗相对较小所以会采样近距离级联远距离距离场阴影的配合方式。同时它可以解决因为屏幕空间渲染技术而导致的阴影失真的问题。另一个主要用处就是距离场AO了。或者可以使用距离场来写Shader。
模型距离场在模型Asset设置中有个分辨率缩放选项的分辨率会影响阴影效果投射到别的物题上的阴影与自阴影。从视频可以看出阴影缺少了很多细节而且并不正确。此时勾选Show——Visualize——MeshDistanceField查看模型距离场从而选择合适的分辨率对debug阴影有很大帮助。
### 如何开启
需要在灯光处勾选“Ray Tracing Distance Field Shadow”选项开启。
### 较为的使用场景
渲染树木植被等高面数、复杂物体的阴影。
### 植被渲染注意
对于树叶等需要双面渲染的物体需要在模型设置中勾选Two-Sided Distance Field Generation以生成正确的距离场。
### 级联阴影切换
可以对灯光中的Cascaded Shadow Maps——Dynamic Shadow Distance MoveableLight进行设置来达到级联阴影与软阴影的切换靠近物体会切换成级联阴影
### 几个调试用命令
r.Shadow.MaxResolution 可选参数256~2048默认2048
r.Shadow.MinResolution 可选参数16~128默认32
r.Shadow.FadeResolution 可选参数64~2048默认64
r.Shadow.DistanceScale 可选参数0.5~2默认1
r.Shadow.RadiusThreshold 可选参数0.01~0.5默认0.03
r.Shadow.CSM.MaxCascades 可选参数1~16默认3
r.Shadow.CSM.TransitionScale可选参数0~2默认1
r.ShadowQuality 可选参数1~5默认5
r.LightFunctionQuality 可选参数0~2默认2
r.DistanceFieldShadowing True~False默认True
r.DistanceFieldAO True~False默认True
r.ParticleLightQuality 可选参数0~2默认1
### 其他注意事项
光源中的SourceRadius会对软阴影产生影响学过光追的人都知道为什么
## 胶囊阴影
因为也是一种软阴影方案所以光源的LightSourceAngle选项也会对此产生影响。
### 如何启用
1. 在角色Asset中指定ShaowPhysicsAsset。
2. 在角色Asset的Lighting选项卡中勾选Capsule Direct Shadow或者Capsule Indirect Shadow
## 接触阴影(Contact Shadows)
ContactShadowLength参数大于0时就会开启默认是关闭的。视频使用值为0.02
### 原理
将接触阴影的长度设为大于零的值后,渲染器将通过场景的深度缓存从像素的位置到光源进行光线追踪。举一个典型的例子来说,将接触阴影长度的最大值设为 1此处的 1 则代表光线遍历整个屏幕。而将接触阴影长度的值设为 0.5 则意味着光线遍历半个屏幕。注意:场景深度缓存中的获得的采样将保持不变,意味着增加接触阴影的长度时将出现更多噪点(穿帮)。长度为 0.75 的接触阴影比长度为 0.1 的接触阴影生成的噪点更多。
### 应用场景
1. 当前平台只支持一个光源且需要使用Marching实现的视差Shader。
2. 角色细节阴影与墙面植物(爬山虎)阴影。
## 级联阴影
级联阴影分为近阴影与远阴影。两者皆可以设置数量、过度距离以及过度参数(在光源中设置)。
当然想要真正显示远阴影则需要勾选物体中的Far Shadow选项。
## 其他技巧
### 天光(SkyLight)
天光也有距离场AO的选项在调试时需要关闭别的光源进行调试。
### HDRI Backdrop
https://docs.unrealengine.com/en-US/Engine/Rendering/LightingAndShadows/HDRIBackdrop/index.html
4.23处的新功能对于CAD产品展示有很大帮助。
# Wiki笔记
部分条目的解释有配图,我懒得贴了,所以推荐直接看原文。
## 问题解答
### 静态部分
#### 为什么我的阴影死黑?
在光照条件下深黑色阴影通常意味着没有填充光。这种情况经常发生在室外环境中一个方向的光代表太阳。UE4有一个内置的方法来提供一个影响填充光的世界我们把它称为天光。
#### 不想生成光照贴图
1. Rendering——Lighting——AllowStaticLighting确保引擎不会生成光照贴图。项目级
2. 打开世界设置——Lightmass——Force No Precomputed Lighting。(关卡级)
#### 双面渲染物体的阴影问题
1. 在StaticMesh——Lighting——LightMassSetting中勾选UseTwoSidedLighting。仅对静态光照
2. 在物体的材质选项中勾选TwoSided
#### 灯光上出现X标记
因为有超过4个光源重叠在一起这样会严重影响性能。动态光在该重叠区域会强制设为一个动态光源而静态光源会在烘焙光照时弹出重叠提示。
### 动态部分
#### 阴影不正确
##### 方向光
###### Dynamic Shadow Distance Movable
以摄像机为起点的阴影覆盖距离该值为0则代表禁用该功能。
###### Dynamic Shadow Distance Stationary
以摄像机为起点的阴影覆盖距离该值为0则代表禁用该功能。
###### Num Dynamic Shadow Cascades
view frustum被分割成的级联的数量。更多的级联将导致更好的阴影分辨率但会显著增加渲染成本。
###### Cascade Distribution Exponent
控制级联的分布,是离相机更近(高值)还是更远(低值)。值1表示转换将与分辨率成正比。
###### Cascade Transition Exponent
级联之间的过度参数,较低的数值产生会较生硬的过度,而较高的数值会产生较平缓的过度。
###### Shadow Distance Fadeout Fraction
阴影淡出参数。较高的值会使阴影显得较淡一些,而较低的值会使阴影显得较深一些。
###### Far Shadow
级联阴影的远阴影开关,可以解决当摄像机处于较远距离时,物体的阴影会消失的问题。
#### 调整级联阴影以得到更好的效果
通过调整上述设置,可以很好地调整阴影的出血值和精度。下一节将尝试调整到最佳成都,以获得更好的精度与阴影。找到一种适合任何特定游戏的平衡将需要耗费时间、精力来进行大量测试。
以下是默认设置下会出现的问题。
![image](https://d26ilriwvtzlb.cloudfront.net/a/ae/1AdjustingSettings2_ProblemAreas.png)
调整后的结果
![image](https://d26ilriwvtzlb.cloudfront.net/8/8c/1AdjustingSettings2_finalResult.png)
##### 对于所有的动态光源
###### Shadow Bias
控制阴影在场景中的精确程度。默认值是0.5,这是权衡了精度与性能的值。
###### Shadow Filter Sharpness
控制阴影边缘的锐化程度。
##### 为什么可移动光源在较远时其遮挡关系会出错
![image](https://d3ar1piqh1oeli.cloudfront.net/4/4a/1.png/642px-1.png)
![image](https://d3ar1piqh1oeli.cloudfront.net/0/09/4.png/642px-4.png)
为了解释这一点我们首先需要了解Ue4的渲染优化方法。引擎根据场景深度来判断场景中Mesh的可见性首先物体需要处于摄像机矩阵中如果Mesh过于远离摄像机Mesh将不会被渲染或是被遮挡。所以就会出现图中的现象。
你可能会注意到,当选择物体时,灯光恢复正常。这是预料之中的,因为它处于焦点状态。
解决方法是在Mesh属性界面调整Bounds Scale选项的大小。默认值设置为1.0。建议的调整范围为1.1、1.2左右,调整的量不宜过大,会影响性能与阴影质量。
可以viewport > Show > Advanced > Bounds 开启包围盒显示进行debug。
另一个解决思路就是使用聚光灯或者使用静态光烘焙光照贴图。
### 静态部分
你可以在世界设置中的LightMass调整以下参数以获得更好的效果
Indirect Lighting Quality设置成2或者更高。
Indirect Lighting Smoothness通常被设置为0.65~0.7之间。数值越低噪点会越多。
#### 如何控制静态照明的全局照明效果?以及光线反弹的美妙之处
默认情况下LightMass的光线反弹次数为3我们可以在Settings > World Settings > LightMass
中修改反弹次数以获得更好的效果。
光线的第一次反弹所需的计算时间是最长的。之后的反弹对实际构建时间的影响较小,但对视觉效果的影响也要小得多。
#### 解决阴影“脏”的问题
首先这种现象的原因与GI中的间接光照有关。
以下使用默认场景来创建2个测试用的关卡其中Mesh的光照贴图分辨率设为256后期空间开启人眼自适应。
![1st Interior Room, lit with direct and 1st bounce lighting](https://d3ar1piqh1oeli.cloudfront.net/4/42/3_Room1_Clean.png/553px-3_Room1_Clean.png)
<center>直接光照与一次反弹的间接光照</center>
![image](https://d3ar1piqh1oeli.cloudfront.net/c/c6/4_Room2_dirty.png/551px-4_Room2_dirty.png)
<center>只有一次反弹的间接光照</center>
一个较为暴力的手法就是提高光照贴图分辨率,但这不是最好的方法。因为这样做会增大内存消耗。
![image](https://d3ar1piqh1oeli.cloudfront.net/7/78/5_lightmap1024.png/550px-5_lightmap1024.png)
<center>光照贴图分辨率为1024的结果</center>
在WorldSettings——LightMass中可以对以下参数进行设置
##### Indirect Lighting Quality
控制间接光照的质量,以获得更佳的效果。
##### Indirect Lighting Smoothness
控制光照贴图中细节的平滑度。
##### Num Indirect Lighting Bounces
间接光照光线的反弹次数。
在对这些参数进行调整后即使是光照贴图分辨率为512其效果也比1024的好了。
![image](https://d3ar1piqh1oeli.cloudfront.net/0/0a/15_FinalResult.png/551px-15_FinalResult.png)
<center>光照贴图分辨率为512</center>
![image](https://d3ar1piqh1oeli.cloudfront.net/7/78/5_lightmap1024.png/550px-5_lightmap1024.png)
<center>光照贴图分辨率为1024</center>

View File

@@ -0,0 +1,7 @@
---
title: VirualShadowMap
date: 2023-03-18 09:27:10
excerpt:
tags:
rating: ⭐
---

View File

@@ -0,0 +1,244 @@
本文为《Lighting with Unreal Engine Masterclass》视频的学习笔记地址为https://youtu.be/ihg4uirMcec。
其中有关LightMass的笔记
http://www.tomlooman.com/lighting-with-unreal-engine-jerome/
翻译版
https://mp.weixin.qq.com/s/CUVJ57s_qsCNzdVHw5OhNQ
### 首先确定你的材质是物理正确的
几个常见的错误:
BaseColor太暗或者太亮
Metallic应该为0或者1。黑色或者白色Mask
Specular应该为0~1范围的浮点数。不应该为颜色值。它会导致反射亮度不一致
常见材质的BaseColor数值都可以网上查询到我记得《全局光照技术》中有说。世界中没有物体是纯白、纯黑的即RGB为0或者1
PS.关于“Metallic应该为0或者1”演讲者的意思是纯材质排除混合材质情况。
~~因为迪士尼的渲染模型也是基于微表面的如果在金属上的一个块微表面区域有40%的区域覆盖着 3D扫描用喷漆表面完全哑光材质那这块微表面的光照该如何计算呢~~
~~而迪士尼的渲染模型里Metallic就有插值效果0-1。如果有问题那为什么不在Shader里直接判断材质属于金属还是绝缘体。~~
### 如何实现一个良好的关照场景
#### 关闭后处理空间中干扰项
以下操作都在Post Process Volume中。用于去除对场景亮度的干扰。
##### 禁用自动曝光
视频中的操作是在后处理空间中将最低亮度与最高亮度设为1。
目前版本已经使用物理灯光所以第一条应该改为将Lens-Exposure
中的Min Ev100与Max Ev100设为0。
##### 禁用SSAO以及SSR空间反射
将ScreenSpaceReflection中的Intensity设为0。将Ambient Occlusion的Intensity设为0。反射球可以不用删掉。
##### 保持默认Tone Mapping
确定Tonemapper新版本为Film中值为默认值。
##### 关闭Vignetting与Bloom效果
将Lens-Bloom中的Intensity设为0。将Lens-Image Effect中Vignette Intensity设为0。
#### 设置测试用球(与色卡
视频使用了Chrome球(BaseColor 1、Metallic 1、 Roughness 0)与Gray球 50%灰(BaseColor 0.18(Linear)、Roughness 1、Metallic 0)进行测试。使用亮度为3.141593(Pi)的方向光此时灰球的亮度为0.5。使用Color Picker、或者截图使用Photoshop查看
但其实引擎已经预制了颜色校准工具只需要在View Options中勾选Show Engine Content之后搜索ColorCalibrator将SM_ColorCalibrator拖到场景中即可。
## 动态实时灯光VS静态烘焙灯光
### 静态烘焙灯光优缺点
优点:
- 拥有GI效果
- 消耗较少的软阴影效果
- 低GPU性能消耗较小
缺点:
- 迭代效率低
- 光照效果是静止的
- 当地图较大时,会占用大量显存。
### 动态实时灯光优缺点
优点:
- 所见即所得
- 光照效果是动态的
缺点:
- 没有GI效果
- GPU性能消耗较大
### 静态烘焙灯光
#### 场景流程
1. 放入方向光(stationary)
2. LightMass烘焙(GI)
3. 放入天光(static)
PS.因为新版本的改动,天光已经可以进行多次反弹,并且可以发射光子了。所以我认为应该第四部,再次烘焙灯光。
#### Min LightMap Resolution(静态模型Detials)
控制UV块之间Padding值。良好的Padding值可以减少像素浪费以及、提高阴影效果在固定LightMap分辨率下。Min LightMap Resolution应该被设置为小于或者等于LightMap Resolution的值。作者一般直接设为与LightMap Resolution相等的值
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightmapresolution.jpg?resize=768%2C323&ssl=1)
#### Num Indirect Lighting Bounce(世界设置)
许多用户喜欢把这个值提高到 100 次,不过好在这个对烘焙时长影响不大,但同时对光照质量影响也不大。实际使用这个值设置为 3 - 5 次就足够了。
下面是对比数据:
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_1.jpg?ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_2.jpg?resize=768%2C325&ssl=1)
![image](https://i0.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_3.jpg?resize=768%2C338&ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_4.jpg?resize=768%2C331&ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_5.jpg?ssl=1)
#### Static Lighting Level Scale(世界设置)
改变lightmass的级别。默认值为 1越小细节越多最终我们使用 0.1。
其原理是增加单位的区域的采样点的量。但这样会产生大量噪点。
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_1.jpg?resize=768%2C453&ssl=1)
![image](https://i0.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_2.jpg?resize=768%2C455&ssl=1)
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_3.jpg?resize=768%2C452&ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_4.jpg?resize=768%2C457&ssl=1)
#### Indirect Lighting Smoothness(世界设置)
该值大于1是会对LightMap进行平滑处理反之会进行锐化过高的数值会导致漏光现象。对于这个属性作者建议设置为0.7~1。
#### Indirect Lighting Quality(世界设置)
增加进行final gathering运算时的采样光线量。提高数值可以减少间接照明时的噪点但代价是极大地增加构建时间。
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_1.jpg?ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_2.jpg?resize=768%2C450&ssl=1)
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_5.jpg?resize=768%2C452&ssl=1)
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_10.jpg?resize=768%2C447&ssl=1)
#### Lighting Build Quality(在构建下拉按钮中)
调整LightMass烘焙质量的总开关。用于整体调整烘焙质量对烘焙时长影响很大。
- Preview耗时 2 分 16 秒)预览级
- Medium耗时 7 分 48 秒)中级
- High耗时 13 分 58 秒)高级
- Production耗时 30 分 22 秒)产品级
作者的习惯:大部分的工作时间都使用预览级,有时会切换到中级进行效果预览。在周末可能会切换到高级或者产品级进行烘焙来查看效果。
#### Lighting Level Scale与Indirect Lighting Quality的设置技巧
保持Static Lighting Level Scale × Indirect Lighting Quality = 1。你就可以得到一个噪点较少的场景。
#### Volume Light Sample Scale
减少Static Lighting Level Scale会增加这些空间采样样本密度
#### 添加LightMass Portal提高室内场景光照质量
给室内场景中的窗户添加LightMass Portal以此可以减少噪点。
其原理是模拟天光效果,向室内发射光子。但也因此会在增加发射光子阶段(渲染过程中)的时间。
## Lighting Scenarios
在游戏制作过程中有时会存在同一场景使用多套Lightmap的情况模拟天气系统以及一天中不同时段的光照等。在Unreal 4引擎中可以利用Lighting Scenarios功能达到此效果。使用Lighting Scenarios时首先需要创建不同的光照Level然后在Level设置页面将其设置为Lighting Scenario类型如下图所示
![image](https://pic2.zhimg.com/80/v2-ce6f0a63eac3abcfcdedf1b68ce108cd_hd.jpg)
另外设置Level的加载方式为使用Blueprint加载
![image](https://pic4.zhimg.com/80/v2-e52d3177593069c8043ca6e8a7879107_hd.jpg)
接着对不同Level设置不同的光照进行烘焙。烘焙结束后在Persistent Level Blueprint中添加Load Stream Level节点对Level进行加载并设置要加载的Level名称Day如下图所示
![image](https://pic3.zhimg.com/80/v2-c3db25960facf2c84981647472c3f416_hd.jpg)
### 动态实时灯光
#### 场景流程
1. 放入方向光(moveable)
2. 放入天光(moveable)
3. 开启DistanceFieldAO
4. 开启SSAO
#### 距离场技术
距离场因为存储了空间信息可以解决屏幕空间渲染技术的缺陷。比如SSAO。距离阴影可以产生与级联阴影不同的软阴影效果。同时你可以在材质编辑器获取距离场数值以制作一些特殊效果。
需要注意的:
- 只有静态物体可以使用距离场
- 避免非规整缩放
- 巨大物体的距离场分辨率会很低(这里建议把大物体进行拆分)
- 距离场AO的更新会分布在多个帧中所以你没有办法得到真正的实时AO。但这个通常不会引起玩家注意。
- 你可以通过提高距离场分辨率来修复一些距离场技术渲染不正确的情况。
- 距离场AO是可以着色的你可以使用它来模拟一些光线反弹效果。
#### Shadow Map
##### Shadow Bias
控制阴影在场景中的精确程度。默认值是0.5,这是权衡了精度与性能的值。
但是在渲染角色时,需要适当权衡这个值,以求在效果正确的基础上对性能消耗最小。
##### 级联阴影
当在一个较大表面上投射阴影时,一个贴图的分辨率可能是不够的,而且会有大量的像素损失。于是就出现使用多个贴图拼在一起显示阴影的技术。
Lighting condition EV100
Daylight
Light sand or snow in full or slightly hazy sunlight (distinct shadows)a 16
Typical scene in full or slightly hazy sunlight (distinct shadows)a, b 15
Typical scene in hazy sunlight (soft shadows) 14
Typical scene, cloudy bright (no shadows) 13
Typical scene, heavy overcast 12
Areas in open shade, clear sunlight 12
Outdoor, natural light
Rainbows
Clear sky background 15
Cloudy sky background 14
Sunsets and skylines
Just before sunset 1214
At sunset 12
Just after sunset 911
The Moon,c altitude > 40°
Full 15
Gibbous 14
Quarter 13
Crescent 12
Blood 0 to 3[6]
Moonlight, Moon altitude > 40°
Full 3 to 2
Gibbous 4
Quarter 6
Aurora borealis and australis
Bright 4 to 3
Medium 6 to 5
Milky Way galactic center 11 to 9
Outdoor, artificial light
Neon and other bright signs 910
Night sports 9
Fires and burning buildings 9
Bright street scenes 8
Night street scenes and window displays 78
Night vehicle traffic 5
Fairs and amusement parks 7
Christmas tree lights 45
Floodlit buildings, monuments, and fountains 35
Distant views of lighted buildings 2
Indoor, artificial light
Galleries 811
Sports events, stage shows, and the like 89
Circuses, floodlit 8
Ice shows, floodlit 9
Offices and work areas 78
Home interiors 57
Christmas tree lights 45
https://youtu.be/t_tT7pwO_j8
对于曝光的建议是:在调整最大最小值后与偏移后(最暗与最亮处的效果匹配),把切换速度调成最高。
该视频的流程:
CubeMap=》Skylight=>LightMaps=>Lightmass Protals=》LightMass Setting=》LightMass Important Volume=》Post Process=》Turn off AO=》Reflection probes
#### 帕拉共
帕拉共因为考虑多平台部分平台可能不支持自适应所以曝光的范围比较小旧版本0.35~0.45
#### 曝光与灯光强度
根据Daedalus51的测试及一些数据方向光强度可以达到125000Lux然后天光5000cd/m²配合16EV值那个只是为了测试场景的数值。
Daedalus51的灯光学院系列视频
https://www.youtube.com/user/51Daedalus/videos
当然这些视频也已经搬运到国内了:
https://www.bilibili.com/av59548243/
物理灯光文档
https://docs.unrealengine.com/zh-CN/Engine/Rendering/LightingAndShadows/PhysicalLightUnits/index.html
### 天光问题
有人提出了一个问题大多数HDR文件你是不知道他用了什么曝光标准以及拍摄时的光照强度
天光转换ev100 0=》16 1=》2^16
#### 使用天空来实现天光效果
添加一个自发光材质给球可以使用EditorSkySphere缩放到Sky Distance Threshold大小后LightMass就会把这个球当做是天空球用以烘焙天光。
https://zhuanlan.zhihu.com/p/70288801
SkySphere的问题 解决方案在材质里乘以 39808
室内保证打光均匀。
选择合理的曝光数值
#### 黑夜曝光方案
min brightness of 0.1 and a max brightness of 1适当增加天光的亮度。

View File

@@ -0,0 +1,132 @@
## 前言
本文是对于《Daedalus51的灯光学院系列视频》、《Lighting with Unreal Engine Masterclass视频》、《Ue4照明技术引导系列视频》的总结。相关网址与链接可以参看前一篇文章。
撰写本文时《Daedalus51的灯光学院系列视频》只看完第一章所以本文后续还会更改请各位读者注意。另外因为本人技术能力有限如有错误还请各位指正。
### 三个视频的观看建议
>Lighting with Unreal Engine Masterclass
从简到深地讲解了静态光照与动态光照特性对比、2种光照方案的技术点以及Epic的基本打光流程。缺点是没有很好的讲解具体的打光思路以及流程。
>Ue4照明技术引导系列视频
该系列一共2个视频主要介绍了光照技术中的各种细节与参数。是功能与参数讲解向视频。PS.个人感觉配套的Wiki比视频有用多了Wiki中讲解若干阴影问题的解决方法。本视频已经被搬运到B站中(机翻)英双字幕。https://www.bilibili.com/video/av59548243/
>Daedalus51的灯光学院系列视频
一共19个视频看完需要1~2个星期。一共4个章节每个章节都是一个案例。通过这个视频你可以大致了解到Ue4的打光流程、每步操作的意义以及部分功能背后技术的原理。让你了解如何判断场景出了哪些问题出现什么问题该如何解决什么时候该做什么。
建议的观看顺序为《Lighting with Unreal Engine Masterclass》=》
《Ue4照明技术引导系列视频》=》《Daedalus51的灯光学院系列视频》
## 灯光类型对比
以下几点都是直接翻译视频中的观点,这里我总结一下:
>动态光照的主要优点是所有效果都是实时的对于效果的迭代会非常快。主要缺点是没有GI。
>静态光照的主要优点有GI但因为效果不是实时的迭代的效率相当会相对较低。
静态光照一般会使用Stationary类型的灯光其中的Dynamic Shadow Distance参数是切换动态与静态阴影开关当为0是所有阴影都将是烘焙的当数值较大时(5000)所有近距离阴影都将是动态的。Daedalus51推荐的数值为3000~8000之间。在这个数值的距离观察时会切换为静态阴影
### 静态烘焙灯光优缺点
优点:
- 拥有GI效果
- 消耗较少的软阴影效果
- 低GPU性能消耗较小
缺点:
- 迭代效率低
- 光照效果是静止的
- 当地图较大时,会占用大量显存。
### 动态实时灯光优缺点
优点:
- 所见即所得
- 光照效果是动态的
缺点:
- 没有GI效果
- GPU性能消耗较大
### Static
烘焙直接光照与间接光照(GI)
GPU性能消耗最少
### Stationary
烘焙间接光照
高质量的直接关照与阴影(动态)
高质量与中等性能消耗
四个及以上的灯光重叠限制多出的灯会使用static方式计算
### Movable
动态光照与阴影
GPU性能消耗最多
没有间接光照
## 前置流程
以下两点主要参考于Lighting with Unreal Engine Masterclass视频部分来自于Daedalus51的视频。
### 实现没有干扰的默认光照场景
使用初始模板建立的场景。
#### 关闭后处理空间中干扰项
以下操作都在Post Process Volume中。用于去除对场景亮度的干扰。
##### 禁用自动曝光
视频中的操作是在后处理空间中将最低亮度与最高亮度设为1。
目前版本已经使用物理灯光所以第一条应该改为将Lens-Exposure
中的Min Ev100与Max Ev100设为0。
##### 禁用SSAO以及SSR空间反射
将ScreenSpaceReflection中的Intensity设为0。将Ambient Occlusion的Intensity设为0。反射球可以不用删掉()。
##### 保持默认Tone Mapping
确定Tonemapper新版本为Film中值为默认值。
##### 关闭Vignetting与Bloom效果
将Lens-Bloom中的Intensity设为0。将Lens-Image Effect中Vignette Intensity设为0。
##### 杂项
后处理的GI选项只对LightMass烘焙过的GI有效。、
不建议使用后处理的AO中的Cubemap Texture已经被天光代替
#### 确认天光与方向光为默认数值
天光亮度为1
方向光亮度为3.141593(π)
#### 设置测试用球(与色卡)
视频使用了Chrome球(BaseColor 1、Metallic 1、 Roughness 0)与Gray球 50%灰(BaseColor 0.18(Linear)、Roughness 1、Metallic 0)进行测试。使用亮度为3.141593(Pi)的方向光此时灰球的亮度为0.5。使用Color Picker、或者截图使用Photoshop查看
但其实引擎已经预制了颜色校准工具只需要在View Options中勾选Show Engine Content之后搜索ColorCalibrator将SM_ColorCalibrator拖到场景中即可。
### 确定你的材质是物理正确的
在将模型导入并赋予材质后检查场景:
《Lighting with Unreal Engine Masterclass》说
几个常见的错误:
BaseColor太暗或者太亮
Metallic应该为0或者1。黑色或者白色Mask
Specular应该为0~1范围的浮点数。不应该为颜色值。它会导致反射亮度不一致
常见材质的BaseColor数值都可以网上查询到我记得《全局光照技术》中有说。世界中没有物体是纯白、纯黑的即RGB为0或者1
PS.关于“Metallic应该为0或者1”演讲者的意思是纯材质排除混合材质情况。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/PbrDiffuse.png)
#### 关于BaseColor
Daedalus51提出场景中的BaseColor应该要较为平缓亮度、对比度与饱和度别太高这一点也在在Ue4的LightMass文档中的“使用LightMass获取最佳质量”章节中有提到。
特别是饱和度饱和度如果过高会让人感觉画面奇怪。1.5章节开头就在调整贴图的饱和度与亮度。
检查的方法是切换到BaseColor查看模式截图再使用Photoshop的Histogram查看颜色分布。Daedalus51使用的方法是使用PS对截图使用平均模糊滤镜之后观察图片的各个值。
但从视频中可以看出,这个步骤更多的是靠经验,所以这里还是建议各位仔细看下对应视频。
##### 在Ue4中修改贴图
一种简单修复方法就是打开TextureAsset直接修改里面的饱和度、亮度、对比度等选项。但是作者还是建议使用Photoshop修改源文件再更新Asset
## 结语
在下一篇文章中我会总结各个步骤的重要细节以及各个功能所应用的场景。但是文章只能记录操作步骤与流程不能记录使用经验所以本人依然推荐想深入学习光照的人去看一下Daedalus51的视频。
#### Daedalus51视频中的场景
第一章节:
户外傍晚:
使用纯动态光照
距离场AOSSAO好像关闭了
CubeMap天光并且自定义下半球颜色
其他为后处理
户外早晨:
使用静态光照Stationary的动态阴影不算的话
CubeMap天光并且自定义下半球颜色
第二章节:
洞穴场景(可以看成是室内)。

View File

@@ -0,0 +1,341 @@
## 前言
在这一章,我将会总结一些有关静态光照相关的参数、使用场景与设置技巧。
本文图较多,手机党请注意。本文仅作为个人笔记使用,不保证所有操作都是正确的,请各位谨慎阅读。
### Daedalus51的流程
首先我总结一下Daedalus51的流程如有遗漏还请指正。以下流程仅为笔记记录使用不一定正确。
个人不太推荐看第二章节后半段文档视频,因为作者一直都在调节材质(场景、粒子)。如果是建筑可视化方向的可以直接看第三章。
#### 1.5章节的流程
1. 去除干扰场景光照的后处理效果(本系列第一篇文章)
2. 检查贴图的固有色是否正确并进行调整。(本系列第一篇文章)
3. 调整天光以符合设计目标。(本系列第一篇文章)
4. 设置曝光相关属性。
5. 将所有灯光都是设置为Stationary类型。并开始设置方向光与其他主光源的基础属性方向、亮度等
6. 设置LightMass属性使用预览级对场景进行测试渲染。
7. 开启LightingOnly模式对光照进行检查。
8. 给场景中的带有反射属性的物体添加反射球并调整属性。Daedalus51的流程中没有关闭空间反射如果你按照我之前写的关闭空间反射这里要记得开启之后开启Reflections模式查看场景反射情况。
9. 设置场景植被(有光照无关)
10. 调整场景雾气
11. 后处理调色
**测试烘焙**<br>
注意去掉世界设置中的Force Precompute Lighting选项。
关闭Compress LightMaps选项压缩灯光贴图为了追求效果但如果游戏项目推荐打开这样可以节约显存占用。
作者一般不会再一开始就开启LightMass的AO烘焙选项世界设置中。因为一般情况下都会也应该使用SkyLight所以我们应该把LightMass的EnvironmentColor设置为0。
**突破材质贴图数目限制**<br>
因为全都使用静态烘焙灯光所以在对应材质中Usage选项卡中勾选Use With Static Lighting这样材质中的贴图采样数数统计才会正确(因为使用LightMass会占用2张贴图)
材质编辑器的Texture Sampler节点的Sampler Source选项决定了一个材质所能使用贴图数量使用Shared:Wrap选项可以突破Dx11的贴图数量限制。
**Stationary类型灯光阴影说明**<br>
Station类型的灯光对于静态物体会烘焙灯光对于动态物体会使用动态阴影。
Dynamic Shadow Distance StationaryLight是切换开关当为0是所有阴影都将是烘焙的当数值较大时(5000)所有近距离阴影都将是动态的。作者推荐3000~8000之间。在这个数值的距离观察时会切换为静态阴影
**有关反射球**
反射球会影响所有物体产生影响(高反射物体如果没有反射球,渲染结果往往是错误的),你可以选择给小物体使用小反射球,之后在给所有物体一个大反射球。(小反射球的优先级高)
**提高空间灯光样本密度(本人加的私货)**
树木、草地等植被因为模型比较密集所以无法获取准确的LightVolume样本信息。可以使用Volumetric LightMap Density Volume来增加空间样本数量。
其他相关空间还有:<br>
**Lightmass Importance Volume**
用于提高指定区域内的LightMap烘焙效果主要是间接照明区域外的默认为低质量效果。
**LightMass Character Indirect Detail Volume**
提高区域内的间接光照缓存的密度(缓存点会更加密集)。
#### 2.1章节的流程
1. 删除无用灯光因为Daedalus51在帮人改场景并将其他灯光关闭。
2. 这个关卡原本没有使用LightMass可以看得出缺少AO
可以使用距离场AO等技术补充在发现后处理空间开启了Dynamic Indirect Lighting后查看LPV其实并没有开启。
3. 检查场景模型UV之后使用基础参数进行测试渲染。
4. 在调整后屏幕空间反射质量后使用LightingOnly查看光照烘焙效果。洞穴墙壁太亮度发现是因为模型没有设置为静态导致没有参与烘焙
5. 设置反射球,以消除错误的反射。
6. 关闭指数雾与其他粒子雾(会影响场景亮度)并且将场景中的射灯模型的自发光材质属性降低,重置所有后处理,至此完成了场景基础修改。(这个理论上应该放到第一步中)
7. 使用BaseColor模式查看固有色并调整贴图的饱和度与亮度。
8. 放置LightMass Portal提高指定区域在LightMass烘焙中所产生的的光线量以此来提高洞穴内的效果。
9. 因为场景方向光没有对准洞口所以作者选择关闭方向光提高天光的间接照明亮度并且在WorldSettings中将间接照明反弹提高到100。目前版本应该设置天光反弹次数而作者的版本没有这个选项所以在视频中场景还是很黑
10. 调节曝光与人眼适应。
11. 调整各种材质效果,与贴花效果。(设置贴花的光照模型)
12. 逐个开启之前关闭的光源并调整光源的颜色与范围。调整灯光颜色以及优化Stationary重叠问题并且把一些作用范围较小的灯光设置为动态不产生阴影以对效果进行快速迭代。
13. 继续调节场景中的细节(粒子、材质、灯光)
14. 增加指数雾与使用反转法线球体制作的体积雾。
15. 调节后处理效果。
**有关LPV**<br>
Daedalus51并不推荐在室内设计中使用LPV外部主光源不能对室内产生足够多的间接照明。因为LPV只适用于方向光所以我们应该关闭Dynamic Indirect Lighting选项
**有关屏幕反射**
因为默认状态下r.sss.quality为3所以在后处理空间中调整质量到60以上都和60是一样的。r.sss.quality 2命令对应的最高质量为50输入r.sss.quality 4 可以解锁100质量。
#### 3.1章节的流程
需要解决两个问题:
- 场景中的窗子太小,同时室内灯光没有开启,导致室内光照不足。(可以看得出参考图的曝光度很高,连天空都看不出细节)
- 场景中植物与材质不符合场景。
大致流程如下:
1. 首先进入LightMap Density模式查看光照贴图密度情况并调整分辨率到合适值。保证附近模型的光照贴图密度大致相同
2. 检查后处理空间、天光与方向光并重置至默认数值。建筑可视化应该关闭屏幕空间AO因为LightMass会烘焙效果更好的阴影
3. 将曝光度设置为1让户外与室内曝光统一。这么调整之后室内会变得很黑此时Daedalus51认为我们不应该直接调整天光与方向光的强度与间接照明倍率而是应该调整曝光数值来解决这个问题。
4. 关闭除了天光外的方向光与其他光源并检查LightMass Portl、Lightmass Importance Volume是否设置正确。
5. 修改LightMass选项后进行预览级质量烘焙。提高反弹次数6=》10设置打包灯光与阴影贴图最大大小关闭压缩LightMap功能
6. 对比参考图后发现是曝光的问题之后修改exposure bias0=>4.6),使得场景达到合适的亮度。
7. 删除BP_Sky_Sphere、高度雾与指数雾添加新的使用Hdr的天空球(参照第二章节中的内容:场景捕获)后进行高级质量烘焙。
8. 删除场景中的反射球使用一个Box反射球作为主反射球并将它大致放置在场景中间之后调整Capture Offset数值来校准反射球。
9. 关闭玻璃材质属性中的屏幕反射功能Translucency-Screen Space Reflections反射球效果依然有效。
10. 检查材质是否正确,并进行调整(固有色、贴图等)
11. 在观察到地板的不正常反射后在后处理空间中开启了屏幕空间反射并调节了反射质量。r.sss.quality 4r.sss.Temporal 0之后发现反射噪点是材质粗糙度造成。
12. 调节LightMass属性Static Lighting Level Scale 1=》0.1Indirect Lighting Quality 1=》6Indirect Lighting Smoothness 1=》0.6使用高级质量进行烘焙。这次烘焙花费4小时烘焙的光照可以说相当好了但在细微处还是有一些噪点作者建议可以可以Indirect Lighting Quality再提高一些。
13. 给场景带有高金属度与低粗糙度的物体添加反射球以解决物体渲染结果不对的问题。
14. 视频3.3的时候作者的电脑突然原地爆炸了鸽了段施加但也因为更新了4.18版本可喜可贺。使用Hdr+天光反弹渲染效果变得更加自然了。作者还在场景中添加了方向光、聚光灯等主光源。修改了地板材质并开启LightMass的AO选项以掩盖SSR的问题
15. 作者展示一个骚操作在材质编辑器中使用PreComputedAOMask节点来获取LightMass烘焙的AO信息。
16. 作者将BP_Sky_Sphere加回来同时把天光类型改成了使用CubeMap。
**使用PreComputedAOMask与澡波贴图来实现一些边角污垢效果**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/PreComputedAOMaskEffect.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/PreComputedAOMaskEffectDark.png)
**解决植被顶点动画模糊的问题**
在Project Settins-Rendering-Optimizations勾选Accurate Velocities from Vertex Deformation选项让TAA能够获取正确的速度值即可解决。这对植被渲染十分重要。
#### 4.1章节的流程
这次修改场景的要求是在保证黑暗之魂3场景风格的基础上尽量让场景光照看起来真实。
Daedalus51首先检查了场景
场景的阳光比较强烈,但是天空却看上去很灰暗,场景的角落也比较黑,看来不真实。一些暗处的物体根本看不到细节。
存在问题:
- 场景的阳光比较强烈,但是天空却看上去很灰暗,场景的角落也比较黑,看来不真实。一些暗处的物体根本看不到细节。
- 很多材质开启曲面细分功能但是曲面细分倍率设置的有问题。曲面细分还会导致物体再LightingOnly模式下不显示所以Daedalus51把曲面细分关掉了。
- 天空看来是淡黄色的,但天光却设置为蓝色(可以看得出建筑物有天光反弹的蓝色)。
- 植被没有渲染细节阴影,但光照贴图分辨率却很高。
场景重置操作内容:
- 重置方向光去掉颜色设置亮度为pi设置间接光照强度为1关闭Light Shalf Bloom
- 重置级联阴影数值但保留Dynamic Shadow Distance Stationary的参数
- 关闭材质的曲面细分功能
- 重置世界设置中的LightMass设置。
- 重置天光参数,亮度、颜色、间接照明强度。关闭半球固定色功能。
- 重置后处理空间,并调整曝光数值。
1. 检查场景,将一些参数重置。
2. 调整LightMass设置中的天光反弹与间接光照反弹1=》10。
3. 删除BP_SkySphere,加入Hdr贴图制作的天空球并通过旋转与位移将天空球与太阳匹配。
4. 关闭大气雾。
5. 调整场景中物体的LightMapDensity主要是降低因为有一些物体不需要细节阴影你无法从一片叶子上获得更多的视觉效果而且可以解决TextureStreamPool溢出与烘焙时间长的问题
6. 因为调节了所有的光照贴图密度所以还需要调节世界设置中的Packed Light And Shadow Map Texture Size。
7. 调整天光关闭指数雾气让天光重新捕获场景。调整方向光的间接照明强度从1=》1.5,让场景更加有艺术感。
8. 使用中级质量对场景进行烘焙。
9. 在烘焙的时候作者检查了场景的BaseColor。
10. 烘焙完成进入LightingOnly检查烘焙结果。可以看得出光照烘焙得不错但场景还是挺黑的这说明BaseColor有问题。
11. 在修改完所有BaseColor贴图与部分材质后再次进行烘焙。并在烘焙过程中移除fake灯光。
12. 作者将场景中的火炬资源整合成一个Actor蓝图(灯光、粒子、模型)同时将灯光的间接照明强度从1=》1.6。并再次使用中级质量进行烘焙。
13. 在项目设置中勾选Accurate Velocities from Vertex Deformation选项解决TAA鬼影问题。
14. 在4.6章节作者开始分析黑魂场景,并指出一些补光错误(黄色的太阳光、背景与雾气,但部分地面上却是蓝色的)
15. 作者并不喜欢默认的BP_Sky_Sphere,他更喜欢Hdr天空。同时他给出一种使用FlowMap让Hdr天空变得动态的方法。GDC14 顽皮狗的演讲之后作者分享了一篇Ue4实现方案http://kedama.works/archives/5但很明显这个方案还有巨大的改进空间。
于是在4.6 55min左右作者开始展示改进过的Shader不过还是存在一个顶部图像破裂的bug。这个内容我放在第二篇文章里
**调整前与调整后的LightMapDensity**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/原始LightMapDensity.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/调整过的LightMapDensity.PNG)
**有关体积光照贴图**
Unreal会根据场景模型分布情况分配体积采样点对于重要的地方可以通过添加体积的方式调整采样点间距**动态物体**会根据这些采样点进行插值着色来实现GI效果。但LightMass只会对静态物体进行采样而完全忽略动态物体。
这里作者又提到了树因为树不适合使用LightMass烘焙光照因为树的结构复杂会导致烘焙时间长而且效果很差但是如果不使用LightMass就不会有GI效果。所以作者想到了设置两组树第一组静态用于烘焙体积光照贴图之后在场景中隐藏。另一种动态用于在场景中展示。这样就解决上面说的问题。
但这样做不能算是个完美的方法所以作者希望EPIC能开发让一些特定的动态物体影响空间探针的功能。
**第一次烘焙完的结果**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/但场景还是死黑.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/光照烘焙的不错.png)
**有关BaseColor**
在检查BaseColor的过程中发现地面的泥土材质的BaseColor太黑了这个贴图的中间值为40而达斯·维达那身黑乎乎的材质颜色Vec3(52,52,52)的中间值也至少为50。具体的做法是调整亮度与饱和度。
所以作者对以下贴图进行修改:
- 调整了泥土贴图40=》81
- 调整了地面的砖石贴图70=>104
- 调整了台阶贴图
- 调整了柱子贴图68=》104
- 调整了砖墙贴图97=》125
- 调整了墙体贴图的金属部分,黑色=》中灰色
- 等场景中的其他贴图
作者不建议固有色的中间值低于50或者高于230。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/PbrDiffuse.png)
>4.5章节作者额外补充一些东西个人强烈建议大家去看一下虽然物理灯光因为引擎存在Bug没有讲完。
首先作者介绍了一下GPULightMass可以看得出烘焙速度非常快。这次作者演示的场景比较简单但包含了室内与室外部分非常适合讲解新的物理灯光系统。
**使用物理灯光的原因**
之前版本中Unreal4的灯光使用的是虚幻单位这是的之前版本的灯光没有一个物理值的衡量标准。你很可能会因为后处理空间、曝光等因素影响而无法设置出正确的亮度值。例如你已经将一个区域的效果调整的很自然但是别的区域就会很奇怪bloom、阴影、光照等效果互相不匹配。 而前人已经积累了大量的摄影知识与经验,所以我们没有必要光靠感觉来设置灯光亮度与曝光度,而需要使用摄影标准来设置灯光,从而使得场景更加真实。
## 环境设置
### 天光
类型static 关闭半球固定色
### 方向光
级联阴影的动态阴影距离设置为12000
### 天空球
引擎天空球模型+Hdr图+对应材质,具体参考第二篇文章写的内容。
### 后处理空间
保持默认
### LightMass设置
静态光照细节缩放等级0.1 光线反弹次数10 天光反弹次数10 间接照明质量4 间接照明平滑0.8 ## 视频中的操作过程 ### 使用摄像机参数来调整曝光度 所以我们能做的第一件事情就是通过sunny16原则来设置相机。 具体步骤为在后处理空间——曝光中将模式改成手动之后按照sunny16原则的户外晴天数值来设置Camera属性。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/使用摄像机属性来调整曝光.png)
### 设置天空球亮度
但此时画面还是黑乎乎的这说明场景中的灯光强度太弱了。此时你直接调整天光强度是无效的因为默认的天光是场景捕获模式。此时设置天空球亮度设置材质在视频中亮度提升到68000左右达到了一个不错的效果。这个时候再进行烘焙模型的样子就不再是黑乎乎的了。 >使用像素检测器,可以检查对应像素的光照强度
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/调整环境球亮度中.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/调整环境球亮度.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/环境球亮度调节完成后烘焙天光.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/室外环境合适的曝光度.png)
### 设置方向光
将方向光亮度设置为75000阴天中午再次烘焙光照。 此时场景出了些问题,金属反射不正常(屏幕空间反射)。可能是作者用的版本对于物理灯光的支持不太好。
### 设置室内曝光度
按照sunny16原则的室内数值来设置Camera属性。但是通过相机参数来调整曝光度有个问题那就是曝光度是固定死的无法根据场景亮度进行自适应。这里作者吐槽自适应曝光不能使用物理参数这个问题目前版本已经解决
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/室内环境合适的曝光度.png)
作者登录了一个灯光供应商的网站,网站上提供个灯光的详细参数,有亮度、色温等。 之后作者讲解了坎德拉与流明这种光照单位,这里可以参考日天大佬的文章。 之后作者展示了点光源切换坎德拉与流明但是效果却不一样的问题。作者使用的版本是4.19)也因为这个原因作者没有继续讲解物理灯光流程,着实可惜。
在4.6章节,作者推荐了这篇文章。我看了一下挺不错的,所以我会在第五篇文章中对其进行简单翻译,原文地址:
https://80.lv/articles/setting-lighting-in-unreal-engine-4-20/
### LightMass Debug
在构建选项里有一个Use Error Coloring选项可以用来标记UV不合格的物题但在产品级质量构建中则不会显示这些Debug标记。
### 静态光照流程及属性总结
这些内容主要来自于《Lighting with Unreal Engine Masterclass》、《解密照片级表现技巧一些关于UE4建筑表现的废话》以及李文磊的《在UE4中实现灯光之美》
#### Min LightMap Resolution(静态模型Detials)
控制UV块之间Padding值。良好的Padding值可以减少像素浪费以及、提高阴影效果在固定LightMap分辨率下。Min LightMap Resolution应该被设置为小于或者等于LightMap Resolution的值。作者一般直接设为与LightMap Resolution相等的值
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightmapresolution.jpg?resize=768%2C323&ssl=1)
#### Export Resolution Scale
缩放导出此材质属性时的分辨率。当需要细节时,这对于提高材质分辨率非常有用。
第一幅图像中使用了一个小光源但是光照图的分辨率太低无法捕捉到清晰的半透明阴影。第二幅图像中材质导出的分辨率过低由Export Resolution Scale控制无法捕捉到清晰的阴影。
![image](https://docs.unrealengine.com/4.27/Images/RenderingAndGraphics/Lightmass/5LowLightmapResolution.jpg)
![image](https://docs.unrealengine.com/4.27/Images/RenderingAndGraphics/Lightmass/5LowExportResolutionScale.jpg)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/分辨率影响.jpg)
#### Num Indirect Lighting Bounce(世界设置)
许多用户喜欢把这个值提高到 100 次,不过好在这个对烘焙时长影响不大,但同时对光照质量影响也不大。实际使用这个值设置为 3 - 5 次就足够了。
下面是对比数据:
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_1.jpg?resize=768%2C325ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_2.jpg?resize=768%2C325ssl=1)
![image](https://i0.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_3.jpg?resize=768%2C325ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_4.jpg?resize=768%2C325ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightbounce_5.jpg?resize=768%2C325ssl=1)
#### Static Lighting Level Scale(世界设置)
改变lightmass的级别。默认值为 1越小细节越多最终我们使用 0.1。
其原理是增加单位的区域的采样点的量。但这样会产生大量噪点。
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_1.jpg?resize=768%2C325ssl=1)
![image](https://i0.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_2.jpg?resize=768%2C325ssl=1)
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_3.jpg?resize=768%2C325ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_lightlevelscale_4.jpg?resize=768%2C325ssl=1)
#### Indirect Lighting Smoothness(世界设置)
该值大于1是会对LightMap进行平滑处理反之会进行锐化过高的数值会导致漏光现象。对于这个属性作者建议设置为0.7~1。
#### Indirect Lighting Quality(世界设置)
增加进行final gathering运算时的采样光线量。提高数值可以减少间接照明时的噪点但代价是极大地增加构建时间。
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_1.jpg?resize=768%2C325ssl=1)
![image](https://i1.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_2.jpg?resize=768%2C325ssl=1)
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_5.jpg?resize=768%2C325ssl=1)
![image](https://i2.wp.com/www.tomlooman.com/wp-content/uploads/2017/10/ue4_indirectlightingquality_10.jpg?resize=768%2C325ssl=1)
#### Lighting Build Quality(在构建下拉按钮中)
调整LightMass烘焙质量的总开关。用于整体调整烘焙质量对烘焙时长影响很大。
- Preview耗时 2 分 16 秒)预览级
- Medium耗时 7 分 48 秒)中级
- High耗时 13 分 58 秒)高级
- Production耗时 30 分 22 秒)产品级
作者的习惯:大部分的工作时间都使用预览级,有时会切换到中级进行效果预览。在周末可能会切换到高级或者产品级进行烘焙来查看效果。
#### Lighting Level Scale与Indirect Lighting Quality的设置技巧
保持Static Lighting Level Scale × Indirect Lighting Quality = 1。你就可以得到一个噪点较少的场景。
#### Volume Light Sample Scale
减少Static Lighting Level Scale会增加这些空间采样样本密度
#### 添加LightMass Portal提高室内场景光照质量
给室内场景中的窗户添加LightMass Portal以此可以减少噪点。
其原理是模拟天光效果,向室内发射光子。但也因此会在增加发射光子阶段(渲染过程中)的时间。
## Lighting Scenarios
在游戏制作过程中有时会存在同一场景使用多套Lightmap的情况模拟天气系统以及一天中不同时段的光照等。在Unreal 4引擎中可以利用Lighting Scenarios功能达到此效果。使用Lighting Scenarios时首先需要创建不同的光照Level然后在Level设置页面将其设置为Lighting Scenario类型如下图所示
![image](https://pic2.zhimg.com/80/v2-ce6f0a63eac3abcfcdedf1b68ce108cd_hd.jpg)
另外设置Level的加载方式为使用Blueprint加载
![image](https://pic4.zhimg.com/80/v2-e52d3177593069c8043ca6e8a7879107_hd.jpg)
接着对不同Level设置不同的光照进行烘焙。烘焙结束后在Persistent Level Blueprint中添加Load Stream Level节点对Level进行加载并设置要加载的Level名称Day如下图所示
![image](https://pic3.zhimg.com/80/v2-c3db25960facf2c84981647472c3f416_hd.jpg)
## BaseLightmass.ini参数
如果你是做室内效果Ue4默认设置的GI可能无法满足你的需求。你可以选择修改引擎配置文件BaseLightmass.ini的参数。以下内容个人感觉给的数值过于巨大将耗费大量时间请大家酌情使用。
**NumHemisphereSample**半球采样数默认值16想要更好的效果可以给128。
**Num Irradiance Calsulation Photons**参与照度计算光子的数量默认是400如果不满足效果可以给2048甚至更多。
结论就是数值越高消除的BUG越多当然花费时间越长所以国外大神给出了一组不同阶段的测试参数用来平衡时间与质量。
### 测试渲染
- NumHemisphereSamples = 64
- IndirectPhotonDensity = 3000
- IndirectIrradiancePhotonDensity = 1500
- IndirectPhotonSearchDistance = 180
### 进一步渲染
- NumHemisphereSamples = 128
- IndirectPhotonDensity = 6000
- IndirectIrradiancePhotonDensity = 3000
- IndirectPhotonSearchDistance = 180
### 最终渲染
- NumHemisphereSamples = 256 or 512 (above 512 will have not much difference)
- IndirectPhotonDensity = 12000 (If there are still flickers of artificial lighting, increase this value along with the parameter below)
- IndirectIrradiancePhotonDensity = 8000
- IndirectPhotonSearchDistance = 180 (180 - 240)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/默认设置.png)
默认设置
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/高质量设置.png)
#### 使用GPULightMass
使用LightMass烘焙光照往往比较慢所以你可以尝试使用GPULightMass。
论坛中的GPULightMass主题
https://forums.unrealengine.com/development-discussion/rendering/1460002-luoshuang-s-gpulightmass
日天大佬写的简单使用说明:
https://zhuanlan.zhihu.com/p/71584366
如果想要最新版本可以在UnrealEngine的github分支中找到。
#### 最终渲染效果提升
这一步视项目需求与目标平台性能而定。
##### 反射质量
在控制台中输入r.SSR.Quality 4
如果电脑性能较强可以修改引擎Shader文件夹中的ScreenSpaceReflections.usf这个文件。在最高质量4中的参数步数以及射线数都是1212。你显卡牛逼的话都调成64。吃不消就往下调。反正你会发现你的帧数会有明显变化。没有效果那么修改完usf文件后退出引擎重新进一次然后控制台输入r.SSR.Quality 4。
##### 抗锯齿
在控制台中输入r.PostProcessAAQuality 6

View File

@@ -0,0 +1,154 @@
## 天光
天光亮度一般来说还是保持为1比较好但以天光作为主要照明是例外夜晚场景
天光有“场景捕获”与“使用CubeMap”两种使用方向。但是本质上还是围绕着CubeMap所以作者更加倾向于使用CubeMap。除非你实现了类似CryEngine的昼夜交替系统。
Daedalus51在视频1.1最后讲解了Skylight技巧推荐大家去看。
### 调试方法
往场景中放入一个Sphere与Cube模型并赋予Chrome材质引擎Content中。假设你已经关闭后处理空间中的屏幕空间反射效果。这样就可以观察天光是否达到你的要求。
在静态光照下LightMass会烘焙出GI效果你可以开启LightingOnly模式查看GI效果。
![image](https://docs.unrealengine.com/4.27/Images/BuildingWorlds/LightingAndShadows/LightTypes/SkyLight/SkyLight_1.webp)
### 使用CubeMap
也就是使用Hdr环境贴图赋予Hdr后记得修改CubeMap Resolution贴图最长边的值必须为2的幂
### 场景捕获
对远景与环境球进行采样,再作为天光数据对场景进行照明。
如果你希望把Hdr图作为环境球在场景中渲染那步骤如下
1. 往场景中加入环境球模型想省事可以使用EngineContent中的SM_SkySphere。
2. 使用以下材质(这里我就直接拿日天大佬的图来展示):
![image](https://pic1.zhimg.com/80/v2-6cb7ed806245a4ef264b909e67fd29a8_hd.jpg)
另外材质的光照模型选择自发光,勾选双面渲染选项。
3. 设置Sky Distance Threshold属性并调整环境球模型默认的SM_SkySphere太小了
4. 关闭模型的Cast Shadow选项。
5. 点击SkyLight标签中Recature按钮你可以在蓝图调用RecaptureSKy函数来进行更新
使用Exr贴图来制作天空球材质好处在于不需要使用材质中的旋转节点直接连入自发光节点即可。同时可以通过旋转模型来旋转天空球。
#### 其他参数说明
**Sky Distance Threshold**当物体处于该数值半径外时天光Actor为坐标为原点天光会对该物体进行采样。
**Capture Emissive**仅对自发光材质进行采样以减少性能消耗。当使用采集每一帧Capture Every Frame建议使用此方法。
**更新天光**需要点击SkyLight标签中**Recature**按钮。
### 下半球颜色控制
在场景中使用了环境球或是默认场景蓝图天球并且天光使用场景捕获时我们只需要上半球的光照照亮场景而下半球的颜色往往会造成“天光漏光”的问题天光不能穿过地面对物体进行照明。此时我们就需要勾选“Lower Hemisphere Is Solid Color”选项并且将“Lower Hemisphere Color”设置为黑色。
在Daedalus51的视频中他展示了另一种使用思路。首先他的场景中是有一大片草地的如果Lower Hemisphere Color使用黑色其实是不正确的。于是他使用PS对开启BaseColor模式的场景进行均匀取色均匀模糊后再取色。再用该颜色设置Lower Hemisphere Color的值。这样效果会更加真实一些。
但也有例外情况,比如汽车等机械产品展示。一般来说这类项目一般会采用**影棚Hdr贴图**此时就需要关闭“Lower Hemisphere Is Solid Color”选项。因为底部的光照效果我们也是需要的。
### 距离场AO
《Lighting with Unreal Engine Masterclass》视频中有提到天光的AO也是距离场AO的重要组成部分。所以如果你要使用距离场AO别忘记天光部分。记得开启AO与距离场观察模式查看其质量
视频偏后段展示了通过设置AO颜色来模拟GI效果不过本人不太推荐使用。
### 动态天光注意事项
如果你的天光是动态类型的,那就需要开启距离场阴影相关功能,不然天光是不会投射阴影的。
### 天光反弹反弹(静态光照有效)
在世界设置中可以设置天光的反弹次数,提高反弹次数以提高效果。
## 动态环境球效果
在Daedalus51的4.6章节视频中,他演示一种动态环境球效果:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Unreal4DynamicSky.gif)
如果在云层移动速度较慢的情况下,效果还不错。以下是材质实现:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/SkyMaterial.png)
作者在其基础上进行了一定改进但是环境球的顶部会有图像畸变bug。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Unreal4dynamicSky.png)
## 调整曝光与ToneMapping
1.2 21min左右开始讲解有关曝光的知识这里强烈建议看一下。不过需要注意该视频制作时虚幻引擎还没有加入物理灯光。
https://www.bilibili.com/video/av59548243/?p=2
刚发现官方已经做了曝光功能的解析视频。强烈推荐去看一下。
https://www.youtube.com/watch?v=Q1xi8NwpIqA
>核心思想在于:因为前人已经积累了大量的摄影知识与经验,所以我们没有必要光靠感觉来设置灯光亮度与曝光度,而需要使用摄影标准来设置灯光,从而使得场景更加真实。
### 恶补相关知识
这里我推荐看一下日天大佬文章与Wiki可以恶补相关知识。<br>
虚幻中灯光的物理单位计算公式:
https://zhuanlan.zhihu.com/p/69248316
公式推导:
https://zhuanlan.zhihu.com/p/69348892
只需要知道```$E=3.14\times2^{EV}$```就可以了
物理灯光文档
https://docs.unrealengine.com/zh-CN/Engine/Rendering/LightingAndShadows/PhysicalLightUnits/index.html
曝光值
https://en.wikipedia.org/wiki/Exposure_value
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/35F7CFB7707F4C8699720C28F3409060.octet-stream)
Sunny16
https://en.wikipedia.org/wiki/Sunny_16_rule
EV使用推荐
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Sunny16.png)
### 操作过程
#### 调整曝光
在设置天光与其他主光源亮度前需要确定好曝光值。在1.2节27min左右Daedalus51打开Visualize——HDR(人眼适应),通过该工具来查看亮度分布、以及亮度调整情况情况。以此来调节后处理空间中的曝光度范围。(调整曝光范围0.03~2 => 0.4~2)
#### 开启ToneMappering
1.2章节34min处这段ToneMappering介绍这段推荐只看英文字幕。
Daedalus51通过命令行查看是否开启ToneMappering输入r.TonemapperFilm,可以查看是否开启ToneMappering功能。0为关闭1为开启输入r.TonemapperFilm 1就可开启ToneMapper功能。
Daedalus51之后调整了ToneMapping曲线在后处理的PostProcess-Film中不过个人不建议调整除非对调色十分了解。
##### 有关Console命令的技巧
这里几个操作都与Console命令有关所以介绍一下Daedalus51介绍的技巧
**查看所有命令详细信息**在Ue4编辑器窗口中点击Help-ConsoleVariables就可以得到console命令表可以查看详细说明。
**在项目启动时默认执行一些命令**
在项目目录的Config文件中DefaultEngine.ini(/Script/Engine.RenderSettings)中输入命令。例如r.SSS.Quality=4
##### 有关其他后处理效果
因为ToneMappering会影响到后处理中调色效果所以Daedalus51推荐使用Lut来做后处理。
## 本人对于曝光的看法
本文主要是以笔记的形式来记录在视频中所学习到东西,所以比较杂乱。因为在看完所有视频与资料后,我在此总结一下我对曝光的看法。
在物理灯光换算方面日天大佬花费大量功夫整理出一张换算表这样是效率最高的工作方法。当然我是一个嫌麻烦的人也没有时间仔细研究这些东西还是使用Unreal4的像素检测器直接检测亮度比较方便。
对于曝光,我大致有两种方案:
### 以EV0作为基准的
一个是以EV0作为基准来调整场景灯光的传统方法。之后再通过曝光换算公式将实际灯光亮度转化为EV0的亮度最后再调整自动曝光范围。但这样也就是失去了物理灯光的意义了。
虽然“虚幻争霸”是采用这种曝光方案来布置灯光。但Unreal4马上就要推出物理大气,同时“堡垒之夜”中已经使用了日天系统Sun And Day可以说“物理化”是一个势在必行的趋势。
### 以真实环境EV作为基准
一个是根据场景的环境特征确定EV使用真实灯光的亮度数据。因为是真实世界测量出的数据所以你可以很容易且快速精准地做出逼真效果。相当适合于建筑室内设计等需求照片级渲染质量需求。
#### 解决场景HDR差距较大所产生光照问题
以上两种方案都需要解决场景动态范围差距较大所产生的暗部亮部效果无法兼顾的“问题”。
严格的说这不能算是一个问题,因为这就是正确的渲染结果。
李文磊老师在《在UE4中实现灯光之美》演讲中有说到
>以我个人经验我觉得这个发范围超过8个档位以外的像素大家可能需要小心点如果是0的EV基础值低于-5.3可能会死黑高于3可能会爆掉这是个人的经验。
我们可以通过减少HDR插件来避免这种“问题”的产生具体可以使用以下两种方式
**降低室外天光亮度**
降低室外主光源的亮度来匹配室内EV。避免使用高EV的环境HDR图比如正午大晴天。选择低EV的环境HDR图例如早晨无云、正午多云的环境HDR图。再使用恰当的EV范围进行自动曝光效果就比较好了。
如果不想让用户看到自动曝光的转换过程,可以提高自动曝光速度。
**提高室内灯光亮度**
提高室内灯光的亮度来匹配室外EV。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/提高室内亮度.jpg)
### 动态环境球与天光捕获问题
上文有介绍Daedalus51的动态环境球方案。那么问题来了天光捕获的是哪一帧的环境球我们无法确定。那么天光烘焙结果就是不确定的么那么TrueSky之类的RayMarching材质天光捕获是否会正确呢
因为本人没有TrueSky而且这个问题也无伤大雅所以本人就不研究了。
#### 个人对于动态天空的意见
其实本人不太喜欢Daedalus51的环境球方案因为会穿帮。个人还是倾向于改造BP_Sky_Sphere。BP_Sky_Sphere的问题主要在云与大气上。
云的话还是推荐做成RayMarching计算透明度与阳光散射效果如果是不运动的云可以预计算效果
![image](http://storage.googleapis.com/wzukusers/user-22455410/images/582eb811b16d53XZwq6l/Tracing_Shadows.gif)
大气效果应该不是物理的不然Unreal4也不会在4.24推出物理大气系统。

View File

@@ -0,0 +1,154 @@
## 前言
动态光照需要使用多种照明技术协同工作这一点与只需要LightMass的静态光照不同。所以这一章我将会总结一些有关动态光照相关的参数、使用场景与设置技巧。
### Daedalus51的流程
#### 1.2章节的流程
1. 去除干扰场景光照的后处理效果(本系列第一篇文章)
2. 检查贴图的固有色是否正确并进行调整。(本系列第一篇文章)
3. 调整天光以符合设计目标。(本系列第一篇文章)
4. 分别开启Visualize Distance Field Ambient Occlusion、Visualize Mesh Distance Field检查距离场以及对应效果。
5. 设置曝光相关属性。
6. 设置方向光与其他主光源的基础属性(方向、亮度等)
7. 给场景中的带有反射属性的物体添加反射球并调整属性。Daedalus51的流程中没有关闭空间反射如果你按照我之前写的关闭空间反射这里要记得开启之后开启Reflections模式查看场景反射情况。
8. 添加补光灯光,以及伪造灯光来增加气氛
9. 放置高度雾。(雾可以改变场景色调)
10. 后处理调色。
**如何补光**<br>
使用 关闭Cast Shadow、Min Roughness为1的点光源进行补光。这种灯光十分柔和可以用于提亮局部死黑部分。配合Light Channel可以只对某一些物体起作用。
**阴影建议**<br>
如果灯光开启了动态阴影那么请不要使用点光源而需要使用聚光灯。Daedalus51的理由是:点光源会向6个方向投射阴影类似Box对性能消耗较大。但事实上因为Ue4光照缓存机制所以只有第一次渲染才会有如此大的消耗
当若干几个物体较黑时可以考虑使用聚光灯并且设置Min Roughness为1,这样可以达到提亮物体的作用。
### 调试用命令
- r.Shadow.MaxResolution 可选参数256~2048默认2048
- r.Shadow.MinResolution 可选参数16~128默认32
- r.Shadow.FadeResolution 可选参数64~2048默认64
- r.Shadow.DistanceScale 可选参数0.5~2默认1
- r.Shadow.RadiusThreshold 可选参数0.01~0.5默认0.03
- r.Shadow.CSM.MaxCascades 可选参数1~16默认3
- r.Shadow.CSM.TransitionScale可选参数0~2默认1
- r.ShadowQuality 可选参数1~5默认5
- r.LightFunctionQuality 可选参数0~2默认2
- r.DistanceFieldShadowing True~False默认True
- r.DistanceFieldAO True~False默认True
- r.ParticleLightQuality 可选参数0~2默认1
- r.Shadow.UnbuiltPreviewInGame
- r.Shadow.UnbuiltNumWholeSceneDynamicShadowCascades
- r.Shadow.MaxCSMResolution
- r.CapsuleShadows
更多技巧请参考第二章的《有关Console命令的技巧》段落。
### 级联阴影(Cascaded Shadow Maps)
级联阴影分为近阴影与远阴影。两者皆可以设置数量、过度距离以及过度参数(在光源中设置)。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/级联阴影.png)
级联阴影技术介绍https://gameinstitute.qq.com/community/detail/117522
下面这部分内容总结于《Lighting Troubleshooting Guide》部分条目的解释有配图我也懒得贴了所以推荐直接看原文。<BR>
https://wiki.unrealengine.com/LightingTroubleshootingGuide
##### 方向光特有属性
###### Dynamic Shadow Distance Movable
以摄像机为起点的阴影覆盖距离该值为0则代表禁用该功能。
###### Dynamic Shadow Distance Stationary
以摄像机为起点的阴影覆盖距离该值为0则代表禁用该功能。
###### Num Dynamic Shadow Cascades
view frustum被分割成的级联的数量。更多的级联将导致更好的阴影分辨率但会显著增加渲染成本。
###### Cascade Distribution Exponent
控制级联的分布,是离相机更近(高值)还是更远(低值)。值1表示转换将与分辨率成正比。
###### Cascade Transition Exponent
级联之间的过度参数,较低的数值产生会较生硬的过度,而较高的数值会产生较平缓的过度。
###### Shadow Distance Fadeout Fraction
阴影淡出参数。较高的值会使阴影显得较淡一些,而较低的值会使阴影显得较深一些。
##### 对于所有的动态光源
###### Shadow Bias
控制阴影在场景中的精确程度。默认值是0.5,这是权衡了精度与性能的值。
###### Shadow Filter Sharpness
控制阴影边缘的锐化程度。
#### 调整级联阴影以得到更好的效果
通过调整上述设置,可以很好地调整阴影的出血值和精度。下一节将尝试调整到最佳成都,以获得更好的精度与阴影。找到一种适合任何特定游戏的平衡将需要耗费时间、精力来进行大量测试。
以下是默认设置下会出现的问题。
![image](https://d26ilriwvtzlb.cloudfront.net/a/ae/1AdjustingSettings2_ProblemAreas.png)
调整后的结果
![image](https://d26ilriwvtzlb.cloudfront.net/8/8c/1AdjustingSettings2_finalResult.png)
#### 远距离阴影(Far Shadow)
一般是方向光作用在地形上的阴影使用的是Cascaded Shadow Maps。<br>
相关选项有:<br>
方向光源-》Cascaded Shadow Maps-》 Far Shadow Cascade Count阴影精细程度与Far ShadowDistance显示距离
地形是默认开启Far Shadow的。如果想对Actor启用则需要手动开启。
#### 级联阴影调试
可以通过show=》advanced=》ShadowFrustums显示阴影调试只对方向光有效。以此可以观察Cascaded的级数切换距离。
### 距离场光线追踪技术
级联阴影在显示远阴影时为了效果会增加阴影级数这样会增加消耗而距离场阴影的消耗相对较小所以会采样近距离级联远距离距离场阴影的配合方式。同时它可以解决因为屏幕空间渲染技术而导致的阴影失真的问题。另一个主要用处就是距离场AO了。或者可以使用距离场来写Shader。
注意距离场技术只适用于Static Mesh。
### 如何开启
首先需要开启距离场生成,在项目设置——渲染——灯光——勾选 生成模型距离场 选项。
之后在需要的灯光处勾选“Ray Tracing Distance Field Shadow”选项开启。
### 距离场检查
距离场的构建质量将决定距离场相关技术的渲染效果。距离场分为模型距离场与全局距离场。你可以通过在Show > Visualize >中勾选Global Distance Field、Mesh Distance Field、Distance Field Ambient Occlusion来对渲染效果进行debug。尤其是AO一下子就能找到效果不好的地方。
Daedalus51推荐给这些选项设置快捷键来提高效率。在编辑器设置中搜索Visualize Global Distance Field、Visualize Mesh Distance Field、Visualize Distance Field Ambient Occlusion后设置快捷键即可。
如果距离场效果不佳可以在对应StaticMesh编辑器中设置Distance Field Resoulition Scale来调高分辨率记得按Apply Changes
### 距离场模型问题
因为距离场使用体积贴图来存储空间信息,所以对于巨大物体(比如摩天大楼)应该把模型拆成小块,以减少性能损耗。
同理,模型的形状也不应该奇形怪状,这样会大大的影响距离场构建质量。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/模型形状不佳而导致距离场构建不佳.png)
### 植被渲染注意
因为植被面数高、结构复杂等因为因素不适合使用LightMass、SSAO进行渲染但距离场AO却十分适合。
对于树叶等需要双面渲染的物体需要在模型设置中勾选Two-Sided Distance Field Generation以生成正确的距离场。
### 级联阴影切换
可以对灯光中的Cascaded Shadow Maps——Dynamic Shadow Distance MoveableLight进行设置来达到级联阴影与软阴影的切换靠近物体会切换成级联阴影
取值可以参考静态光照的Dynamic Shadow Distance属性。
### 其他注意事项
光源中的SourceRadius、SourceLength、LightSourceAngle会对软阴影产生影响学过光追的人都知道为什么
天光也有距离场AO的选项千万别忘记了。
## 胶囊阴影
这是骨骼物体的软阴影方案所以光源的SourceRadius、SourceLength、LightSourceAngle选项也会对此产生影响。
- Capsule Direct Shadow此属性将启用来自直接可移动光照的柔和阴影。
- Capsule Indirect Shadow此属性将启用来自预计算光照光照图和天光的柔和阴影。
- Capsule Direct Shadow Min Visibility调整胶囊阴影的明暗度
### 如何启用
1. 在角色Asset中指定ShaowPhysicsAsset。
2. 在角色Asset的Lighting选项卡中勾选Capsule Direct Shadow或者Capsule Indirect Shadow
### 接触阴影(Contact Shadows)
是一种为了弥补级联阴影的缺陷,用于补充角色细节、场景细节阴影的技术。它是一种根据深度信息进行着色的后处理渲染技术。
ContactShadowLength参数大于0时就会开启默认是关闭的。。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/接触阴影.png)
https://docs.unrealengine.com/zh-CN/Engine/Rendering/LightingAndShadows/ContactShadows/index.html
#### 原理
将接触阴影的长度设为大于零的值后,渲染器将通过场景的深度缓存从像素的位置到光源进行光线追踪。举一个典型的例子来说,将接触阴影长度的最大值设为 1此处的 1 则代表光线遍历整个屏幕。而将接触阴影长度的值设为 0.5 则意味着光线遍历半个屏幕。注意:场景深度缓存中的获得的采样将保持不变,意味着增加接触阴影的长度时将出现更多噪点(穿帮)。长度为 0.75 的接触阴影比长度为 0.1 的接触阴影生成的噪点更多。
#### 应用场景
1. 当前平台只支持一个光源且需要使用Marching实现的视差Shader。
2. 角色细节阴影与墙面植物(爬山虎)阴影。
### Light Propagation Volumes
### HDRIBackdrop
4.23的新功能这个东西会把地面部分的HDR投射到一个平面上这样场景中物体的阴影就可以投射到“HDR背景上”。适合于产品展示。奥秘就在这个环境球上。
当然Engine还提供了其他环境球可以根据需要选择。如何选择请参考文档https://docs.unrealengine.com/4.27/en-US/BuildingWorlds/LightingAndShadows/HDRIBackdrop/
不过使用的时候需要注意以下设置:
- HDR贴图设置最大纹理尺寸Maximum Texture Size应与已导入HDR图像的较大分辨率值匹配。
- 应将 Mip生成设置Mip Gen Settings设为NoMipmaps以使用完全品质。
- 调整Hdr Size拍摄场景的大小
- 如果原始设置下如果摄像仪偏离Projection Center点远处的背景就会产生形变解决的方法是勾选Use Camera Projection选项

View File

@@ -0,0 +1,43 @@
## 相关学习资料
### Unreal Circle线下培训专场 2019-09-10带翻译新手教程
https://www.bilibili.com/video/av67278159/?p=9
**建议:除非你一点基础都没有不然不推荐学习。**
### Ue4照明技术引导(静态光照) 2017-1-25视频、学习用场景、wiki、新手教程比Unreal Circle那个稍微进阶一些
- https://www.youtube.com/watch?v=jCsrWzt9F28&list=WL&index=7&t=0s
- https://forums.unrealengine.com/unreal-engine/events/107078-training-livestream-lighting-techniques-and-guides-jan-24-live-from-epic-hq?134388=
- https://cdn2.unrealengine.com/CommunityTab%2FEnglish%2F2017%2FJan+23%2FLiveStream_Lighting-11f85d1762b463154b5f53f7468e135f65955bed.zip
- https://wiki.unrealengine.com/LightingTroubleshootingGuide
**建议讲解了一些较为有用细节问题间接光照缓存、自发光物体烘焙、远阴影等配套的Wiki更是总结大部分光照问题。但是4.18更新了较多内容,有一些知识已经不适用了,但依然推荐学习。**
### Ue4照明技术引导(动态光照)
- https://www.youtube.com/watch?v=nm1slxtF_qA
- https://forums.unrealengine.com/unreal-engine/events/113380-training-livestream-lighting-techniques-and-guides-2-dynamic-light-april-4-live-from-epic-hq?140547=
**建议讲解了级联阴影、距离场、细节阴影的相关细节与几个debug技巧推荐学习**
### 解密照片级表现技巧 2016-7-27为数不多的中文干货教程
此为静态光照、与LightMass使用经验教程主要讲一些参数的经验值
https://www.engineworld.cn/forum.php?mod=viewthread&tid=432&highlight=%E8%A7%A3%E5%AF%86%E7%85%A7%E7%89%87%E7%BA%A7%E8%A1%A8%E7%8E%B0%E6%8A%80%E5%B7%A7
**建议:里面的参数已经可以使用,但本人不建议那么把数值调那么大,建议各位在逐步测试后,再使用。**
### 虚幻4GPU构建方式和注意事项
https://www.bilibili.com/video/av66480934 <br>
https://forums.unrealengine.com/development-discussion/rendering/1460002-luoshuang-s-gpulightmass
**建议国人开发的东西此大神已经是EPIC员工了效果还不错建议大家去尝试。**
### 精通 UE4 Lightmass 2017-11-02为数不多的中文干货教程
本质是对官方视频 https://www.youtube.com/watch?v=ihg4uirMcec
的解读<br>
https://mp.weixin.qq.com/s/CUVJ57s_qsCNzdVHw5OhNQ
### UE4 国外顶级大佬灯光系统全面讲解(机翻字幕)
https://www.bilibili.com/video/av59548243/p1
### 虚幻4中的物理灯光
https://zhuanlan.zhihu.com/p/69248316
https://zhuanlan.zhihu.com/p/69348892
https://zhuanlan.zhihu.com/p/70288801
https://zhuanlan.zhihu.com/p/71515823
https://zhuanlan.zhihu.com/p/71584366

View File

@@ -0,0 +1,199 @@
## 前言
在这一章我将会翻译一些我个人觉得有用的文章以此来作为这个系列的结尾。
本文图较多,手机党请注意。本文仅作为个人笔记使用,不保证所有操作都是正确的,请各位谨慎阅读
## 推荐学习顺序
对于有一定基础知道各种灯光概念却不知如何下手的读者首先推荐观看Daedalus51的第一章节视频。因为第一章节中的知识与技巧几乎覆盖了整合视频。后续的视频虽然也还行但是相对来说意义就没那么大除非你相当有时间。
之后就是推荐看我写的《灯光总结系列》,里面总结各种操作与参数,你可以把它作为参考书进行对照。但这几篇文章因为本人能力、精力有限,没有经过仔细整理,看完会些混乱。
所以之后我推荐看Daedalus51推荐的流程文章与李文磊老师的《在UE4中实现灯光之美》。
另外推荐几个官方的视频:
Ue4曝光入门
https://www.youtube.com/watch?v=Ehg4sLxOH1o
Ue4校色入门
https://www.youtube.com/watch?v=0HMFczWSRig
Ue4后处理入门
https://www.youtube.com/watch?v=aGsUU_bvOgw&feature=youtu.be
## Daedalus51推荐的流程文章
原文地址https://80.lv/articles/setting-lighting-in-unreal-engine-4-20/
**场景初步调试**
首先清理场景这一步包括了调整材质、移除所有旧灯光、放置Lightmass Importance Volumes与Lightmass Portals。一开始的测试渲染会使用默认的LightMass设置。基础效果差不多后调整到以下参数并切换至中级效果进行烘焙至到最终渲染。
体积光照贴图密度取决于环境雾精度。如果对性能没有要求可以使用更高的采样与更高的光照贴图分辨率。
![image](https://new-cdn.80.lv/upload/content/27/images/5d2c340c7051a/widen_920x0.jpg)
再光照模式下查看效果,场景只烘焙了自发光。
![image](https://new-cdn.80.lv/upload/content/90/images/5d2c340d4579f/widen_920x0.jpg)
光照贴图密度
![image](https://new-cdn.80.lv/upload/content/90/images/5d2c340dea0d7/widen_920x0.jpg)
最终烘焙所用的LightMass设置参数
**设置主要光源**
在设置完场景后,开始规划主要光源。首先需要一个来自外部的明亮光源来提供室内绝大多数的照明,但需要保证室内暗部
的灯光依然可见。所以作者选择阴天正午时分的Hdr环境图来充当天光(使用场景捕获+环境球模型+环境球材质)。调整天光材质的亮度至到其物理亮度达到阴天亮度值使用Ue4的像素检测工具可以检测物理亮度值。你可以使用Ue4的像素检测工具检查环境球基础关键区域天空、亮度云层、暗部云层、太阳但在检查时需要关闭ToneMappering并保持曝光度为0
以及关闭雾气等处于摄像鱼天空之间的显示效果,不然像素检测工具会给出错误的数值。
中午时分的天空亮度大约在400(云彩暗部)~10000云彩亮部 `${cd/m^2}$` 。基于作者想要达到效果曝光值EV定在5~7之间就比较室内渲染。对于外部的环境球可以使用EV10。注意一下HDR亮度
![image](https://new-cdn.80.lv/upload/content/2d/images/5d2c340e80bbe/widen_920x0.jpg)
**自发光光源**
作者以相同的方式来设置自发光物体。以下是室内LightingOnly模式下只烘焙自发光的效果应该还有天光本人并不赞同使用自发光来代替灯光除非你的灯光形状非同一般。
![image](https://new-cdn.80.lv/upload/content/9a/images/5d2c340f54990/widen_920x0.jpg)
因为场景中的金属物体会反射到环境球,所以下一步就应该添加反射球了。
细节光照模式(关闭屏幕空间反射)第一张为只有环境球反射高光效果。第二张为只有反射球效果。
![image](https://new-cdn.80.lv/upload/content/c5/images/5d2c341001d0d/contain_620x465.jpg)
![image](https://new-cdn.80.lv/upload/content/c5/images/5d2c3410db9c0/contain_620x465.jpg)
**灯光形状与反射捕捉**
在几次测试烘焙后作者开始放置实际光源与反射球。作者更加倾向于以真实环境作为参考来设置灯光属性例如光源形状与参数。作者会尽可能地使用Static类型的灯光如果该灯光会作用于角色等动态物体那就会使用Stationary类型的灯光。体积光照贴图可以弥补动态光照的不足但这取决于你对显存与GPU运算能力的把控。
作者更喜欢使用色温来控制灯光颜色。毕竟大多数灯光厂家都会标注灯光色温而且Unreal4可以直接通过色温来控制灯光颜色。以下是场景中主光源的设置参数
![image](https://new-cdn.80.lv/upload/content/72/images/5d2c341182d05/widen_920x0.jpg)
**后处理校色与效果打磨**
因为本人对校色不了解,这边就直接贴原文了:
Once Im happy with the lighting, I move over to color grading and polishing. I adjusted some of the Base Colors to push them more in line with a “vintage” green/orange color palette too. During early color grading explorations, I tend to grade in DaVinci Resolve because of the control and scopes, but I eventually recreate the grade directly in the engine to keep it HDR-ready. Unreal lacks some of the tools available in Resolve, but you can achieve a near identical result with what Epic has provided thus far.
Depending on the look, you can usually get most of the way there with the Global controls, then fine tune the shadows, mid-tones, and highlights with their respective controls. Highlights and Shadows will have a threshold value that acts as the range of what is considered a highlight or shadow, and anything in between those two will then be considered mid-tones.
To explain my color grading a little easier, here is a screenshot of my settings. Its worth pointing out that I avoid adjusting the tone mapper. Combined with color grading, it can get very messy to manage. Its better to consider your tone mapper as your “film stock,” and only modify it if you want to change the overall look and not on a per shot basis. I also try to work in stages and as simple as possible. Instead of jumping around between the different controls, I go from Global to Shadows, to Midtones, to Highlights (or whatever I know is the most effective for my grade) and only use what I need. If I can achieve the look I want with one or two sliders, compared to 5 or 6 with 10% influence, Id rather do that because its cleaner, easier to control, and easier to revisit later on without being completely lost.
![image](https://new-cdn.80.lv/upload/content/ab/images/5d2c34122ebc9/widen_920x0.jpg)
- White Balance: I warmed up the scene slightly to increase the feeling of everything being bathed in yellow/orange from the incandescent bulbs and diffused light fixtures
- Tint: a similar concept to the White Balance, but on a green/magenta scale. I pushed it more towards green to aid in the yellowish/sickly feeling. It also helps make the orange in the Base Color pop a little more
- Global:
- Saturation: I adjusted the saturation overall to bring out the orange more
- Shadows:
- Saturation: Desaturated shadows. A common look that film stocks have along with desaturated highlights, but the ACES tone mapper does that one for you
- Contrast: I use Contrast pretty often because its designed around keeping 18% gray neutral and it naturally gives you complementary colors, quickly getting you a more pleasing color palette
- Offset: Added a little bit of blue to the shadows
- Midtones:
- Contrast: Same idea as the shadows. Pushed complimentary colors a tiny bit which helped with additional blues to smooth out the shadow offset
- Gain: Works kind of like a multiplier, and I wanted to get some bleaching around the light areas
- Highlights:
- Gain: Mostly tinted the white spots (emissives and sky) green to push the same feeling as the overall Tint from above, but with a tiny bit of a roll-off into the higher end of the mid-tones to add to the “bleaching”
## 《在Ue4中实现灯光之美》中的灯光Fake技巧
在传统的打光我们会去调整灯光的反差比如提高对比降低对比比如在太阳底下我们拍摄电影我们会通过柔光板或者遮光板去降低反差在UE里面也可以通过一些简单的方式通过透明物体去投影按需求去控制影子的浓度降低对比度。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Ue4遮光板.jpg)
使用贴图与定制的贴花材质来区域阴影伪造。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/HackAO材质.jpg)
使用后处理材质控制场景亮度、饱和度。对于使用LightMass烘焙的场景可以在调整曝光度比值与间接光照强度。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/HackAO.jpg)
一些场景的光线强度分布差距较大时,会出现只能讨好一边的情况,所以我们可以其中一边为标准调整另一边的灯光亮度。在传统的拍电影过程中也能实现,拍电影的灯光功率都非常大,为了平衡室内外的灯光,室内就是打了这么亮的灯光,当然在引擎中或者游戏中我们非常容易的就能实现。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Hack曝光度.jpg)
## 问题解答
原文地址:
https://wiki.unrealengine.com/LightingTroubleshootingGuide
### 静态部分
#### 为什么我的阴影死黑?
在光照条件下深黑色阴影通常意味着没有填充光。这种情况经常发生在室外环境中一个方向的光代表太阳。UE4有一个内置的方法来提供一个影响填充光的世界我们把它称为天光。
#### 不想生成光照贴图
1. Rendering——Lighting——AllowStaticLighting确保引擎不会生成光照贴图。项目级
2. 打开世界设置——Lightmass——Force No Precomputed Lighting。(关卡级)
#### 双面渲染物体的阴影问题
1. 在StaticMesh——Lighting——LightMassSetting中勾选UseTwoSidedLighting。仅对静态光照
2. 在物体的材质选项中勾选TwoSided
#### 灯光上出现X标记
因为有超过4个光源重叠在一起这样会严重影响性能。动态光在该重叠区域会强制设为一个动态光源而静态光源会在烘焙光照时弹出重叠提示。
### 动态部分
#### 阴影不正确
##### 方向光
###### Dynamic Shadow Distance Movable
以摄像机为起点的阴影覆盖距离该值为0则代表禁用该功能。
###### Dynamic Shadow Distance Stationary
以摄像机为起点的阴影覆盖距离该值为0则代表禁用该功能。
###### Num Dynamic Shadow Cascades
view frustum被分割成的级联的数量。更多的级联将导致更好的阴影分辨率但会显著增加渲染成本。
###### Cascade Distribution Exponent
控制级联的分布,是离相机更近(高值)还是更远(低值)。值1表示转换将与分辨率成正比。
###### Cascade Transition Exponent
级联之间的过度参数,较低的数值产生会较生硬的过度,而较高的数值会产生较平缓的过度。
###### Shadow Distance Fadeout Fraction
阴影淡出参数。较高的值会使阴影显得较淡一些,而较低的值会使阴影显得较深一些。
###### Far Shadow
级联阴影的远阴影开关,可以解决当摄像机处于较远距离时,物体的阴影会消失的问题。
#### 调整级联阴影以得到更好的效果
通过调整上述设置,可以很好地调整阴影的出血值和精度。下一节将尝试调整到最佳程度,以获得更好的精度与阴影。找到一种适合任何特定游戏的平衡将需要耗费时间、精力来进行大量测试。
以下是默认设置下会出现的问题。
![image](https://d26ilriwvtzlb.cloudfront.net/a/ae/1AdjustingSettings2_ProblemAreas.png)
调整后的结果
![image](https://d26ilriwvtzlb.cloudfront.net/8/8c/1AdjustingSettings2_finalResult.png)
##### 对于所有的动态光源
###### Shadow Bias
控制阴影在场景中的精确程度。默认值是0.5,这是权衡了精度与性能的值。
###### Shadow Filter Sharpness
控制阴影边缘的锐化程度。
##### 为什么可移动光源在较远时其遮挡关系会出错
![image](https://d3ar1piqh1oeli.cloudfront.net/4/4a/1.png/642px-1.png)
![image](https://d3ar1piqh1oeli.cloudfront.net/0/09/4.png/642px-4.png)
为了解释这一点我们首先需要了解Ue4的渲染优化方法。引擎根据场景深度来判断场景中Mesh的可见性首先物体需要处于摄像机矩阵中如果Mesh过于远离摄像机Mesh将不会被渲染或是被遮挡。所以就会出现图中的现象。
你可能会注意到,当选择物体时,灯光恢复正常。这是预料之中的,因为它处于焦点状态。
解决方法是在Mesh属性界面调整Bounds Scale选项的大小。默认值设置为1.0。建议的调整范围为1.1、1.2左右,调整的量不宜过大,会影响性能与阴影质量。
可以viewport > Show > Advanced > Bounds 开启包围盒显示进行debug。
另一个解决思路就是使用聚光灯或者使用静态光烘焙光照贴图。
### 静态部分
你可以在世界设置中的LightMass调整以下参数以获得更好的效果
Indirect Lighting Quality设置成2或者更高。
Indirect Lighting Smoothness通常被设置为0.65~0.7之间。数值越低噪点会越多。
#### 如何控制静态照明的全局照明效果?以及光线反弹的美妙之处
默认情况下LightMass的光线反弹次数为3我们可以在Settings > World Settings > LightMass
中修改反弹次数以获得更好的效果。
光线的第一次反弹所需的计算时间是最长的。之后的反弹对实际构建时间的影响较小,但对视觉效果的影响也要小得多。
#### 解决阴影“脏”的问题
首先这种现象的原因与GI中的间接光照有关。
以下使用默认场景来创建2个测试用的关卡其中Mesh的光照贴图分辨率设为256后期空间开启人眼自适应。
![1st Interior Room, lit with direct and 1st bounce lighting](https://d3ar1piqh1oeli.cloudfront.net/4/42/3_Room1_Clean.png/553px-3_Room1_Clean.png)
<center>直接光照与一次反弹的间接光照</center>
![image](https://d3ar1piqh1oeli.cloudfront.net/c/c6/4_Room2_dirty.png/551px-4_Room2_dirty.png)
<center>只有一次反弹的间接光照</center>
一个较为暴力的手法就是提高光照贴图分辨率,但这不是最好的方法。因为这样做会增大内存消耗。
![image](https://d3ar1piqh1oeli.cloudfront.net/7/78/5_lightmap1024.png/550px-5_lightmap1024.png)
<center>光照贴图分辨率为1024的结果</center>
在WorldSettings——LightMass中可以对以下参数进行设置
##### Indirect Lighting Quality
控制间接光照的质量,以获得更佳的效果。
##### Indirect Lighting Smoothness
控制光照贴图中细节的平滑度。
##### Num Indirect Lighting Bounces
间接光照光线的反弹次数。
在对这些参数进行调整后即使是光照贴图分辨率为512其效果也比1024的好了。
![image](https://d3ar1piqh1oeli.cloudfront.net/0/0a/15_FinalResult.png/551px-15_FinalResult.png)
<center>光照贴图分辨率为512</center>
![image](https://d3ar1piqh1oeli.cloudfront.net/7/78/5_lightmap1024.png/550px-5_lightmap1024.png)
<center>光照贴图分辨率为1024</center>

View File

@@ -0,0 +1,16 @@
---
title: 解决UE5植被阴影出现噪点的问题
date: 2022-09-26 11:04:44
excerpt:
tags: Lumen
rating: ⭐
---
## 调节参数
默认为32改成4。
>r.Lumen.ScreenProbeGather.DownsampleFactor 4
默认为0.02改成0.01,阴影会变亮一些,也可以减少噪点。
>r.Lumen.ScreenProbeGather.ScreenTraces.HZBTraversal.RelativeDepthThickness = "0.01"
最后需要将预览质量从EPIC调整到Cinematic草上的噪点就没有了。

View File

@@ -0,0 +1,38 @@
---
title: 音乐控制DMX灯光的简单方法
date: 2022-11-09 17:13:17
excerpt: 摘要
tags:
rating: ⭐
---
## 前言
最近公司有个活涉及到DMX打光的活还比较急。为了减少美术同学的压力所以我就想通过音乐分析+播放Sequence的方式来实现。
UE有自带基于FFT的音乐分析功能但只能根据频率进行对应的判断比较麻烦所以放弃了。具体操作可以参考
https://www.bilibili.com/video/BV1FP411c79v/
## AudioAnalysisTools
这里推荐使用audio-analysis-tools原因是免费且开源默认带有一些预设IsHiHat、IsBeat、IsKick、IsSnare同时可以通过频率范围来判断节拍。但也正因为此一些功能不够完善需要自己改一下。
下载地址https://www.unrealengine.com/marketplace/zh-CN/product/audio-analysis-tools
以下是我做的一个案例通过音乐分析与K帧来播放对应的子Sequence预先K好DXM动画来实现音乐控制DMX灯光效果
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/AudioAnalysisTools_1.png)
IsBeatRange是根据指定频率声音的音量进行判断这里可以根据需要来设置判断条件
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/AudioAnalysisTools_2.png)
因为逻辑是写在Actor中的所以搞了按键事件来停止音乐播放。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/AudioAnalysisTools_3.png)
不过需要注意启用这个插件之后导入Wav就会变成这个资产。如果想要获得SoundWave资产就必须停用插件并且再次导入。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/AudioAnalysisTools_4.png)
## DMX移植
只需要在新工程里启用DMX插件之后将官方的DMX的工程迁移即可。需要注意的是Sequence里的需要使用DMX里面设置好DMXLibrary以及补丁这中文翻译槽点满满即可。
## 后续工作
这个方案有一些缺点:
1. AudioAnalysisTools需要播放声音才能获取的数据。
2. AudioAnalysisTools无法使用UE自带的Asset同时会导致无法在导入后生成SoundWave Asset。
3. 无法在Sequence停止播放时停下更好的控制或者在拖动轨道时执行正确的行为。
这就需要通过自定义Sequence Track以及修改插件才能完美实现。

View File

@@ -0,0 +1,10 @@
---
title: Vertex Interpolator材质节点
date: 2022-11-04 10:01:35
excerpt:
tags:
rating: ⭐
---
推荐查看视频https://www.youtube.com/watch?v=KyjlrKwbXCw
本质上是一种将VS数据进行插值最后传递到PS中的节点。需要与CustomUV、VertexColor配合使用。

View File

@@ -0,0 +1,48 @@
---
title: 自定义材质输出节点
date: 2022-09-02 11:30:37
excerpt:
tags:
rating: ⭐
---
## 前言
参考文章https://zhuanlan.zhihu.com/p/163121357
## 自定义材质输出节点
继承`UMaterialExpressionCustomOutput`类并实现对应函数即可。
具体可以参考`BentNormal`材质输出节点。Shader代码位于BasePassPixelShader.usf的ApplyBentNormal()CPP代码位于UMaterialExpressionBentNormalCustomOutput类中UE4.27)。这种节点会生成`NUM_MATERIAL_OUTPUTS_节点名`的宏。
之后就可以在BasePassPixelShader.usf中通过宏与函数获取到对应的节点计算结果。比如
```c++
void ApplyBentNormal( in FMaterialPixelParameters MaterialParameters, in float Roughness, inout float3 BentNormal, inout float DiffOcclusion, inout float SpecOcclusion )
{
#if NUM_MATERIAL_OUTPUTS_GETBENTNORMAL > 0
#if MATERIAL_TANGENTSPACENORMAL
BentNormal = normalize( TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, GetBentNormal0(MaterialParameters) ) );
#else
BentNormal = GetBentNormal0(MaterialParameters);
#endif
FSphericalGaussian HemisphereSG = Hemisphere_ToSphericalGaussian( MaterialParameters.WorldNormal );
FSphericalGaussian NormalSG = ClampedCosine_ToSphericalGaussian( MaterialParameters.WorldNormal );
FSphericalGaussian VisibleSG = BentNormalAO_ToSphericalGaussian( BentNormal, DiffOcclusion );
FSphericalGaussian DiffuseSG = Mul( NormalSG, VisibleSG );
float VisibleCosAngle = sqrt( 1 - DiffOcclusion );
#if 1 // Mix full resolution normal with low res bent normal
BentNormal = DiffuseSG.Axis;
//DiffOcclusion = saturate( Integral( DiffuseSG ) / Dot( NormalSG, HemisphereSG ) );
DiffOcclusion = saturate( Integral( DiffuseSG ) * 0.42276995 );
#endif
float3 N = MaterialParameters.WorldNormal;
float3 V = MaterialParameters.CameraVector;
SpecOcclusion = DotSpecularSG( Roughness, N, V, VisibleSG );
SpecOcclusion /= DotSpecularSG( Roughness, N, V, HemisphereSG );
SpecOcclusion = saturate( SpecOcclusion );
#endif
}
```

View File

@@ -0,0 +1,12 @@
---
title: 海洋渲染笔记
date: 2022-08-16 16:22:54
excerpt:
tags: Ocean
rating: ⭐
---
## 油管教程
https://www.youtube.com/channel/UCUk2j7f9mDQc6abLLJmN5RQ
- [Augmented Gerstner Waves WIP](https://www.youtube.com/watch?v=FphketSe6NA)
- [Augmented Gerstner Waves WIP 2](https://www.youtube.com/watch?v=4vieHzntefQ)
- [Technical breakdown of my "Augmented Gerstner Waves" demo](https://www.youtube.com/watch?v=UWGwq-_w08c)

View File

@@ -0,0 +1,47 @@
---
title: RayTracingGEM学习笔记——(1)
date: 2022-08-09 13:55:15
tags: RayTracing
rating: ⭐️⭐️
---
# RayTracingGEM
## Shader
- RayGeneration Shader是整个DXR可编程管线的入口负责投射光线将光照计算结果返回RenderTarget
- Closest Hit Shader光线与场景最近的有效交点主要用于着色计算
- Miss Shader光线与场景没有交点常用的操作是会去采样天空盒等
- Any Hit Shader可选的任意一次相交都会触发而且不保证触发顺序通常用来做Alpha Test
- Intersection Shader可选的非三角面片的程序化自定义的几何图形与光线的相交判断
使用CS生成样本集。
## Ray
DirectX Raytracing的光线定义如下
```c++
struct RayDesc
{
float3 Origin;
float TMin;
float3 Direction;
float TMax;
};
```
- WorldRayOrigin() WorldRayDirection() RayTMin() RayFlags()
- DispatchRaysIndex()查询光线index。
- DispatchRaysDimensions()查询光线维度。
- RayTCurrent()返回对应状态Hit、Intersection、Miss的Distance
- InstanceID()用户定义的ID
- InstanceIndex() and PrimitiveIndex()系统定义的ID
- ObjectRayDirection() and ObjectRayOrigin()实例空间坐标的Ray数据。
使用TraceRay()进行光线追踪计算。其工作流程如下:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RTGem_TraceRay().png)
DXR还提供了3个额外功能来控制光线与图元相交以及命中Shader的行为。
- ReportHit()设定若干属性返回Ray Hit结果。
- IgnoreHit()在any-hit shader中停止处理当前当前Hit点。
- AcceptHitAndEndSearch()在any-hit shader中接受当前Hit跳过任何未搜索到的加速结构节点。并立即传入closest hit点到closest-hit shader来使用。一般用于简化着色或者光照计算。
###
- 3.8 BASIC DXR INITIALIZATION AND SETUP
76页

View File

@@ -0,0 +1,15 @@
---
title: ToonRayTacingShadowDebug
date: 2022-12-19 15:17:25
excerpt:
tags:
rating: ⭐
---
## ToonEngine
- UnbatchedLights
- RayTracingShaowTest.DirectionalLight
- ShadowBatch
- RayTracingShaowTest.DirectionalLight
- RayTracedShadow (spp=1) 1150x712
- DispatchRays

View File

@@ -0,0 +1,331 @@
---
title: UE5RayTracing渲染管线笔记——(1)
date: 2022-08-09 13:55:15
tags: RayTracing
rating: ⭐️⭐️
---
## 任务
- [ ] 查看这个如何针对各个View构建场景
## 渲染事件
RayTracingScene位于LumenSceneUpdate之后。
## 收集场景信息
### 场景信息
在FScene中定了这2个变量来存储RayTracing专用的场景信息
```c#
FRayTracingScene RayTracingScene;
TArray<FLightSceneInfo*, TInlineAllocator<4>> RayTracedLights;
```
FRayTracingScene还存储着FRayTracingGeometryInstance数组、TArray<const FRayTracingGeometry*> GeometriesToBuild、RayTracingSceneBuffer、RayTracingSceneSRV。
### 收集过程
主要的逻辑位于GatherRayTracingWorldInstancesForView()中通过RayTracingCollector来收集场景中的图元。在FSceneRenderer定义了MeshCollector与RayTracingCollector其中MeshCollector的GatherDynamicMeshElements()在计算可见性阶段被调用()。
```c#
// Gather mesh instances, shaders, resources, parameters, etc. and build ray tracing acceleration structure
FRayTracingScene& RayTracingScene = Scene->RayTracingScene;
RayTracingScene.Reset(); // Resets the internal arrays, but does not release any resources.
const int32 ReferenceViewIndex = 0;
FViewInfo& ReferenceView = Views[ReferenceViewIndex];
// Prepare the scene for rendering this frame.
GatherRayTracingWorldInstancesForView(GraphBuilder, ReferenceView, RayTracingScene);
```
- GatherRayTracingWorldInstancesForView()
- FGPUScenePrimitiveCollector DummyDynamicPrimitiveCollector;
- 给RayTracingCollector的内部变量赋值RayTracingCollector.AddViewMeshArrays(&View,&View.RayTracedDynamicMeshElements,&View.SimpleElementCollector,&DummyDynamicPrimitiveCollector,ViewFamily.GetFeatureLevel(),&DynamicIndexBufferForInitViews,&DynamicVertexBufferForInitViews,&DynamicReadBufferForInitViews);
- 创建Mesh资源收集器`View.RayTracingMeshResourceCollector = MakeUnique<FRayTracingMeshResourceCollector>(...);`
- 初始化Rtx裁剪变量View.RayTracingCullingParameters.Init(View);
- 创建FRayTracingMaterialGatheringContext MaterialGatheringContext{Scene,&View,ViewFamily,GraphBuilder,*View.RayTracingMeshResourceCollector};
- 声明FRelevantPrimitive结构体实现InstancingKey()用于返回图元类型掩码。并定义FRelevantPrimitive数组长度为场景图元总数。
- 遍历所有图元,
### 加速结构构建
>RayTracingGem中有提到了加速结构的Rebuild与Refit概念。
该步骤会在BasePass()之前调用。DispatchRayTracingWorldUpdates()的注释说:
>异步构建可能会与BasePass重合。 Async AS builds can potentially overlap with BasePass
GeometriesToBuild在GatherRayTracingWorldInstancesForView()被填充之后在DispatchRayTracingWorldUpdates()中通过**GRayTracingGeometryManager.ForceBuildIfPending(GraphBuilder.RHICmdList, RayTracingScene.GeometriesToBuild);**更新。
- FRayTracingGeometryManager GRayTracingGeometryManager全局的场景管理类。
- ForceBuildIfPending():添加需要强制构建的多边形。
- ProcessBuildRequests():在排序请求后,调用**InCmdList.BuildAccelerationStructures(BuildParams);**构建加速结构。Render() 2634 =>DispatchRayTracingWorldUpdates()=>ProcessBuildRequests()=>InCmdList.BuildAccelerationStructures(BuildParams);
加速结构存在一个UAV上以FRayTracingGeometryBuildParams为单位。里面存储了`FRayTracingGeometryRHIRef Geometry、BuildMode、以及TArrayView<const FRayTracingGeometrySegment> Segments;`
### FRayTracingScene
使用这个类来管理Rtx场景。
## RayTracingCommon.h
UE使用宏来简化RayTracingShader的编写。 比如RayTracingShader入口函数
```c++
#ifndef RAY_TRACING_ENTRY_RAYGEN
#define RAY_TRACING_ENTRY_RAYGEN(name)\
[shader("raygeneration")] void name()
#endif // RAY_TRACING_ENTRY_RAYGEN
#ifndef RAY_TRACING_ENTRY_INTERSECTION
#define RAY_TRACING_ENTRY_INTERSECTION(name)\
[shader("intersection")] void name()
#endif //RAY_TRACING_ENTRY_INTERSECTION
#ifndef RAY_TRACING_ENTRY_CLOSEST_HIT
#define RAY_TRACING_ENTRY_CLOSEST_HIT(name, payload_type, payload_name, attributes_type, attributes_name)\
[shader("closesthit")] void name(inout payload_type payload_name, in attributes_type attributes_name)
#endif //RAY_TRACING_ENTRY_CLOSEST_HIT
#ifndef RAY_TRACING_ENTRY_ANY_HIT
#define RAY_TRACING_ENTRY_ANY_HIT(name, payload_type, payload_name, attributes_type, attributes_name)\
[shader("anyhit")] void name(inout payload_type payload_name, in attributes_type attributes_name)
#endif // RAY_TRACING_ENTRY_ANY_HIT
#ifndef RAY_TRACING_ENTRY_MISS
#define RAY_TRACING_ENTRY_MISS(name, payload_type, payload_name)\
[shader("miss")] void name(inout payload_type payload_name)
#endif //RAY_TRACING_ENTRY_MISS
```
所以Name需要与**IMPLEMENT_GLOBAL_SHADER**中定义的Shader入口函数名相同。
RayTracing函数
- FMinimalPayload Payload=TraceVisibilityRay()
- FMaterialClosestHitPayload Payload = TraceMaterialRay()
以及其他工具函数:
- DispatchRaysIndex()
- GetPixelCoord()
## RenderRayTracingReflections
- SortedDeferred
FRayTracingDeferredReflectionsRGS
## RenderDiffuseIndirectAndAmbientOcclusion
RenderRayTracingAmbientOcclusion()
- 遍历每个View
- 计算使用对应方式计算GI。将结果传递给FDiffuseIndirectInputs对象。
- 通过AmbientOcclusionRGS()RayTracing降噪得到AmbientOcclusionMask并传递给FDiffuseIndirectInputs.AmbientOcclusionMask。
- 如果有头发会多渲染头发的AO。
- 调用FDiffuseIndirectCompositePS将之前的渲染结果与GI、AO效果合成在一起。
FDiffuseIndirectCompositePS()
### AmbientOcclusionRGS
RayTracingAmbientOcclusionRGS.usf
- 计算UV、当前像素的FGBufferData以及WorldPosition与CameraDirection
- 对于非SHADINGMODELID_TWOSIDED_FOLIAGE并且开启CONFIG_SHOOT_WITH_GEOMETRIC_NORMAL则重新计算法线
- 通过HalfFOV * WorldDepth * ViewInvSize.z计算像素半径
- 计算通过DDX与DDY计算几何法线。
- 计算3个球形高斯分布没看懂
- 初始化RayTracing相关变量开始RayTracing。如果不开启追踪将**Visibility = 1.0;RayCount = SamplesPerPixel;SamplesPerPixelLocal = 0.0;**
- 使用RandomSequence生成随机样本。之后调用GenerateCosineNormalRay()生成Ray。
1. 调用RandomSequence_GenerateSample2D()取得2维随机样本。默认使用Sobol低差异序列其他还有Halton与Hash随机(https://github.com/skeeto/hash-prospector)
2. 进行余弦-半球采样并转换局部坐标为世界空间。
3. 完成Ray的初始化。
- 调用ApplyCameraRelativeDepthBias()对Ray的起点进行摄像机->像素坐标方向的偏移一个ε,以解决浮点数不精确的问题。
- 计算光线采样权重, **max(dot(WorldNormal, Ray.Direction), 0.05) / max(RayPDF, 0.05);**
- 调用TraceVisibilityRay()进行RayTracing。
- 累加采样结果。Tracing范围内没有遮挡物Visibility就是1否则就是1-IntensityLocal。该值为后处理空间里设定的AO亮度值RayTracingAOIntensity。如果Ray Hit还会设置新的ClosestRayHitDistance值。
- 输出结果。 OcclusionMask=ShadingDotGeometric * (Visibility / RayCount);HitDistance=ClosestRayHitDistance;这两个UAV都是屏幕空间降噪器的贴图变量Shader处理完之后会进行降噪处理
- 否则直接使用世界法线ShadingDotGeometric=1.0
## RenderRayTracingSkyLight
- 初始化FPathTracingSkylight SkylightParameters与FSkyLightData SkyLightData如果天光功能未开启则返回黑色OutSkyLightTexture与OutHitDistanceTexture。
- 使用CVarRayTracingSkyLightScreenPercentage计算ResolutionFraction。
- 创建RDG Texture资源RayTracingSkylight与RayTracingSkyLightHitDistance。
- 调用GenerateSkyLightVisibilityRays()生成Ray样本集256*256格式为`RWStructuredBuffer<SkyLightVisibilityRays>``SkyLightVisibilityRays为方向与PDF值float4 DirectionAndPdf;`。
- FGenerateSkyLightVisibilityRaysCS的流程
1. 计算坐标UAV坐标与SkyLightSamplingStrategyPdf会使用SkylightPdf
- 大概率是在PrepareSkyTexture()中进行了资源绑定SkylightParameters->SkylightPdf = GraphBuilder.RegisterExternalTexture(Scene->PathTracingSkylightPdf, TEXT("PathTracer.SkylightPdf"));
2. 计算每个像素数据。
1. 生成随机序列使用Hilbert curve算法: https://github.com/hcs0/Hackers-Delight/blob/master/hilbert/hil_s_from_xy.c.txt
2. 使用Sobol算子采样来得到样本。
3. 使用样本来计算天光采样结果,这一步会根据上下半球进行区分。
- ```FSkyLightSample {float3 Direction;float3 Radiance;float Pdf;};```
4. 计算最终的半球混合PDFfloat MisWeightOverPdf = 1.0 / lerp(UniformPdf, SkyLightPdf, SkyLightSamplingStrategyPdf);
5. 计算Ray的Index并将结果写入。
- 创建用于输出结果的UAV对象OutSkyLightTexture、OutHitDistanceTexture并且取得SceneTextures。
- 遍历所有View计算天光结果。
- 填充FRayTracingSkyLightRGS::FParameters。如果视口内有头发将会额外绑定HairStrandsVoxelUniformParameters。
- 设置FRayTracingSkyLightRGS变体。
- 计算FIntPoint RayTracingResolution = View.ViewRect.Size() / UpscaleFactor;
- RayTraceDispatch()。
- Denoising
- 如果SceneViewState有效返回SkyLightVisibilityRaysDimensions。
- 合成SkyLight
### FRayTracingSkyLightRGS
FRayTracingSkyLightRGS是一个GlobalShader但因为是一个RayTracing Shader所以宏的类型为
```c++
IMPLEMENT_GLOBAL_SHADER(FRayTracingSkyLightRGS, "/Engine/Private/Raytracing/RaytracingSkylightRGS.usf", "SkyLightRGS", SF_RayGen);
```
TLAS数据位于通过Scene->RayTracingScene->RayTracingSceneSRV。
```c++
PassParameters->TLAS = View.GetRayTracingSceneViewChecked();
FRHIShaderResourceView* FViewInfo::GetRayTracingSceneViewChecked() const
{
FRHIShaderResourceView* Result = nullptr;
check(Family);
if (Family->Scene)
{
if (FScene* Scene = Family->Scene->GetRenderScene())
{
Result = Scene->RayTracingScene.GetShaderResourceViewChecked();
}
}
checkf(Result, TEXT("Ray tracing scene SRV is expected to be created at this point."));
return Result;
}
FRHIShaderResourceView* FRayTracingScene::GetShaderResourceViewChecked() const
{
checkf(RayTracingSceneSRV.IsValid(), TEXT("Ray tracing scene SRV was not created. Perhaps BeginCreate() was not called."));
return RayTracingSceneSRV.GetReference();
}
```
#### AddPass
RayGem的AddPass()标记为ERDGPassFlags::Compute。RHICmdList.RayTraceDispatch()需要RayGem管线状态、Shader、RayTracingSceneRHI、RayTracing资源与分辨率。
FRayTracingPipelineStateInitializer管线状态需要:
- MaxPayloadSizeInBytes
- RayGenShaderTable
- HitGroupTable
- bAllowHitGroupIndexing
```c++
FIntPoint RayTracingResolution = View.ViewRect.Size() / UpscaleFactor;
GraphBuilder.AddPass(
RDG_EVENT_NAME("SkyLightRayTracing %dx%d", RayTracingResolution.X, RayTracingResolution.Y),
PassParameters,
ERDGPassFlags::Compute,
[PassParameters, this, &View, RayGenerationShader, RayTracingResolution](FRHIRayTracingCommandList& RHICmdList)
{
//资源绑定将Texture、UniformStruct绑定的工具函数
FRayTracingShaderBindingsWriter GlobalResources;
SetShaderParameters(GlobalResources, RayGenerationShader, *PassParameters);
//取得RayTracing管线状态
FRayTracingPipelineState* Pipeline = View.RayTracingMaterialPipeline;
//如果没开启RayTracing天光材质则重新创建一个RayTracing管线状态。看得出主要需求RayGemShader与HitGroupTable
if (CVarRayTracingSkyLightEnableMaterials.GetValueOnRenderThread() == 0)
{
// Declare default pipeline
FRayTracingPipelineStateInitializer Initializer;
Initializer.MaxPayloadSizeInBytes = RAY_TRACING_MAX_ALLOWED_PAYLOAD_SIZE; // sizeof(FPackedMaterialClosestHitPayload)
FRHIRayTracingShader* RayGenShaderTable[] = { RayGenerationShader.GetRayTracingShader() };
Initializer.SetRayGenShaderTable(RayGenShaderTable);
FRHIRayTracingShader* HitGroupTable[] = { View.ShaderMap->GetShader<FOpaqueShadowHitGroup>().GetRayTracingShader() };
Initializer.SetHitGroupTable(HitGroupTable);
Initializer.bAllowHitGroupIndexing = false; // Use the same hit shader for all geometry in the scene by disabling SBT indexing.
Pipeline = PipelineStateCache::GetAndOrCreateRayTracingPipelineState(RHICmdList, Initializer);
}
FRHIRayTracingScene* RayTracingSceneRHI = View.GetRayTracingSceneChecked();
RHICmdList.RayTraceDispatch(Pipeline, RayGenerationShader.GetRayTracingShader(), RayTracingSceneRHI, GlobalResources, RayTracingResolution.X, RayTracingResolution.Y);
});
```
#### Shader
- 计算DispatchThreadId以及对应的屏幕UV。并且取得对应的FGBufferData。
- 计算出WorldPosition以及CameraDirection。
- 判断是否需要追踪光线: 是否是有限深度 && 当前像素的ShaderModel不是Unlit。如果需要追踪采样数为传入Shader的 SkyLight.SamplesPerPixel否则为0。
- 调用SkyLightEvaluate(),进行光追计算。
- 初始化相关函数。
- 计算天光采样PDF。
- 采样循环
- 根据bDecoupleSampleGeneration()选择执行使用SkyLightVisibilityRays的样本 或者使用随机序列生成样本。
- 如果当前像素的ShadingModel是Hair需要重新计算CurrentWorldNormal。
- 偏移当前光线的深度并计算NoL。
- 设置RayFlags并且调用TraceVisibilityRay()进行光线追踪。返回FMinimalPayload存光线命中距离信息
- 如果命中累加RayDistance与HitCount。如没命中累加BentNormal并且计算FDirectLighting光照计算Hair会用另一套计算方式最后累加ExitantRadiance、DiffuseThroughput、DiffuseExitantRadiance。
- ExitantRadiance、DiffuseThroughput、DiffuseExitantRadiance除以样本数目HitDistance = RayDistance / HitCount
- 如果当前像素的ShadingModel是Hair增加头发多重散射贡献值。
- 合成估算结果。DiffuseExitantRadiance.r = Albedo.r > 0.0 ? DiffuseExitantRadiance.r / Albedo.r : DiffuseExitantRadiance.r;
- 乘以曝光值。
- 返回RWSkyOcclusionMaskUAV[DispatchThreadId]=float4(ClampToHalfFloatRange(DiffuseExitantRadiance.rgb), AmbientOcclusion);RWSkyOcclusionRayDistanceUAV[DispatchThreadId] = float2(HitDistance, SamplesPerPixel);
#### 降噪Denoising
- 调用IScreenSpaceDenoiser接口取得默认降噪器
- 设置IScreenSpaceDenoiser::FDiffuseIndirectInputs的Color与RayHitDistance(OutSkyLightTexture、OutHitDistanceTexture)
- 设置IScreenSpaceDenoiser::FAmbientOcclusionRayTracingConfig的ResolutionFraction与RayCountPerPixel(ResolutionFraction、GetSkyLightSamplesPerPixel(SkyLight))
- 调用DenoiseSkyLight()输出降噪后的结果覆盖OutSkyLightTexture。
```c++
if (GRayTracingSkyLightDenoiser != 0)
{
//取得默认降噪器
const IScreenSpaceDenoiser* DefaultDenoiser = IScreenSpaceDenoiser::GetDefaultDenoiser();
const IScreenSpaceDenoiser* DenoiserToUse = DefaultDenoiser;// GRayTracingGlobalIlluminationDenoiser == 1 ? DefaultDenoiser : GScreenSpaceDenoiser;
//降噪器变量结构体需要使用之前的渲染结果以及Hit距离结果
IScreenSpaceDenoiser::FDiffuseIndirectInputs DenoiserInputs;
DenoiserInputs.Color = OutSkyLightTexture;
DenoiserInputs.RayHitDistance = OutHitDistanceTexture;
{
//初始化RayTracingConfig
IScreenSpaceDenoiser::FAmbientOcclusionRayTracingConfig RayTracingConfig;
RayTracingConfig.ResolutionFraction = ResolutionFraction;
RayTracingConfig.RayCountPerPixel = GetSkyLightSamplesPerPixel(SkyLight);
RDG_EVENT_SCOPE(GraphBuilder, "%s%s(SkyLight) %dx%d",
DenoiserToUse != DefaultDenoiser ? TEXT("ThirdParty ") : TEXT(""),
DenoiserToUse->GetDebugName(),
View.ViewRect.Width(), View.ViewRect.Height());
//降噪
IScreenSpaceDenoiser::FDiffuseIndirectOutputs DenoiserOutputs = DenoiserToUse->DenoiseSkyLight(
GraphBuilder,
View,
&View.PrevViewInfo,
SceneTextures,
DenoiserInputs,
RayTracingConfig);
//输出结果
OutSkyLightTexture = DenoiserOutputs.Color;
}
}
```
## RenderRayTracingDebug
位于渲染Fog与Translucency、VirtualTextureFeedbackEnd()之后
```c++
#if RHI_RAYTRACING
if (IsRayTracingEnabled())
{
// Path tracer requires the full ray tracing pipeline support, as well as specialized extra shaders.
// Most of the ray tracing debug visualizations also require the full pipeline, but some support inline mode.
if (ViewFamily.EngineShowFlags.PathTracing
&& FDataDrivenShaderPlatformInfo::GetSupportsPathTracing(Scene->GetShaderPlatform()))
{
for (const FViewInfo& View : Views)
{
RenderPathTracing(GraphBuilder, View, SceneTextures.UniformBuffer, SceneTextures.Color.Target);
}
}
else if (ViewFamily.EngineShowFlags.RayTracingDebug)
{
for (const FViewInfo& View : Views)
{
RenderRayTracingDebug(GraphBuilder, View, SceneTextures.Color.Target);
}
}
}
#endif
```
DebugVisualizationMode具有3中模式
- TRAVERSAL使用ComputeShader
- RAY_TRACING_DEBUG_VIZ_PRIMARY_RAYS使用SF_RayGenFRayTracingPrimaryRaysRGS
- DebugVisualizationMode == RAY_TRACING_DEBUG_VIZ_INSTANCES || DebugVisualizationMode == RAY_TRACING_DEBUG_VIZ_TRIANGLES;使用SF_RayGenFRayTracingDebugRGS

View File

@@ -0,0 +1,185 @@
---
title: Lumen学习笔记1——官方文档与视频笔记
date: 2022-08-26 14:50:26
excerpt:
tags: Lumen
rating: ⭐⭐
---
## 前言
其他lumen学习视频https://www.youtube.com/watch?v=CFKNoeUPQGQ
Lumen | Inside Unreal https://www.youtube.com/watch?v=QdV_e-U7_pQ
Lumen的设计目标
- 大型开放世界
- 需要流式加载
- 需要控制百万级的实例物体
- 室内环境
- 实现实时GI十分困难
因为Lumen的关系UE5改进了距离场系统分辨率是UE4的2倍。这也让DFAO获益。
### 效果支持
- 反射
- 反射效果会带有GI同时支持Clear Coat材质。
- GI
- 支持对半透明物体、体积雾提供GI效果。但与不透明物体相比想过会差很多
### UE5.0版本Lumen限制
- 流明全局照明不能与光照贴图中的[静态照明](https://docs.unrealengine.com/5.0/en-US/static-light-mobility-in-unreal-engine)一起使用。Lumen Reflections 应该在虚幻引擎 5 的未来版本中扩展为与光照贴图中的全局照明一起使用,这将为使用静态照明技术的项目提供进一步扩大渲染质量的方法。
- Lumen Reflections 不支持多重镜面反射。与折射。
- 5.1才会支持薄片材质(用于制作树叶)。
- Lumen[](https://docs.unrealengine.com/5.0/en-US/single-layer-water-shading-model-in-unreal-engine) 目前不支持 [单层水材质。](https://docs.unrealengine.com/5.0/en-US/single-layer-water-shading-model-in-unreal-engine)
- Lumen 目前不支持场景捕捉或分屏。
- Lumen与[Forward Shading](https://docs.unrealengine.com/5.0/en-US/forward-shading-renderer-in-unreal-engine)**不**兼容。
- 对模型有一要求,如果是想汽车这种外壳薄、内部结构复杂的模型。作者说之后会去解决。
## 开启方式
开启Lumen的相关选项
- ProjectSettings-Rendering
- Global Illumination -> Dynamic Global Illumination Method -> Lumen
- Reflection -> Reflection Method -> Lumen
- Software RayTracing -> Generate Mesh Distance Fields -> True
硬件Lumen支持开启
- ProjectSettings-Rendering
- Lumen -> UseHardware RayTracing when available -> True
- Hardware RayTracing -> Support Hardware RayTracing -> True
另外还可以通过PostProcessVolumn的相关选项开启GI与Reflection还可以设置质量。
## 控制方式
- 场景内的灯光属性
- 材质的BaseColor与Roughness
- 曝光
## 工作原理
分硬件光线追踪与软件光线追踪2种方式。同时Lumen也是一种混合方案。
### Surface Cache
起名为Cards生成方式为Mesh Capture的方式它会在低分辨率下捕获材质信息。
- 可以使用`r.Lumen.Visualize.CardPlacement 1`显示构建方式。
- `Show->Visualize->LumenScene`可以查看Surface Cache。当Lumen显示不正常时就可以使用这个工具来查找问题。
![Lumen Card Placement Visualization](https://docs.unrealengine.com/5.0/Images/building-virtual-worlds/lighting-and-shadows/global-illumination/lumen/TechOverview/mesh-card-placement-visualization-alt.jpg)
注意:
1. `只能使用简单的内部结构的模型`不然会导致SurfaceCache生成错误。
2. 对于高面数模型需要使用Nanite进行处理不然效率会非常低。作者建议使用Nanite对场景模型进行高效的LOD设置哪怕是个小模型
3. 实例化的静态网格物体必须是Nanite。
### 软件光线追踪
1. 首先使用深度缓存(屏幕空间)进行追踪。
2. 如过Ray Miss或者离开屏幕范围就会使用ComputeShader对SDF进行追踪。Mesh距离场与全局距离场
3. 将光照的Ray Hits结果写入`Surface Cache`
#### 优化
`ProjectSettings-Rendering-Lumen-SoftRayTracingMode``DetailTracing``GlobalTracing`选项后者会跳过Mesh SDF直接去追踪全局距离场。对于一些模型叠加比较厉害的场景可以使用`GlobalTracing`来提高效率。
#### 限制
1. 目前仅支持StaticMesh、StaticMeshInstance 、Landscape。
2. 不支持有WorldPositionOffset修改的材质。
### 硬件光线追踪
- 最高质量,但是性能高消耗+显卡限制。
- 需要显卡支持Dx12。
硬件光线追踪不会直接去追踪原始的Nanite模型因为API不兼容问题而是会去追踪Nanite提供的一个简化 几何代理版本。所以如果场景GI效果不佳可以适当提高Proxy TrianglePercent比例1% -> 2%~5%。
#### 限制
- EA版中 Instance 数量不能超过100000
- 高面数的骨骼模型在Lumen中会非常消耗资源
- 模型不能有大量的重叠(对于有大量重叠的场景可以考虑使用软件光线追踪)
- 速度比软件光线追踪慢50%,但更加精确
### Lumen Final Gather
RayTracing相当耗费资源因此只能只能承担1/2 Rays per pixel但室内场景中需要200+采样才会有可以接受的结果。因为需要尽可能得利用每一次Ray Hit取得的结果。
一些常用的方法:
- Irradiance Fields二阶球谐与体素GI
- 独特的扁平外观distinctive flat look
- 缓慢的光照更新速度
- 需要人工放置VolumnBox
- 屏幕空间降噪器
- 性能消耗很大
- 降噪在光追之后,效果很有限
Lumen使用了**屏幕空间辐射度缓存** 这样允许我们从很小的一组位置进行追踪。通常会将采样降低约1/16并对每个采样点进行更多的光线追踪采样。牺牲密度来提高精度 之后使用插值的方式来填补周围像素。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Lumen_DownsampledTracing.png)
Lumen还使用**世界空间的辐射度缓存**,也就是探针(数量较少),这些探针将会重复运用于多个像素。使用`r.Lumen.RadianceCache.Visualize 1`可以显示出探针。与VolumeLightMap不同Lumn并不使用这些探针直接渲染GI效果而是从像素开始RayTracing。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Lumen_WorldRadianceCache.png)
Lumen与降噪器效果比较
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Lumen_GIComparison.png)
### Lumen Reflection
- 对于较为粗糙的物体Roughness < 0.4)会使用额外的光线进行光线追踪会耗费额外资源
- 对于较为光滑的物体Roughness > 0.4)会重用追踪结果高光波瓣会收敛在diffuse上。也就是说GI会考虑到高光
- 降噪采用Spatial与Temporal降噪器
屏幕上一半的反射是由Lumen Finaly Gather提供的。这样是的Lumen GI与Lumen Reflection能很好的结合在一起但这样也加重了Lumen的消耗。
在默认情况下渲染反射效果时即使打开光线追踪当光线命中物体会直接从表面缓存获取到所有光照结果。这种方式的渲染效率是Rtx反射的两倍但效果挺差的。将`Lumen Reflections-Quality`值提高到4时Lumen会去实际计算反射结果以提供更准确的效果。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Lumen_ReflectionQuality.png)
此时多重反弹的反射效果与天光依然由表面缓存提供。
#### Lumen Reflection vs Ray Traced Reflection
- Lumen Reflection Support
- Screen Traces
- Software Ray Tracing
- Dynamic GI In Reflection(表面缓存)
- Shadowed Moveable Skylight In Reflection(表面缓存)
- Clear Coat 2 Layers of Reflections
- Ray Traced Reflections Support
- LightMap GI In Reflections
- Multi-Bounce Reflections
- FutureLumen Reflections
- Bring Over Remaining Feature From Ray Traced Reflection
## 最佳实践
- Emissive
1. 使用Lumen时不要使用自发光材质模型代替灯光。容易产生大量噪点
2. 使用较大的自发光材质模型做出一些效果。比如夜晚房间中的电视机效果。以及场景中微弱补光。
3. 使用较小的自发光材质模型+灯光。![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Lumen_Emissive1.png)
- BaseColor
1. BaseColor与GI影响很大。
2. 非常暗以及糟乱BaseColor会产生很烂的GI效果。![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Lighting/Lumen_BaseColor.png)
3. 作者建议使用材质集+材质参数来调整MegaScane资产BaseColor贴图的亮度这样可以保证所有材质的亮度保持统一。
- Indirect Lighting Intensity灯光的选项
1. 现在不支持屏幕空间追踪。(预览版)
2. 将其设置为1外的值会出现伪影。预览版
- Performance
1. Epic 质量为游戏主机上30帧的设置。
2. High质量为游戏主机上60帧的设置。
## 其他
1. 是否支持物理灯光作者认为是支持的同时内部也有一些项目使用。但没有经过严格测试。可以确定Emissive材质物体会直接失效因为Emissive物体也需要提高到相应的亮度才能显示。
2. 目前Lumen Reflection 不支持 LightMap而且LightMap还会额外占用显存。
# 王祢的讲解视频(详细的算法解释)
- UnrealCircleNanite技术简介 | Epic Games China 王祢
- UOD2021虚幻引擎5渲染特性解析 Lumen | Epic Games 王祢
1. RadianceCaching
# 中文直播
[中文直播]第42期 | Lumen高品质渲染解析 | Epic 纪大伟 https://www.bilibili.com/video/BV15d4y1V7p9?vd_source=d47c0bb42f9c72fd7d74562185cee290
Ctrl+L调整灯光勾选灯光选项。
## MeshCrad
1. MeshCrad构建数量数量过多会卡。
2. `r.Lumen.Visualize.CardPlacement 1`显示构建的MeshCard。
## SurfaceCache
- 黄色模型完全没有SurfaceCache
- 粉色只有部分模型有SurfaceCache
解决错误
1. 在后期盒子里调整SceneDetail
2. 把错误的模型拆开。
## 灯光矫正

View File

@@ -0,0 +1,121 @@
---
title: Lumen学习笔记2——代码分析
date: 2023-02-12 10:50:24
excerpt:
tags: Lumen
rating: ⭐
---
本文的内容为丛越的知乎文章的知识点浓缩。
## Lumen相关可视化命令
- r.Lumen.Visualize.CardPlacement 1开启Lumen Card的显示。
- r.Lumen.RadianceCache.Visualize 1开启World Space Probe。
- r.Lumen.ScreenProbeGather.VisualizeTraces 1开启屏幕空间探针追踪可视化。
## Software Lumen的加速结构(距离场)
使用SDF(Signed Distance Field)作为存软件方案的加速结构。分为Mesh DF与Global DF。
## Surface Cache
Surface Cache 是一系列运行时生成的图集(Atlas),以很低的分辨率存储了整个场景物体表面的材质属性。
Lumen 采用 Cube Map 理念仅生成**6 个轴对齐方向**(上、下、左、右、前、后)的 **Material Attribute**,在 Tracing 时将 Hit Point 简单的投影到这 6 个方向,对 Surface Cache 执行采样,就可以以极高的性能获取 Material Attribute 进行 Lighting。
在UE5中被称之为**Card**。
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230212113939.png)
## Radiance Cache
对于 Surface Cache 上的每个 Texel 来说Direct Lighting 的结果就是该点的 Radiance如果把 Surface 上的所有 Texel 的 Radiance 保存下来,这样就包含了整个场景的光照信息,这就是 Radiance Cache。有了 Radiance Cache就可以在 Tracing 时直接进行采样作为 Tracing 方向对应的 Radiance将这些 Radiance 收集起来积分计算出 Irradiance从而得到最终的光照值。还可以基于此生成 Indirect Lighting 并与历史帧累积起来,这样就实现了 Lumen 所宣称的无限反弹Infinite Bounces的全局光照能力。
从左到右分别为 Albedo、Normal、Depth、RadianceDirect Lighting 以及最终的渲染画面。
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230212110227.png)
## Lumen Scene
可以在`ViewMode-Lumen-LumenScene`中查看。
Lumen Scene 包含了 DF 描述的场景几何表达以及 Surface Cache 描述的场景材质表达,是一个完整的系统。
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230212112912.png)
## Screen Space Probe(针对MF Tracing)
Lumen 是一个基于 Probe 的 RTGI 系统,通过 Probe 执行 Ray Tracing。
Screen Space Probe 仅用于**DF Tracing**,在**1.8** 米范围内进行**Mesh DF Tracing**,那么对于更远的距离会使用**Global DF Tracing**(以及屏幕空间外的区域)。但是却**无法**同 Mesh DF 一样从 Surface Cache 上**获取 Material Attribute**,原因正如上所述,**Global DF** 是由 **Mesh DF** 合并而来,全局只有一份,缺少了 **Mesh DF** 信息,而 Surface Cache 又与 Mesh 相关,因此无法通过 Global DF 获取 Mesh 对应的 Surface Cache 数据。
Lumen作者解释时还说了下图中的东西。我个人理解为
>首先进行屏幕空间的追踪即直接获取到SurfaceCache的材质与照度数据如果失败则依次追踪**Mesh DF**与**Global DF**。
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230212114305.png)
那么 Lumen 又是如何对 Global DF 进行 Lighting 的呢?这就要引入 [[#Voxel Lighting]]。
### Screen Space Probe放置方法
首先每隔 16 个像素均匀的放置,然后进行自适应放置,检查 Probe 网格中的像素是否可被插值,即 4 个 Corner Probe 是否被当前像素遮挡,如果是则在当前像素位置上增加一个 Probe如此迭代每次迭代增加一倍分辨率直到插值差值失败的像素很少或达到指定的分辨率阈值。如下图所示橘红色为差值失败的像素
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230212113224.png)
## Voxel Lighting
Lumen的**Voxel Lighting**存储的是较粗颗粒度的**空间光照信息**应该6轴向的光照信息。从采样**Radiance Cache**获得。为了精确,采用了和**Surface Cache**一样的世界坐标6轴向对齐方案。最后通过采样3轴向的光照数据再根据Ray方向进行权重插值取得结果。
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230214145323.png)
为了进一步优化性能减少内存Lumen 还为 Voxel Lighting 生成了 Clipmap用于覆盖不同范围这样可以根据采样点所在的 Clipmap 获取光照。通过 ClipmapVoxel Lighting 可以覆盖从最小 50 米到最大 6.4 公里的范围,默认最大覆盖 400 米。
**Voxel Lighting**的另一个用途是用于生成 **Indirect Lighting**,这就是**Radiosity Indirect Lighting**。
## Radiosity Indirect Lighting
实际上严格来说 Lumen 并不是 Ray Tracing而是 Ray Casting因为光线在与 SDF 相交后并没有再次 Bounce因此最多只能产生一次 Bounce 的 Indirect Lighting为了弥补这一点Lumen 使用 Radiosity 来生成 Indirect Lighting。
传统的 Radiosity 方法需要将场景划分为 Patch而 Lumen 已经拥有了粗粒度的 Global DF 以及粗粒度的 Voxel Lighting因此可以**直接从Surface Cache 上射出光线**,与 **Global DF 进行 Ray Tracing 求交**,交点采样**上一帧的 Voxel Lighting** 后转换为**二阶球谐**,最后再根据 Normal 计算**Diffuse Transfer 球谐系数**,计算出最终的 Indirect Radiance。
这个**Indirect Radiance也保存在Radiance Cache中**,称为**Indirect Lighting**并与Direct Lighting 合并计算得到最终的 Final Lighting而下一祯的 Voxel Lighting 又来自于这一祯的 Radiance Cache因此后续所有的 Lumen 光照流程自然具有了间接光照。
## World Space Probe当Global DF求交失败时进行采样
尽管 Voxel Lighting 可以Trace到更远距离的光照但对于远光**Distant Lighting**)会更容易出现噪点,因为**小而亮**的特征产生的噪声会随着该特征的距离增加而增加,另外还存在着长距离 Tracing 的性能问题,并且距离的长短不均匀变化也会导致 Tracing 性能的不稳定。
Lumen 使用了单独的采样方案来解决这个问题,这就是**Word Space Probe**。通过在世界空间中布置 Probe向各个方向上对 **Global DF Tracing**,对 Voxel Lighting 进行采样并缓存下来,这样就得到了 **Word Space Probe 的 Radiance Cache**
乍看起来这与传统的 Irradiance Field 很相似,但不同之处在于 Word Space Probe 在**Screen Probe 周围分布 8 个 Probe**,因此是稀疏的放置的,另外由于 World Space Probe 与视角相关,为了优化性能也使用了 Clipmap每个 Clipmap 的 World Probe 数量相同,但范围不同,默认 4 级最大可设置为 6 级。下图为 World Space Probe 的可视化视图,可以明显看到 Probe 在 Clipmap 中的稀疏分布:
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230214153405.png)
**Global DF Tracing 失败时**,可以快速查找 Screen Space Probe 周围的 8 个 Word Space Probe在 Ray 方向上**对 Word Space Probe Radiance Cache 采样,三线性插值混合这些 Radiance**,从而得到最终的 Distant Lighting这种方式使光照更加稳定大大缓解了噪声。此外还可以与采样的 Voxel Lighting 进行插值混合,使光照过渡更加平滑。下图是 Radiance Cache 对比图,左图关闭,右图开启:
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230214163930.png)
## Importance Sampling
Lumen 引入了业界降噪常用的方法重要性采样Importance Sampling通过使用上一帧的 Radiance Cache 作为生成当前帧的光线方向的引导,使光线尽可能朝向光源方向追踪,这样就可以在不增加光线预算的情况下实现了降噪,如下所示:
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230214165242.png)
## Shadow
阴影的本质就是光照的可见性。Lumen 为 Surface Cache 标记 Shadow Mask这样在 Lighting 时直接乘以这个 Mask 即可。Shadow Mask 可以直接使用之前已经生成的Virtual Shadow Map。
但是这并不够完整,原因在于 **VirtualShadow Map 的生成是 Camera Visibility相关**的,而 **Surface Cache 与 Camera Visibility 无关**,这会 Surface Cache 的 Shadow Mask 缺失,因此**需要对那些没有 Mask 的 Surface 再执行一次 Ray Tracing 来判断光源的可见性**。因为 Lumen Scene 已经具有 SDF 表达,因此可直接使用 Mesh Global DF Ray Tracing其实本质上就是在**Surface Cache 上进行 DF Shadowing**。
## 降噪
同目前主流的 RTGI 一样Lumen 也采用了基于 Temporal Spatial Filter 来降噪Lumen 的 Temporal Filter 发生在整个 Lumen Pipeline 的最后阶段,而 Spatial Filter 则在各个流程之中已经进行。例如 Screen Space Radiance Cache访问时在 Probe 空间执行 3x3 的滤波,由于 Radiance Caceh 是 1/16 屏幕大小的,因此 3x3 的 Kernel 相当于屏幕空间 48x48 的 Kernel 大小,这样就以很低的成本实现大 Kernel 的 Filtering。下面是仅使用 Spatial Filter 的对比图,降噪效果明显:
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230214165813.png)
## Reflections
为了优化性能 Lumen 实现两种 Reflections 机制,当 **Roughness 大于 0.4 时**重用**Screen Space Radiance Cache**的结果,因为这时高光的 GGX Lobe 已经汇聚到 Diffuse 上,而且会自动利用已经做完的 Sample 和 Filtering 结果。当**Roughness < 0.4 的反射使用额外的光线进行 Tracing**过程与Indirect Diffuse 类似,也包含了三种 Trace
1. Screen Ray Marching采样上一帧的 Scene Color。
2. Mesh DF Tracing ,采样 Screen Space Radiance Cache。
3. Global DF Tracing ,采样 Voxel Lighting 和 World Space Radiance Cache。
最后使用双边滤波器进行降噪输出 。
## Translucency Volume GI
Lumen 还为半透明材质及体积雾的 Light Scattering 提供了低分辨率的 GI。
Lumen 将 Frustum 体素化为 Froxel对可见的 Froxel 执行 Global DF Ray Tracing采样当前帧的 Voxel Lighting 和 World Space Radiance Cache 获取 Radiance使用 3D Texture 存储每个 Froxel 的 Radiance然后使用一定次数的 Spatial Filtering 降噪,最后进行积分转换为二阶球谐系数,用于半透明材质及体积雾来计算 Lighting。
## 丛越整理的的流程
1. Lumen Scene Update
1. 根据**Mesh Cards**生成**Surface Cache**。
2. Lumen Scene Lighting
1. Surface Cache Direct Lighting
2. Radiosity Indirect Lighting
3. Direct Lighting + Indirect Lighting 合并生成**Screen Space Probe Radiance Cache**。
4. 体素化相机范围内场景并根据**Surface Cache Lighting**生成**Voxel Lighting**
5. 根据 Voxel Lighting 生成 Translucency Volume Lighting。
3. Final Gather
1. 根据**G-Buffer**放置**Screen Space Probe**
2. 在每个**Screen Space Probe**周围放置 **World Space Probe** 并根据 **Voxel Lighting** 生成 **World Space Probe Radiance Cache**
3. **Screen Tracing**,采样前一帧的 Scene Color。
4. 在近距离范围内对每个**Screen Probe**执行 **Mesh DF Ray Tracing**,采样**Screen Space Probe Radiance Cache**。
5. 在中远距离范围内对每个**Screen Probe** 执行**Global DF Ray Tracing**,采样**三个方向的 Voxel Lighting**,并同时采样 **8 个 World Space Probe Radiance Cache** 与**Voxel Lighting** 混合。
6. 插值、积分和时序过滤,最终得到 **Scene Indirect Diffuse**
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230214170223.png)
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230214170237.png)

View File

@@ -0,0 +1,12 @@
---
title: Lumen学习笔记3——代码分析
date: 2023-02-10 15:32:29
excerpt:
tags: Lumen
rating: ⭐
---
# Lumen
Lumen的逻辑开始于DeferredShadingRenderer.cpp的`RenderLumenSceneLighting()`

View File

@@ -0,0 +1,9 @@
---
title: 未命名
date: 2023-04-29 20:20:07
excerpt: 摘要
tags:
rating: ⭐
---
# Mesh形变算法
https://blog.csdn.net/chenweiyu11962/article/details/130437525

View File

@@ -0,0 +1,77 @@
---
title: Nanite学习笔记
date: 2022-09-20 17:07:44
excerpt:
tags: Nanite
rating: ⭐
---
# Nanite使用笔记浓缩自官方文档
## StaticMesh转换成Nanite的方法
1. NaniteSettings-EnableNaiteSupport=>true
2. Apply Changes
3. ImportSettings-Mesh-BuildNanite=>true
## 功能支持
Nanite支持混合模式Blend Mode **不透明Opaque** 类型的材质。其他材质类型则不被允许。
- 使用遮罩和半透明的混合模式
- 延迟贴花
- 将Nanite网格体用于网格体贴花。
- Nanite支持将贴花投射到其表面。
- 线框
- 双面材质
- 像素深度偏移
- 世界位置偏移
- 自定义逐实例数据
如果在材质中使用以下内容并指定给启用了Nanite的网格体则会导致网格体显示异常
- 顶点插值器节点
- 自定义UV
## 设置属性
- **位置精度Position Precision**:模型精度,精度越高,占用硬盘空间越多。
- **最低驻留Minimum Residency**内存缓存大小通过将模型缓存在内存中来减少固态硬盘的IO压力。
- **保持三角形百分比Keep Triangle Percent**:保留的三角形的百分比。减少此百分比可优化磁盘大小。
- **优化相对误差Trim Relative Error**:优化选项,该选项会移除小于该值的模型细节。默认为全部保留。
- **回退三角形百分比Fallback Triangle Percent**设置减少Nanite源网格体时保留的三角形百分比。当无法使用细节丰富的Nanite数据时例如平台不支持Nanite渲染或者使用Nanite数据不现实例如在复杂碰撞中时。**最低模型的面数比例**设成100%即为原始模型。
- **回退相对误差Fallback Relative Error**:设置允许为回退网格体移除的最大相对误差量。所生成回退网格体中视觉影响小于此相对误差量的所有细节将一律移除。
## 检查Nanite内容工具
选择 **工具Tools>Nanite工具Nanite Tools** 即可从主菜单打开Nanite工具Nanite Tools窗口。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20220920172530.png)
# 对启用了Nanite的网格体使用自定义回退网格体LOD
回退网格体在引擎的许多功能中都会用到例如复杂的逐多边形碰撞、光线追踪、光源烘培等等。它也可用于不支持Nanite的平台。生成回退网格体时启用了Nanite的网格体始终使用源网格体的 **LOD0** 槽来自动生成回退网格体。但是有时需要使用手动指定的回退网格体或一系列传统LOD而不是自动生成的网格体。
这种控制级别允许你在项目中使用Nanite同时也可以直接控制你在光线追踪反射中看到的几何体或不支持Nanite的平台中的几何体。按照以下步骤指定你自己的自定义回退网格体或使用一系列LOD
1. 将 **回退三角形百分比Fallback Triangle Percent** 设置为 **0** ,以便回退网格体尽可能小,因为在使用此方法时它将被忽略。
2. 使用此[传统LOD设置](https://docs.unrealengine.com/5.1/zh-CN/creating-levels-of-detail-in-blueprints-and-python-in-unreal-engine)程序将一个或多个LOD添加到网格体。
3. 使用 **LOD导入LOD Import** 下拉菜单,从 **LOD设置LOD Settings** 分段 **导入LOD关卡1Import LOD Level 1** 。
4. 在 **LOD设置LOD Settings** 分段下,将 **最低LODMinimum LOD** 设置为 **1** 。这会使得Nanite生成的回退网格体被忽略。
复杂碰撞是一种特殊情况。使用 **通用设置General Settings** 下 **用于碰撞的LODLOD for Collision** 属性指定用于碰撞的LOD。所有LOD都可用于碰撞包括LOD 0。
# 性能优化
尽可能避免以下情况:
### 聚合几何体(毛发、树叶、草之类堆叠的模型)
聚合几何体Aggregate geometry 是指许多不连贯的对象在远处合并成单个体积例如毛发、树叶和草。这种类型的几何体打破了Nanite的细节级别和遮挡剔除方式。
Nanite本身是一种分层细节级别结构它依赖的方法是将小三角形精简为大三角形在差异小到无法感知时Nanite会选择较粗糙的三角形。这在连续的表面上效果很好但在聚合体几何体上效果不佳从远处看时它们更像是部分不透明的云而不是固体表面。
因此Nanite可能认为它无法像处理常见的固体表面那样大幅度减少聚合几何体因此在覆盖相同数量的像素时会绘制出更多的三角形。
### 紧密堆叠的表面(地面大量穿插的细节模型)
由于各种实际存在的限制传统网格体的遮挡剔除使得大规模的模型搭建kitbashing流程几乎不可能实现。Nanite的高精细遮挡剔除可以实现使用这些类型的工作流有助于减少开发流程中的麻烦。
正如上述"聚合几何体"小节中介绍的导致过度绘制的一种情况是可见表面与底部隐藏表面的距离过于接近。如果某个几何体妥当地隐藏在可见表面之下Nanite检测并剔除它的成本是相当低的甚至可以认为没有开销。然而如果有一些相互堆叠的几何体并且都位于最顶部的表面上Nanite可能无法确定哪个位于上面或下面导致两个几何体同时被绘制出来。
这种特殊剔除情况通常最糟糕因为Nanite不知道哪个表面在最上层导致绘制出所有内容。像这样的精度误差会随着屏幕尺寸和距离的变化而变化所以尽管10厘米的距离足够分开各个层并且在近处看起来很好但在更远的位置距离差可能会小于一个像素从而导致过度绘制。
### 面片法线和硬边法线
有个值得注意的问题是,在导入细节丰富的网格体时,因为网格体有面片法线,两个不同多边形之间的法线不平滑。此问题很常见,并且容易忽视,应该加以避免,因为网格体中顶点共享不足会导致渲染性能和数据大小的开销变得非常大。
理想情况下一个网格体的顶点数量要少于三角形数量。如果这个比例是2:1或更高那就可能出现问题尤其是当三角形数量较多时。如果比例为3:1意味着网格体完全是面状的每个三角形都有单独的三个顶点没有一个顶点是和其他三角形共享的。大多数情况下这是法线不一样导致的因为法线不平滑。
考虑到这一点顶点越多意味着数据越多。这也意味着顶点变换工作更多而比率高于2:1时会陷入一些缓慢的渲染路径。在硬表面建模中专门使用不会引起任何问题没有不用的理由。然而若意外遇到100%面片极密集的网格体开销要比预期的高得多。另外要注意在其他DCC软件包中生成的密集有机型表面的导入法线其硬法线阈值在低模网格体上可能是合理的但在Nanite中会增加不必要的开销。

View File

@@ -0,0 +1,179 @@
---
title: VirualShadowMap优化笔记
date: 2023-02-08 12:29:43
excerpt:
tags: VSM Nanite
rating: ⭐
---
# 前言
VirualShadowMap与Nanite关系很大如果场景中有很多非Nanite物体就需要将其都穿成Nanite之后可以用Nanite Tool或者VirualShadowMap Cache显示模型来检查场景具体可以参考[[Nanite学习笔记]]。具体优化步骤可以参考[[#优化案例]]。
# 相关命令某个A站作品使用的参数
## VT
- r.VT.MaxUploadsPerFrameInEditor 8
- r.VT.MaxUploadsPerFrame 8
- r.VT.MaxContinuousUpdatesPerFrameInEditor 2
- r.VT.MaxContinuousUpdatesPerFrame 2
- r.VT.MaxAnisotropy 8
## Lumen相关命令
- r.MeshDrawCommands.DynamicInstancing 0
- r .Shadow.Virtual.NonNanite.IncludeInCoarsePages 0
- r.Lumen.Reflections.DownsampleFactor 1.98
- r.Lumen.ScreenProbeGather.Temporal.DistanceThreshold 1.7
- r.RayTracing.NormalBias 5.0
- r.Lumen.ScreenProbeGather.MaxRayIntensity 10
- r.Lumen.ScreenProbeGather.ScreenTraces.HZBTraversal 0
- r.Lumen.Reflections.HierachicalScreenTraces.MaxIterations 4
## VirualShadowMap
- r.Shadow.RadiusThreshold 0.05
- r.ShadowVirtualClip .LastLevel 15
- r.Shadow.Virtual.ResolutionLodBiasDirectional -1.1
- r.Shadow.Virtual.Clipmap.UseConservativeCulling 0
- r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange 3500
- r.Shadow.Virtual.ForceOnlyVirtualShadowMaps 1
- r.Shadow.Virtual.0.CulingFarCulingFar
# 其他优化思路
其次local灯光的数量对VSM的性能有较大影响。一方面可以考虑减少不必要的局部光源数量此外可以尝试使用One Pass Projection功能实验性来提高性能。开启方法如下
- `r.UseClusteredDeferredShading 1`
- `r.Shadow.Virtual.OnePassProjection 1`
然后还可以通过调整下列CVar默认值16到更小的数字来强行限制每个像素受到影响的灯光数量来提高性能。
`r.Shadow.Virtual.OnePassProjection.MaxLightsPerPixel`
此外,还有一些实践思路供参考,例如:
减少屏幕上大面积像素受到多个大范围局部灯光的影响(**减少多个局部灯光重叠影响同一片大面积区域**
​减小局部灯光的**Source Radius**属性和方向光的**Source Angle**属性可以降低**VSM Ray的数量**
关闭数量庞大的**次要模型的投影**
​关闭**超远距离**的背景物体模型的**投影**等;
# VirualShadowMap使用笔记浓缩自官方文档
启用**VirualShadowMap**之后:
1. 无论距离如何,**Nanite几何体**始终使用**虚拟阴影贴图**渲染阴影,因为这是性能最高的选项,可提供最高质量。可以通过控制台变量 `r.Shadow.Virtual.UseFarShadowCulling 0` 使非Nanite几何体的行为方式与Nanite相同。
2. **静态烘焙阴影**将会失效。
3. **距离场阴影**主要作用于非Nanite物体比如大量植被超出**动态阴影距离可移动光源Dynamic Shadow Distance Movable Light** 距离的阴影渲染。
4. 局部光源(**点光源和聚光光源**)不受影响,依然会使用**距离场阴影**进行渲染。
5. **光线追踪阴影**的优先级仍然高于VSM
## 相关命令
- r.Shadow.Virtual.ResolutionLodBiasLocal调整分辨率。
- r.Shadow.Virtual.NonNanite.IncludeInCoarsePages 0 尝试禁止非Nanite对象渲染到CoarsePages。
- 可视化参数
- r.Shadow.Virtual.Visualize [mode] 在Virtual Shadow Map可视化模式下此命令指定要显示的通道。**Cache** 和 **vpage** 是用于可视化的两个常用选择,**none** 可禁用vsm可视化。
- mask
- Mip
- vpage
- cache
- raycount
- clipmapvirtual
- ShowFlag.VisualizeVirtualShadowMap指定可视化模式时启用虚拟阴影贴图可视化。
- r.Shadow.Virtual.Visualize.Layout选择虚拟阴影贴图可视化的布局。
- **0** 表示全屏
- **1** 表示缩略图
- **2** 表示分屏
- r.Shadow.Virtual.Visualize.DumpLightNames将带有虚拟阴影贴图的当前光源列表输出到控制台。
- r.Shadow.Virtual.Visualize.LightName [光源名称]:按名称指定光源,接受部分或全部匹配。
- r.Shadow.Virtual.Cache.DrawInvalidatingBounds 1显示缓存失效边界。
- r.Shadow.Virtual.Cache 0禁用缓存。
## 相关可视化
- View Modes-Virtual Shadow Map在大纲中选中光源可以查看对应光源的渲染信息。
- Show > Visualize > **仅绘制导致VSM失效的几何体Draw only Geometry Causing VSM Invalidation** 
- r.ShaderPrintEnable 1显示计数器
- r.Shadow.Virtual.ShowStats 1或2以仅显示页统计数据
## 非Nanite多边形渲染
可变形多边形SkeletalMesh、WorldPositionOffset、PixelDepthOffset都会使**VirtualShadowCache** 失效。
在某些情况下,例如草,有时是植被,仅使用[接触阴影](https://docs.unrealengine.com/5.1/zh-CN/contact-shadows-in-unreal-engine)足以替代高分辨率阴影贴图。如果前景中需要细节丰富的阴影,请考虑以下事项以帮助降低性能开销:
- 非Nanite对象仍然遵循常规的阴影CPU剔除设置例如 `r.Shadow.RadiusThreshold`。使用这些来帮助控制将这些对象渲染到虚拟阴影贴图的开销。
- 在有大量植被的场景中,强烈建议使用 `r.Shadow.Virtual.NonNanite.IncludeInCoarsePages 0` 禁用粗页中的非Nanite对象。或者如果不需要请考虑[完全禁用粗页](https://docs.unrealengine.com/5.1/zh-CN/virtual-shadow-maps-in-unreal-engine#%E7%B2%97%E9%A1%B5)。
- 使用网格体LOD在效果不再明显的距离处切换到不使用WPO/PDO的材质。在某些情况下可以为远处的这些对象**关闭动态阴影投射**,并完全依赖屏幕空间**接触阴影**。
对于定向光源,还有其他可用选项:
- 距离场阴影替代超出 **Dynamic Shadow Distance Movable Light** 距离范围的非Nanite几何体该距离通过光源的级联阴影贴图Cascaded Shadow Maps分段设置。为远处的非Nanite切换到**距离场阴影**可以大大提高性能因为此几何体没有Nanite提供的细粒度LOD缩放。
- 在某些情况下创建移除WPO/PDO的材质LOD可能不切实际但这些转换的最终效果在远处不明显。使用 `r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange` 设置距离(以厘米为单位),超过该距离时,将忽略这些材质的缓存失效。
# Shadow Map Ray Tracing(阴影贴图光线追踪)
一种渲染**软阴影**的方式。可通过以下命令来设置采样数值:
- `r.Shadow.Virtual.SMRT.RayCountLocal` :每个像素采样光线数量。
- `r.Shadow.Virtual.SMRT.RayCountDirectional`:每个像素采样光线数量。
- `r.Shadow.Virtual.SMRT.SamplesPerRayLocal`每个像素受到VSM的光线追踪影响的数量。
- `r.Shadow.Virtual.SMRT.SamplesPerRayDirectional`每个像素受到VSM的光线追踪影响的数量。
- `r.Shadow.Virtual.SMRT.MaxRayAngleFromLight`:通过降低半影的精度来优化性能。
- `r.Shadow.Virtual.SMRT.RayLengthScaleDirectional`:通过降低半影的精度来优化性能。
# ## GPU分析和优化
- **RenderVirtualShadowMaps(Nanite)** 包含所有与Nanite几何体渲染到VSM中相关的内容。所有定向光源都在单个Nanite通道中渲染所有局部光源都在第二个通道中渲染。
- **RenderVirtualShadowMaps非Nanite** 负责处理非Nanite几何体的渲染。每个可见光源都有一个单独的通道各种对象和实例拥有单独的绘制调用这点与传统阴影贴图渲染相同。
- **图集Atlas** 和 **立方体贴图Cubemap** 与其他类似通道包括VSM通道都只是渲染传统阴影贴图。在虚拟阴影贴图的路径中仍有少部分类型的几何体不受支持它们遵循传统路径。如果没有不受支持的几何体投射阴影这些通道将不会运行或分配阴影贴图存储。这些通道和相关的开销可以使用cvar `r.Shadow.Virtual.ForceOnlyVirtualShadowMaps 1` 完全禁用,在这种情况下,所有不受支持的几何体类型都完全不会投射阴影。
## 提升非Nanite性能
除了改进缓存之外还有许多方法可以提高非Nanite阴影渲染的性能。
- 在你的项目的几何体中**尽可能多**的部分上**启用Nanite**。
- Nanite几何体在虚拟阴影贴图中的渲染效率要高得多无论多边形数量如何都应该在每个适用的情况下作为首选。
- Nanite几何体可以遮挡非Nanite几何体并避免虚假缓存失效。因此唯一的非Nanite对象应该是Nanite本身不支持的对象例如变形对象蒙皮网格体或使用世界位置偏移WPO和像素深度偏移PDO的材质。
- **非Nanite对象**应具有**完整的网格体LOD层级设置**
- 非Nanite网格体具有LOD设置很重要否则渲染到小页的开销会非常高。
- 如果可以,建议在**距离太远**而使效果不明显时切换到**非变形网格体无WPO/PDO材质**。
- CPU剔除控制台变量对于渲染到虚拟阴影贴图的非Nanite网格体仍然有用
- 使用控制台变量 `r.Shadow.RadiusThreshold`调整渲染到虚拟阴影贴图中的非Nanite对象的CPU剔除值。这有助于控制远处小型对象的开销。
- 将[距离场阴影](https://docs.unrealengine.com/5.1/zh-CN/distance-field-soft-shadows-in-unreal-engine)用于非Nanite对象的远距离阴影投射。
- 对于定向光源在超出某个范围时通常需要切换到距离场阴影与级联阴影贴图相同。使用虚拟阴影贴图仅非Nanite几何体将切换到使用距离场阴影而Nanite几何体仍采用全细节阴影。
- 在粗页中禁用非Nanite几何体可以提高性能
- 在粗页中禁用非Nanite几何体通常可以实现大幅的性能提升因为非Nanite几何体在渲染到大页中时通常效率低下。
## 阴影投射
**阴影投射Shadow Projection** 类别是使用阴影贴图光线追踪对阴影贴图取样产生的开销。这些通道位于 **光源Lights | DirectLighting | UnbatchedLights** 之下通常每个相关光源都有一个VSM投射通道。产生最高开销的通道一般都是 **VirtualShadowMapProjection** 中的主SMRT循环。其余的开销应该相对较低。
# 其他
## 虚拟现实
虚拟阴影贴图尚未完全支持虚拟现实。右眼视角可能存在定向光源瑕疵。
## 分屏
分屏受到的测试极少,性能可能很差。
## 物理页池溢出
使用虚拟阴影贴图,场景中所有光源的所有阴影数据都存储在一个大型纹理池中。默认池大小受 **阴影Shadow** 可扩展性设置的影响,但在具有许多使用高分辨率阴影的光源的场景中,可能需要进行调整。
或者,可能需要在低端硬件上进行调整,以节省显存。
页池大小可以使用 `r.Shadow.Virtual.MaxPhysicalPages` 进行调整。连续使用 `r.ShaderPrintEnable 1` 和 `r.Shadow.Virtual.ShowStats 2` 启用虚拟阴影贴图统计数据,将显示有关当前页池使用情况的统计数据。
## 场景捕获
在一些情况下,场景捕获组件会导致整个虚拟阴影贴图缓存无效化。具体症状体现为 _Invalidations_ 在虚拟阴影贴图数据中变低但是缓存的页面也变低甚至会变为0缓存的页面会全部变成红色。
发生该情况试着隐藏或移除场景中的场景捕获Actor来验证它们是否在导致这个问题。
## 材质
**仅支持简单的次表面材质**。尚未实现次表面轮廓和传输。如果某个材质正在使用它们,则该材质将被遮蔽,就好像它不透明一样。
### 阴影分辨率
与传统阴影贴图相比,虚拟阴影贴图的分辨率显著提升,但浅光源角(或投影锯齿)和非常大的局部光源可能耗尽可用的虚拟分辨率。根据几何体的表面,这可能会呈现为盒状阴影和偏差问题。
定向光源裁剪图不太容易耗尽分辨率,但非常窄的摄像机视野最终也会耗尽这些分辨率。
阴影贴图的投影锯齿并没有简单的解决方案。即使使用虚拟阴影贴图,也必须注意避免最坏的情况,并平衡分辨率和性能。
# 优化案例
## 场景存在问题
1. 场景中的很躲静态物体没有启用Nanite使得VirualShadowMap性能变低。
2. 远景的天空球SM_Skybox_Mesh、雾气面片、近景3个人 投射阴影。
3. 植被问题。
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%9B%BE%E7%89%871.png)
## 解决方法
1.让场景模型启用Nanite。
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%9B%BE%E7%89%872.png)
可以通过ViewMode - VirualShadowMap - Cache模式来显示缓存失效情况,以此来找到非Nanaite物体。
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%9B%BE%E7%89%873.png)
之后使用Show > Visualize > 仅绘制导致VSM失效的几何体Draw only Geometry Causing VSM Invalidation来精准查找未开启Nanite的物体。
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%9B%BE%E7%89%874.png)
2. 关闭远景天空球SM_Skybox_Mesh、雾气面片与近景3个人模型的 Cast Shadow选项。尝试启用远景天空球的Nanite选项。
3. 植被分两种情况:有/无WorldPositionOffset。无的话直接转成Nanite即可。有的话建议去除 Cast Shadow选项并且勾选Contact Shadow。

View File

@@ -0,0 +1,8 @@
---
title: 未命名
date: 2022-09-04 19:50:57
excerpt:
tags:
rating: ⭐
---
可以使用DrawDynamicMeshPass()实现在插件中使用MeshDraw绘制Pass。

View File

@@ -0,0 +1,65 @@
---
title: Lighting阶段的ShadowMap逻辑
date: 2023-04-10 14:45:45
excerpt:
tags:
rating: ⭐
---
## PS Parameter
`FDeferredLightPS::FParameters GetDeferredLightPSParameters()`
- LightAttenuationTexture = ShadowMaskTexture ? ShadowMaskTexture : WhiteDummy;
- One pass projection
- VirtualShadowMap = VirtualShadowMapUniformBuffer;
- VirtualShadowMapId = VirtualShadowMapId;
- ShadowMaskBits = ShadowMaskBits ? ShadowMaskBits : GSystemTextures.GetZeroUIntDummy(GraphBuilder);
最后输出
- SceneColorTexture
- SceneDepthTexture
## Lighting阶段中VSM 渲染函数
RenderDeferredShadowProjections(),输出`ScreenShadowMaskTexture`
## 问题所在
LightAttenuation
- x整个场景的方向光阴影。
- y整个场景的方向光SSS阴影。
- zLight function + per-object shadows
- wper-object SSS shadowing in w
zw用于非方向光或者移动端渲染路径。
```c++
if (LightData.bRadialLight || SHADING_PATH_MOBILE)
{
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;
// SSS uses a separate shadowing term that allows light to penetrate the surface
//@todo - how to do static shadowing of SSS correctly? Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing;
Shadow.TransmissionThickness = LightAttenuation.w;
}
else
{
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
// Also fix up the fade between dynamic and static shadows // to work with plane splits rather than spheres.
float DynamicShadowFraction = DistanceFromCameraFade(SceneDepth, LightData);
// For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows
Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction);
// Fade between SSS dynamic shadowing and static shadowing based on distance
Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);
Shadow.SurfaceShadow *= LightAttenuation.z;
Shadow.TransmissionShadow *= LightAttenuation.z;
// Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light)
Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w);
}
```
**DeferredLightingCommon.ush**与**DeferredLightPixelShaders.usf**中的**LightAttenuation**即为ShadowMap数据。具体可以参考**GetLightAttenuationFromShadow()**可以看得出EPIC之后打算通过抖动来消除VSM的阴影锯齿。
主要的阴影效果由**LightAttenuation**与ShadingModels.ush中的NoL提供。我们只需要调整自阴影也就是z通道其他的整个场景的阴影。

View File

@@ -0,0 +1,44 @@
---
title: RenderLights
date: 2023-04-09 10:23:21
excerpt:
tags:
rating: ⭐
---
# 关键函数
取得所有ShadowMap的投影信息
```c++
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
const TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowMaps = VisibleLightInfo.ShadowsToProject;
for (int32 ShadowIndex = 0; ShadowIndex < ShadowMaps.Num(); ShadowIndex++)
{
const FProjectedShadowInfo* ProjectedShadowInfo = ShadowMaps[ShadowIndex];
}
```
# 透明体积图元渲染
## InjectSimpleTranslucencyLightingVolumeArray
插入简单透明体积物体渲染。应该是根据3D贴图渲染体积效果。默认状态下不运行。
- InjectSimpleLightsTranslucentLighting
- InjectSimpleTranslucentLightArray
## InjectTranslucencyLightingVolume
在收集用于渲染透明体积的灯光代理信息后进行渲染,主要用于云的渲染。
- InjectTranslucencyLightingVolume
# 直接光照
## RenderVirtualShadowMapProjectionMaskBits
- VirtualShadowMapProjectionMaskBits
- VirtualShadowMapProjection(RayCount:%u(%s),SamplesPerRay:%u,Input:%s%s)
输出到名为`Shadow.Virtual.MaskBits`与`Shadow.Virtual.MaskBits(HairStrands)`的UAV。
## AddClusteredDeferredShadingPass
## RenderSimpleLightsStandardDeferred
## RenderLight
针对每个灯在ShadowProjectionOnOpaque渲染ShadowMask
- VirualShadowMapProjection
- CompositeVirtualShadowMapMask

View File

@@ -0,0 +1,155 @@
# MeshDraw学习笔记
## 前言
源码版4.27.0
参考文章Yivanlee的MeshDraw系列文章。
## 图元渲染数据收集
- FDeferredShadingSceneRenderer::Render()
- InitViews()
- ComputeViewVisibility()
- GatherDynamicMeshElements()
- GetDynamicMeshElements()
InitViews():计算可见性以及初始化胶囊阴影、天空环境图、大气雾、体积雾。
GatherDynamicMeshElements():遍历场景中的所有图元类,调用`GetDynamicMeshElements()`接口函数获取渲染数据,之后调用`FMeshElementCollector``AllocateMesh()`创建一块FMeshBatch类型的内存并且使用渲染数据进行填充。
`FMeshBatch`承载`MaterialRenderProxy`以及其他渲染数据,比如:
- FVertexFactory
- FMaterialRenderProxy
- FLightCacheInterface
- uint32 CastShadow : 1; // Whether it can be used in shadow renderpasses.
- uint32 bUseForMaterial : 1; // Whether it can be used in renderpasses requiring material outputs.
- uint32 bUseForDepthPass : 1; // Whether it can be used in depth pass.
- uint32 bUseAsOccluder : 1; // Hint whether this mesh is a good occluder.
- uint32 bWireframe
`FPrimitiveSceneProxy::GetDynamicMeshElements()`
FPrimitiveSceneProxy为了解决游戏线程与渲染线程之间数据传递造成的锁死问题而诞生的方案可以理解为在渲染线程中的Scene镜像。
无论是StaticMesh还是SkeletonMesh都重写了基类的UPrimitiveComponent::CreateSceneProxy()创建对应的SceneProxy类之后再用此提交渲染数据的提交渲染信息与请求的逻辑位于GetDynamicMeshElements()。
该函数函数在渲染线程中运行根据情况使用不通的FMaterialRenderProxy子类传递给FMeshBatch与FMeshElementCollectorFMaterialRenderProxy及其子类可以看做Material信息镜像。这些情况大致包含
- DebugView
- 渲染网格模式
- Lod对应的Material
- 顶点色可视
这一步可以理解为传递Material给MeshDraw框架。
## MeshDraw渲染
`ComputeViewVisibility()`执行完`GatherDynamicMeshElements()`收集完图元渲染数据后会对每个需要渲染的View调用`SetupMeshPass()``SetupMeshPass()`会遍历`EMeshPass`中所定义的枚举再使用对应创建函数来构建FMeshPassProcessor最后执行`DispatchPassSetup()`填充所需的渲染相关信息后渲染线程中创建当前Pass的绘制任务。
EMeshPass定义了Pass:
```js
DepthPass,
BasePass,
AnisotropyPass,
SkyPass,
SingleLayerWaterPass,
CSMShadowDepth,
Distortion,
Velocity,
TranslucentVelocity,
TranslucencyStandard,
TranslucencyAfterDOF,
TranslucencyAfterDOFModulate,
TranslucencyAll, /** Drawing all translucency, regardless of separate or standard. Used when drawing translucency outside of the main renderer, eg FRendererModule::DrawTile. */
LightmapDensity,
DebugViewMode, /** Any of EDebugViewShaderMode */
CustomDepth,
MobileBasePassCSM, /** Mobile base pass with CSM shading enabled */
MobileInverseOpacity, /** Mobile specific scene capture, Non-cached */
VirtualTexture,
DitheredLODFadingOutMaskPass
```
### FMeshPassProcessor
`FMeshPassProcessor`是Mesh处理器的基类主要作用是设置渲染状态、绑定Shader与UniformStructBuffer最后生成MeshDrawCommands并且加入绘制队列。只要与模型相关的Pass都会继承该类在派生类中都会重写`AddMeshBatch()`一般会在对应的生成MeshDrawCommands函数或是`DrawDynamicMeshPass()`中的回调函数中调用。以及实现具体的处理函数`Process()`
#### DrawDynamicMeshPass
该函数中有一个回调函数,
回调函数的逻辑顺序为:
1. 使用FScene、FSceneView、FMeshPassProcessorRenderState、EDepthDrawingMode、FMeshPassDrawListContext等变量创建一个FMeshPassProcessor。
2. 之后按照有效的View使用AddMeshBatch()往FMeshPassProcessor中添加MeshBatch。
#### AddMeshBatch
其作用为往一个Pass中增加FMeshBatch。
主要逻辑:
1. 判断是否需要绘制后进行寻找FMaterial递归。
2. 从FMaterialRenderProxy中寻找FMaterial如FMaterial无效则从父类寻找直至找到为止。底层为各个材质模型的默认材质
3. 找到有效FMaterial后调用TryAddMeshBatch()。
#### TryAddMeshBatch
收集BlendMode、MeshDrawingPolic、RasterizerFillMode、RasterizerCullMode等所需变量后传递给处理函数`Process()`。在`Process()`中取得所需Shader与渲染数据后FMeshPassProcessorRenderState、FMeshDrawCommandSortKey、FMeshMaterialShaderElementData等调用`BuildMeshDrawCommands()`创建MeshDrawCommands。
以FDepthPassMeshProcessor为例`Process()`的主要逻辑顺序为取得所需的Shader包括顶点、Hull、Domain、Vertex、Pixel。初始化MeshMaterial数据BuildMeshDrawCommands构建MeshDraw命令。`Process()`同时也是个模板函数(根据不同渲染需求构建对应的MeshDrawCommand)用来切换构建的MeshDrawCommand中的EMeshPassFeatures形参用于设置顶点输入流类型Default、PositionOnly、PositionAndNormalOnly
`FMeshPassProcessorRenderState`是MeshPassProcessor的渲染状态集。存储信息如下
- FRHIBlendState* BlendState;
- FRHIDepthStencilState* DepthStencilState;
- FExclusiveDepthStencil::Type DepthStencilAccess;
- FRHIUniformBuffer* ViewUniformBuffer;
- FRHIUniformBuffer* InstancedViewUniformBuffer;
- FRHIUniformBuffer* ReflectionCaptureUniformBuffer;
- FRHIUniformBuffer* PassUniformBuffer;
- uint32 StencilRef;
### BuildMeshDrawCommands
BuildMeshDrawCommands()大致逻辑为:
1. 创建`FMeshDrawCommand`对象。以下简称为MDC。
2. 根据`FMeshPassProcessorRenderState`中的`StencilRef`来设置MDC的模板index。
3. 创建`FGraphicsMinimalPipelineStateInitializer`对象设置PrimitiveType、ImmutableSamplerState根据PassShadersType模板参数`FGraphicsMinimalPipelineStateInitializer`引用设置对应Shader的ShaderResource与ShaderIndex设置MDC的RasterizerState、BlendState、DepthStencilState、DrawShadingRate通过VertexFactory来设置MDC的PrimitiveIdStreamIndex。
4. 判断PassShadersType模板参数是那种类型的Shader之后取得对应`FMeshDrawSingleShaderBindings`,最后将`FShaderUniformBufferParameter``FViewUniformShaderParameters``FDistanceCullFadeUniformShaderParameters``FDitherUniformShaderParameters``FInstancedViewUniformShaderParameters`加入`FMeshDrawSingleShaderBindings`中。(`FShaderUniformBufferParameter`会在对应Shader类中绑定实际的UniformBuffer
5. 遍历`FMeshBatch`中存储的所有FMeshBatchElement将之前的MDC对象加入`DrawListStorage`中并取得其引用根据PassShadersType模板参数从MDC引用取得对应`FMeshDrawSingleShaderBindings`,最后将`FPrimitiveUniformShaderParameters`加入`FMeshDrawSingleShaderBindings`中。
6. 结束当前MDC构建并且将其加入`DrawListContext`的绘制列表中。
## 场景与FMeshPassProcessor的关系
在FScene::AddPrimitive(UPrimitiveComponent* Primitive)创建图元类对应的场景代理,计算矩阵、边界盒来构建`FCreateRenderThreadParameters`对象,最后向渲染线程加入图元场景信息。
在ActorComponents的UpdateAllPrimitiveSceneInfosForScenes()会在渲染线程执行UpdateAllPrimitiveSceneInfos()。UpdateAllPrimitiveSceneInfos()=》AddToScene()=>AddStaticMeshes()=>CacheMeshDrawCommands()中遍历所有类型的Pass并且创建对应的`FMeshPassProcessor`然后调用AddMeshBatch()。
## MeshDraw与RGD
MeshDraw部分不考虑Shader以SingleLayerWater为例子
- 构建FSingleLayerWaterPassMeshProcessor类
- 在构造函数中设置PassDrawRenderState。CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha
- 重写AddMeshBatch()收集OverrideSettings、MeshFillMode、MeshCullMode、MaterialRenderProxy后传入Process().
- 实现Process(),取得Shader、初始化ShaderElementData、计算SortKey之后调用BuildMeshDrawCommands()构建MeshDrawCommands。
- 实现CreateSingleLayerWaterPassProcessor()与对应的FRegisterPassProcessorCreateFunction以用来创建FSingleLayerWaterPassMeshProcessor。
调用的逻辑位于FDeferredShadingSceneRenderer::RenderSingleLayerWaterInner
- 取得GBuffer并绑定深度模板从RTPool中取得一个纯白贴图(WhiteDummy)
- 遍历有效View开始渲染
- 使用上述2个RT与FOpaqueBasePassUniformParameters填充FSingleLayerWaterPassParameters
- 使用RDG创建一个Pass里面执行更新ViewUniformBuffer后用上述两个UniformBuffer构建FRDGParallelCommandListSet最后使用View.ParallelMeshDrawCommandPasses[EMassPass::SingleLayerWaterPass].DispatchDraw()进行绘制。
## 调整渲染方式以实现背面剔除
目标是设置为非DoubleSide以及ReverseCullMode模式一些给顶点工厂添加数据
Mesh.bDisableBackfaceCulling
Mesh.ReverseCulling
重写FSkeletalMeshSceneProxy::GetDynamicElementsSection()
```c#
ERasterizerCullMode FMeshPassProcessor::ComputeMeshCullMode(const FMeshBatch& Mesh, const FMaterial& InMaterialResource, const FMeshDrawingPolicyOverrideSettings& InOverrideSettings)
{
const bool bMaterialResourceIsTwoSided = InMaterialResource.IsTwoSided();
const bool bInTwoSidedOverride = !!(InOverrideSettings.MeshOverrideFlags & EDrawingPolicyOverrideFlags::TwoSided);
const bool bInReverseCullModeOverride = !!(InOverrideSettings.MeshOverrideFlags & EDrawingPolicyOverrideFlags::ReverseCullMode);
const bool bIsTwoSided = (bMaterialResourceIsTwoSided || bInTwoSidedOverride);
const bool bMeshRenderTwoSided = bIsTwoSided || bInTwoSidedOverride;
return bMeshRenderTwoSided ? CM_None : (bInReverseCullModeOverride ? CM_CCW : CM_CW);
}
```
## 修改ShaderModel
修改Pin
添加ShaderModel枚举
BasePassPixelShader.usf中的FPixelShaderInOut_MainPS()

View File

@@ -0,0 +1,324 @@
## 前言
>RDG = Rendering Dependency Graph
RDG主要包含两个重要组件一个是FRDGBuilder负责构建Render graph的资源及添加pass等构建RenderGraph。另一个是FRDGResourceRenderGraph的资源类所有资源都由它派生。
官方入门ppt:https://epicgames.ent.box.com/s/ul1h44ozs0t2850ug0hrohlzm53kxwrz
因为加载速度较慢所以我搬运到了有道云笔记http://note.youdao.com/noteshare?id=a7e2856ad141f44f6b48db6e95419920&sub=E5276AAD6DAA40409586C0552B8E163A
另外我还推荐看:
https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
https://zhuanlan.zhihu.com/p/101149903
**本文也将引用上文中的若干内容。**
**注意**
1. 本文为了便于理解把诸如typedef FShaderDrawSymbols SHADER;这种类型别名都改成原本的类型名了。
2. 本文属于本人边学边写的学习笔记,不可避免地会有错误。如有发现还请指出,尽请见谅。
## 推荐用于学习的代码
PostProcessTestImage.cpp
GpuDebugRendering.cpp
- ShaderPrint.cpp 具体实现
- ShaderPrint.h 绘制函数声明
- ShaderPrintParameters.h 渲染变量与资源声明
本人推荐这个ShaderPrint简单的同时又可以进行扩展以此实现更多debug方式。本文中没有说明出处的代码默认来自于ShaderPrint中。
## 相关头文件
你可以查看RenderGraph.h查看官方对于RDG系统的介绍。
- #include "RenderGraphDefinitions.h"
- #include "RenderGraphResources.h"
- #include "RenderGraphPass.h"
- #include "RenderGraphBuilder.h"
- #include "RenderGraphUtils.h"
- #include "ShaderParameterStruct.h"
- #include "ShaderParameterMacros.h"
## 资源声明与绑定
### Struct的字节对齐问题
在声明Struct时需要注意一下字节对齐的问题。这里我引用一段papalqi博客中的文章
>当然在设置中我们可能还需要注意一些简单的问题。由于unreal 采用16字节自动对齐的原则所以在编写代码时我们实际上对任何成员的顺序是需要注意的。例如下图中的顺序调整。宏系统的另一个特性是着色器数据的自动对齐。Unreal引擎使用与平台无关的数据对齐规则来实现着色器的可移植性。
主要规则是每个成员都是按照其大小的下一个幂进行对齐的但前提是大于4个字节。例如
- 指针是8字节对齐的即使在32位平台上也是如此
- 浮点、uint32、int32是四字节对齐的
- FVector2DFIntPoint是8字节对齐的
- FVector和FVector 4是16字节对齐的。
>作者(Author)papalqi 链接(URL)https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
**所以我们需要根据位宽对变量与资源进行排序:**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRDG-%E5%8F%98%E9%87%8F%E4%BD%8D%E5%AE%BD%E8%87%AA%E5%8A%A8%E5%AF%B9%E9%BD%90.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRDG-%E5%8F%98%E9%87%8F%E4%BD%8D%E5%AE%BD%E6%89%8B%E5%8A%A8%E5%AF%B9%E9%BD%90.png)
进行手动位宽对齐,以减少额外的内存与带宽占用。
### 资源的初始化与绑定
其中资源分为使用RDG托管与非托管的。下面是ShaderPrint的部分代码
```
// Initialize graph managed resources
// Symbols buffer contains Count + 1 elements. The first element is only used as a counter.
FRDGBufferRef SymbolBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(ShaderPrintItem), GetMaxSymbolCount() + 1), TEXT("ShaderPrintSymbolBuffer"));
FRDGBufferRef IndirectDispatchArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(4), TEXT("ShaderPrintIndirectDispatchArgs"));
FRDGBufferRef IndirectDrawArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(5), TEXT("ShaderPrintIndirectDrawArgs"));
// Non graph managed resources
FUniformBufferRef UniformBuffer = CreateUniformBuffer(View);
FShaderResourceViewRHIRef ValuesBuffer = View.ShaderPrintValueBuffer.SRV;
FTextureRHIRef FontTexture = GEngine->MiniFontTexture != nullptr ? GEngine->MiniFontTexture->Resource->TextureRHI : GSystemTextures.BlackDummy->GetRenderTargetItem().ShaderResourceTexture;;
```
**使用RDG托管的资源**会使用GraphBuilder.CreateBuffer()创建一个FRDGBufferDesc在之后会使用GraphBuilder.CreateUAV()、CreateSRV()、CreateTexture()创建具体资源时,作为第一个形参。
**不使用RDG托管的资源**都只需要定义、计算后直接绑定即可Uniform的创建请见下文。
```
FShaderBuildIndirectDispatchArgsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FShaderBuildIndirectDispatchArgsCS::FParameters>();
PassParameters->UniformBufferParameters = UniformBuffer;
PassParameters->ValuesBuffer = ValuesBuffer;
PassParameters->RWSymbolsBuffer = GraphBuilder.CreateUAV(SymbolBuffer, EPixelFormat::PF_R32_UINT);
PassParameters->RWIndirectDispatchArgsBuffer = GraphBuilder.CreateUAV(IndirectDispatchArgsBuffer, EPixelFormat::PF_R32_UINT);
```
GpuDebugRendering.cpp中的代码可以看得出是直接绑定的。
```
//bIsBehindDepth是一个之前设置的bool变量
ShaderDrawVSPSParameters* PassParameters = GraphBuilder.AllocParameters<ShaderDrawVSPSParameters>();
PassParameters->ShaderDrawPSParameters.ColorScale = bIsBehindDepth ? 0.4f : 1.0f;
```
### 声明资源宏
这里介绍几种常用的。这些宏位于Runtime\RenderCore\Public\ShaderParameterMacros.h中。另外还有一组RDG版本的宏这些宏声明的资源需要先使用 GraphBuilder.CreateBuffer()初始化资源后再调用对应GraphBuilder.CreateXXXX()完成创建,。这个头文件中还包含了若干案例代码,为了文章的简洁性这里就不贴了。
#### 常规变量
```c++
SHADER_PARAMETER(float, MyScalar)
SHADER_PARAMETER(FMatrix, MyMatrix)
SHADER_PARAMETER_RDG_BUFFER(Buffer<float4>, MyBuffer)
```
#### 结构体
在ShaderPrint中全局的结构体的声明都写在头文件中。在自己定义的GlobalShader调用`SHADER_PARAMETER_STRUCT_REF(FMyNestedStruct, MyStruct)`来设置全局结构体的指针进行引用。使用结构体的Shader需要使用`SHADER_USE_PARAMETER_STRUCT(FMyShaderClassCS, FGlobalShader);`进行标记。
```c++
//声明一个全局的结构体变量
EGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, RENDERER_API)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, "MyShaderBindingName");
```
```c++
//声明一个全局结构体的引用
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FGlobalViewParameters,)
SHADER_PARAMETER(FVector4, ViewSizeAndInvSize)
// ...
END_GLOBAL_SHADER_PARAMETER_STRUCT()
BEGIN_SHADER_PARAMETER_STRUCT(FOtherStruct)
SHADER_PARAMETER_STRUCT_REF(FMyNestedStruct, MyStruct)
END_SHADER_PARAMETER_STRUCT()
```
```c++
//为使用结构化着色器参数API的着色器类打上标签。
class FMyShaderClassCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMyShaderClassCS);
SHADER_USE_PARAMETER_STRUCT(FMyShaderClassCS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters)
SHADER_PARAMETER(FMatrix, ViewToClip)
//...
END_SHADER_PARAMETER_STRUCT()
};
```
#### 数组
```c++
SHADER_PARAMETER_ARRAY(float, MyScalarArray, [8])
SHADER_PARAMETER_ARRAY(FMatrix, MyMatrixArray, [2])
SHADER_PARAMETER_RDG_BUFFER_ARRAY(Buffer<float4>, MyArrayOfBuffers, [4])
```
#### Texture
```c++
SHADER_PARAMETER_TEXTURE(Texture2D, MyTexture)
SHADER_PARAMETER_TEXTURE_ARRAY(Texture2D, MyArrayOfTextures, [8])
```
#### SRV
```c++
SHADER_PARAMETER_SRV(Texture2D, MySRV)
SHADER_PARAMETER_SRV_ARRAY(Texture2D, MyArrayOfSRVs, [8])
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, MySRV)
SHADER_PARAMETER_RDG_BUFFER_SRV_ARRAY(Buffer<float4>, MyArrayOfSRVs, [4])
```
#### UAV
```c++
SHADER_PARAMETER_UAV(Texture2D, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV_ARRAY(RWBuffer<float4>, MyArrayOfUAVs, [4])
```
#### Sampler
```c++
SHADER_PARAMETER_SAMPLER(SamplerState, MySampler)
SHADER_PARAMETER_SAMPLER_ARRAY(SamplerState, MyArrayOfSamplers, [8])
```
#### 不太懂是干什么的
```c++
//Adds a render graph tracked buffer upload.
//Example:
SHADER_PARAMETER_RDG_BUFFER_UPLOAD(Buffer<float4>, MyBuffer)
```
```c++
BEGIN_SHADER_PARAMETER_STRUCT(ShaderDrawVSPSParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugVS::FParameters, ShaderDrawVSParameters)
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugPS::FParameters, ShaderDrawPSParameters)
END_SHADER_PARAMETER_STRUCT()
```
## 设置变量
首先我们要了解清楚给一个Pass设置变量的步骤
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesPassParameterSetup.png)
>当增加一个RGPass它必须带有Shader参数可以是任何Shader参数比如UnifromBufferTexture等。且参数使用" GraphBuilder.AllocaParameter "来分配保留所有参数的结构体因为Lambda执行被延迟确保了正确的生命周期。参数采用宏的形式来声明。且参数结构体的声明最好的方法是内联直接在每个Pass的ShaderClass内声明好结构。
>首先得在Shader里使用宏SHADER_USE_PARAMETERSTRUCT(FYouShader, ShaderType)设置Shader需要使用Prameter。然后需要实现一个FParameter的宏包裹的结构体里面声明该Pass需要用到的所有参数参数基本上都是靠新的RDG系列宏来声明。需要注意一点的是对于UnifromBuffer需要使用StructRef来引用一层可以理解为Parameter结构体里面还有一个结构体。
```c++
FShaderDrawSymbols::FParameters* PassParameters = GraphBuilder.AllocParameters<FShaderDrawSymbols::FParameters>();
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction);
PassParameters->UniformBufferParameters = UniformBuffer;
PassParameters->MiniFontTexture = FontTexture;
PassParameters->SymbolsBuffer = GraphBuilder.CreateSRV(SymbolBuffer);
PassParameters->IndirectDrawArgsBuffer = IndirectDrawArgsBuffer;
```
对于代码中UniformBuffer变量ShaderPrint是这么设置的
```
FUniformBufferRef UniformBuffer = CreateUniformBuffer(View);
```
```
typedef TUniformBufferRef<FUniformBufferParameters> FUniformBufferRef;
// Fill the uniform buffer parameters
void SetUniformBufferParameters(FViewInfo const& View, FUniformBufferParameters& OutParameters)
{
const float FontWidth = (float)FMath::Max(CVarFontSize.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().X, 1);
const float FontHeight = (float)FMath::Max(CVarFontSize.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().Y, 1);
const float SpaceWidth = (float)FMath::Max(CVarFontSpacingX.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().X, 1);
const float SpaceHeight = (float)FMath::Max(CVarFontSpacingY.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().Y, 1);
OutParameters.FontSize = FVector4(FontWidth, FontHeight, SpaceWidth + FontWidth, SpaceHeight + FontHeight);
OutParameters.MaxValueCount = GetMaxValueCount();
OutParameters.MaxSymbolCount = GetMaxSymbolCount();
}
// Return a uniform buffer with values filled and with single frame lifetime
FUniformBufferRef CreateUniformBuffer(FViewInfo const& View)
{
FUniformBufferParameters Parameters;
SetUniformBufferParameters(View, Parameters);
return FUniformBufferRef::CreateUniformBufferImmediate(Parameters, UniformBuffer_SingleFrame);
}
```
看得出创建步骤为:
1. 创建之前使用宏声明的结构体的对象。
2. 对结构体中变量进行赋值。
3. 包一层TUniformBufferRef并使用CreateUniformBufferImmediate返回FUniformBufferRef。
4. 绘制函数中对对应命名空间FParameters结构体进行资源绑定。
可以看得出对于Uniform中的普通变量是直接设置的。
### 创建Buffer
调用GraphBuilder.CreateBuffer()即可创建缓存返回一个FRDGBufferRef对象。但CreateBuffer()的第一个形参中FRDGBufferDesc可以使用Desc有好几种CreateIndirectDesc、CreateStructuredDesc、CreateBufferDesc。其中CreateIndirectDesc应该是与RDG的IndirectDraw/Dispatch机制有关。对于结构体可以使用CreateStructuredDesc声明资源时用StructuredBuffer与RWStructuredBuffer宏。使用CreateBufferDesc需要手动计算占用空间与元素个数。代码大致如下
```
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(4), TEXT("BurleyIndirectDispatchArgs"));
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(ShaderDrawDebugElement), GetMaxShaderDrawElementCount()), TEXT("ShaderDrawDataBuffer"));
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("HairDebugSampleCounter"));
```
### 绑定SRV与UAV
使用SRV/UAV宏后其对应的Buffer需要使用GraphBuilder.CreateSRV/GraphBuilder.CreateUAV进行绑定。注意这里分为Buffer_UAV与Texture_UAV
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesReadingFromABufferUsingAnSRV.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesBindingUAVsForPixelShaders.png)
**Buffer_UAV**:在创建Buffer后再调用CreateUAV
```
//HairStrandsClusters.cpp中的代码
FRDGBufferRef GlobalRadiusScaleBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(float), ClusterData.ClusterCount), TEXT("HairGlobalRadiusScaleBuffer"));
Parameters->GlobalRadiusScaleBuffer = GraphBuilder.CreateUAV(GlobalRadiusScaleBuffer, PF_R32_FLOAT);
```
**使用Texture2D来绑定SRV与UAV。**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesCreateAUACForTexture.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesCreateASRVForTexture.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesPassParameter.png)
### 绑定RenderTarget
如需使用RenderTarget只要在FParameter结构体声明中加一行RENDER_TARGET_BINDING_SLOTS()即可。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RenderTargetBindingsSlots.png)
之后就可以进行RT的绑定。
**绑定储存颜色数据的RT**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesBindingAColorRenderTarget.png)
颜色数据RT绑定时需要注意数组下标号。
**绑定深度模板缓存**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/BindingDepthStencilTarget.png)
FParameter中的RenderTarget对象为FShadowMapRenderTargets类其地址与类内的容器变量ColorTargets一样所以可以使用这个写法。
```
class FShadowMapRenderTargets
{
public:
TArray<IPooledRenderTarget*, SceneRenderingAllocator> ColorTargets;
IPooledRenderTarget* DepthTarget;
}
```
### 绑定非当前GraphPass的资源
>需要注意的是一个GraphPass内的资源有可能不是由Graph创建的这个时候就需要使用GraphBuilder.RegisterExternalBuffer/Texture来把某个PoolRT或者RHIBuffer转成RDGResource才能使用。同样的吧一个RDGResource转成PoolRT或者RHIBuffer的方法则是GraphBuilder.QueueExternalBuffer/Texture感觉这两对更适合叫ImportResource和ExportResource。如下图。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRegistration.png)
>Check out GRenderTargetPool.CreateUntrackedElement()to get a TRefCountPtr<IPooledRenderTarget>if need to register a different from RHI resource (for instance the very old FRenderTarget)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesExtractionQueries.png)
## AddPass
>GraphBuilder.AddPass()主要用来配置管线状态用于延迟执行。比如使用 FGraphicsPipelineStateInitializer对象来配置PSO并调用RHI的API来进行绘制。或者使用SetComputeSahder()来DispatchCompute。注意此时还不会实际执行绘制而是在所有AddPass完成后调用GraphBuilder.Execute()才实际执行。而且更主要的是SetShaderParameters()也是在这儿做这个函数是UE封装的因为我们的一个Pass只能有一个AllocParameter所以这个东西里面是塞了UnifromBuffer SRV UAV等等各种东西。在以前的流程里面是自己再Shader里封装Shader.SetSRV/UAV/Unifrom等等的函数而现在则只需要吧所有参数塞一起并在GraphPass内设置即可。RDG会自动检测参数的每一个成员和类型自动SetUAV/SRV/Unifrom。
```
GraphBuilder.AddPass(
RDG_EVENT_NAME("DrawSymbols"),
PassParameters,
ERDGPassFlags::Raster,
[VertexShader, PixelShader, PassParameters](FRHICommandListImmediate& RHICmdListImmediate)
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdListImmediate.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdListImmediate, GraphicsPSOInit);
SetShaderParameters(RHICmdListImmediate, VertexShader, VertexShader.GetVertexShader(), *PassParameters);
SetShaderParameters(RHICmdListImmediate, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
RHICmdListImmediate.DrawIndexedPrimitiveIndirect(GTwoTrianglesIndexBuffer.IndexBufferRHI, PassParameters->IndirectDrawArgsBuffer->GetIndirectRHICallBuffer(), 0);
});
```

View File

@@ -0,0 +1,295 @@
## 前言
在插件中使用RDG调用ComputeShader的方法我花了没几天就搞定了。但PixelShader相对来说就麻烦了怎么搞都没有绘制到RT上。最后还是通过改写DrawFullscreenPixelShader的代码搞定了。
另外说一下PixelShader的调用和传统的GlobalShader调用很相似。
## 设置Shader虚拟目录
这个之前忘记说了,所以这里补充一下,具体操作为在插件的模块启动函数中添加以下代码::
```
void FBRPluginsModule::StartupModule()
{
FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("BRPlugins"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/BRPlugins"), PluginShaderDir);
}
```
之后就可以使用这个虚拟目录来定义Shader
```
IMPLEMENT_GLOBAL_SHADER(FSimpleRDGComputeShader, "/BRPlugins/Private/SimpleComputeShader.usf", "MainCS", SF_Compute);
```
## 参考案例
这里我没什么管理可以推荐的感觉都不是很好。但你可以搜索ERDGPassFlags::Raster就可以找到RDG调用PixelShader的代码。
## 使用DrawFullscreenPixelShader绘制
在RDG中已经封装了一个绘制函数DrawFullscreenPixelShader()可以拿来做测试。使用的方法也比较简单直接在GraphBuilder.AddPass的Lambda中调用DrawFullscreenPixelShader即可。但
其使用的顶点格式是公共资源(CommonRenderResources.h)中的GFilterVertexDeclaration.VertexDeclarationRHI、GScreenRectangleVertexBuffer.VertexBufferRHI、GScreenRectangleIndexBuffer.IndexBufferRHI。
```
void FScreenRectangleVertexBuffer::InitRHI()
{
TResourceArray<FFilterVertex, VERTEXBUFFER_ALIGNMENT> Vertices;
Vertices.SetNumUninitialized(6);
Vertices[0].Position = FVector4(1, 1, 0, 1);
Vertices[0].UV = FVector2D(1, 1);
Vertices[1].Position = FVector4(0, 1, 0, 1);
Vertices[1].UV = FVector2D(0, 1);
Vertices[2].Position = FVector4(1, 0, 0, 1);
Vertices[2].UV = FVector2D(1, 0);
Vertices[3].Position = FVector4(0, 0, 0, 1);
Vertices[3].UV = FVector2D(0, 0);
//The final two vertices are used for the triangle optimization (a single triangle spans the entire viewport )
Vertices[4].Position = FVector4(-1, 1, 0, 1);
Vertices[4].UV = FVector2D(-1, 1);
Vertices[5].Position = FVector4(1, -1, 0, 1);
Vertices[5].UV = FVector2D(1, -1);
// Create vertex buffer. Fill buffer with initial data upon creation
FRHIResourceCreateInfo CreateInfo(&Vertices);
VertexBufferRHI = RHICreateVertexBuffer(Vertices.GetResourceDataSize(), BUF_Static, CreateInfo);
}
```
DrawFullscreenPixelShader()使用的VertexShader是FScreenVertexShaderVSusf为FullscreenVertexShader.usf。代码如下
```
#include "../Common.ush"
void MainVS(
float2 InPosition : ATTRIBUTE0,
float2 InUV : ATTRIBUTE1, // TODO: kill
out float4 Position : SV_POSITION)
{
Position = float4(InPosition.x * 2.0 - 1.0, 1.0 - 2.0 * InPosition.y, 0, 1);
}
```
这里可以看得出一个问题那就是PixelShader无法获得UV坐标。所以DrawFullscreenPixelShader()能做事情很有限。因此本人写的例子是自定义了顶点格式。
## CommonRenderResources
Ue4的已经帮我们设置好了几个基础的VertexDeclaration位于RenderCore\Public\CommonRenderResources.h。
GEmptyVertexDeclaration.VertexDeclarationRHI在Shader中的Input为
```
in uint InstanceId : SV_InstanceID,
in uint VertexId : SV_VertexID,
```
GFilterVertexDeclaration.VertexDeclarationRHI在Shader中的Input为
```
in float4 InPosition : ATTRIBUTE0,
in float2 InUV : ATTRIBUTE1,
```
对应的顶点缓存与索引缓存为TGlobalResource<FScreenRectangleVertexBuffer> GScreenRectangleVertexBuffer与TGlobalResource<FScreenRectangleIndexBuffer> GScreenRectangleIndexBuffer;
如果你想在调用PixelShader时使用FScreenRectangleVertexBuffer就需要转换UV坐标了(-1,1)=>(0,1)因为FScreenRectangleVertexBuffer的UV定义范围为(-1,1)。
## RenderTarget的传入与绑定
传入的RenderTarget皆可以用Texture2D类型声明。例如
```
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FSimpleUniformStructParameters, SimpleUniformStruct)
SHADER_PARAMETER_TEXTURE(Texture2D, TextureVal)
SHADER_PARAMETER_SAMPLER(SamplerState, TextureSampler)
SHADER_PARAMETER(FVector4, SimpleColor)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
```
绑定需要在上面的声明宏中加入RENDER_TARGET_BINDING_SLOTS(),之后设置变量时绑定:
```
FSimpleRDGPixelShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGPixelShader::FParameters>();
Parameters->RenderTargets[0] = FRenderTargetBinding(RDGRenderTarget, ERenderTargetLoadAction::ENoAction);
```
还可以绑定DepthStencil
```
Parameters->RenderTargets.DepthStencil = FDepthStencilBinding(OutDepthTexture,ERenderTargetLoadAction::ELoad,ERenderTargetLoadAction::ELoad,FExclusiveDepthStencil::DepthNop_StencilWrite);
```
在USF中对应Out为
```
out float4 OutColor : SV_Target0,
out float OutDepth : SV_Depth
```
如果有多个RenderTarget绑定会如SV_Target0、SV_Target1、SV_Target2一般递增。
## 资源清理
本人案例中因为只有一个Pass所以就没有用这两个函数。
```
ValidateShaderParameters(PixelShader, Parameters);
ClearUnusedGraphResources(PixelShader, Parameters);
```
## RDGPixelDraw
直接上代码了。
```
void RDGDraw(FRHICommandListImmediate &RHIImmCmdList, FTexture2DRHIRef RenderTargetRHI, FSimpleShaderParameter InParameter, const FLinearColor InColor, FTexture2DRHIRef InTexture)
{
check(IsInRenderingThread());
//Create PooledRenderTarget
FPooledRenderTargetDesc RenderTargetDesc = FPooledRenderTargetDesc::Create2DDesc(RenderTargetRHI->GetSizeXY(),RenderTargetRHI->GetFormat(), FClearValueBinding::Black, TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV, false);
TRefCountPtr<IPooledRenderTarget> PooledRenderTarget;
//RDG Begin
FRDGBuilder GraphBuilder(RHIImmCmdList);
FRDGTextureRef RDGRenderTarget = GraphBuilder.CreateTexture(RenderTargetDesc, TEXT("RDGRenderTarget"));
//Setup Parameters
FSimpleUniformStructParameters StructParameters;
StructParameters.Color1 = InParameter.Color1;
StructParameters.Color2 = InParameter.Color2;
StructParameters.Color3 = InParameter.Color3;
StructParameters.Color4 = InParameter.Color4;
StructParameters.ColorIndex = InParameter.ColorIndex;
FSimpleRDGPixelShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGPixelShader::FParameters>();
Parameters->TextureVal = InTexture;
Parameters->TextureSampler = TStaticSamplerState<SF_Trilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
Parameters->SimpleColor = InColor;
Parameters->SimpleUniformStruct = TUniformBufferRef<FSimpleUniformStructParameters>::CreateUniformBufferImmediate(StructParameters, UniformBuffer_SingleFrame);
Parameters->RenderTargets[0] = FRenderTargetBinding(RDGRenderTarget, ERenderTargetLoadAction::ENoAction);
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel; //ERHIFeatureLevel::SM5
FGlobalShaderMap *GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FSimpleRDGVertexShader> VertexShader(GlobalShaderMap);
TShaderMapRef<FSimpleRDGPixelShader> PixelShader(GlobalShaderMap);
//ValidateShaderParameters(PixelShader, Parameters);
//ClearUnusedGraphResources(PixelShader, Parameters);
GraphBuilder.AddPass(
RDG_EVENT_NAME("RDGDraw"),
Parameters,
ERDGPassFlags::Raster,
[Parameters, VertexShader, PixelShader, GlobalShaderMap](FRHICommandList &RHICmdList) {
FRHITexture2D *RT = Parameters->RenderTargets[0].GetTexture()->GetRHI()->GetTexture2D();
RHICmdList.SetViewport(0, 0, 0.0f, RT->GetSizeX(), RT->GetSizeY(), 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GTextureVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
RHICmdList.SetStencilRef(0);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *Parameters);
RHICmdList.SetStreamSource(0, GRectangleVertexBuffer.VertexBufferRHI, 0);
RHICmdList.DrawIndexedPrimitive(
GRectangleIndexBuffer.IndexBufferRHI,
/*BaseVertexIndex=*/0,
/*MinIndex=*/0,
/*NumVertices=*/4,
/*StartIndex=*/0,
/*NumPrimitives=*/2,
/*NumInstances=*/1);
});
GraphBuilder.QueueTextureExtraction(RDGRenderTarget, &PooledRenderTarget);
GraphBuilder.Execute();
//Copy Result To RenderTarget Asset
RHIImmCmdList.CopyTexture(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FRHICopyTextureInfo());
}
```
调用方法和之前的ComputeShader部分相同这里就不赘述了。具体的可以参考我的插件。
### 自定义顶点格式部分
```
struct FTextureVertex
{
FVector4 Position;
FVector2D UV;
};
class FRectangleVertexBuffer : public FVertexBuffer
{
public:
/** Initialize the RHI for this rendering resource */
void InitRHI() override
{
TResourceArray<FTextureVertex, VERTEXBUFFER_ALIGNMENT> Vertices;
Vertices.SetNumUninitialized(6);
Vertices[0].Position = FVector4(1, 1, 0, 1);
Vertices[0].UV = FVector2D(1, 1);
Vertices[1].Position = FVector4(-1, 1, 0, 1);
Vertices[1].UV = FVector2D(0, 1);
Vertices[2].Position = FVector4(1, -1, 0, 1);
Vertices[2].UV = FVector2D(1, 0);
Vertices[3].Position = FVector4(-1, -1, 0, 1);
Vertices[3].UV = FVector2D(0, 0);
//The final two vertices are used for the triangle optimization (a single triangle spans the entire viewport )
Vertices[4].Position = FVector4(-1, 1, 0, 1);
Vertices[4].UV = FVector2D(-1, 1);
Vertices[5].Position = FVector4(1, -1, 0, 1);
Vertices[5].UV = FVector2D(1, -1);
// Create vertex buffer. Fill buffer with initial data upon creation
FRHIResourceCreateInfo CreateInfo(&Vertices);
VertexBufferRHI = RHICreateVertexBuffer(Vertices.GetResourceDataSize(), BUF_Static, CreateInfo);
}
};
class FRectangleIndexBuffer : public FIndexBuffer
{
public:
/** Initialize the RHI for this rendering resource */
void InitRHI() override
{
// Indices 0 - 5 are used for rendering a quad. Indices 6 - 8 are used for triangle optimization.
const uint16 Indices[] = {0, 1, 2, 2, 1, 3, 0, 4, 5};
TResourceArray<uint16, INDEXBUFFER_ALIGNMENT> IndexBuffer;
uint32 NumIndices = UE_ARRAY_COUNT(Indices);
IndexBuffer.AddUninitialized(NumIndices);
FMemory::Memcpy(IndexBuffer.GetData(), Indices, NumIndices * sizeof(uint16));
// Create index buffer. Fill buffer with initial data upon creation
FRHIResourceCreateInfo CreateInfo(&IndexBuffer);
IndexBufferRHI = RHICreateIndexBuffer(sizeof(uint16), IndexBuffer.GetResourceDataSize(), BUF_Static, CreateInfo);
}
};
class FTextureVertexDeclaration : public FRenderResource
{
public:
FVertexDeclarationRHIRef VertexDeclarationRHI;
virtual void InitRHI() override
{
FVertexDeclarationElementList Elements;
uint32 Stride = sizeof(FTextureVertex);
Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, Position), VET_Float2, 0, Stride));
Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, UV), VET_Float2, 1, Stride));
VertexDeclarationRHI = RHICreateVertexDeclaration(Elements);
}
virtual void ReleaseRHI() override
{
VertexDeclarationRHI.SafeRelease();
}
};
/*
* Vertex Resource Declaration
*/
extern TGlobalResource<FTextureVertexDeclaration> GTextureVertexDeclaration;
extern TGlobalResource<FRectangleVertexBuffer> GRectangleVertexBuffer;
extern TGlobalResource<FRectangleIndexBuffer> GRectangleIndexBuffer;
```
```
TGlobalResource<FTextureVertexDeclaration> GTextureVertexDeclaration;
TGlobalResource<FRectangleVertexBuffer> GRectangleVertexBuffer;
TGlobalResource<FRectangleIndexBuffer> GRectangleIndexBuffer;
```

View File

@@ -0,0 +1,249 @@
## 前言
UE4 RDG(RenderDependencyGraph)渲染框架本质上是在原有渲染框架上基础上进行再次封装它主要的设计目的就为了更好的管理每个资源的生命周期。同时Ue4的渲染管线已经都替换成了RDG框架了但依然有很多非重要模块以及第三方插件没有替换所以掌握以下RDG框架还是十分有必要的。
上一篇文章已经大致介绍了RDG框架的使用方法。看了前文的资料与官方的ppt再看一下渲染管线的的代码基本就可以上手了写Shader。但作为一个工作与Ue4一点关系的业余爱好者用的2014年的电脑通过修改渲染管线的方式来写Shader不太现实编译一次3小时真心伤不起。同时google与Epic论坛也没有在插件中使用RDG的资料所以我就花了些时间探索了一下用法最后写了本文。**因为非全职开发UE4时间精力有限不可避免得会有些错误还请见谅。** 代码写在我的插件里如果感觉有用麻烦Star一下。位于在Rendering下的SimpleRDG.cpp与SimpleRenderingExample.h中。
[https://github.com/blueroseslol/BRPlugins](https://github.com/blueroseslol/BRPlugins)
首先还是从ComputeShader开始因为比较简单。
## 参考文件
下面推荐几个参考文件强烈推荐看GenerateMips包含RDG Compute与GlobalShader两个案例。
- GenerateMips.cpp
- ShaderPrint.cpp
- PostProcessCombineLUTs.cpp
搜索ERDGPassFlags::Compute就可以找到RDG调用ComputeShader的代码。
## RDG的数据导入与导出
RDG需要使用RegisterExternalBuffer/Texture导入数据GraphBuilder.QueueExternalBuffer/Texture取出渲染结果这需要使用一个TRefCountPtr<IPooledRenderTarget>对象。直接使用RHIImmCmdList.CopyTexture尝试将FRDGTextureRef的数据拷贝出来时会触发禁止访问的断言。
## UAV
UAV用于保存ComputeShader的计算结果它的创建步骤如下
### 实现使用宏声明Shader变量
```
SHADER_PARAMETER_UAV(Texture2D, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)
```
### 使用对应的函数创建并且绑定到对应Shader变量上
SHADER_PARAMETER_UAV对应CreateTexture()SHADER_PARAMETER_RDG_BUFFER_UAV对应CreateUAV()。(前者没试过)
可以使用FRDGTextureUAVDesc与Buff两种方式进行创建。
## SRV创建与使用
UAV是不能直接在Shader里读取所以需要通过创建SRV的方式来读取。因为我并没有测试SRV所以这里贴一下FGenerateMips中的部分代码
```c++
TSharedPtr<FGenerateMipsStruct> FGenerateMips::SetupTexture(FRHITexture* InTexture, const FGenerateMipsParams& InParams)
{
check(InTexture->GetTexture2D());
TSharedPtr<FGenerateMipsStruct> GenMipsStruct = MakeShareable(new FGenerateMipsStruct());
FPooledRenderTargetDesc Desc;
Desc.Extent.X = InTexture->GetSizeXYZ().X;
Desc.Extent.Y = InTexture->GetSizeXYZ().Y;
Desc.TargetableFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV;
Desc.Format = InTexture->GetFormat();
Desc.NumMips = InTexture->GetNumMips();;
Desc.DebugName = TEXT("GenerateMipPooledRTTexture");
//Create the Pooled Render Target Resource from the input texture
FRHIResourceCreateInfo CreateInfo(Desc.DebugName);
//Initialise a new render target texture for creating an RDG Texture
FSceneRenderTargetItem RenderTexture;
//Update all the RenderTexture info
RenderTexture.TargetableTexture = InTexture;
RenderTexture.ShaderResourceTexture = InTexture;
RenderTexture.SRVs.Empty(Desc.NumMips);
RenderTexture.MipUAVs.Empty(Desc.NumMips);
for (uint8 MipLevel = 0; MipLevel < Desc.NumMips; MipLevel++)
{
FRHITextureSRVCreateInfo SRVDesc;
SRVDesc.MipLevel = MipLevel;
RenderTexture.SRVs.Emplace(SRVDesc, RHICreateShaderResourceView((FTexture2DRHIRef&)InTexture, SRVDesc));
RenderTexture.MipUAVs.Add(RHICreateUnorderedAccessView(InTexture, MipLevel));
}
RHIBindDebugLabelName(RenderTexture.TargetableTexture, Desc.DebugName);
RenderTexture.UAV = RenderTexture.MipUAVs[0];
//Create the RenderTarget from the PooledRenderTarget Desc and the new RenderTexture object.
GRenderTargetPool.CreateUntrackedElement(Desc, GenMipsStruct->RenderTarget, RenderTexture);
//Specify the Sampler details based on the input.
GenMipsStruct->Sampler.Filter = InParams.Filter;
GenMipsStruct->Sampler.AddressU = InParams.AddressU;
GenMipsStruct->Sampler.AddressV = InParams.AddressV;
GenMipsStruct->Sampler.AddressW = InParams.AddressW;
return GenMipsStruct;
}
```
```c++
void FGenerateMips::Compute(FRHICommandListImmediate& RHIImmCmdList, FRHITexture* InTexture, TSharedPtr<FGenerateMipsStruct> GenMipsStruct)
{
check(IsInRenderingThread());
//Currently only 2D textures supported
check(InTexture->GetTexture2D());
//Ensure the generate mips structure has been initialised correctly.
check(GenMipsStruct);
//Begin rendergraph for executing the compute shader
FRDGBuilder GraphBuilder(RHIImmCmdList);
FRDGTextureRef GraphTexture = GraphBuilder.RegisterExternalTexture(GenMipsStruct->RenderTarget, TEXT("GenerateMipsGraphTexture"));
···
FRDGTextureSRVDesc SRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(GraphTexture, MipLevel - 1);
FGenerateMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FGenerateMipsCS::FParameters>();
PassParameters->MipInSRV = GraphBuilder.CreateSRV(SRVDesc);
}
```
可以看出是先通过CreateUntrackedElement()创建IPooledRenderTarget之后再调用RegisterExternalTexture进行注册最后再调用CreateSRV创建SRV。
另外IPooledRenderTarget除了有CreateUntrackedElement()还有FindFreeElement()。这个函数就适合在多Pass RDG中使用了。
```
FRDGTextureRef GraphTexture = GraphBuilder.RegisterExternalTexture(GenMipsStruct->RenderTarget, TEXT("GenerateMipsGraphTexture"));
FRDGTextureSRVDesc SRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(GraphTexture, MipLevel - 1);
FGenerateMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FGenerateMipsCS::FParameters>();
PassParameters->MipInSRV = GraphBuilder.CreateSRV(SRVDesc);
```
### 在Shader中读取SRV
读取SRV与读取Texture相同首先需要创建采样器
```
SHADER_PARAMETER_SAMPLER(SamplerState, MipSampler)
PassParameters->MipSampler = RHIImmCmdList.CreateSamplerState(GenMipsStruct->Sampler);
```
之后就可以想Texture2D那样进行取值了
```
#pragma once
#include "Common.ush"
#include "GammaCorrectionCommon.ush"
float2 TexelSize;
Texture2D MipInSRV;
#if GENMIPS_SRGB
RWTexture2D<half4> MipOutUAV;
#else
RWTexture2D<float4> MipOutUAV;
#endif
SamplerState MipSampler;
[numthreads(8, 8, 1)]
void MainCS(uint3 DT_ID : SV_DispatchThreadID)
{
float2 UV = TexelSize * (DT_ID.xy + 0.5f);
#if GENMIPS_SRGB
half4 outColor = MipInSRV.SampleLevel(MipSampler, UV, 0);
outColor = half4(LinearToSrgb(outColor.xyz), outColor.w);
#else
float4 outColor = MipInSRV.SampleLevel(MipSampler, UV, 0);
#endif
#if GENMIPS_SWIZZLE
MipOutUAV[DT_ID.xy] = outColor.zyxw;
#else
MipOutUAV[DT_ID.xy] = outColor;
#endif
}
```
## RDGCompute完整代码
以下就是我写的例子,因为比较简单而且有注释,所以就不详细解释了。
```c++
void RDGCompute(FRHICommandListImmediate &RHIImmCmdList, FTexture2DRHIRef RenderTargetRHI, FSimpleShaderParameter InParameter)
{
check(IsInRenderingThread());
//Create PooledRenderTarget
FPooledRenderTargetDesc RenderTargetDesc = FPooledRenderTargetDesc::Create2DDesc(RenderTargetRHI->GetSizeXY(),RenderTargetRHI->GetFormat(), FClearValueBinding::Black, TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV, false);
TRefCountPtr<IPooledRenderTarget> PooledRenderTarget;
//RDG Begin
FRDGBuilder GraphBuilder(RHIImmCmdList);
FRDGTextureRef RDGRenderTarget = GraphBuilder.CreateTexture(RenderTargetDesc, TEXT("RDGRenderTarget"));
//Setup Parameters
FSimpleUniformStructParameters StructParameters;
StructParameters.Color1 = InParameter.Color1;
StructParameters.Color2 = InParameter.Color2;
StructParameters.Color3 = InParameter.Color3;
StructParameters.Color4 = InParameter.Color4;
StructParameters.ColorIndex = InParameter.ColorIndex;
FSimpleRDGComputeShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGComputeShader::FParameters>();
FRDGTextureUAVDesc UAVDesc(RDGRenderTarget);
Parameters->SimpleUniformStruct = TUniformBufferRef<FSimpleUniformStructParameters>::CreateUniformBufferImmediate(StructParameters, UniformBuffer_SingleFrame);
Parameters->OutTexture = GraphBuilder.CreateUAV(UAVDesc);
//Get ComputeShader From GlobalShaderMap
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel; //ERHIFeatureLevel::SM5
FGlobalShaderMap *GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FSimpleRDGComputeShader> ComputeShader(GlobalShaderMap);
//Compute Thread Group Count
FIntVector ThreadGroupCount(
RenderTargetRHI->GetSizeX() / 32,
RenderTargetRHI->GetSizeY() / 32,
1);
//ValidateShaderParameters(PixelShader, Parameters);
//ClearUnusedGraphResources(PixelShader, Parameters);
GraphBuilder.AddPass(
RDG_EVENT_NAME("RDGCompute"),
Parameters,
ERDGPassFlags::Compute,
[Parameters, ComputeShader, ThreadGroupCount](FRHICommandList &RHICmdList) {
FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *Parameters, ThreadGroupCount);
});
GraphBuilder.QueueTextureExtraction(RDGRenderTarget, &PooledRenderTarget);
GraphBuilder.Execute();
//Copy Result To RenderTarget Asset
RHIImmCmdList.CopyTexture(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FRHICopyTextureInfo());
//RHIImmCmdList.CopyToResolveTarget(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FResolveParams());
}
```
## 调用绘制函数
与传统方法类似调用上述渲染函数时需要使用ENQUEUE_RENDER_COMMAND(CaptureCommand)[]。下面是我写在蓝图函数库的代码。
```
void USimpleRenderingExampleBlueprintLibrary::UseRDGComput(const UObject *WorldContextObject, UTextureRenderTarget2D *OutputRenderTarget, FSimpleShaderParameter Parameter)
{
check(IsInGameThread());
FTexture2DRHIRef RenderTargetRHI = OutputRenderTarget->GameThread_GetRenderTargetResource()->GetRenderTargetTexture();
ENQUEUE_RENDER_COMMAND(CaptureCommand)
(
[RenderTargetRHI, Parameter](FRHICommandListImmediate &RHICmdList) {
SimpleRenderingExample::RDGCompute(RHICmdList, RenderTargetRHI, Parameter);
});
}
```
## 如何使用
直接在蓝图中调用即可
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/UseRDGCompute.png)
注意RenderTarget的格式需要与UAV的格式一一对应。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RTFormat.png)
结果:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RDGComputeShaderResult.png)

View File

@@ -0,0 +1,116 @@
# 参考文章
UE4版本https://zhuanlan.zhihu.com/p/446587397
# c++部分
## 添加ShaderModel
- EngineType.h 在EMaterialShadingModel中添加ShaderModel枚举
## 添加材质编辑器引脚
- SceneTypes.h 在EMaterialProperty中添加2个引脚属性名称枚举。
- Material.h 在UMaterial类中添加FScalarMaterialInput类型变量CustomData2与CustomData3。
### MaterialExpressions.cpp
- 给MakeMaterialAttributes节点增加对应引脚
- 添加CustomData2与CustomData3声明。位于MaterialExpressionMakeMaterialAttributes.h
- 修改UMaterialExpressionMakeMaterialAttributes::Compile()增加CustomData2与CustomData3的对应列。
- 给BreakMaterialAttributes节点增加对应引脚
- 修改UMaterialExpressionBreakMaterialAttributes::UMaterialExpressionBreakMaterialAttributes()增加CustomData2与CustomData3的对应列。
- 修改UMaterialExpressionBreakMaterialAttributes::Serialize(),增加两列`Outputs[OutputIndex].SetMask(1, 1, 0, 0, 0); ++OutputIndex;`
- 修改BuildPropertyToIOIndexMap()增加CustomData2与CustomData3的对应列并且将最后一行的index改成合适的数值。
- 修改断言条件`static_assert(MP_MAX == 32`=>`static_assert(MP_MAX == 34`
### MaterialShared.cpp
- 修改FMaterialAttributeDefinitionMap::InitializeAttributeMap(),给CustomData2与CustomData3添加对应码只需与之前的不重复即可。
- 修改FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial(),修改新添加的ShaderModel的引脚在材质编辑器中的显示名称。
### MaterialShader.cpp
- 修改GetShadingModelString()给新增加的ShaderModel添加返回字符串。
- 修改UpdateMaterialShaderCompilingStats(),给性能统计添加新增加的ShaderModel条件判断,`else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, MSM_ThinTranslucent ,MSM_NPRShading}))`
### Material.cpp
- 修改UMaterial::PostLoad(),给新增加的引脚添加对应的两行代码,来对材质属性进行重新排序,`DoMaterialAttributeReorder(&CustomData2, UE4Ver, RenderObjVer);`
- 修改UMaterial::GetExpressionInputForProperty(),给新增加的引脚添加对应的两行代码。
- 修改UMaterial::CompilePropertyEx(),给新增加的引脚添加对应的两行代码。编译材质属性。
- 修改static bool IsPropertyActive_Internal()控制材质编辑器中引脚是否开启。给CustomData添加对应的代码。
### HLSLMaterialTranslator.cpp
控制材质节点编译拼接成完整的ShaderCode。
- 修改FHLSLMaterialTranslator::FHLSLMaterialTranslator()给SharedPixelProperties数组中引脚对应index赋值为true。
- 修改FHLSLMaterialTranslator::GetMaterialEnvironment()给新增加的ShaderModel添加宏。
- 修改FHLSLMaterialTranslator::GetMaterialShaderCode()。给新增加的引脚添加对应的两行`LazyPrintf.PushParam(*GenerateFunctionCode(MP_CustomData1));`。该函数会读取`/Engine/Private/MaterialTemplate.ush`并对替换字符格式化。
- 修改FHLSLMaterialTranslator::Translate(),为新增加的两个引脚增加两行:
```c#
Chunk[MP_CustomData2] = Material->CompilePropertyAndSetMaterialProperty(MP_CustomData2 ,this);//NPR Shading
Chunk[MP_CustomData3] = Material->CompilePropertyAndSetMaterialProperty(MP_CustomData3 ,this);
```
### MaterialGraph.cpp
材质界面代码。修改UMaterialGraph::RebuildGraph(),用来显示新添加的两个引脚:
```c#
MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData2, Material), MP_CustomData2, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData2, Material)));
MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData3, Material), MP_CustomData3, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData3, Material)));
```
### 解决添加引脚后
添加引脚后会出现`PropertyConnectedBitmask cannot contain entire EMaterialProperty enumeration.`的编译错误。需要将
```c#
uint32 PropertyConnectedBitmask;
```
=》
```c#
uint64 PropertyConnectedBitmask;
```
再将函数中的转换类型改成uint64即可。
```c#
ENGINE_API bool IsConnected(EMaterialProperty Property) { return ((PropertyConnectedBitmask >> (uint64)Property) & 0x1) != 0; }
ENGINE_API void SetConnectedProperty(EMaterialProperty Property, bool bIsConnected)
{
PropertyConnectedBitmask = bIsConnected ? PropertyConnectedBitmask | (1i64 << (uint64)Property) : PropertyConnectedBitmask & ~(1i64 << (uint64)Property);
}
```
# Shader部分
## MaterialTemplate.ush
给新增加的引脚增加对应的格式化代码:
```c#
half GetMaterialCustomData2(FMaterialPixelParameters Parameters)
{
%s;
}
half GetMaterialCustomData3(FMaterialPixelParameters Parameters)
{
%s;
}
```
## ShadingCommon.ush
- 新增加的ShaderModel添加ID宏`#define SHADINGMODELID_NPRSHADING 12`
- 修改float3 GetShadingModelColor(uint ShadingModelID)给添加的ShaderModel设置一个显示颜色。
## BasePassCommon.ush
- 修改#define WRITES_CUSTOMDATA_TO_GBUFFER宏在最后的条件判断中添加新增加的ShaderModel。
## DeferredShadingCommon.ush
- 修改bool HasCustomGBufferData(int ShadingModelID)在条件判断中添加新增加的ShaderModel。
## ShadingModelsMaterial.ush
- 修改void SetGBufferForShadingModel()该函数用于设置输出的GBuffer给新增加的ShaderModel添加对应的代码段
```c#
#ifdef MATERIAL_SHADINGMODEL_NPRSHADING
else if(ShadingModel == SHADINGMODELID_NPRSHADING)
{
GBuffer.CustomData.x=saturate(GetMaterialCustomData0(MaterialParameters));
GBuffer.CustomData.y=saturate(GetMaterialCustomData1(MaterialParameters));
GBuffer.CustomData.z=saturate(GetMaterialCustomData2(MaterialParameters));
GBuffer.CustomData.w=saturate(GetMaterialCustomData3(MaterialParameters));
}
#endif
```
## 光照实现
- ShadingModel.ushBxDF实现。
- DeferredLightingCommon.ush延迟光照实现。

View File

@@ -0,0 +1,113 @@
BasePassPixelShader.usf中的FPixelShaderInOut_MainPS()为BasePass阶段Shader的主要逻辑。会被PixelShaderOutputCommon.usf的MainPS(),即PixelShader入口函数中被调用。
该阶段会取得材质编辑器各个引脚的计算结果在一些计算下最终输出GBuffer以备后续光照计算。可以认为是“紧接”材质编辑器的下一步工作。相关的c++逻辑位于FDeferredShadingSceneRenderer::Render()的RenderBasePass()中。
但因为个人能力与时间所限,只能写一篇杂乱笔记作为记录,以供后续使用。
<!--more-->
### 780~889计算变量并且填充FMaterialPixelParameters MaterialParameters。BaseColor、Metallic、Specular就位于867~877。
### 915~1072计算GBuffer或者DBuffer
- 915~942:计算贴花相关的DBuffer
- 954~1028按照ShaderModel来填充GBuffer。983~1008 Velocity、1013~1022 使用法线来调整粗糙度在皮肤以及车漆ShaderModel中有用到
- 1041GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;
- 1059~1072使用法线清漆ShaderModel还会计算的底层法线计算BentNormal以及GBufferAO。使用SphericalGaussian
### 1081~1146
#### 1086~1116计算DiffuseColorForIndirect
DiffuseColorForIndirectDiffuseDir只在Hair中计算
- 次表面与皮肤DiffuseColorForIndirect += SubsurfaceColor;
- 布料DiffuseColorForIndirect += SubsurfaceColor * saturate(GetMaterialCustomData0(MaterialParameters));
- 头发DiffuseColorForIndirect = 2*PI * HairShading( GBuffer, L, V, N, 1, TransmittanceData, 0, 0.2, uint2(0,0);
#### 1118~1120计算预间接光照结果
GetPrecomputedIndirectLightingAndSkyLight
采样对应的预结算缓存:
1. PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING根据TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL来判断是进行读取顶点AO值还是采样体积关照贴图来作为IrradianceSH的值。最后累加到OutDiffuseLighting上。
2. CACHED_VOLUME_INDIRECT_LIGHTING采样IndirectLightingCache最后累加到最后累加到OutDiffuseLighting上。
3. 采样HQ_TEXTURE_LIGHTMAP或者LQ_TEXTURE_LIGHTMAP最后累加到OutDiffuseLighting上。
调用GetSkyLighting()取得天光值并累加到OutDiffuseLighting上。最后计算OutDiffuseLighting的亮度值最后作为OutIndirectIrradiance输出。
#### 1138计算DiffuseColor
DiffuseColor=Diffuse间接照明 * Diffse颜色 + 次表面间接光照 * 次表面颜色+AO
```c#
DiffuseColor += (DiffuseIndirectLighting * DiffuseColorForIndirect + SubsurfaceIndirectLighting * SubsurfaceColor) * AOMultiBounce( GBuffer.BaseColor, DiffOcclusion );
```
#### 1140~1146SingleLayerWater 覆盖颜色操作
```c#
GBuffer.DiffuseColor *= BaseMaterialCoverageOverWater;
DiffuseColor *= BaseMaterialCoverageOverWater;
```
### 1148~1211
1. 使用ForwardDirectLighting的DiffuseLighting与SpecularLighting累加ColorTHIN_TRANSLUCENT Model则为 DiffuseColor与ColorSeparateSpecular。
2. SIMPLE_FORWARD_DIRECTIONAL_LIGHT调用GetSimpleForwardLightingDirectionalLight()计算方向光结果。
根据光照模式累加最后累加到Color上
```c#
#if STATICLIGHTING_SIGNEDDISTANCEFIELD
DirectionalLighting *= GBuffer.PrecomputedShadowFactors.x;
#elif PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING
DirectionalLighting *= GetVolumetricLightmapDirectionalLightShadowing(VolumetricLightmapBrickTextureUVs);
#elif CACHED_POINT_INDIRECT_LIGHTING
DirectionalLighting *= IndirectLightingCache.DirectionalLightShadowing;
#endif
Color += DirectionalLighting;
```
```c#
float3 GetSimpleForwardLightingDirectionalLight(FGBufferData GBuffer, float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 WorldNormal, float3 CameraVector)
{
float3 V = -CameraVector;
float3 N = WorldNormal;
float3 L = ResolvedView.DirectionalLightDirection;
float NoL = saturate( dot( N, L ) );
float3 LightColor = ResolvedView.DirectionalLightColor.rgb * PI;
FShadowTerms Shadow = { 1, 1, 1, InitHairTransmittanceData() };
FDirectLighting Lighting = EvaluateBxDF( GBuffer, N, V, L, NoL, Shadow );
// Not computing specular, material was forced fully rough
return LightColor * (Lighting.Diffuse + Lighting.Transmission);
}
```
### 1213~1273渲染雾效果
包括VertexFog、PixelFog、体积雾以及体积光效果lit translucency)
体积雾只要使用View.VolumetricFogGridZParams中的值计算UV调用Texture3DSampleLevel采样FogStruct.IntegratedLightScattering最后的值为float4(VolumetricFogLookup.rgb + GlobalFog.rgb * VolumetricFogLookup.a, VolumetricFogLookup.a * GlobalFog.a);。
### 1283~1310取得材质中自发光值得计算结果并且累加到Color上
```c#
half3 Emissive = GetMaterialEmissive(PixelMaterialInputs);
#if !POST_PROCESS_SUBSURFACE && !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
// For skin we need to keep them separate. We also keep them separate for thin translucent.
// Otherwise just add them together.
Color += DiffuseColor;
#endif
#if !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
Color += Emissive;
```
### 1312~1349SingleLayerWater光照计算
计算SunIlluminance、WaterDiffuseIndirectIlluminance、Normal、ViewVector、EnvBrdf预积分G F * 高光颜色位于BRDF.ush后根据设置采用对应的方式(前向渲染与延迟渲染方式)
```c#
const float3 SunIlluminance = ResolvedView.DirectionalLightColor.rgb * PI; // times PI because it is divided by PI on CPU (=luminance) and we want illuminance here.
const float3 WaterDiffuseIndirectIlluminance = DiffuseIndirectLighting * PI;// DiffuseIndirectLighting is luminance. So we need to multiply by PI to get illuminance.
```
### 1352~1372超薄透明物体光照计算
### 1375~1529GBuffer相关
1. BlendMode处理
2. GBuffer.IndirectIrradiance = IndirectIrradiance;
3. 调用LightAccumulator_Add()累加关照对BaseColor的影响。Out.MRT[0]=FLightAccumulator.TotalLight
4. 调用EncodeGBuffer(),填充GBuffer12345数据。
5. Out.MRT[4] = OutVelocity;
6. Out.MRT[GBUFFER_HAS_VELOCITY ? 5 : 4] = OutGBufferD;
7. Out.MRT[GBUFFER_HAS_VELOCITY ? 6 : 5] = OutGBufferE;
8. Out.MRT[0].rgb *= ViewPreExposure;
### 1553FinalizeVirtualTextureFeedback

View File

@@ -0,0 +1,304 @@
# 参考
Tone mapping进化论 https://zhuanlan.zhihu.com/p/21983679
## Filmic tone mapping
2010年Uncharted 2的ToneMapping方法。这个方法的本质是把原图和让艺术家用专业照相软件模拟胶片的感觉人肉tone mapping后的结果去做曲线拟合得到一个高次曲线的表达式。这样的表达式应用到渲染结果后就能在很大程度上自动接近人工调整的结果。
```c#
float3 F(float3 x)
{
const float A = 0.22f;
const float B = 0.30f;
const float C = 0.10f;
const float D = 0.20f;
const float E = 0.01f;
const float F = 0.30f;
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
float3 Uncharted2ToneMapping(float3 color, float adapted_lum)
{
const float WHITE = 11.2f;
return F(1.6f * adapted_lum * color) / F(WHITE);
}
```
## Academy Color Encoding SystemACES
>是一套颜色编码系统或者说是一个新的颜色空间。它是一个通用的数据交换格式一方面可以不同的输入设备转成ACES另一方面可以把ACES在不同的显示设备上正确显示。不管你是LDR还是HDR都可以在ACES里表达出来。这就直接解决了VDR的问题不同设备间都可以互通数据。
>更好的地方是按照前面说的ACES为的是解决所有设备之间的颜色空间转换问题。所以这个tone mapper不但可以用于HDR到LDR的转换还可以用于从一个HDR转到另一个HDR。也就是从根本上解决了VDR的问题。这个函数的输出是线性空间的所以要接到LDR的设备只要做一次sRGB校正。要接到HDR10的设备只要做一次Rec 2020颜色矩阵乘法。Tone mapping部分是通用的这也是比之前几个算法都好的地方。
```c#
float3 ACESToneMapping(float3 color, float adapted_lum)
{
const float A = 2.51f;
const float B = 0.03f;
const float C = 2.43f;
const float D = 0.59f;
const float E = 0.14f;
color *= adapted_lum;
return (color * (A * color + B)) / (color * (C * color + D) + E);
}
```
## ToneMapPass
位于PostProcessing.cpp中
```c#
FTonemapInputs PassInputs;
PassSequence.AcceptOverrideIfLastPass(EPass::Tonemap, PassInputs.OverrideOutput);
PassInputs.SceneColor = SceneColor;
PassInputs.Bloom = Bloom;
PassInputs.EyeAdaptationTexture = EyeAdaptationTexture;
PassInputs.ColorGradingTexture = ColorGradingTexture;
PassInputs.bWriteAlphaChannel = AntiAliasingMethod == AAM_FXAA || IsPostProcessingWithAlphaChannelSupported();
PassInputs.bOutputInHDR = bTonemapOutputInHDR;
SceneColor = AddTonemapPass(GraphBuilder, View, PassInputs);
```
如代码所示需要给Shader提供渲染结果、Bloom结果、曝光结果、合并的LUT。
1. 获取输出RT对象如果输出RT无效则根据当前设备来设置RT格式默认为PF_B8G8R8A8。(LinearEXR=>PF_A32B32G32R32FLinearNoToneCurve、LinearWithToneCurve=>PF_FloatRGBA)
2. 从后处理设置中获取BloomDirtMaskTexture。
3. 从控制台变量获取SharpenDiv6。
4. 计算色差参数ChromaticAberrationParams
5. 创建共有的Shader变量 FTonemapParameters并将所有参数都进行赋值。
6. 为桌面端的ToneMapping生成排列向量。
7. 根据RT类型使用PixelShader或者ComputeShader进行渲染。
8. 返回右值。
BuildCommonPermutationDomain()构建的FCommonDomain应该是为了给引擎传递宏。其中Settings为FPostProcessSettings。
using FCommonDomain = TShaderPermutationDomain<
- FTonemapperBloomDim(USE_BLOOM):Settings.BloomIntensity > 0.0
- FTonemapperGammaOnlyDim(USE_GAMMA_ONLY):true
- FTonemapperGrainIntensityDim(USE_GRAIN_INTENSITY):Settings.GrainIntensity > 0.0f
- FTonemapperVignetteDim(USE_VIGNETTE):Settings.VignetteIntensity > 0.0f
- FTonemapperSharpenDim(USE_SHARPEN):CVarTonemapperSharpen.GetValueOnRenderThread() > 0.0f
- FTonemapperGrainJitterDim(USE_GRAIN_JITTER):Settings.GrainJitter > 0.0f
- FTonemapperSwitchAxis(NEEDTOSWITCHVERTICLEAXIS):函数形参bSwitchVerticalAxis
- FTonemapperMsaaDim(METAL_MSAA_HDR_DECODE):函数形参bMetalMSAAHDRDecode
- FTonemapperUseFXAA(USE_FXAA):View.AntiAliasingMethod == AAM_FXAA
>;
using FDesktopDomain = TShaderPermutationDomain<
- FCommonDomain,
- FTonemapperColorFringeDim(USE_COLOR_FRINGE):
- FTonemapperGrainQuantizationDim(USE_GRAIN_QUANTIZATION) FTonemapperOutputDeviceDim为LinearNoToneCurve与LinearWithToneCurve时为false否则为true。
- FTonemapperOutputDeviceDim(DIM_OUTPUT_DEVICE):ETonemapperOutputDevice(CommonParameters.OutputDevice.OutputDevice)
>;
```
enum class ETonemapperOutputDevice
{
sRGB,
Rec709,
ExplicitGammaMapping,
ACES1000nitST2084,
ACES2000nitST2084,
ACES1000nitScRGB,
ACES2000nitScRGB,
LinearEXR,
LinearNoToneCurve,
LinearWithToneCurve,
MAX
};
```
### Shader
>在当前实现下,渲染场景的完整处理通过 ACES Viewing Transform 进行处理。此流程的工作原理是使用"参考场景的"和"参考显示的"图像。
- 参考场景的 图像保有源材质的原始 线性光照 数值,不限制曝光范围。
- 参考显示的 图像是最终的图像,将变为所用显示的色彩空间。
使用此流程后,初始源文件用于不同显示时便无需每次进行较色编辑。相反,输出的显示将映射到 正确的色彩空间。
>ACES Viewing Transform 在查看流程中将按以下顺序进行
- Look Modification Transform (LMT) - 这部分抓取应用了创意"外观"(颜色分级和矫正)的 ACES 颜色编码图像, 输出由 ACES 和 Reference Rendering TransformRRT及 Output Device TransformODT渲染的图像。
- Reference Rendering Transform (RRT) - 之后,这部分抓取参考场景的颜色值,将它们转换为参考显示。 在此流程中,它使渲染图像不再依赖于特定显示器,反而能保证它输出到特定显示器时拥有正确而宽泛的色域和动态范围(尚未创建的图像同样如此)。
- Output Device Transform (ODT) - 最后,这部分抓取 RRT 的 HDR 数据输出,将其与它们能够显示的不同设备和色彩空间进行比对。 因此,每个目标需要将其自身的 ODT 与 Rec709、Rec2020、DCI-P3 等进行比对。
默认参数:
r.HDR.EnableHDROutput:设为 1 时,它将重建交换链并启用 HDR 输出。
r.HDR.Display.OutputDevice
- 0:sRGB (LDR) (默认)
- 1:Rec709 (LDR)
- 2:显式伽马映射 (LDR)
- 3:ACES 1000-nit ST-2084 (Dolby PQ) (HDR)
- 4:ACES 2000-nit ST-2084 (Dolby PQ) (HDR)
- 5:ACES 1000-nit ScRGB (HDR)
- 6:ACES 2000-nit ScRGB (HDR)
r.HDR.Display.ColorGamut
- 0:Rec709 / sRGB, D65 (默认)
- 1:DCI-P3, D65
- 2:Rec2020 / BT2020, D65
- 3:ACES, D60
- 4:ACEScg, D60
我的测试设备是:
- 宏碁(Acer) 暗影骑士24.5英寸FastIPS 280Hz小金刚HDR400
- ROG 枪神5 笔记本 HDIM连接
- UE4.27.2 源码版
经过实际还是无法打开HDR输出着实有些可惜。所以一般显示器的Shader代码为使用RenderDoc抓帧
```c#
float4 TonemapCommonPS(
float2 UV,
float3 ExposureScaleVignette,
float4 GrainUV,
float2 ScreenPos,
float2 FullViewUV,
float4 SvPosition
)
{
float4 OutColor = 0;
const float OneOverPreExposure = View_OneOverPreExposure;
float Grain = GrainFromUV(GrainUV.zw);
float2 SceneUV = UV.xy;
float4 SceneColor = SampleSceneColor(SceneUV);
SceneColor.rgb *= OneOverPreExposure;
float ExposureScale = ExposureScaleVignette.x;
float SharpenMultiplierDiv6 = TonemapperParams.y;
float3 LinearColor = SceneColor.rgb * ColorScale0.rgb;
float2 BloomUV = ColorToBloom_Scale * UV + ColorToBloom_Bias;
BloomUV = clamp(BloomUV, Bloom_UVViewportBilinearMin, Bloom_UVViewportBilinearMax);
float4 CombinedBloom = Texture2DSample(BloomTexture, BloomSampler, BloomUV);
CombinedBloom.rgb *= OneOverPreExposure;
float2 DirtLensUV = ConvertScreenViewportSpaceToLensViewportSpace(ScreenPos) * float2(1.0f, -1.0f);
float3 BloomDirtMaskColor = Texture2DSample(BloomDirtMaskTexture, BloomDirtMaskSampler, DirtLensUV * .5f + .5f).rgb * BloomDirtMaskTint.rgb;
LinearColor += CombinedBloom.rgb * (ColorScale1.rgb + BloomDirtMaskColor);
LinearColor *= ExposureScale;
LinearColor.rgb *= ComputeVignetteMask( ExposureScaleVignette.yz, TonemapperParams.x );
float3 OutDeviceColor = ColorLookupTable(LinearColor);
float LuminanceForPostProcessAA = dot(OutDeviceColor, float3 (0.299f, 0.587f, 0.114f));
float GrainQuantization = 1.0/256.0;
float GrainAdd = (Grain * GrainQuantization) + (-0.5 * GrainQuantization);
OutDeviceColor.rgb += GrainAdd;
OutColor = float4(OutDeviceColor, saturate(LuminanceForPostProcessAA));
[branch]
if(bOutputInHDR)
{
OutColor.rgb = ST2084ToLinear(OutColor.rgb);
OutColor.rgb = OutColor.rgb / EditorNITLevel;
OutColor.rgb = LinearToPostTonemapSpace(OutColor.rgb);
}
return OutColor;
}
```
关键函数是这个对非HDR设备进行Log编码。
```c#
half3 ColorLookupTable( half3 LinearColor )
{
float3 LUTEncodedColor;
// Encode as ST-2084 (Dolby PQ) values
#if (DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES1000nitST2084 || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES2000nitST2084 || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES1000nitScRGB || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES2000nitScRGB || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_LinearEXR || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_NoToneCurve || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_WithToneCurve)
// ST2084 expects to receive linear values 0-10000 in nits.
// So the linear value must be multiplied by a scale factor to convert to nits.
LUTEncodedColor = LinearToST2084(LinearColor * LinearToNitsScale);
#else
LUTEncodedColor = LinToLog( LinearColor + LogToLin( 0 ) );
#endif
float3 UVW = LUTEncodedColor * ((LUTSize - 1) / LUTSize) + (0.5f / LUTSize);
#if USE_VOLUME_LUT == 1
half3 OutDeviceColor = Texture3DSample( ColorGradingLUT, ColorGradingLUTSampler, UVW ).rgb;
#else
half3 OutDeviceColor = UnwrappedTexture3DSample( ColorGradingLUT, ColorGradingLUTSampler, UVW, LUTSize ).rgb;
#endif
return OutDeviceColor * 1.05;
}
float3 LogToLin( float3 LogColor )
{
const float LinearRange = 14;
const float LinearGrey = 0.18;
const float ExposureGrey = 444;
// Using stripped down, 'pure log', formula. Parameterized by grey points and dynamic range covered.
float3 LinearColor = exp2( ( LogColor - ExposureGrey / 1023.0 ) * LinearRange ) * LinearGrey;
//float3 LinearColor = 2 * ( pow(10.0, ((LogColor - 0.616596 - 0.03) / 0.432699)) - 0.037584 ); // SLog
//float3 LinearColor = ( pow( 10, ( 1023 * LogColor - 685 ) / 300) - .0108 ) / (1 - .0108); // Cineon
//LinearColor = max( 0, LinearColor );
return LinearColor;
}
float3 LinToLog( float3 LinearColor )
{
const float LinearRange = 14;
const float LinearGrey = 0.18;
const float ExposureGrey = 444;
// Using stripped down, 'pure log', formula. Parameterized by grey points and dynamic range covered.
float3 LogColor = log2(LinearColor) / LinearRange - log2(LinearGrey) / LinearRange + ExposureGrey / 1023.0; // scalar: 3log2 3mad
//float3 LogColor = (log2(LinearColor) - log2(LinearGrey)) / LinearRange + ExposureGrey / 1023.0;
//float3 LogColor = log2( LinearColor / LinearGrey ) / LinearRange + ExposureGrey / 1023.0;
//float3 LogColor = (0.432699 * log10(0.5 * LinearColor + 0.037584) + 0.616596) + 0.03; // SLog
//float3 LogColor = ( 300 * log10( LinearColor * (1 - .0108) + .0108 ) + 685 ) / 1023; // Cineon
LogColor = saturate( LogColor );
return LogColor;
}
```
## CombineLUTS Pass
实际会在GetCombineLUTParameters()中调用也就是CombineLUTS (PS) Pass。实际的作用是绘制一个3D LUT图毕竟ToneMapping实际也就是一个曲线所以可以合并到一起。核心函数位于PostProcessCombineLUTs.usf的**float4 CombineLUTsCommon(float2 InUV, uint InLayerIndex)**
- 计算原始LUT Neutral 。
- 对HDR设备使用ST2084解码对LDR设备使用Log解码。**LinearColor = LogToLin(LUTEncodedColor) - LogToLin(0);**
- 白平衡。
- 在sRGB色域之外扩展明亮的饱和色彩以伪造广色域渲染(Expand bright saturated colors outside the sRGB gamut to fake wide gamut rendering.)
- 颜色矫正:对颜色ColorSaturation、ColorContrast、ColorGamma、ColorGain、ColorOffset矫正操作。
- 蓝色矫正。
- ToneMapping与之前计算结果插值。
- 反蓝色矫正。
- 从AP1到sRGB的转换并Clip掉gamut的值。
- 颜色矫正。
- Gamma矫正。
- 线性颜色=》设备颜色OutDeviceColor = LinearToSrgb( OutputGamutColor );部分HDR设备则会调用对应的矩阵调整并用LinearToST2084().
- 简单处理OutColor.rgb = OutDeviceColor / 1.05;
所以核心的Tonemapping函数为位于TonemaperCommon.ush的**half3 FilmToneMap( half3 LinearColor)**与**half3 FilmToneMapInverse( half3 ToneColor)**
```
// Blue correction
ColorAP1 = lerp( ColorAP1, mul( BlueCorrectAP1, ColorAP1 ), BlueCorrection );
// Tonemapped color in the AP1 gamut
float3 ToneMappedColorAP1 = FilmToneMap( ColorAP1 );
ColorAP1 = lerp(ColorAP1, ToneMappedColorAP1, ToneCurveAmount);
// Uncorrect blue to maintain white point
ColorAP1 = lerp( ColorAP1, mul( BlueCorrectInvAP1, ColorAP1 ), BlueCorrection );
```
## ue4
后处理中的ToneMapping曲线参数为
Slope: 0.98
Toe: 0.3
Shoulder: 0.22
Black Clip: 0
White Clip: 0.025
![](https://docs.unrealengine.com/5.0/Images/designing-visuals-rendering-and-graphics/post-process-effects/color-grading/DefaultSettings_FilmicToneMapper.webp)

View File

@@ -0,0 +1,86 @@
之前有看过MeshDraw的流程发现MeshDraw部分还是和材质编辑器帮得死死的。不过应该可以通过自定义MeshDrawPass、图元类、定点工厂来直接使用VS与PS Shader。不过这样就无法使用材质编辑器进行简单的Shader编写与使用Material Instance来调节参数只能使用c++进行参数传递非常得不方便在使用材质编辑器的情况使用CustomNode可以更多的自由度比如使用一些UE在ush定义的函数以及循环等;更好的可读性以及方便项目升级与后续项目使用等。
这里我总结了一些CustomNode使用方法。使用CustomNode时最好将ConsoleVariables.ini中的 r.ShaderDevelopmentMode设置为1。这样可以看到更多的Shader错误信息但老实说UE4的Shader错误提示真心不能与U3D比……
>采用CustomNode+Include usf文件的方式使用Ctrl+Shift+.不会真正的更新代码,必须手动断开节点连接再连接上,才会触发重新编译。
<!--more-->
### IncludeFilePaths
给Material添加ush与usf文件包含只支持以上2个后缀名。CustomNode会在生成的CustomExpressionX()之前加上
```c#
#include "你填入的文件路径"
```
这样你就可以在插件中的模块c++的StartupModule()中定义Shader映射目录之后将Shader函数代码写在插件里。映射操作大致如下
```c#
void FXXXModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
FString PluginShaderDir=FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("XXX"))->GetBaseDir(),TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/Plugin/YYY"),PluginShaderDir);
}
```
### Additional Defines
用来定义宏假设DefineName为ValueDefineValue为1。那么Custom将会在生成的CustomExpressionX()之前加上:
```c#
#ifndef Value
#define Value 1
#endif//Value
```
### Additional Outputs
设置完OutputName与OutputType后就会在生成的函数的形参利添加对应类型的引用
```c#
CustomExpression0(FMaterialPixelParameters Parameters, inout MaterialFloat Ret1)
```
之后就可以CustomNode节点中给这个形参赋值最后从CustomNode节点生成的输出节点中去到数值。
### 在CustomNode中使用节点代码
前几年刚接触CustomNode的时候一直都在思考如果使用一些带有`Parameters`参数的函数,比如`AbsoluteWorldPosition:GetWorldPosition(Parameters)`。这几天在回过头看了一下,只需要在函数中添加一个`FMaterialPixelParameters Parameters`或者`FMaterialVertexParameters Parameters`形参,之后就可以在函数利使用这些函数了。
#### 常用节点HlSL代码
- AbsoluteWorldPosition:GetWorldPosition(Parameters)
- AbsoluteWorldPosition(ExcludingMaterialOffsets):GetPrevWorldPosition(Parameters)
- VertexNormalPosition:Parameters.TangentToWorld[2];
- PixelNormalWS:Parameters.WorldNormal
- ObjectPosition:GetObjectWorldPosition(Parameters)
- CameraPosition:ResolvedView.WorldCameraOrigin
- LightVector:Parameters.LightVector
- ResolvedView.DirectionalLightDirection
- ResolvedView.DirectionalLightColor.rgb
- ResolvedView.SkyLightColor.rgb;
- ResolvedView.PreExposure
- EyeAdaptation:EyeAdaptationLookup() 位于EyeAdaptationCommon.ush
需要开启大气雾:
- SkyAtmosphereLightColor:·.AtmosphereLightColor[LightIndex].rgb
- SkyAtmosphereLightDirection:ResolvedView.AtmosphereLightDirection[LightIndex].xyz
节点代码中的`Parameters`为`FMaterialPixelParameters Parameters`或者`FMaterialVertexParameters Parameters`结构体两者都可以在MaterialTemplate.ush中找到成员定义。
其他一些节点中会使用`View.XXX`来获取变量,这里的`View`等于`ResolvedView`。具体的变量可以通过查看`FViewUniformShaderParameters`c++)。
剩下的一些节点代码可以通过材质编辑器的Window-ShaderCode-HLSL来找到。具体的方式是将所需节点连到对应引脚上之后将生成过得代码复制出来再进行寻找。当然也可以直接在FHLSLMaterialTranslator中来寻找对应节点的代码。
一些常用shader函数都可以在Common.ush找到。
## Texture
在CustomNode里使用Texture只需要给CustomNode连上TextureObject节点之后材质编辑器会自动生成对应的Sampler。例如Pin名称为XXX那就会生成XXXSampler之后向函数传递这2个参数即可。
函数中的形参类型为Texture2D与SamplerState。
TextureCoord虽然可以通过代码调用但会出现错误可能是因为材质编辑器没有检测到TextureCoord节点以至于没有添加对应代码所致。所以TextureCoord节点还是需要连接到CustomNode的pin上无法通过代码省略。
#### 默认贴图
所以一些控制类的贴图可以使用引擎里的资源这些资源在Engine Content中需要勾选显示Engine Content选项后才会显示
Texture2D'/Engine/EngineResources/WhiteSquareTexture.WhiteSquareTexture'
Texture2D'/Engine/EngineResources/Black.Black'
## HLSL分支控制关键字
一些if与for语句会产生变体所以在CustomNode里可以通过添加以下关键字来进行控制变体产生。
### if语句
- branch添加了branch标签的if语句shader会根据判断语句只执行当前情况的代码这样会产生跳转指令。
- flatten添加了flatten标签的if语句shader会执行全部情况的分支代码然后根据判断语句来决定使用哪个结果。
### for语句
- unroll添加了unroll标签的for循环是可以展开的直到循环条件终止代价是产生更多机器码
- loop添加了loop标签的for循环不能展开流式控制每次的循环迭代for默认是loop

View File

@@ -0,0 +1,122 @@
# UE4渲染用贴图资源实时更换
最近做卡通渲染美术同学反应每次更换渲染贴图都需要手动替换资源并重启引擎相当麻烦。所以花时间了解了一下UE4的渲染资源逻辑。并且找到了解决方法。
## 可参考的资源
在FViewUniformShaderParameter中有
SHADER_PARAMETER_TEXTURE(Texture2D, PreIntegratedBRDF)
SHADER_PARAMETER_TEXTURE(Texture2D, PerlinNoiseGradientTexture) 在渲染管线中调用FSystemTextures::InitializeCommonTextures生成之后通过GSystemTextures加载
SHADER_PARAMETER_TEXTURE(Texture3D, PerlinNoise3DTexture) 在渲染管线中调用FSystemTextures::InitializeCommonTextures生成之后通过GSystemTextures加载
SHADER_PARAMETER_TEXTURE(Texture2D, AtmosphereTransmittanceTexture)
在Material界面中有SubsufaceProfileSubsufaceProfile类SubsurfaceProfile.h
### USubsurfaceProfile
资源的类型为USubsurfaceProfile实现了2个接口函数
```c++
void USubsurfaceProfile::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
const FSubsurfaceProfileStruct SettingsLocal = this->Settings;
USubsurfaceProfile* Profile = this;
ENQUEUE_RENDER_COMMAND(UpdateSubsurfaceProfile)(
[SettingsLocal, Profile](FRHICommandListImmediate& RHICmdList)
{
// any changes to the setting require an update of the texture
GSubsurfaceProfileTextureObject.UpdateProfile(SettingsLocal, Profile);
});
}
```
```c++
void USubsurfaceProfile::BeginDestroy()
{
USubsurfaceProfile* Ref = this;
ENQUEUE_RENDER_COMMAND(RemoveSubsurfaceProfile)(
[Ref](FRHICommandList& RHICmdList)
{
GSubsurfaceProfileTextureObject.RemoveProfile(Ref);
});
Super::BeginDestroy();
}
```
在UMaterialInterface::UpdateMaterialRenderProxy()中开始触发下列渲染资源操作:
- 升级渲染资源**GSubsurfaceProfileTextureObject.UpdateProfile(SettingsLocal, Profile);**
- 移除渲染资源**GSubsurfaceProfileTextureObject.RemoveProfile(Ref);**
本质上更新SubsurfaceProfileEntries数组后将渲染进程里的GSSProfiles释放掉。在渲染管线中主要通过**GetSubsufaceProfileTexture_RT(RHICmdList);**来获取这个资源,其顺序为
>GetSubsufaceProfileTexture_RT()=>GSubsurfaceProfileTextureObject.GetTexture(RHICmdList);=>return GSSProfiles;
如果GSSProfiles无效则调用CreateTexture()对SubsurfaceProfile进行编码并生成新的GSSProfiles。
### PreIntegratedBRDF
1. 在FViewUniformShaderParameters中添加**PreIntegratedBRDF**贴图变量
2. 在FViewUniformShaderParameters::FViewUniformShaderParameters(),进行初始化**PreIntegratedBRDF = GWhiteTexture->TextureRHI;**
3. 在FViewInfo::SetupUniformBufferParameters()中进行资源指定:**ViewUniformShaderParameters.PreIntegratedBRDF = GEngine->PreIntegratedSkinBRDFTexture->Resource->TextureRHI;**
1. 在UEngine中定义资源指针**class UTexture2D* PreIntegratedSkinBRDFTexture;**与**FSoftObjectPath PreIntegratedSkinBRDFTextureName;**
2. 在UEngine::InitializeObjectReferences()中调用**LoadEngineTexture(PreIntegratedSkinBRDFTexture, *PreIntegratedSkinBRDFTextureName.ToString());**载入贴图。
```c++
template <typename TextureType>
static void LoadEngineTexture(TextureType*& InOutTexture, const TCHAR* InName)
{
if (!InOutTexture)
{
InOutTexture = LoadObject<TextureType>(nullptr, InName, nullptr, LOAD_None, nullptr);
}
if (FPlatformProperties::RequiresCookedData() && InOutTexture)
{
InOutTexture->AddToRoot();
}
}
```
### AtmosphereTransmittanceTexture
1. 在FViewUniformShaderParameters中添加**AtmosphereTransmittanceTexture**贴图变量
2. 在FViewUniformShaderParameters::FViewUniformShaderParameters(),进行初始化**AtmosphereTransmittanceTexture = GWhiteTexture->TextureRHI;**
3. 在FViewUniformShaderParameters::SetupUniformBufferParameters()中:**ViewUniformShaderParameters.AtmosphereTransmittanceTexture = OrBlack2DIfNull(AtmosphereTransmittanceTexture);**
4. 在FogRendering阶段InitAtmosphereConstantsInView()中进行资源指定:**View.AtmosphereTransmittanceTexture = (FogInfo.TransmittanceResource && FogInfo.TransmittanceResource->TextureRHI.GetReference()) ? (FTextureRHIRef)FogInfo.TransmittanceResource->TextureRHI : GBlackTexture->TextureRHI;**
5. 在UAtmosphericFogComponent::UpdatePrecomputedData()中对FogInfo进行预计算在Tick事件中调用每帧调用
在UpdatePrecomputedData()中的最后还会调用以下函数来对资源进行更新:
```c++
PrecomputeCounter = EValid;
FPlatformMisc::MemoryBarrier();
Scene->AtmosphericFog->bPrecomputationAcceptedByGameThread = true;
// Resolve to data...
ReleaseResource();
// Wait for release...
FlushRenderingCommands();
InitResource();
FComponentReregisterContext ReregisterContext(this);
```
生成贴图逻辑(部分)如下:
```c++
FScene* Scene = GetScene() ? GetScene()->GetRenderScene() : NULL;
{
int32 SizeX = PrecomputeParams.TransmittanceTexWidth;
int32 SizeY = PrecomputeParams.TransmittanceTexHeight;
int32 TotalByte = sizeof(FColor) * SizeX * SizeY;
check(TotalByte == Scene->AtmosphericFog->PrecomputeTransmittance.GetBulkDataSize());
const FColor* PrecomputeData = (const FColor*)Scene->AtmosphericFog->PrecomputeTransmittance.Lock(LOCK_READ_ONLY);
TransmittanceData.Lock(LOCK_READ_WRITE);
FColor* TextureData = (FColor*)TransmittanceData.Realloc(TotalByte);
FMemory::Memcpy(TextureData, PrecomputeData, TotalByte);
TransmittanceData.Unlock();
Scene->AtmosphericFog->PrecomputeTransmittance.Unlock();
}
```
### 本人尝试方法
个人觉得SubsurfaceProfile的方法是最好的但也因为相对麻烦所以放弃。我最后选择了修改UEngine(GEngine)中对应的的UTexture指针来实现渲染资源替换因为每帧都还会调用SetupUniformBufferParameters()来指定渲染用的资源。
为了保证载入的资源的生命周期我实现了一个UEngineSubSystem子类声明了若干UTexture指针并且移植了LoadEngineTexture()。这样就可以实现Runtime更换渲染资源了。大致代码如下
```c++
UToonEngineSubsystem* EngineSubsystem = GEngine->GetEngineSubsystem<UToonEngineSubsystem>();
EngineSubsystem->LoadEngineTexture(EngineSubsystem->ToonRampTexture, *ToonRampTexture->GetPathName());
GEngine->ToonRampTexture=EngineSubsystem->ToonRampTexture;
```

View File

@@ -0,0 +1,141 @@
## Ue4后处理逻辑简析
### APostProcessVolume
通常我们在后处理体积也就是APostProcessVolume设置后处理效果。它存储了struct FPostProcessSettings Settings;
加入关卡后会存储在UWorld的PostProcessVolumes中之后依次调用DoPostProcessVolume=》OverridePostProcessSettings之后修改FSceneView中的FFinalPostProcessSettings FinalPostProcessSettings。对所有属性进行插值计算
最后就可以通过View.FinalPostProcessSettings来读取后处理参数了。
### AddPostProcessingPasses
控制渲染的变量主要用下方式获取
- 从FViewInfo里直接获取
- 从FFinalPostProcessSettings获取View.FinalPostProcessSettings
- 从FEngineShowFlags获取View.Family->EngineShowFlags
- 从ConsoleVariable中获取
获取各种Buffer与变量之后
```c#
const FIntRect PrimaryViewRect = View.ViewRect;
const FSceneTextureParameters SceneTextureParameters = GetSceneTextureParameters(GraphBuilder, Inputs.SceneTextures);
const FScreenPassRenderTarget ViewFamilyOutput = FScreenPassRenderTarget::CreateViewFamilyOutput(Inputs.ViewFamilyTexture, View);
const FScreenPassTexture SceneDepth(SceneTextureParameters.SceneDepthTexture, PrimaryViewRect);
const FScreenPassTexture SeparateTranslucency(Inputs.SeparateTranslucencyTextures->GetColorForRead(GraphBuilder), PrimaryViewRect);
const FScreenPassTexture CustomDepth((*Inputs.SceneTextures)->CustomDepthTexture, PrimaryViewRect);
const FScreenPassTexture Velocity(SceneTextureParameters.GBufferVelocityTexture, PrimaryViewRect);
const FScreenPassTexture BlackDummy(GSystemTextures.GetBlackDummy(GraphBuilder));
// Scene color is updated incrementally through the post process pipeline.
FScreenPassTexture SceneColor((*Inputs.SceneTextures)->SceneColorTexture, PrimaryViewRect);
// Assigned before and after the tonemapper.
FScreenPassTexture SceneColorBeforeTonemap;
FScreenPassTexture SceneColorAfterTonemap;
// Unprocessed scene color stores the original input.
const FScreenPassTexture OriginalSceneColor = SceneColor;
// Default the new eye adaptation to the last one in case it's not generated this frame.
const FEyeAdaptationParameters EyeAdaptationParameters = GetEyeAdaptationParameters(View, ERHIFeatureLevel::SM5);
FRDGTextureRef LastEyeAdaptationTexture = GetEyeAdaptationTexture(GraphBuilder, View);
FRDGTextureRef EyeAdaptationTexture = LastEyeAdaptationTexture;
// Histogram defaults to black because the histogram eye adaptation pass is used for the manual metering mode.
FRDGTextureRef HistogramTexture = BlackDummy.Texture;
const FEngineShowFlags& EngineShowFlags = View.Family->EngineShowFlags;
const bool bVisualizeHDR = EngineShowFlags.VisualizeHDR;
const bool bViewFamilyOutputInHDR = GRHISupportsHDROutput && IsHDREnabled();
const bool bVisualizeGBufferOverview = IsVisualizeGBufferOverviewEnabled(View);
const bool bVisualizeGBufferDumpToFile = IsVisualizeGBufferDumpToFileEnabled(View);
const bool bVisualizeGBufferDumpToPIpe = IsVisualizeGBufferDumpToPipeEnabled(View);
const bool bOutputInHDR = IsPostProcessingOutputInHDR();
```
读取参数并设置
```c#
TOverridePassSequence<EPass> PassSequence(ViewFamilyOutput);
PassSequence.SetNames(PassNames, UE_ARRAY_COUNT(PassNames));
PassSequence.SetEnabled(EPass::VisualizeStationaryLightOverlap, EngineShowFlags.StationaryLightOverlap);
PassSequence.SetEnabled(EPass::VisualizeLightCulling, EngineShowFlags.VisualizeLightCulling);
#if WITH_EDITOR
PassSequence.SetEnabled(EPass::SelectionOutline, GIsEditor && EngineShowFlags.Selection && EngineShowFlags.SelectionOutline && !EngineShowFlags.Wireframe && !bVisualizeHDR && !IStereoRendering::IsStereoEyeView(View));
PassSequence.SetEnabled(EPass::EditorPrimitive, FSceneRenderer::ShouldCompositeEditorPrimitives(View));
#else
PassSequence.SetEnabled(EPass::SelectionOutline, false);
PassSequence.SetEnabled(EPass::EditorPrimitive, false);
#endif
PassSequence.SetEnabled(EPass::VisualizeShadingModels, EngineShowFlags.VisualizeShadingModels);
PassSequence.SetEnabled(EPass::VisualizeGBufferHints, EngineShowFlags.GBufferHints);
PassSequence.SetEnabled(EPass::VisualizeSubsurface, EngineShowFlags.VisualizeSSS);
PassSequence.SetEnabled(EPass::VisualizeGBufferOverview, bVisualizeGBufferOverview || bVisualizeGBufferDumpToFile || bVisualizeGBufferDumpToPIpe);
PassSequence.SetEnabled(EPass::VisualizeHDR, EngineShowFlags.VisualizeHDR);
#if WITH_EDITOR
PassSequence.SetEnabled(EPass::PixelInspector, View.bUsePixelInspector);
#else
PassSequence.SetEnabled(EPass::PixelInspector, false);
#endif
PassSequence.SetEnabled(EPass::HMDDistortion, EngineShowFlags.StereoRendering && EngineShowFlags.HMDDistortion);
PassSequence.SetEnabled(EPass::HighResolutionScreenshotMask, IsHighResolutionScreenshotMaskEnabled(View));
PassSequence.SetEnabled(EPass::PrimaryUpscale, PaniniConfig.IsEnabled() || (View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::SpatialUpscale && PrimaryViewRect.Size() != View.GetSecondaryViewRectSize()));
PassSequence.SetEnabled(EPass::SecondaryUpscale, View.RequiresSecondaryUpscale() || View.Family->GetSecondarySpatialUpscalerInterface() != nullptr);
```
这些操作一直到`PassSequence.Finalize();`。
### 后处理Pass处理
主要的Pass有这么一些
```c#
TEXT("MotionBlur"),
TEXT("Tonemap"),
TEXT("FXAA"),
TEXT("PostProcessMaterial (AfterTonemapping)"),
TEXT("VisualizeDepthOfField"),
TEXT("VisualizeStationaryLightOverlap"),
TEXT("VisualizeLightCulling"),
TEXT("SelectionOutline"),
TEXT("EditorPrimitive"),
TEXT("VisualizeShadingModels"),
TEXT("VisualizeGBufferHints"),
TEXT("VisualizeSubsurface"),
TEXT("VisualizeGBufferOverview"),
TEXT("VisualizeHDR"),
TEXT("PixelInspector"),
TEXT("HMDDistortion"),
TEXT("HighResolutionScreenshotMask"),
TEXT("PrimaryUpscale"),
TEXT("SecondaryUpscale")
```
之前读取了参数对这些Pass是否开启进行了设置。之后以这种格式使用Shader对传入的图形进行后处理。
```c#
if (PassSequence.IsEnabled(EPass::MotionBlur))
{
FMotionBlurInputs PassInputs;
PassSequence.AcceptOverrideIfLastPass(EPass::MotionBlur, PassInputs.OverrideOutput);
PassInputs.SceneColor = SceneColor;
PassInputs.SceneDepth = SceneDepth;
PassInputs.SceneVelocity = Velocity;
PassInputs.Quality = GetMotionBlurQuality();
PassInputs.Filter = GetMotionBlurFilter();
// Motion blur visualization replaces motion blur when enabled.
if (bVisualizeMotionBlur)
{
SceneColor = AddVisualizeMotionBlurPass(GraphBuilder, View, PassInputs);
}
else
{
SceneColor = AddMotionBlurPass(GraphBuilder, View, PassInputs);
}
}
SceneColor = AddAfterPass(EPass::MotionBlur, SceneColor);
```
这些效果的代码都在UnrealEngine\Engine\Source\Runtime\Renderer\Private\PostProcess中。
### 后处理材质调用
AddPostProcessMaterialChain
=》
AddPostProcessMaterialPass()为实际的绘制函数。最后在AddDrawScreenPass()中进行绘制。DrawScreenPass()=>DrawPostProcessPass=>DrawPostProcessPass()
### 推荐参考的后处理代码
PostProcessBloomSetup.h
VisualizeShadingModels.cpp

View File

@@ -0,0 +1,16 @@
#### 渲染循环发起以及渲染函数
渲染更新由UGameEngine::Tick()发起。
```
UGameEngine::Tick
|
-RedrawViewports()
|
-GameViewport->Viewport->Draw
|
-EnqueueBeginRenderFrame()
SetRequiresVsync()
EnqueueEndRenderFrame()
```
#### FDeferredShadingSceneRenderer
FDeferredShadingSceneRenderer继承自FSceneRenderer从Render函数中可以了解到延迟渲染的整个过程。每个Pass的渲染流程。

View File

@@ -0,0 +1,314 @@
# Yivanlee 添加Pass与GBuffer笔记
### 给BaseScalability.ini 添加渲染质量命令行
```ini
[EffectsQuality@0]
[EffectsQuality@1]
r.ToonDataMaterials=0
[EffectsQuality@2]
[EffectsQuality@3]
[EffectsQuality@Cine]
r.ToonDataMaterials=1
```
### 增加bUsesToonData选项
1. MaterialRelevance.h的FMaterialRelevance
2. HLSLMaterialTranslator.h与HLSLMaterialTranslator.cpp的FHLSLMaterialTranslator类
3. MaterialInterface.cpp的UMaterialInterface::GetRelevance_Internal
4. PrimitiveSceneInfo.cpp的FBatchingSPDI.DrawMesh()
5. SceneCore.h的FStaticMeshBatchRelevance类
### 定义Stat宏
RenderCore.cpp与RenderCore.h里定义ToonDataPass渲染Stat。
```c#
//h
DECLARE_CYCLE_STAT_EXTERN(TEXT("ToonData pass drawing"), STAT_ToonDataPassDrawTime, STATGROUP_SceneRendering, RENDERCORE_API);
//cpp
DEFINE_STAT(STAT_ToonDataPassDrawTime);
```
BasePassRendering.cpp里定义渲染状态宏。
```c#
DECLARE_CYCLE_STAT(TEXT("ToonDataPass"), STAT_CLM_ToonDataPass, STATGROUP_CommandListMarkers);
DECLARE_CYCLE_STAT(TEXT("AfterToonDataPass"), STAT_CLM_AfterToonDataPass, STATGROUP_CommandListMarkers);
```
### 添加渲染用的RT
SceneRenderTargets.h与SceneRenderTargets.cpp
```c++
//h
TRefCountPtr<IPooledRenderTarget> ToonBufferA;
//cpp
FSceneRenderTargets::FSceneRenderTargets(const FViewInfo& View, const FSceneRenderTargets& SnapshotSource)
: LightAccumulation(GRenderTargetPool.MakeSnapshot(SnapshotSource.LightAccumulation))
···
, ToonBufferA(GRenderTargetPool.MakeSnapshot(SnapshotSource.ToonBufferA))
```
修改SetupSceneTextureUniformParameters(),在GBuffer代码段中增加`SceneTextureParameters.ToonBufferATexture = bCanReadGBufferUniforms && EnumHasAnyFlags(SetupMode, ESceneTextureSetupMode::GBufferF) && SceneContext.ToonBufferA ? GetRDG(SceneContext.ToonBufferA) : BlackDefault2D;`
在SceneTextureParameters.h与SceneTextureParameters.cpp中将新增加的RT添加到FSceneTextureParameters中并且在GetSceneTextureParameters中注册RT,并在另一个同名函数中添加`Parameters.ToonBufferATexture = (*SceneTextureUniformBuffer)->ToonBufferATexture;`。
在FSceneTextureUniformParameters中添加`SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ToonBufferATexture)`
### 添加SceneVisibility中的ToonDataPass定义
在SceneVisibility.h中的MarkRelevant()添加
```c#
if (StaticMeshRelevance.bUseToonData)
{
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::ToonDataPass);
}
```
在ComputeDynamicMeshRelevance()中添加
```c#
if (ViewRelevance.bUsesToonData)
{
PassMask.Set(EMeshPass::ToonDataPass);
View.NumVisibleDynamicMeshElements[EMeshPass::ToonDataPass] += NumElements;
}
```
#### 修改DecodeGBufferData()以及相关函数
- 修改RayTracingDeferredShadingCommon.ush的DecodeGBufferData()
- 修改DeferredShadingCommon.ush中的FGBufferData添加ToonDataA变量并修改DecodeGBufferData()、GetGBufferDataUint()、GetGBufferData()、
- 修改SceneTextureParameters.ush中的ToonData变量声明`Texture2D ToonBufferATexture;`、`#define ToonBufferATextureSampler GlobalPointClampedSampler`以及`GetGBufferDataFromSceneTextures()`;SceneTexturesCommon.ush中的`#define SceneTexturesStruct_ToonBufferATextureSampler SceneTexturesStruct.PointClampSampler`
### 增加ToonDataPass MeshDrawPass已实现增加GBuffer
- 在MeshPassProcessor.h增加ToonDataPass MeshDrawPass定义。
- 在DeferredShadingRenderer.h添加渲染函数声明。
- 在新添加的ToonDataRendering.h与ToonDataRendering.cpp中添加MeshDrawPass声明与定义。
- 在ToonDataPassShader.usf中实现
```
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AnisotropyPassShader.usf: Outputs Anisotropy and World Tangent to GBufferF
=============================================================================*/
#include "Common.ush"
#include "/Engine/Generated/Material.ush"
#include "/Engine/Generated/VertexFactory.ush"
#include "DeferredShadingCommon.ush"
struct FToonDataPassVSToPS
{
float4 Position : SV_POSITION;
FVertexFactoryInterpolantsVSToPS Interps;
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float3 PixelPositionExcludingWPO : TEXCOORD7;
#endif
};
#if USING_TESSELLATION
struct FAnisotropyPassVSToDS
{
FVertexFactoryInterpolantsVSToDS FactoryInterpolants;
float4 Position : VS_To_DS_Position;
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float3 PixelPositionExcludingWPO : TEXCOORD7;
#endif
OPTIONAL_VertexID_VS_To_DS
};
#define FVertexOutput FAnisotropyPassVSToDS
#define VertexFactoryGetInterpolants VertexFactoryGetInterpolantsVSToDS
#else
#define FVertexOutput FToonDataPassVSToPS
#define VertexFactoryGetInterpolants VertexFactoryGetInterpolantsVSToPS
#endif
#if USING_TESSELLATION
#define FPassSpecificVSToDS FAnisotropyPassVSToDS
#define FPassSpecificVSToPS FToonDataPassVSToPS
FAnisotropyPassVSToDS PassInterpolate(FAnisotropyPassVSToDS a, float aInterp, FAnisotropyPassVSToDS b, float bInterp)
{
FAnisotropyPassVSToDS O;
O.FactoryInterpolants = VertexFactoryInterpolate(a.FactoryInterpolants, aInterp, b.FactoryInterpolants, bInterp);
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
TESSELLATION_INTERPOLATE_MEMBER(PixelPositionExcludingWPO);
#endif
return O;
}
FToonDataPassVSToPS PassFinalizeTessellationOutput(FAnisotropyPassVSToDS Interpolants, float4 WorldPosition, FMaterialTessellationParameters MaterialParameters)
{
FToonDataPassVSToPS O;
O.Interps = VertexFactoryAssignInterpolants(Interpolants.FactoryInterpolants);
O.Position = mul(WorldPosition, ResolvedView.TranslatedWorldToClip);
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
O.PixelPositionExcludingWPO = Interpolants.PixelPositionExcludingWPO;
#endif
return O;
}
#include "Tessellation.ush"
#endif
/*=============================================================================
* Vertex Shader
*============================================================================*/
void MainVertexShader(
FVertexFactoryInput Input,
OPTIONAL_VertexID
out FVertexOutput Output
#if USE_GLOBAL_CLIP_PLANE && !USING_TESSELLATION
, out float OutGlobalClipPlaneDistance : SV_ClipDistance
#endif
#if INSTANCED_STEREO
, uint InstanceId : SV_InstanceID
#if !MULTI_VIEW
, out float OutClipDistance : SV_ClipDistance1
#else
, out uint ViewportIndex : SV_ViewPortArrayIndex
#endif
#endif
)
{
#if INSTANCED_STEREO
const uint EyeIndex = GetEyeIndex(InstanceId);
ResolvedView = ResolveView(EyeIndex);
#if !MULTI_VIEW
OutClipDistance = 0.0;
#else
ViewportIndex = EyeIndex;
#endif
#else
uint EyeIndex = 0;
ResolvedView = ResolveView();
#endif
FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
float4 WorldPos = VertexFactoryGetWorldPosition(Input, VFIntermediates);
float4 WorldPositionExcludingWPO = WorldPos;
float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPos.xyz, TangentToLocal);
// Isolate instructions used for world position offset
// As these cause the optimizer to generate different position calculating instructions in each pass, resulting in self-z-fighting.
// This is only necessary for shaders used in passes that have depth testing enabled.
{
WorldPos.xyz += GetMaterialWorldPositionOffset(VertexParameters);
}
#if USING_TESSELLATION
// Transformation is done in Domain shader when tessellating
Output.Position = WorldPos;
#else
{
float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPos);
#if ODS_CAPTURE
float3 ODS = OffsetODS(RasterizedWorldPosition.xyz, ResolvedView.TranslatedWorldCameraOrigin.xyz, ResolvedView.StereoIPD);
Output.Position = INVARIANT(mul(float4(RasterizedWorldPosition.xyz + ODS, 1.0), ResolvedView.TranslatedWorldToClip));
#else
Output.Position = INVARIANT(mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip));
#endif
}
#if INSTANCED_STEREO && !MULTI_VIEW
BRANCH
if (IsInstancedStereo())
{
// Clip at the center of the screen
OutClipDistance = dot(Output.Position, EyeClipEdge[EyeIndex]);
// Scale to the width of a single eye viewport
Output.Position.x *= 0.5 * ResolvedView.HMDEyePaddingOffset;
// Shift to the eye viewport
Output.Position.x += (EyeOffsetScale[EyeIndex] * Output.Position.w) * (1.0f - 0.5 * ResolvedView.HMDEyePaddingOffset);
}
#elif XBOXONE_BIAS_HACK
// XB1 needs a bias in the opposite direction to fix FORT-40853
// XBOXONE_BIAS_HACK is defined only in a custom node in a particular material
// This should be removed with a future shader compiler update
Output.Position.z -= 0.0001 * Output.Position.w;
#endif
#if USE_GLOBAL_CLIP_PLANE
OutGlobalClipPlaneDistance = dot(ResolvedView.GlobalClippingPlane, float4(WorldPos.xyz - ResolvedView.PreViewTranslation.xyz, 1));
#endif
#endif
#if USING_TESSELLATION
Output.FactoryInterpolants = VertexFactoryGetInterpolants( Input, VFIntermediates, VertexParameters );
#else
Output.Interps = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);
#endif // #if USING_TESSELLATION
#if INSTANCED_STEREO
#if USING_TESSELLATION
Output.Interps.InterpolantsVSToPS.EyeIndex = EyeIndex;
#else
Output.Interps.EyeIndex = EyeIndex;
#endif
#endif
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
Output.PixelPositionExcludingWPO = WorldPositionExcludingWPO.xyz;
#endif
OutputVertexID( Output );
}
/*=============================================================================
* Pixel Shader
*============================================================================*/
void MainPixelShader(
in INPUT_POSITION_QUALIFIERS float4 SvPosition : SV_Position,
FVertexFactoryInterpolantsVSToPS Input
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
, float3 PixelPositionExcludingWPO : TEXCOORD7
#endif
OPTIONAL_IsFrontFace
OPTIONAL_OutDepthConservative
, out float4 ToonBufferA : SV_Target0
#if MATERIALBLENDING_MASKED_USING_COVERAGE
, out uint OutCoverage : SV_Coverage
#endif
)
{
#if INSTANCED_STEREO
ResolvedView = ResolveView(Input.EyeIndex);
#else
ResolvedView = ResolveView();
#endif
// Manual clipping here (alpha-test, etc)
FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Input, SvPosition);
FPixelMaterialInputs PixelMaterialInputs;
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition);
float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(SvPosition);
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, bIsFrontFace, TranslatedWorldPosition, PixelPositionExcludingWPO);
#else
CalcMaterialParameters(MaterialParameters, PixelMaterialInputs, SvPosition, bIsFrontFace);
#endif
#if OUTPUT_PIXEL_DEPTH_OFFSET
ApplyPixelDepthOffsetToMaterialParameters(MaterialParameters, PixelMaterialInputs, OutDepth);
#endif
#if MATERIALBLENDING_MASKED_USING_COVERAGE
OutCoverage = DiscardMaterialWithPixelCoverage(MaterialParameters, PixelMaterialInputs);
#endif
//float Anisotropy = GetMaterialAnisotropy(PixelMaterialInputs);
//float3 WorldTangent = MaterialParameters.WorldTangent;
ToonBufferA = float4(0.2, 0.1, 0.8, 1.0);
}
```

View File

@@ -0,0 +1,170 @@
## 描边
- RenderToonOutlineToBaseColor
- RenderToonOutlineToSceneColor
相关Shader
- ToonDataPassShader.usf
- ToonOutline.usf
- ToonShadingModel.ush
相关RT
```
//Begin YivanLee's Modify
TRefCountPtr<IPooledRenderTarget> SceneColorCopy;
TRefCountPtr<IPooledRenderTarget> BaseColorCopy;
TRefCountPtr<IPooledRenderTarget> ToonBufferDepth;
TRefCountPtr<IPooledRenderTarget> ToonOutlineTexture;
TRefCountPtr<IPooledRenderTarget> ToonOutlineMaskBlurTexture;
TRefCountPtr<IPooledRenderTarget> ToonIDOutlineTexture;
//ToonDataTexture01 is ToonNormal
TRefCountPtr<IPooledRenderTarget> ToonDataTexture01;
//ToonDataTexture02 is R: ShadowController G: B: A:
TRefCountPtr<IPooledRenderTarget> ToonDataTexture02;
//ToonDataTexture03 is OutlineColorMask and OutlineMask
TRefCountPtr<IPooledRenderTarget> ToonDataTexture03;
//ToonDataTexture04 is IDTexture
TRefCountPtr<IPooledRenderTarget> ToonDataTexture04;
//End YivanLee's Modify
```
### GBuffer
ToonData0 = float4(N * 0.5 + 0.5, 1.0f);//WorldNormal
ToonData1 = GetMaterialToonDataA(MaterialParameters);//Shadow controller
ToonData2 = GetMaterialToonDataB(MaterialParameters);//OutlinleColor,OutlineMask
ToonData3 = GetMaterialToonDataC(MaterialParameters);//IDTexture,OutlineWidth
### BasePass部分
位于FDeferredShadingSceneRenderer::RenderBasePass()最后,
```
if (ShouldRenderToonDataPass())
{
//Begin Recreate ToonData Render targets
SceneContext.ReleaseToonDataTarget();
SceneContext.AllocateToonDataTarget(GraphBuilder.RHICmdList);
SceneContext.ReleaseToonDataGBuffer();
SceneContext.AllocateToonDataGBuffer(GraphBuilder.RHICmdList);
//End Recreate ToonData Render targets
TStaticArray<FRDGTextureRef, MaxSimultaneousRenderTargets> ToonDataPassTextures;
uint32 ToonDataTextureCount = SceneContext.GetToonDataGBufferRenderTargets(GraphBuilder, ToonDataPassTextures);
TArrayView<FRDGTextureRef> ToonDataPassTexturesView = MakeArrayView(ToonDataPassTextures.GetData(), ToonDataTextureCount);
ERenderTargetLoadAction ToonTargetsAction;
if (bEnableParallelBasePasses)//Windows DirectX12
{
ToonTargetsAction = ERenderTargetLoadAction::ELoad;
}
else//Windows DirectX11
{
ToonTargetsAction = ERenderTargetLoadAction::EClear;
}
FRenderTargetBindingSlots ToonDataPassRenderTargets = GetRenderTargetBindings(ToonTargetsAction, ToonDataPassTexturesView);
ToonDataPassRenderTargets.DepthStencil = FDepthStencilBinding(SceneDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil);
ToonDataPassRenderTargets.ShadingRateTexture = GVRSImageManager.GetVariableRateShadingImage(GraphBuilder, ViewFamily, nullptr, EVRSType::None);
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_ToonDataPass));
RenderToonDataPass(GraphBuilder, ToonDataPassTextures, ToonDataTextureCount, ToonDataPassRenderTargets, bEnableParallelBasePasses);
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_AfterToonDataPass));
RenderToonOutlineToBaseColor(GraphBuilder, SceneDepthTexture, bEnableParallelBasePasses);
}
```
#### RenderNormalDepthOutline
- ToonOutlineMain:使用拉普拉斯算子与Sobel算子计算并混合结果。计算Depth与Normal最后Length(float4(Normal,Depth));
- ToonIDOutlinePSMain:使用Sobel计算描边。
#### RenderToonIDOutline
#### CombineOutlineToBaseColor
### 渲染管线Render()
RenderToonOutlineToSceneColor()位于RenderLights()之后与RenderDeferredReflectionsAndSkyLighting()之前的位置。
## ShaderModel
### DefaultLitBxDF
```c#
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight);
```
### Toon
- ToonDataAR ShadowOffset GBA 未使用
- ToonDataBRGB OutlineColor A OutlineMask
- ToonDataCRGB IDMap A OutlineWidth
- PreIntegratedToonBRDF: R 为NoL 预积分Ramp G 为GGX高光预积分值。
- PreIntegratedToonSkinBRDFRGB为皮肤预积分颜色。
- SubsurfaceColor该数据存放在CustomData.rgb位置,在天光计算中其作用
PS.ToonShadingStandard没有使用SubsurfaceColor。
#### ToonShadingStandard
在原始的PBR公式做了以下修改
固有色部分
1. 使用ShadowOffset(ToonDataA.r)来控制阴影区域的偏移也就是类似UTS的Step。但使用lerp(Context.NoL, 1.0, ShadowOffset),这导致偏移并不易于控制。
2. 计算FallOffMask预积分衰减调整系数。使用ShadowOffset过的NoL与Metalic作为UV对PreIntegratedToonBRDF图进行查表返回r值。
3. Lighting.Diffuse = AreaLight.FalloffColor * FallOffMask * GBuffer.BaseColor / 3.1415927f;
高光部分
1. D预积分GGX使用NoH与Roughness作为UV对PreIntegratedToonBRDF进行查表返回g值。
2. F边缘光效果系数return smoothstep(0.67, 1.0, 1 - NoV);
3. Lighting.Specular = (F + D) * (AreaLight.FalloffColor * GBuffer.SpecularColor * FallOffMask * 8);
```c++
float ShadowOffset = GBuffer.ToonDataA.r;
float FallOffMask = Falloff * GetPreintegratedToonBRDF(lerp(Context.NoL, 1.0, ShadowOffset), GBuffer.Metallic);
Lighting.Diffuse = AreaLight.FalloffColor * FallOffMask * GBuffer.BaseColor / 3.1415927f;
float R2 = GBuffer.Roughness * GBuffer.Roughness;
float ToonGGX = GetPreintegratedToonSpecBRDF(Context.NoH, GBuffer.Roughness);
float D = lerp(ToonGGX, 0.0, R2);
float3 F = GetToonF(Context.NoV);
Lighting.Specular = (F + D) * (AreaLight.FalloffColor * GBuffer.SpecularColor * FallOffMask * 8);
```
#### ToonShadingSkin
在ToonShadingStandard的基础上做了以下修改
固有色部分:
1. 使用ShadowOffset来偏移Context.NoL * Shadow.SurfaceShadow来获得ShadowMask。
2. 使用ShadowMask与Opacity作为UV来查询PreIntegratedToonSkinBRDF返回rgb值。
3. Lighting.Diffuse = AreaLight.FalloffColor * FallOffMask * GBuffer.BaseColor / 3.1415927f * PreintegratedBRDF;
高光部分相同。
#### ToonShadingHair
在ToonShadingStandard的基础上做了以下修改
固有色部分相同。
高光部分增加各向异性计算:
```c++
float3 H = normalize(L + V);
float HoL = dot(H, geotangent);
float sinTH = saturate(sqrt(1 - HoL * HoL));
float spec = pow(sinTH, lerp(256, 4, GBuffer.Roughness));
float R2 = GBuffer.Roughness * GBuffer.Roughness;
float3 F = GetToonF(Context.NoV);
spec += F;
Lighting.Specular = AreaLight.FalloffColor * FallOffMask * spec * GBuffer.BaseColor;
```
#### 天光(环境光)
阴影部分的光照主要为环境光逻辑为于ReflectionEnvironmentSkyLighting()
```
/*BeginYivanLee's Modify*/
float3 SkyLighting = float3(0.0, 0.0, 0.0);
BRANCH
if(ShadingModelID == SHADINGMODELID_TOONSTANDARD || ShadingModelID == SHADINGMODELID_TOONHAIR || ShadingModelID == SHADINGMODELID_TOONSKIN)
{
float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
float3 SkyToonLighting = GBuffer.BaseColor * SubsurfaceColor.rgb;
float3 SkyDiffuseLighting = SkyLightDiffuse(GBuffer, AmbientOcclusion, BufferUV, ScreenPosition, BentNormal, DiffuseColor) * CloudAmbientOcclusion;
SkyLighting = lerp(SkyDiffuseLighting, SkyToonLighting, 0.8f);
}
else
{
SkyLighting = SkyLightDiffuse(GBuffer, AmbientOcclusion, BufferUV, ScreenPosition, BentNormal, DiffuseColor) * CloudAmbientOcclusion;
}
/*EndYivanLee's Modify*/
```

View File

@@ -0,0 +1,210 @@
---
title: 参考SubsurfaceProfile 模改ToonDataAsset
date: 2023-02-04 20:38:39
excerpt:
tags:
rating: ⭐⭐
---
# 原理
## PreShader
在材质编译期,将名为 **__SubsurfaceProfile** 的Uniform表达式塞入材质中。
```c++
int32 FHLSLMaterialTranslator::NumericParameter(EMaterialParameterType ParameterType, FName ParameterName, const UE::Shader::FValue& InDefaultValue)
{
const UE::Shader::EValueType ValueType = GetShaderValueType(ParameterType);
check(InDefaultValue.GetType() == ValueType);
UE::Shader::FValue DefaultValue(InDefaultValue);
// If we're compiling a function, give the function a chance to override the default parameter value
FMaterialParameterMetadata Meta;
if (GetParameterOverrideValueForCurrentFunction(ParameterType, ParameterName, Meta))
{
DefaultValue = Meta.Value.AsShaderValue();
check(DefaultValue.GetType() == ValueType);
}
const uint32* PrevDefaultOffset = DefaultUniformValues.Find(DefaultValue);
uint32 DefaultOffset;
if (PrevDefaultOffset)
{
DefaultOffset = *PrevDefaultOffset;
}
else
{
DefaultOffset = MaterialCompilationOutput.UniformExpressionSet.AddDefaultParameterValue(DefaultValue);
DefaultUniformValues.Add(DefaultValue, DefaultOffset);
}
FMaterialParameterInfo ParameterInfo = GetParameterAssociationInfo();
ParameterInfo.Name = ParameterName;
const int32 ParameterIndex = MaterialCompilationOutput.UniformExpressionSet.FindOrAddNumericParameter(ParameterType, ParameterInfo, DefaultOffset);
return AddUniformExpression(new FMaterialUniformExpressionNumericParameter(ParameterInfo, ParameterIndex), GetMaterialValueType(ParameterType), TEXT(""));
}
```
`const int32 ParameterIndex = MaterialCompilationOutput.UniformExpressionSet.FindOrAddNumericParameter(ParameterType, ParameterInfo, DefaultOffset);`
`return AddUniformExpression(new FMaterialUniformExpressionNumericParameter(ParameterInfo, ParameterIndex), GetMaterialValueType(ParameterType), TEXT(""));`
之后在`Chunk[MP_SubsurfaceColor] = AppendVector(SubsurfaceColor, CodeSubsurfaceProfile);`将结果编译成`MaterialFloat4(MaterialFloat3(1.00000000,1.00000000,1.00000000),Material.PreshaderBuffer[2].x)`
## 填充PreShader结构体
1. 从MeshDraw框架的FMeshElementCollector::AddMesh()开始,执行`MeshBatch.MaterialRenderProxy->UpdateUniformExpressionCacheIfNeeded(Views[ViewIndex]->GetFeatureLevel());`开始更新材质的UniformExpression。
2. `FMaterialRenderProxy::UpdateUniformExpressionCacheIfNeeded()`:取得材质指针之后评估材质表达式。
3. `FMaterialRenderProxy::EvaluateUniformExpressions()`从渲染线程取得材质的ShaderMap再从ShaderMap取得UniformExpressionSet。
4. `FUniformExpressionSet::FillUniformBuffer`Dump preshader results into buffer.
1. FEmitContext::EmitPreshaderOrConstantPreshaderHeader = &UniformExpressionSet.UniformPreshaders.AddDefaulted_GetRef();
# 将ToonData的ID塞入材质
存在问题如何将SubsurfaceProfile Asset的ID塞入材质中
```c++
int32 FMaterialCompiler::ScalarParameter(FName ParameterName, float DefaultValue)
{
return NumericParameter(EMaterialParameterType::Scalar, ParameterName, DefaultValue);
}
int32 FHLSLMaterialTranslator::NumericParameter(EMaterialParameterType ParameterType, FName ParameterName, const UE::Shader::FValue& InDefaultValue)
{
const UE::Shader::EValueType ValueType = GetShaderValueType(ParameterType);
check(InDefaultValue.GetType() == ValueType);
UE::Shader::FValue DefaultValue(InDefaultValue);
// If we're compiling a function, give the function a chance to override the default parameter value
FMaterialParameterMetadata Meta;
if (GetParameterOverrideValueForCurrentFunction(ParameterType, ParameterName, Meta))
{ DefaultValue = Meta.Value.AsShaderValue();
check(DefaultValue.GetType() == ValueType);
}
const uint32* PrevDefaultOffset = DefaultUniformValues.Find(DefaultValue);
uint32 DefaultOffset;
if (PrevDefaultOffset)
{
DefaultOffset = *PrevDefaultOffset;
}else
{
DefaultOffset = MaterialCompilationOutput.UniformExpressionSet.AddDefaultParameterValue(DefaultValue);
DefaultUniformValues.Add(DefaultValue, DefaultOffset);
}
FMaterialParameterInfo ParameterInfo = GetParameterAssociationInfo();
ParameterInfo.Name = ParameterName;
const int32 ParameterIndex = MaterialCompilationOutput.UniformExpressionSet.FindOrAddNumericParameter(ParameterType, ParameterInfo, DefaultOffset);
return AddUniformExpression(new FMaterialUniformExpressionNumericParameter(ParameterInfo, ParameterIndex), GetMaterialValueType(ParameterType), TEXT(""));
}
bool FMaterialHLSLGenerator::GetParameterOverrideValueForCurrentFunction(EMaterialParameterType ParameterType, FName ParameterName, FMaterialParameterMetadata& OutResult) const
{
bool bResult = false;
if (!ParameterName.IsNone())
{ // Give every function in the callstack on opportunity to override the parameter value
// Parameters in outer functions take priority // For example, if a layer instance calls a function instance that includes an overriden parameter, we want to use the value from the layer instance rather than the function instance for (const FFunctionCallEntry* FunctionEntry : FunctionCallStack)
{ const UMaterialFunctionInterface* CurrentFunction = FunctionEntry->MaterialFunction;
if (CurrentFunction)
{
if (CurrentFunction->GetParameterOverrideValue(ParameterType, ParameterName, OutResult))
{ bResult = true;
break;
}
}
}
}
return bResult;
}
// Finds a parameter by name from the game thread, traversing the chain up to the BaseMaterial.
FScalarParameterValue* GameThread_GetScalarParameterValue(UMaterialInstance* MaterialInstance, FName Name)
{
UMaterialInterface* It = 0;
FMaterialParameterInfo ParameterInfo(Name); // @TODO: This will only work for non-layered parameters
while(MaterialInstance)
{
if(FScalarParameterValue* Ret = GameThread_FindParameterByName(MaterialInstance->ScalarParameterValues, ParameterInfo))
{
return Ret;
}
It = MaterialInstance->Parent;
MaterialInstance = Cast<UMaterialInstance>(It);
}
return 0;
}
template <typename ParameterType>
ParameterType* GameThread_FindParameterByName(TArray<ParameterType>& Parameters, const FHashedMaterialParameterInfo& ParameterInfo)
{
for (int32 ParameterIndex = 0; ParameterIndex < Parameters.Num(); ParameterIndex++)
{
ParameterType* Parameter = &Parameters[ParameterIndex];
if (Parameter->ParameterInfo == ParameterInfo)
{
return Parameter;
}
}
return NULL;
}
void UMaterialFunctionInstance::OverrideMaterialInstanceParameterValues(UMaterialInstance* Instance)
{
// Dynamic parameters
Instance->ScalarParameterValues = ScalarParameterValues;
Instance->VectorParameterValues = VectorParameterValues;
Instance->DoubleVectorParameterValues = DoubleVectorParameterValues;
Instance->TextureParameterValues = TextureParameterValues;
Instance->RuntimeVirtualTextureParameterValues = RuntimeVirtualTextureParameterValues;
Instance->FontParameterValues = FontParameterValues;
// Static parameters
FStaticParameterSet StaticParametersOverride = Instance->GetStaticParameters();
StaticParametersOverride.EditorOnly.StaticSwitchParameters = StaticSwitchParameterValues;
StaticParametersOverride.EditorOnly.StaticComponentMaskParameters = StaticComponentMaskParameterValues;
Instance->UpdateStaticPermutation(StaticParametersOverride);
}
```
将SubsurfaceProfile 塞入 Material
```c++
int32 UMaterialExpressionStrataLegacyConversion::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
}
```
## MaterialRenderProxy
```c++
void SetSubsurfaceProfileRT(const USubsurfaceProfile* Ptr) { SubsurfaceProfileRT = Ptr; }
const USubsurfaceProfile* GetSubsurfaceProfileRT() const { return SubsurfaceProfileRT; }
/** 0 if not set, game thread pointer, do not dereference, only for comparison */
const USubsurfaceProfile* SubsurfaceProfileRT;
```
## UMaterialInterface
```c++
uint8 bOverrideSubsurfaceProfile:1;
TObjectPtr<class USubsurfaceProfile> SubsurfaceProfile;
void UMaterialInterface::UpdateMaterialRenderProxy(FMaterialRenderProxy& Proxy)
//还有所有子类实现 UMaterialInstance、UMaterial
USubsurfaceProfile* UMaterialInterface::GetSubsurfaceProfile_Internal() const
```
## MaterialShared.h
```c++
inline bool UseSubsurfaceProfile(FMaterialShadingModelField ShadingModel)
{
return ShadingModel.HasShadingModel(MSM_SubsurfaceProfile) || ShadingModel.HasShadingModel(MSM_Eye);
}
```
## UMaterial
```c++
USubsurfaceProfile* UMaterial::GetSubsurfaceProfile_Internal() const
{
checkSlow(IsInGameThread());
return SubsurfaceProfile;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,76 @@
## 论文与相关资料
### 寒霜的快速SSS
在GDCVault中搜索对应的演讲之后就可以下载了。一些PPT比较大可以直接去控件里找下载地址。
https://www.slideshare.net/colinbb/colin-barrebrisebois-gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurfacescattering-look-7170855
#### 视频
https://www.gdcvault.com/play/1014536/Approximating-Translucency-for-a-Fast
因为视频是blob模式的所以可以去下面的网站下载
http://downloadblob.com/
#### ppt
https://twvideo01.ubm-us.net/o1/vault/gdc2011/slides/Colin_BarreBrisebois_Programming_ApproximatingTranslucency.pptx
### SIGGRAPH2019ppt
http://advances.realtimerendering.com/s2019/index.htm
## Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look
### 数据管理
数据可以分成材质相关与灯光类型相关。在寒霜中材质相关会使用GBuffer传递UE4可以使用CustomData吧光类型相关会在LightPass中传递。
### 计算厚度值
通过AO的方式来计算厚度:
- 反转表面法线
- 渲染AO
- 反转颜色最后渲染到贴图中
### 技术细节
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/DirectandTranslucencyLightingVectors.png)
LT应该是Light Translucency的意思。v代表Vectorf代表floati代表int。
```
half3 vLTLight = vLight + vNormal * fLTDistortion;
half fLTDot = pow(saturate(dot(vEye, -vLTLight)), iLTPower) * fLTScale;
half3 fLT = fLightAttenuation * (fLTDot + fLTAmbient) * fLTThickness;
outColor.rgb += cDiffuseAlbedo * cLightDiffuse * fLT;
```
#### fLTAmbient
Ambient项代表了始终存的各个方向的透射值。材质相关变量。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/FastSSS_AmbientTerm.png)
#### iLTPower
强度衰减项,直接透射强度。与视口相关。可以通过预计算进行优化。材质相关变量。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/FastSSS_PowerTerm.png)
#### fLTDistortion
透射方向形变项,用于模拟光线传输的不规则的效果,类似于毛玻璃的效果。主要的功能是控制法线对于透射光方向的影响。与视口相关。材质相关变量。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/FastSSS_DistortionTerm.png)
#### fLTThickness
厚底项预计算的Local坐标的表面厚度值。材质相关变量。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/FastSSS_ThicknessTerm.png)
#### fLTScale
缩放项,用于缩放直接透射效果。视口相关。灯光相关变量。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/FastSSS_ScaleTerm.png)
### 最终效果
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/FastSSS_FinalyResult.png)
### GBuffer设计
最低的要求是GBufer中使用8位位宽灰度贴图的方式来存储translucency。使用24位位宽以颜色贴图的方式可以实现材质对于不同光谱的光线不同的散射效果。
### 技术缺点
因为是一种近似技术所以只适合在凸物体上演示。这种技术对变形物体不起作用因为需要烘焙厚度贴图。此外我们可以使用实时AO算法配合倒置法线来计算厚度。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/FastSSS_ConcaveHull.png)
PS.该技术的详细描述可以在《GPU PRO2》中找到。

View File

@@ -0,0 +1,202 @@
## UE4 渲染功能探究
New: Planar Reflections
New: High Quality Reflections
## UE4.26 SingleLayerWater笔记
官方论坛讨论
https://forums.unrealengine.com/development-discussion/rendering/1746626-actually-realistic-water-shader#post1789028
### SingleLayerCommon.ush
计算光照强度、透明度。
struct WaterVolumeLightingOutput
{
float3 Luminance;
float3 WaterToSceneTransmittance;
float3 WaterToSceneToLightTransmittance;
};
Output.Luminance = WaterVisibility * (ScatteredLuminance + Transmittance * BehindWaterSceneLuminance);
Output.WaterToSceneTransmittance = Transmittance;
Output.WaterToSceneToLightTransmittance;
目前没有开启RayMarching,所以核心代码为:
```
const float3 OpticalDepth = ExtinctionCoeff * BehindWaterDeltaDepth;
float3 Transmittance = exp(-OpticalDepth);
float3 ScatteredLuminance = ScatteringCoeff * (AmbScattLuminance + SunScattLuminance * DirectionalLightShadow);
ScatteredLuminance = (ScatteredLuminance - ScatteredLuminance * Transmittance) / ExtinctionCoeffSafe;
// Apply Fresnel effect to out-scattering towards the view
ScatteredLuminance *= CameraIsUnderWater ? 1.0 : (1.0 - EnvBrdf); // Under water is less visible due to Fresnel effect
Transmittance *= CameraIsUnderWater ? (1.0 - EnvBrdf) : 1.0; // Above " " " " "
// Add single in-scattering apply colored transmittance to scene color
Output.Luminance = WaterVisibility * (ScatteredLuminance + Transmittance * (BehindWaterSceneLuminance* ColorScaleBehindWater));
Output.WaterToSceneTransmittance = Transmittance;
Output.WaterToSceneToLightTransmittance = Transmittance * MeanTransmittanceToLightSources;
```
```c++
const float BehindWaterDeltaDepth = CameraIsUnderWater ? WaterDepth : max(0.0f, SceneDepth - WaterDepth);
const float3 ScatteringCoeff = max(0.0f, GetSingleLayerWaterMaterialOutput0(MaterialParameters));
const float3 AbsorptionCoeff = max(0.0f, GetSingleLayerWaterMaterialOutput1(MaterialParameters));
const float PhaseG = clamp(GetSingleLayerWaterMaterialOutput2(MaterialParameters), -1.0f, 1.0f);
//Sample the optional Material Input ColorScaleBehindWater and fade it out at shorelines to avoid hard edge intersections
float3 ColorScaleBehindWater = lerp(1.0f, max(0.0f, GetSingleLayerWaterMaterialOutput3(MaterialParameters)), saturate(BehindWaterDeltaDepth * 0.02f));
const float3 ExtinctionCoeff = ScatteringCoeff + AbsorptionCoeff;
// Max to avoid division by 0 with the analytical integral below.
// 1e-5 is high enough to avoid denorms on mobile
const float3 ExtinctionCoeffSafe = max(ScatteringCoeff + AbsorptionCoeff, 1e-5);
float DirLightPhaseValue = 0.0f; // Default when Total Internal Reflection happens.
{
#if SIMPLE_SINGLE_LAYER_WATER
DirLightPhaseValue = IsotropicPhase();
#else
float IorFrom = 1.0f; // assumes we come from air
float IorTo = DielectricF0ToIor(DielectricSpecularToF0(Specular)); // Wrong if metal is set to >1. But we still keep refraction on the water surface nonetheless.
const float relativeIOR = IorFrom / IorTo;
float3 UnderWaterRayDir = 0.0f;
if (WaterRefract(MaterialParameters.CameraVector, MaterialParameters.WorldNormal, relativeIOR, UnderWaterRayDir))
{
DirLightPhaseValue = SchlickPhase(PhaseG, dot(-ResolvedView.DirectionalLightDirection.xyz, UnderWaterRayDir));
}
#endif
}
// We also apply transmittance from light to under water surface. However, the scene has been lit by many sources already.
// So the transmittance toabove surface is simply approximated using the travel distance from the scene pixel to the water top, assuming a flat water surface.
// We cannot combine this transmittance with the transmittance from view because this would change the behavior of the analytical integration of light scattering integration.
const float3 BehindWaterSceneWorldPos = SvPositionToWorld(float4(MaterialParameters.SvPosition.xy, SceneDeviceZ, 1.0));
const float DistanceFromScenePixelToWaterTop = max(0.0, MaterialParameters.AbsoluteWorldPosition.z - BehindWaterSceneWorldPos.z);
const float3 MeanTransmittanceToLightSources = exp(-DistanceFromScenePixelToWaterTop * ExtinctionCoeff);
#if SIMPLE_SINGLE_LAYER_WATER
const float3 BehindWaterSceneLuminance = 0.0f; // Cannot read back the scene color in this case
#else
// We use the pixel SvPosition instead of the scene one pre refraction/distortion to avoid those extra ALUs.
float3 BehindWaterSceneLuminance = SceneColorWithoutSingleLayerWaterTexture.SampleLevel(SceneColorWithoutSingleLayerWaterSampler, ViewportUV, 0).rgb;
BehindWaterSceneLuminance = MeanTransmittanceToLightSources * (USE_PREEXPOSURE ? ResolvedView.OneOverPreExposure : 1.0f) * BehindWaterSceneLuminance;
#endif
float3 SunScattLuminance = DirLightPhaseValue * SunIlluminance;
float3 AmbScattLuminance = IsotropicPhase() * AmbiantIlluminance;
#define VOLUMETRICSHADOW 0
#if !VOLUMETRICSHADOW || SIMPLE_SINGLE_LAYER_WATER
const float3 OpticalDepth = ExtinctionCoeff * BehindWaterDeltaDepth;
float3 Transmittance = exp(-OpticalDepth);
float3 ScatteredLuminance = ScatteringCoeff * (AmbScattLuminance + SunScattLuminance * DirectionalLightShadow);
ScatteredLuminance = (ScatteredLuminance - ScatteredLuminance * Transmittance) / ExtinctionCoeffSafe;
#else
// TODO Make the volumetric shadow part work again
float3 Transmittance = 1.0f;
float3 ScatteredLuminance = 0.0f;
const float RayMarchMaxDistance = min(BehindWaterDeltaDepth, 200.0f); // 20 meters
const float RayMarchStepSize = RayMarchMaxDistance / 10.0f; // Less samples wil lresult in a bit brighter look due to TransmittanceToLightThroughWater being 1 on a longer first sample. Would need it part of analiytical integration
const float ShadowDither = RayMarchStepSize * GBufferDither;
for (float s = 0.0f; s < RayMarchMaxDistance; s += RayMarchStepSize)
{
// Only jitter shadow map sampling to not lose energy on first sample
float Shadow = ComputeDirectionalLightDynamicShadowing(MaterialParameters.AbsoluteWorldPosition - (s + ShadowDither)*MaterialParameters.CameraVector, GBuffer.Depth);
float3 WP = MaterialParameters.AbsoluteWorldPosition - s * MaterialParameters.CameraVector;
float WaterHeightAboveSample = max(0.0, MaterialParameters.AbsoluteWorldPosition.z - WP.z);
float3 TransmittanceToLightThroughWater = 1.0; // no self shadow, same energy as above analytical solution
//float3 TransmittanceToLightThroughWater = exp(-ExtinctionCoeff * WaterHeightAboveSample); // self shadow as transmittance to water level, close to reference, depends a bit on sample count due to first sample being critical for dense medium
float3 SampleTransmittance = exp(-ExtinctionCoeff * RayMarchStepSize); // Constant
float3 SS = (ScatteringCoeff * TransmittanceToLightThroughWater * (SunScattLuminance * Shadow + AmbScattLuminance));
ScatteredLuminance += Transmittance * (SS - SS * SampleTransmittance) / ExtinctionCoeffSafe;
Transmittance *= SampleTransmittance;
}
// The rest of the medium
const float3 OpticalDepth2 = ExtinctionCoeff * max(0.0, BehindWaterDeltaDepth - RayMarchMaxDistance);
if (any(OpticalDepth2 > 0.0f))
{
float3 Transmittance2 = exp(-OpticalDepth2);
float3 ScatteredLuminance2 = ScatteringCoeff * (SunScattLuminance + AmbScattLuminance);
ScatteredLuminance += Transmittance * (ScatteredLuminance2 - ScatteredLuminance2 * Transmittance2) / ExtinctionCoeffSafe;
Transmittance *= Transmittance2;
}
#endif
// Apply Fresnel effect to out-scattering towards the view
ScatteredLuminance *= CameraIsUnderWater ? 1.0 : (1.0 - EnvBrdf); // Under water is less visible due to Fresnel effect
Transmittance *= CameraIsUnderWater ? (1.0 - EnvBrdf) : 1.0; // Above " " " " "
// Add single in-scattering apply colored transmittance to scene color
Output.Luminance = WaterVisibility * (ScatteredLuminance + Transmittance * (BehindWaterSceneLuminance* ColorScaleBehindWater));
Output.WaterToSceneTransmittance = Transmittance;
Output.WaterToSceneToLightTransmittance = Transmittance * MeanTransmittanceToLightSources;
}
```
海洋是不透明的使用SceneColor缓存合成出的透明效果。
## GDC2012 神秘海域3演讲
### 渲染方案
### FlowShader
没看懂为什么需要用2张贴图叠加是因为要过度么
4.5.1.1 Flow Map变体《神秘海域3》Flow Map + Displacement
另外Flow Map可以和其他渲染技术结合使用比如《神秘海域3》中的Flow Map + Displacement
![image](https://pic4.zhimg.com/80/v2-a973e1d3ce0356a1f0823c644e14a21b_720w.jpg)
4.5.1.2 Flow Map变体《堡垒之夜》Flow Map + Distance Fields + Normal Maps
以及《堡垒之夜》中的Flow Map + Distance Fields + Normal Maps [GDC 2019, Technical Artist Bootcamp Distance Fields and Shader Simulation Tricks]
4.5.1.3 Flow Map变体《神秘海域4》Flow Map + Wave Particles
或者《神秘海域4》中的Flow Map + Wave Particles[SIGGRAPH 2016, Rendering Rapids in Uncharted 4],都是进阶模拟水体表面流动与起伏效果的不错选择。
### Wave System
如果我们能找到一个好的模型,程序化的几何和动画是不错的。
仿真计算成本太高即使在SPU中设计者也很难控制。
Perlin噪音效果在视觉上不是很好往往看起来很人工化
FFT技术很好但是参数很难被艺术家控制和调整。也是很难搞好的
**Gerstner waves**
简单易于控制效果,但高频细节不够多,只能叠加几组大浪,否则太消耗资源。
**FFT Waves**
真实,细节丰富。但是美术难以控制效果。
神秘海域3采用4组Gerstner waves+4组波动粒子的方式来实现Wave Vector Displacement。
#### 大浪
大浪采用贝塞尔曲线建模完成
**之后再叠加大浪**
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/93488B8FEA8F4AA689BD7E1691988284.octet-stream)
这是整个波系的局部公式。
bspline是一个均匀的、非理性的bspline。我们本可以使用贝塞尔但它需要更多的代码。
grid(u,v)函数返回一个给定坐标u,v的标量值。在这种情况下我们有一个波标的乘数
## Sea of Thieves approach [Ang, 2018]
## Crest Siggraph2019
### Light Scattering
使用了类似盗贼之海的光线散射算法,光线散射项是基于海面置换项的水平长度。这里补充一下:使它在我们的框架中更好地工作--我们通过将置换项除以波长来做一种特殊的归一化,并将这个归一化版本用于光散射项。
基于海平面高度的散射在海洋参数发生改变时容易出问题。
如果将置换项除以波长,就可以针对大波与小波进行缩放。
### Shadering
Cascade scale used to scale shading inputs
Normals
Foam
Underwater bubbles
Works for range of viewpoints
Breaks up patterns
Combats mipmapping
Increase visible range of detail
Doubles texture samples

View File

@@ -0,0 +1,252 @@
```c++
// Manually clamp scene texture UV as if using a clamp sampler.
MaterialFloat2 ClampSceneTextureUV(MaterialFloat2 BufferUV, const uint SceneTextureId)
{
float4 MinMax = GetSceneTextureUVMinMax(SceneTextureId);
return clamp(BufferUV, MinMax.xy, MinMax.zw);
}
```
## 相关函数的HLSL代码
```c++
float GetPixelDepth(FMaterialVertexParameters Parameters)
{
FLATTEN
if (View.ViewToClip[3][3] < 1.0f)
{
// Perspective
return GetScreenPosition(Parameters).w;
}
else
{
// Ortho
return ConvertFromDeviceZ(GetScreenPosition(Parameters).z);
}
}
float GetPixelDepth(FMaterialPixelParameters Parameters)
{
FLATTEN
if (View.ViewToClip[3][3] < 1.0f)
{
// Perspective
return GetScreenPosition(Parameters).w;
}
else
{
// Ortho
return ConvertFromDeviceZ(GetScreenPosition(Parameters).z);
}
}
float2 GetSceneTextureUV(FMaterialVertexParameters Parameters)
{
return ScreenAlignedPosition(GetScreenPosition(Parameters));
}
float2 GetSceneTextureUV(FMaterialPixelParameters Parameters)
{
return SvPositionToBufferUV(Parameters.SvPosition);
}
float2 GetViewportUV(FMaterialVertexParameters Parameters)
{
#if POST_PROCESS_MATERIAL
return Parameters.WorldPosition.xy;
#else
return BufferUVToViewportUV(GetSceneTextureUV(Parameters));
#endif
}
float2 GetPixelPosition(FMaterialVertexParameters Parameters)
{
return GetViewportUV(Parameters) * View.ViewSizeAndInvSize.xy;
}
#if POST_PROCESS_MATERIAL
float2 GetPixelPosition(FMaterialPixelParameters Parameters)
{
return Parameters.SvPosition.xy - float2(PostProcessOutput_ViewportMin);
}
float2 GetViewportUV(FMaterialPixelParameters Parameters)
{
return GetPixelPosition(Parameters) * PostProcessOutput_ViewportSizeInverse;
}
#else
float2 GetPixelPosition(FMaterialPixelParameters Parameters)
{
return Parameters.SvPosition.xy - float2(View.ViewRectMin.xy);
}
float2 GetViewportUV(FMaterialPixelParameters Parameters)
{
return SvPositionToViewportUV(Parameters.SvPosition);
}
```
## 本质上是调用了SceneTextureLookup
float4 SceneTextureLookup(float2 UV, int SceneTextureIndex, bool bFiltered)
{
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(UV, false);
switch(SceneTextureIndex)
{
// order needs to match to ESceneTextureId
case PPI_SceneColor:
return float4(CalcSceneColor(UV), 0);
case PPI_SceneDepth:
return ScreenSpaceData.GBuffer.Depth;
case PPI_DiffuseColor:
return float4(ScreenSpaceData.GBuffer.DiffuseColor, 0);
case PPI_SpecularColor:
return float4(ScreenSpaceData.GBuffer.SpecularColor, 0);
case PPI_SubsurfaceColor:
return IsSubsurfaceModel(ScreenSpaceData.GBuffer.ShadingModelID) ? float4( ExtractSubsurfaceColor(ScreenSpaceData.GBuffer), ScreenSpaceData.GBuffer.CustomData.a ) : ScreenSpaceData.GBuffer.CustomData;
case PPI_BaseColor:
return float4(ScreenSpaceData.GBuffer.BaseColor, 0);
case PPI_Specular:
return ScreenSpaceData.GBuffer.Specular;
case PPI_Metallic:
return ScreenSpaceData.GBuffer.Metallic;
case PPI_WorldNormal:
return float4(ScreenSpaceData.GBuffer.WorldNormal, 0);
case PPI_SeparateTranslucency:
return float4(1, 1, 1, 1); // todo
case PPI_Opacity:
return ScreenSpaceData.GBuffer.CustomData.a;
case PPI_Roughness:
return ScreenSpaceData.GBuffer.Roughness;
case PPI_MaterialAO:
return ScreenSpaceData.GBuffer.GBufferAO;
case PPI_CustomDepth:
return ScreenSpaceData.GBuffer.CustomDepth;
case PPI_PostProcessInput0:
return Texture2DSample(PostProcessInput_0_Texture, bFiltered ? PostProcessInput_BilinearSampler : PostProcessInput_0_SharedSampler, UV);
case PPI_PostProcessInput1:
return Texture2DSample(PostProcessInput_1_Texture, bFiltered ? PostProcessInput_BilinearSampler : PostProcessInput_1_SharedSampler, UV);
case PPI_PostProcessInput2:
return Texture2DSample(PostProcessInput_2_Texture, bFiltered ? PostProcessInput_BilinearSampler : PostProcessInput_2_SharedSampler, UV);
case PPI_PostProcessInput3:
return Texture2DSample(PostProcessInput_3_Texture, bFiltered ? PostProcessInput_BilinearSampler : PostProcessInput_3_SharedSampler, UV);
case PPI_PostProcessInput4:
return Texture2DSample(PostProcessInput_4_Texture, bFiltered ? PostProcessInput_BilinearSampler : PostProcessInput_4_SharedSampler, UV);
case PPI_DecalMask:
return 0; // material compiler will return an error
case PPI_ShadingModelColor:
return float4(GetShadingModelColor(ScreenSpaceData.GBuffer.ShadingModelID), 1);
case PPI_ShadingModelID:
return float4(ScreenSpaceData.GBuffer.ShadingModelID, 0, 0, 0);
case PPI_AmbientOcclusion:
return ScreenSpaceData.AmbientOcclusion;
case PPI_CustomStencil:
return ScreenSpaceData.GBuffer.CustomStencil;
case PPI_StoredBaseColor:
return float4(ScreenSpaceData.GBuffer.StoredBaseColor, 0);
case PPI_StoredSpecular:
return float4(ScreenSpaceData.GBuffer.StoredSpecular.rrr, 0);
case PPI_WorldTangent:
return float4(ScreenSpaceData.GBuffer.WorldTangent, 0);
case PPI_Anisotropy:
return ScreenSpaceData.GBuffer.Anisotropy;
default:
return float4(0, 0, 0, 0);
}
}
##
FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal = true)
{
FScreenSpaceData Out;
Out.GBuffer = GetGBufferData(UV, bGetNormalizedNormal);
float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct_ScreenSpaceAOTextureSampler, UV, 0);
Out.AmbientOcclusion = ScreenSpaceAO.r;
return Out;
}
## GetGBufferData使用的GBufferXTexture的传入位置
比如AOUpsamplePSc++中为FDistanceFieldAOUpsamplePS就带有FSceneTextureUniformParameters, SceneTextures。都是由对应的渲染函数的TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTexturesUniformBuffer形参传入。
```c#
FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true)
{
float4 GBufferA = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct_GBufferATextureSampler, UV, 0);
float4 GBufferB = Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct_GBufferBTextureSampler, UV, 0);
float4 GBufferC = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct_GBufferCTextureSampler, UV, 0);
float4 GBufferD = Texture2DSampleLevel(SceneTexturesStruct.GBufferDTexture, SceneTexturesStruct_GBufferDTextureSampler, UV, 0);
float CustomNativeDepth = Texture2DSampleLevel(SceneTexturesStruct.CustomDepthTexture, SceneTexturesStruct_CustomDepthTextureSampler, UV, 0).r;
int2 IntUV = (int2)trunc(UV * View.BufferSizeAndInvSize.xy);
uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(IntUV, 0)) STENCIL_COMPONENT_SWIZZLE;
#if ALLOW_STATIC_LIGHTING
float4 GBufferE = Texture2DSampleLevel(SceneTexturesStruct.GBufferETexture, SceneTexturesStruct_GBufferETextureSampler, UV, 0);
#else
float4 GBufferE = 1;
#endif
float4 GBufferF = Texture2DSampleLevel(SceneTexturesStruct.GBufferFTexture, SceneTexturesStruct_GBufferFTextureSampler, UV, 0);
#if WRITES_VELOCITY_TO_GBUFFER
float4 GBufferVelocity = Texture2DSampleLevel(SceneTexturesStruct.GBufferVelocityTexture, SceneTexturesStruct_GBufferVelocityTextureSampler, UV, 0);
#else
float4 GBufferVelocity = 0;
#endif
float SceneDepth = CalcSceneDepth(UV);
//FGBufferData DecodeGBufferData()通过解码GBuffer之后返回FGBufferData 结构体
return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
}
```
## GetGBufferDataFromSceneTextures中使用的GBufferXTexture的传入位置
SingleLayerWaterCompositePS中使用了GetGBufferDataFromSceneTextures()来获取GBuffer这个数据是在SingleLayerWaterRendering.cpp中传入的。传入的变量FSingleLayerWaterCommonShaderParameters->FSceneTextureParameters SceneTextures中带有GBufferABCEDF与Depth、Stencil、Velocity贴图。
```c++
Texture2D SceneDepthTexture;
Texture2D<uint2> SceneStencilTexture;
Texture2D GBufferATexture;
Texture2D GBufferBTexture;
Texture2D GBufferCTexture;
Texture2D GBufferDTexture;
Texture2D GBufferETexture;
Texture2D GBufferVelocityTexture;
Texture2D GBufferFTexture;
Texture2D<uint> SceneLightingChannels;
FGBufferData GetGBufferDataFromSceneTextures(float2 UV, bool bGetNormalizedNormal = true)
{
float4 GBufferA = GBufferATexture.SampleLevel(GBufferATextureSampler, UV, 0);
float4 GBufferB = GBufferBTexture.SampleLevel(GBufferBTextureSampler, UV, 0);
float4 GBufferC = GBufferCTexture.SampleLevel(GBufferCTextureSampler, UV, 0);
float4 GBufferD = GBufferDTexture.SampleLevel(GBufferDTextureSampler, UV, 0);
float4 GBufferE = GBufferETexture.SampleLevel(GBufferETextureSampler, UV, 0);
float4 GBufferF = GBufferFTexture.SampleLevel(GBufferFTextureSampler, UV, 0);
float4 GBufferVelocity = GBufferVelocityTexture.SampleLevel(GBufferVelocityTextureSampler, UV, 0);
uint CustomStencil = 0;
float CustomNativeDepth = 0;
float DeviceZ = SampleDeviceZFromSceneTextures(UV);
float SceneDepth = ConvertFromDeviceZ(DeviceZ);
//FGBufferData DecodeGBufferData()通过解码GBuffer之后返回FGBufferData 结构体
return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
}
```

View File

@@ -0,0 +1,53 @@
## 前言
思路是使用平面方程来判断模型裁切之后在另一面使用UnLit的自发光材质显示剖面。但Ue4的BlendingMaterialAttributes不能指定UnLit作为ShaderModel。所以可以使用我之前开发的多Pass插件搞定。
## 外部普通表面材质
使用平面方程来做一个Mask即可。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/OutsideSurfaceMaterial.png)
## 内部剖面材质
坡面材质需要使用UnLit材质模型这样就不会有阴影与法线的干扰了。但Unlit不能翻转法线所以需要再勾选**双面渲染**选项。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/InsideCrossSectionMaterial.png)
## 使用方法说明
https://zhuanlan.zhihu.com/p/69139579
1. 使用上面链接介绍的代码新建一个蓝图类并且挂载UStrokeStaticMeshComponent。
2. 赋予UStrokeStaticMeshComponent所需剖切的模型以及上文制作好的外部表面材质。
3. 赋予SecondPassMaterial内部剖面材质并勾选UStrokeStaticMeshComponent的NeedSecondPass变量。
4. 将蓝图类拖到关卡中并且设置2个材质的PlaneNormal与PlanePoint。PlanePoint为世界坐标PlaneNormal需要归一化。
## 最终效果
锯齿是因为垃圾笔记本散热差,把效果开低的关系。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/CrossSectionMaterial.png)
## 错误的尝试
之前还尝试WorldPositionOffset的思路来做不过后来发现没有实用性。因为剖面的大小与形状是不确定的用投射的方式来平移内部可见顶点会造成多余的顶点平移到平面外的问题。所以只适合拿来做规整模型的剖切效果。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/SectionCuttingPlaneShow1.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/SectionCuttingPlaneShow3.png)
错误结果:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/SectionCuttingPlaneShow2.png)
实现节点如下:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/CrossSectionFailedd.PNG)
CustomNode代码
```
// Input PlaneNormal
// Input PlanePoint
// Input WorldPosition
float NormalPower2= pow(PlaneNormal.x,2) + pow(PlaneNormal.y,2) + pow(PlaneNormal.z,2);
float D=dot(PlaneNormal.xyz,PlanePoint.xyz)*-1;
float posX= ((pow(PlaneNormal.y,2) + pow(PlaneNormal.z,2))*WorldPosition.x-PlaneNormal.x*(PlaneNormal.y*WorldPosition.y+PlaneNormal.z*WorldPosition.z+D))/NormalPower2;
float posY=((pow(PlaneNormal.x,2) + pow(PlaneNormal.z,2))*WorldPosition.y-PlaneNormal.y*(PlaneNormal.x*WorldPosition.x+PlaneNormal.z*WorldPosition.z+D))/NormalPower2;
float posZ=((pow(PlaneNormal.x,2) + pow(PlaneNormal.y,2))*WorldPosition.z-PlaneNormal.z*(PlaneNormal.x*WorldPosition.x+PlaneNormal.y*WorldPosition.y+D))/NormalPower2;
return float3(posX,posY,posZ)-WorldPosition.xyz;
```

View File

@@ -0,0 +1,57 @@
早几年学习后处理Shader的时候遇到编辑器视口中的渲染结果与视口大小不匹配的问题
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/PostProcessMaterialError.png)
PS.该问题不会在使用SceneTexture节点制作的后处理效果材质中发生。但它不易于维护与修改。所以我花了些时间来解决这个问题。
## 问题成因与解决方法
可以看得出这个问题是因为ViewSize与BufferSize不一造成的。经过一系列测试使用ViewportUVToBufferUV()对UV坐标进行转换就可以解决问题该函数位于Common.h。
```c#
float2 ViewportUVToBufferUV(float2 ViewportUV)
{
float2 PixelPos = ViewportUV * View.ViewSizeAndInvSize.xy;
return (PixelPos + View.ViewRectMin.xy) * View.BufferSizeAndInvSize.zw;
}
```
ScreenUV使用GetViewportUV(Parameters)来获取Step为ResolvedView.ViewSizeAndInvSize.zw用法大致如下
```c#
float GetDepthTestWeight(float2 ScreenUV,float Stepx, float Stepy, float3x3 KernelX, float3x3 KernelY,float DepthTestThreshold)
{
float3x3 image;
float CurrentPixelDepth=GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV)).GBuffer.Depth;
image = float3x3(
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(-Stepx,Stepy))).GBuffer.Depth-CurrentPixelDepth),
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(0,Stepy))).GBuffer.Depth-CurrentPixelDepth),
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(Stepx,Stepy))).GBuffer.Depth-CurrentPixelDepth),
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(-Stepx,0))).GBuffer.Depth-CurrentPixelDepth),
0,
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(Stepx,0))).GBuffer.Depth-CurrentPixelDepth),
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(-Stepx,-Stepy))).GBuffer.Depth-CurrentPixelDepth),
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(0,-Stepy))).GBuffer.Depth-CurrentPixelDepth),
length(GetScreenSpaceData(ViewportUVToBufferUV(ScreenUV + float2(Stepx,-Stepy))).GBuffer.Depth-CurrentPixelDepth)
);
UNROLL
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
// image[i][j] = image[i][j] > CurrentPixelDepth * DepthTestThreshold ? 1 : 0;
image[i][j] = saturate(image[i][j] / (CurrentPixelDepth * DepthTestThreshold));
}
}
float2 StrokeWeight;
StrokeWeight.x = convolve(KernelX, image);
StrokeWeight.y = convolve(KernelY, image);
return length(StrokeWeight);
}
```
当然如果是直接在渲染管线里添加渲染Pass的方式来实现后处理效果那也不会出现这个问题因为每帧都会按照View大小创建新的buffer所以不会出现这个问题。
## 材质编辑器的相关大小节点。
- ScreenPosition代码为GetViewportUV(FMaterialVertexParameters Parameters)。
- ViewSize可视视口的大小。代码为View.ViewSizeAndInvSize.xyzw为倒数值。
- MF_ScreenResolution具体有VisibleResolution(就是ViewSize节点输出结果) 与BufferResolution(ViewProperty的RenderTargetSize)
- SceneTexelSize场景纹素大小这是一个float2的值对应着UVuvuv均为正数。
- MF_ScreenAlignedPixelToPixelUVsRenderTargetSize / TextureSize。 其中TextureSize可以是ViewSize。可以用来实现一些修改View大小后后处理尺寸不变得效果。
- 后处理节点的SizeClampSceneTextureUV(ViewportUVToSceneTextureUVSceneTextureId)

View File

@@ -0,0 +1,8 @@
# 在UE4中实现LowPoly效果
## 代码
![](../../public/UnrealEngine/LowPoly1.png)
## 效果
![](../../public/UnrealEngine/LowPoly2.jpg)

View File

@@ -0,0 +1,8 @@
---
title: 技能CD转圈效果实现
date: 2022-11-04 09:38:11
excerpt:
tags:
rating: ⭐
---
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/UEShader_SkillIconCDEffect.png)

View File

@@ -0,0 +1,209 @@
---
title: 海洋、云Demo制作方案
date: 2022-11-04 09:38:11
excerpt:
tags:
rating: ⭐
---
# 海洋方案
浅墨的总结https://zhuanlan.zhihu.com/p/95917609
波形Gerstner 波、快速傅立叶变换、空间-频谱混合方法、离线FFT贴图烘焙Offline FFT Texture
着色Depth Based LUT Approach、Sub-Surface Scattering Approximation Approach
白沫:浪尖=>[Tessendorf 2001] 基于雅克比矩阵的方法、岸边=》[GDC 2018]《孤岛惊魂5》基于有向距离场的方法、交互白浪ASher的Niagara方案https://zhuanlan.zhihu.com/p/100700549
## 波形方案
### 线性波形叠加方法
正弦波Sinusoids Wave[Max 1981]
Gerstner 波 Gerstner Wave [Fournier 1986]
### 统计学模型法
快速傅立叶变换Fast Fourier Transform[Mastin 1987]
空间-频谱混合方法Spatial -Spectral Approaches[Thon 2000]
### 波动粒子方法(不考虑)
波动粒子方法Wave Particles [Yuksel 2007]
水波小包方法Water Wave Packets[Jeschke 2017]
水面小波方法Water Surface Wavelets[Jeschke 2018]
### 预渲染
流型图Flow Map[Vlachos 2010]
离线FFT贴图烘焙Offline FFT Texture [Torres 2012]
离线流体帧动画烘焙bake to flipbook[Bowles 2017]
## 水体渲染中的着色方案
《神秘海域3》在2012年SIGGRAPH上的技术分享中有一张分析水体渲染技术非常经典的图如下。
![](https://pic1.zhimg.com/80/v2-4823537ae9b63c38fb34795315074932_720w.jpg)
- 漫反射Diffuse
- 镜面反射Specular
- 法线贴图Normal Map
- 折射Reflection
- 通透感Translucency
- 基于深度的查找表方法Depth Based LUT Approach
- 次表面散射Subsurface Scattering
- 白沫Foam/WhiteCap
- 流动表现Flow
- 水下雾效Underwater Haze
### 通透感Translucency
#### 水体散射效果
##### 基于深度的查找表方法Depth Based LUT Approach
Depth Based-LUT方法的思路是计算视线方向的水体像素深度然后基于此深度值采样吸收/散射LUTAbsorb/Scatter LUT纹理以控制不同深度水体的上色得到通透的水体质感表现。
![](https://picb.zhimg.com/80/v2-1dcd2811c3c1c1e6d74eb87399878ab3_720w.jpg)
![](https://picb.zhimg.com/80/v2-f51ba3db9aeb111e0762c1c0739f33df_720w.jpg)
##### 次表面散射近似方法Sub-Surface Scattering Approximation Approach
- [SIGGRAPH 2019] Crest Ocean System改进的《盗贼之海》SSS方案。
- [GDC 2011] 寒霜引擎的Fast SSS方案。
经过Crest Ocean System改进的《盗贼之海》的SSS方案可以总结如下
假设光更有可能在波浪的一侧被水散射与透射。
基于FFT模拟产生的顶点偏移为波的侧面生成波峰mask
根据视角光源方向和波峰mask的组合将深水颜色和次表面散射水体颜色之间进行混合得到次表面散射颜色。
将位移值Displacement除以波长并用此缩放后的新的位移值计算得出次表面散射项强度。
对应的核心实现代码如下:
```hlsl
float v = abs(i_view.y);
half towardsSun = pow(max(0., dot(i_lightDir, -i_view)),_SubSurfaceSunFallOff);
half3 subsurface = (_SubSurfaceBase + _SubSurfaceSun * towardsSun) *_SubSurfaceColour.rgb * _LightColor0 * shadow;
subsurface *= (1.0 - v * v) * sssIndensity;
```
col += subsurface;
其中sssIndensity即散射强度由采样位移值计算得出。
![](https://picb.zhimg.com/80/v2-6cab41811b287dffce3627ab89250f77_720w.jpg)
图 《Crest Ocean System》中基于次表面散射近似的水体表现
![](https://pic4.zhimg.com/80/v2-127e25cc5edb14b9ab20d5331dc7a3fd_720w.jpg)
[GDC 2011] 寒霜引擎的Fast SSS方案
[GDC 2011]上Frostbite引擎提出的Fast Approximating Subsurface Scattering方案也可以用于水体渲染的模拟。
![](https://pic2.zhimg.com/80/v2-d0362f75e76028d9c518f3bb37e043e0_720w.jpg)
### 白沫Foam/WhiteCap
白沫Foam在有些文献中也被称为WhitecapWhite Water是一种复杂的现象。即使白沫下方的材质具有其他颜色白沫也通常看起来是白色的。出现这种现象的原因是因为白沫是由包含空气的流体薄膜组成的。随着每单位体积薄膜的数量增加所有入射光都被反射而没有任何光穿透到其下方。这种光学现象使泡沫看起来比材质本身更亮以至于看起来几乎是白色的。
![](https://pic3.zhimg.com/80/v2-290d266a361649e1a048d3c5e53522cb_720w.jpg)
对于白沫的渲染而言,白沫可被视为水面上的纹理,其可直接由对象交互作用,浪花的飞溅,或气泡与水表面碰撞而产生。
白沫的渲染方案,按大的渲染思路而言,可以分为两类:
- 基于动态纹理dynamic texture
- 基于粒子系统particle system
按照类型,可以将白沫分为三种:
- 浪尖白沫
- 岸边白沫
- 交互白沫
而按照渲染方法,可将白沫渲染的主流方案总结如下:
- 基于粒子系统的方法[Tessendorf 2001]
- 基于Saturate高度的模拟方法 [GPU Gems 2]
- 基于雅可比矩阵的方法 [Tessendorf 2001]
- 屏幕空间方法 [Akinci 2013]
- 基于场景深度的方法 [Kozin 2018][Sea of Thieves]
- 基于有向距离场的方法 [GDC 2018][Far Cry 5]
#### 浪尖白沫
##### 5.2.1 浪尖白沫:[Tessendorf 2001] 基于雅克比矩阵的方法
Tessendorf在其著名的水体渲染paper《Simulating Ocean Water》[Tessendorf 2001]中提出了可以基于雅克比矩阵Jacobian为负的部分作为求解白沫分布区域的方案。据此即可导出一张或多张标记了波峰白沫区域的Folding Map贴图。
![](https://pic4.zhimg.com/80/v2-c9f35b65405599ea96d9701ccb4156c9_720w.jpg)
《战争雷霆War Thunder》团队在CGDC 2015上对此的改进方案为取雅克比矩阵小于M的区域作为求解白沫的区域其中M~0.3...05。
![](https://picb.zhimg.com/80/v2-5edc9e15ff8d57d88334c3c131f599c9_720w.jpg)
图 《战争雷霆War Thunder》选取雅克比矩阵小于M的区域作为求解白沫的区域 [CGDC 2015]
另外《盗贼之海Sea of Thieves》团队在SIGGRPAPH 2018上提出可以对雅可比矩阵进行偏移以获得更多白沫。且可以采用渐进模糊Progressive Blur来解决噪点noisy问题以及提供风格化的白沫表现。
![](https://pic1.zhimg.com/80/v2-82bfb8a8f2e6eb78037fbed281bb11ca_720w.jpg)
!(https://vdn1.vzuu.com/SD/cfb76434-ec75-11ea-acfd-5ab503a75443.mp4?disable_local_cache=1&bu=pico&expiration=1601438226&auth_key=1601438226-0-0-1b75c02f8faa2d5a94b74ad8ebb6e595&f=mp4&v=hw)
图 《盗贼之海》基于雅可比矩阵偏移 + 渐进模糊Progressive Blur的风格化白沫表现
##### 浪尖白沫:[GPU Gems 2] 基于Saturate高度的方法
《GPU Gems 2》中提出的白沫渲染方案思路是将一个预先创建的白沫纹理在高于某一高度H0的顶点上进行混合。白沫纹理的透明度根据以下公式进行计算
`$ Foam.a=saturate(\frac {H-H_0} {H_{max}-H_0}) $`
- 其中, `$H_{max}$` 是泡沫最大时的高度, `$H_0$` 是基准高度,`$H$` 是当前高度。
- 白沫纹理可以做成序列帧来表示白沫的产生和消失的演变过程。此动画序列帧既可以由美术师进行制作,也可以采用程序化生成。
- 将上述结果和噪声图进行合理的结合,可以得到更真实的泡沫表现。
#### 岸边白沫:[2012]《刺客信条3》基于Multi Ramp Map的方法
《刺客信条3》中的岸边白沫的渲染方案可以总结为
- 以规则的间距对地形结构进行离线采样,标记出白沫出现的区域。
- 采用高斯模糊和Perlin噪声来丰富泡沫的表现形式以模拟海岸上泡沫的褪色现象。
- 由于白沫是白色的因此在RG和B通道中的每个通道中都放置三张灰度图然后颜色ramp图将定义三者之间的混合比率来实现稠密、中等、稀疏三个级别的白沫。要修改白沫表现美术师只需对ramp图进行颜色校正即可。如下图所示
![](https://picb.zhimg.com/80/v2-105c23770b47d723f8c853e7670d72c0_720w.jpg)
![](https://pic1.zhimg.com/80/v2-cf82916125139f7d2d807639c62056ae_720w.jpg)
#### 浪尖白沫&岸边白沫:[GDC 2018]《孤岛惊魂5》基于有向距离场的方法
GDC 2018上《孤岛惊魂5》团队分享的白沫渲染技术也不失为一种优秀的方案主要思路是基于单张Noise贴图控制白沫颜色结合两个offset采样Flow Map控制白沫的流动并基于有向距离场Signed Distance FieldSDF控制岩石和海岸线处白沫的出现然后根据位移对白沫进行混合。
![](https://pic3.zhimg.com/80/v2-7b29981b76be6f41d5ed586843a244d4_720w.jpg)
### 水面高光
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/OceanSpecular.jpg)
### 水焦散
水焦散的渲染 Rendering Water Caustics
GPU Gems 1
ShaderBits.com
### 其他Demo
6.5 NVIDIA UE4 WaveWorks
GDC 2017上NVIDIA和Unreal Engine合作推出了WaveWorks以集成到Unreal Engine 4.15引擎中的形式放出。
源代码传送门https://github.com/NvPhysX/UnrealEngine/tree/WaveWorks
demo视频https://www.youtube.com/watch?v=DhrNvZLPBGE&list=PLN8o8XBheMDxCCfKijfZ3IlTP3cUCH6Jq&index=11&t=0s
## 云方案
eg2020的一篇很思路很新的论文
有空写篇文章分享一下思路
下面是改编了一下的shader
raymarch的次数降低了很多倍同样也增加了不少sdf的采样次数
优化思路想到再发。
https://www.shadertoy.com/view/WdKczW
### ASher的方案
https://zhuanlan.zhihu.com/p/107016039
https://www.bilibili.com/video/BV1FE411n7rU
https://www.bilibili.com/video/BV1L54y1271W
### 大表哥2
分帧渲染
https://zhuanlan.zhihu.com/p/127435500
安泊霖的分析:
https://zhuanlan.zhihu.com/p/91359071
### 地平线:黎明时分
在有道云里有pdf
https://zhuanlan.zhihu.com/p/97257247
https://zhuanlan.zhihu.com/p/57463381
### UE4云层阴影实现
https://zhuanlan.zhihu.com/p/130785313
## 其他相关
### GPU Gems 1
https://developer.nvidia.com/gpugems/gpugems/foreword
http://http.download.nvidia.com/developer/GPU_Gems/CD_Image/Index.html
浅墨的总结:
https://zhuanlan.zhihu.com/p/35974789
https://zhuanlan.zhihu.com/p/36499291
真实感水体渲染Realistic Water Rendering
无尽草地的渲染Rendering Countless Blades of Waving Grass
水焦散渲染Rendering Water Caustics

View File

@@ -0,0 +1,57 @@
---
title: 皮皮虾材质方案
date: 2022-12-06 16:06:06
excerpt:
tags:
rating: ⭐
---
## 效果分析
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221206161132.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221206161138.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221206161147.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221206161156.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221206161203.png)
由观察可得皮皮虾由外表面的透明甲壳材质与内部的肉材质组成。结构如下图:
![900](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221206162332.png)
解决方案:
- 外层甲壳材质:
- 透明度:以最薄处作为基础的透明度,叠加上 BumpOffset/POM 的HeightTexture的采样结果深度乘以参数计算出的透明度。
- Roughness很光滑光滑 0~0.2
- Metallic非金属我们不需要反射
- 内部肉材质:
- 贴图使用 BumpOffset/POM 进行UV偏移。
- Roughness粗糙 1。
- Metallic非金属
- 中间层
- 中间发光亮片:通过建模实现,内部放上一些小亮片,并且添加独立的自发光材质控制。
- 中间雾气效果:通过建模实现,里面放上几个条带,添加类似雾气粒子的材质。
## 多层材质的距离感
可以使用2种方案主要都是通过偏移UV的方式来实现视差效果
1. BumpOffset
2. POM
POM的会有较大的性能损耗但与BumpOffset相比在视角较小时依然有着不错的效果。部分效果不佳的地方mask掉防止穿帮。所以BumpOffset可以作为其下位代替。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221206162642.png)
如果这2个方法不行就只能考虑使用2个模型也就是一个外壳一个内部肉不推荐
## 具体方案
### 两层模型
- 外壳使用DefaultLit、Translucent
- 内部使用Subsurface
缺点:内部模型需要蒙皮。
### MultiDraw
MultiDraw在5.1支持不透明材质渲染透明效果了。所以可以是只用一个模型+HeightTexture配合MultiDraw二次绘制第二个材质效果。
缺点:有穿帮的危险。
### BumpOffset/POM
使用DefaultLit或者车漆材质尝试 外壳/内部肉(视差),参考龙虾人的眼睛
1. 实现DefaultLit版本
2. 查看车漆材质并且实现
缺点5.1版本前无法做到材质物理上的正确。

View File

@@ -0,0 +1,118 @@
---
title: SNN/Kuwahara Filter
date: 2022-10-19 13:44:01
excerpt:
tags: PostProcess
rating: ⭐⭐
---
## 参考文章
## Kuwahara算法发明者是日本人
Wikihttps://en.wikipedia.org/wiki/Kuwahara_filter
各种Kuwahara算法https://zhuanlan.zhihu.com/p/354523349
- Kuwahara
- 广义KuwaharaGeneralized Kuwahara filter
- 各向异性Kuwahara Anisotropic Kuwahara filterhttps://www.shadertoy.com/view/ltyyDd
- Adaptative Kuwahara filterhttps://link.springer.com/article/10.1007/s11760-015-0791-3
- Ganeralized Kuwaharahttps://www.shadertoy.com/view/ltGcWD
## SNNSymmetric nearest neighbour filter
Wikihttps://subsurfwiki.org/wiki/Symmetric_nearest_neighbour_filter
https://www.shadertoy.com/view/MlyfWd
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221020095856.png)
比较相反方向的像素对,并选择那些值最接近中心输入像素的像素(见图)。然后它返回所选像素的平均值。通过这种方式,它可以保留更多的边缘细节。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221020100058.png)
```c++
vec3 CalcSNN(in vec2 fragCoord) {
vec2 src_size = iResolution.xy;
vec2 inv_src_size = 1.0f / src_size;
vec2 uv = fragCoord * inv_src_size;
vec3 c0 = texture(iChannel0, uv).rgb;
vec4 sum = vec4(0.0f, 0.0f, 0.0f, 0.0f);
//{(1,0)~(5,0)}
for (int i = 1; i <= half_width; ++i) {
vec3 c1 = texture(iChannel0, uv + vec2(+i, 0) * inv_src_size).rgb;
vec3 c2 = texture(iChannel0, uv + vec2(-i, 0) * inv_src_size).rgb;
float d1 = CalcDistance(c1, c0);
float d2 = CalcDistance(c2, c0);
if (d1 < d2) {
sum.rgb += c1;
} else {
sum.rgb += c2;
}
sum.a += 1.0f;
}
//{(-5,1)~(5,5)}
for (int j = 1; j <= half_width; ++j) {
for (int i = -half_width; i <= half_width; ++i) {
vec3 c1 = texture(iChannel0, uv + vec2(+i, +j) * inv_src_size).rgb;
vec3 c2 = texture(iChannel0, uv + vec2(-i, -j) * inv_src_size).rgb;
float d1 = CalcDistance(c1, c0);
float d2 = CalcDistance(c2, c0);
if (d1 < d2) {
sum.rgb += c1;
} else {
sum.rgb += c2;
}
sum.a += 1.0f;
}
}
return sum.rgb / sum.a;
}
```
### 双边滤波
https://www.shadertoy.com/view/4dfGDH
https://zhuanlan.zhihu.com/p/180497579
高斯是基于距离的权重,双边是这个基础上增加了颜色权重。
### 破晓传说后处理
BasePass中。角色会渲染出奇怪的黄色。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221022204459.png)
- R通道为1
- G通道为猜测为灯光光照结果因为能看得出场景的光照。
- B通道为猜测为天光+AO很不确定
- A通道为角色Mask
G、B概率是存了2种数据。但最后依然是在Lighting渲染出整个场景与角色的光照。
9650之前是BasePass
14046~14139脸的SSS效果
14146眼睛
14258~14296角色头发
14318~14420
14438~14589
14620~14848渲染角色外的场景
14868开始渲染角色
光照渲染用了很多Ramp
15117~15159锐化。智能锐化 1半径、130%强度效果接近,但可以看得出,角色暗部的锐化效果会更加明显。
15182SNN处理场景不对角色进行处理
15202描边。法线+深度描边。
15219描边合并。
15240提取暗部细节。
中间是粒子
17738LUT 调整颜色
17758有点像计算 sobel 过滤u与v之后`vec3 col = vec3(dot(u, u), dot(v, v), dot(u, v));`的结果。
17776感觉是重要步骤但看不出是啥
17790边缘模糊
绘制UI

View File

@@ -0,0 +1,71 @@
---
title: ScreenPercentage
date: 2022-11-09 10:32:14
excerpt:
tags:
rating: ⭐
---
## ScreenPercentage
基本都与后处理盒子的ScreenPercentage相关。
- ScreenPercentageScene.h
- ScreenPercentageInterfaceSceneView.h
- ScreenPercentageShowFlag.h
- ScreenPercentageTemporalAA.cppFTAAStandaloneCS::FParameters
- ScreenPercentagePlanarReflectionComponent.h
最后用于TAA上采样计算。
## DPI
使用`UserInterfaceSettings->GetDPIScaleBasedOnSize(ViewportSize);`作为采样步长的因子即可。
```c++
float SGameLayerManager::GetGameViewportDPIScale() const
{
const FSceneViewport* Viewport = SceneViewport.Get();
if (Viewport == nullptr)
{ return 1;
}
const auto UserInterfaceSettings = GetDefault<UUserInterfaceSettings>(UUserInterfaceSettings::StaticClass());
if (UserInterfaceSettings == nullptr)
{ return 1;
}
FIntPoint ViewportSize = Viewport->GetSize();
float GameUIScale;
if (bUseScaledDPI)
{ float DPIValue = UserInterfaceSettings->GetDPIScaleBasedOnSize(ScaledDPIViewportReference);
float ViewportScale = FMath::Min((float)ViewportSize.X / (float)ScaledDPIViewportReference.X, (float)ViewportSize.Y / (float)ScaledDPIViewportReference.Y);
GameUIScale = DPIValue * ViewportScale;
} else
{
GameUIScale = UserInterfaceSettings->GetDPIScaleBasedOnSize(ViewportSize);
}
// Remove the platform DPI scale from the incoming size. Since the platform DPI is already
// attempt to normalize the UI for a high DPI, and the DPI scale curve is based on raw resolution // for what a assumed platform scale of 1, extract that scale the calculated scale, since that will // already be applied by slate. const float FinalUIScale = GameUIScale / Viewport->GetCachedGeometry().Scale;
return FinalUIScale;
}
```
```c++
// this function requires the UserInterfaceSettings header to be included
#include Runtime/Engine/Classes/Engine/UserInterfaceSettings.h
// this function can be marked as Blueprint Pure in its declaration, as it simply returns a float
float MyBPFL::GetUMG_DPI_Scale() {
// need a variable here to pass to the GetViewportSize function
FVector2D viewportSize;
// as this function returns through the parameter, we just need to call it by passing in our FVector2D variable
GEngine->GameViewport->GetViewportSize(viewportSize);
// we need to floor the float values of the viewport size so we can pass those into the GetDPIScaleBasedOnSize function
int32 X = FGenericPlatformMath::FloorToInt(viewportSize.X);
int32 Y = FGenericPlatformMath::FloorToInt(viewportSize.Y);
// the GetDPIScaleBasedOnSize function takes an FIntPoint, so we construct one out of the floored floats of the viewport
// the fuction returns a float, so we can return the value out of our function here
return GetDefault<UUserInterfaceSettings>(UUserInterfaceSettings::StaticClass())->GetDPIScaleBasedOnSize(FIntPoint(X,Y));
}
```

View File

@@ -0,0 +1,113 @@
---
title: UE GBuffer存储数据
date: 2022-10-11 13:07:27
excerpt:
tags: Rendering
rating: ⭐
---
https://zhuanlan.zhihu.com/p/400677108
| MRT | R | G | B | A |
| -------------------------------- | -------------------------- | -------------------------- | -------------------------- | ------------------------------------- |
| MRT0 | SceneColor.r | SceneColor.g | SceneColor.b | Alpha(Opacity?) |
| MRT1 GBufferA | WorldNormal.r | WorldNormal.g | WorldNormal.b | PerObjectGBufferData |
| MRT2 GBUfferB | Metallic | Specular | Roughness | ShadingModelId && SelectiveOutputMask |
| MRT3 GBUfferC | BaseColor.r | BaseColor.g | BaseColor.b | IndirectIrradiance |
| MRT4 GBUfferD(根据需求插入) | Velocity.r | Velocity.g | Velocity.b | Velocity.a |
| MRT5 GBUfferE(Velocity不渲染为D) | CustomData.r | CustomData.g | CustomData.b | CustomData.a |
| MRT6 GBUfferF(Velocity不渲染为E) | PrecomputedShadowFactors.r | PrecomputedShadowFactors.g | PrecomputedShadowFactors.b | PrecomputedShadowFactors.a |
| MRT7 | | | | |
## Depth Stencil
[0] sandbox bit (bit to be use by any rendering passes, but must be properly reset to 0 after using)
STENCIL LOD抖动过渡后处理贴花Local光源都有使用用来绘制临时Mask
**[1] unallocated**
**[2] unallocated**
[3] Temporal AA mask for translucent object.
标记后TAA将不再读取History数据用来处理半透物体的TAA问题
**1-3在移动延迟会被借用储存ShadingMode但小于ShadingMode总数量不清楚怎么处理的。但因为毕竟有重复使用不适合直接占用这两个值。**
[4] Lighting channels
[5] Lighting channels
[6] Lighting channels
[7] Primitive receive decal bit
## GBuffer
**SceneColorDeferred场景颜色**
RGBAHalf移动默认R11G11B10
Emissive直接写入这里非自发光写入BaseColor。A通道正常储存Alpha值通常情况没用。
>**原神将其和BaseColor合并了由Alpha来标记自发光强度。这使得自发光颜色必须和BaseColor相同限制了其表达。在需要不同颜色的时候原神则用叠加半透材质的方法实现。**
**用这个方法需要给原本在BaseColor.a的IndirectIrradiance * AO 再找个位置。**
**GBufferA法线**
R10G10B10A2
移动延迟用八面体Encode法线到RGB为PrecomputedShadowFactors.x。
PC直接储存法线到RGB禁用的分支里可以Encode法线到RG如果这样做了B可以空出一个10bit但移动没法使用。不如和移动一样把PrecomputedShadowFactors.x移到这来。不过PrecomputedShadowFactors.x其实也不需要10bit可以拆2bit做特殊用途
A为PerObjectGBufferDatabit1:CastContactShadowbit2:HasDynamicIndirectShadowCasterRepresentation
**GBufferB**
RGBA8888
金属高光粗糙ShadingModelID+SelectiveOutputMask各占4bitShading Mode最大值16
SelectiveOutputMask记录了绘制时以下宏的开启结果
- MATERIAL_USES_ANISOTROPY 禁止计算各向异性
- !GBUFFER_HAS_PRECSHADOWFACTOR 禁止读取GBufferE数据作为预计算阴影
- GBUFFER_HAS_PRECSHADOWFACTOR && WRITES_PRECSHADOWFACTOR_ZERO 当不读取GBufferE时若此值为1时预计算阴影设为0否则为1。
- WRITES_VELOCITY_TO_GBUFFER 禁止从Gbuffer中读取速度值。
**因为没写速度的时候Buffer里是0并不是零速度所以必须靠它来Mask但如果将速度图Clear为零速度按说可以不需要这个Mask就可以用在别处**
**并不觉得高光值得占用8bit的容量可以压缩其他数据到高光上。**
> 常见的做法还有将F0储存至RGB让SSR无需采样BaseColor但这样一来粗糙度又需要另找地方储存可储存在法线B通道上
**GBufferCBaseColor**
RGBA8888
RGB储存Lit物体的BaseColorA在有静态光照时候储存随机抖动过的IndirectIrradiance * Material AO否则直接储存Material AO。
**GBufferD自定义数据**
**GBufferE静态阴影**
**GBufferVelocity速度**
## 实用函数
LightingPass中的调用
```c++
uint PixelShadingModelID = GetScreenSpaceData(ScreenUV).GBuffer.ShadingModelID;
```
最终:
```c++
FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true)
{
float4 GBufferA = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct_GBufferATextureSampler, UV, 0);
float4 GBufferB = Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct_GBufferBTextureSampler, UV, 0);
float4 GBufferC = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct_GBufferCTextureSampler, UV, 0);
float4 GBufferD = Texture2DSampleLevel(SceneTexturesStruct.GBufferDTexture, SceneTexturesStruct_GBufferDTextureSampler, UV, 0);
float4 ToonDataA = Texture2DSampleLevel(SceneTexturesStruct.ToonBufferATexture, SceneTexturesStruct_ToonBufferATextureSampler, UV, 0);
float CustomNativeDepth = Texture2DSampleLevel(SceneTexturesStruct.CustomDepthTexture, SceneTexturesStruct_CustomDepthTextureSampler, UV, 0).r;
int2 IntUV = (int2)trunc(UV * View.BufferSizeAndInvSize.xy);
uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(IntUV, 0)) STENCIL_COMPONENT_SWIZZLE;
#if ALLOW_STATIC_LIGHTING
float4 GBufferE = Texture2DSampleLevel(SceneTexturesStruct.GBufferETexture, SceneTexturesStruct_GBufferETextureSampler, UV, 0);
#else
float4 GBufferE = 1;
#endif
float4 GBufferF = Texture2DSampleLevel(SceneTexturesStruct.GBufferFTexture, SceneTexturesStruct_GBufferFTextureSampler, UV, 0);
#if WRITES_VELOCITY_TO_GBUFFER
float4 GBufferVelocity = Texture2DSampleLevel(SceneTexturesStruct.GBufferVelocityTexture, SceneTexturesStruct_GBufferVelocityTextureSampler, UV, 0);
#else
float4 GBufferVelocity = 0;
#endif
float SceneDepth = CalcSceneDepth(UV);
return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, ToonDataA, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
}
```
>GBuffer.ShadingModelID = DecodeShadingModelId(InGBufferB.a);

View File

@@ -0,0 +1,83 @@
---
title: UE4.25传统渲染方式的代码迁移指南
date: 2020-11-10 13:55:15
tags: Shader
rating: ⭐️
---
## 前言
4.25的GlobalShader的调用代码发生了一下变化我在写完RDG例子后顺便写了一下GlobalShader的使用案例。具体的可以参考我的插件
[https://github.com/blueroseslol/BRPlugins](https://github.com/blueroseslol/BRPlugins)
代码位于SimpleGlobalShader.cpp中。
## 变量使用LAYOUT_FIELD宏进行包裹
对于新定义用于设置变量的GlobalShader基类使用需要使用**DECLARE_INLINE_TYPE_LAYOUT**(FYourShaderName,NonVirtual)宏进行声明。另外还有**DECLARE_TYPE_LAYOUT**(FYourShaderName,NonVirtual)宏,不太清楚差别,但作用应该是一样的。
除此之外还有
```
class FSimpleGlobalShader : public FGlobalShader
{
DECLARE_INLINE_TYPE_LAYOUT(FSimpleGlobalShader, NonVirtual);
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("TEST_MICRO"), 1);
}
FSimpleGlobalShader(){}
FSimpleGlobalShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer):FGlobalShader(Initializer)
{
SimpleColorVal.Bind(Initializer.ParameterMap, TEXT("SimpleColor"));
TextureVal.Bind(Initializer.ParameterMap, TEXT("TextureVal"));
TextureSampler.Bind(Initializer.ParameterMap, TEXT("TextureSampler"));
}
template<typename TRHIShader>
void SetParameters(FRHICommandList& RHICmdList,TRHIShader* ShaderRHI,const FLinearColor &MyColor, FTexture2DRHIRef InInputTexture)
{
SetShaderValue(RHICmdList, ShaderRHI, SimpleColorVal, MyColor);
SetTextureParameter(RHICmdList, ShaderRHI, TextureVal, TextureSampler,TStaticSamplerState<SF_Trilinear,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI(), InInputTexture);
}
private:
LAYOUT_FIELD(FShaderResourceParameter, TextureVal);
LAYOUT_FIELD(FShaderResourceParameter, TextureSampler);
LAYOUT_FIELD(FShaderParameter, SimpleColorVal);
};
```
第二个形参视类内是否有虚函数选择Abstract或者NonVirtual。源代码里基本上都是使用NonVirtual的。
另一个不常用的宏是**DECLARE_EXPORTED_TYPE_LAYOUT**(FYourShaderName, YOUR_API, Virtual)它用于声明需要被其他类继承的基类。具体的使用方法还请参照UE4源码。
Shader变量需要使用LAYOUT_FIELD宏进行包裹第一个形参为类型普通类型声明为FShaderParameterTexture2d与Sampler声明为FShaderResourceParameter。例如
```
LAYOUT_FIELD(FShaderParameter, SimpleColorVal);
LAYOUT_FIELD(FShaderResourceParameter, TextureVal);
LAYOUT_FIELD(FShaderResourceParameter, TextureSampler);
```
## Shader声明改变
声明宏由DECLARE_SHADER_TYPE(FSimplePixelShader, Global)转变为DECLARE_GLOBAL_SHADER(FSimplePixelShader)。
## 去除序列化相关函数
去掉virtual bool Serialize(FArchive& Ar) override函数。
LAYOUT_FIELD宏会实现这部分功能。
## FImageWriteTask
FImageWriteTask现在接受TArray64的数据。而不是 TArray。如果你想传递TArray数据可以这么写
```
TArray<FColor> OutBMP;
// Fill OutBMP somehow
TUniquePtr<TImagePixelData<FColor>> PixelData = MakeUnique<TImagePixelData<FColor>>(DestSize);
PixelData->Pixels = OutBMP;
ImageTask->PixelData = MoveTemp(PixelData);
```

View File

@@ -0,0 +1,175 @@
---
title: UE的透明渲染功能笔记
date: 2022-10-30 21:36:08
excerpt:
tags: Rendering
rating: ⭐
---
其主函数为FDeferredShadingSceneRenderer::RenderTranslucency()位于FXSystem与头发合成之后渲染Distortion、速度之前。
```c++
// Draw translucency.
if (bCanOverlayRayTracingOutput && TranslucencyViewsToRender != ETranslucencyView::None)
{ RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderTranslucency);
SCOPE_CYCLE_COUNTER(STAT_TranslucencyDrawTime);
// Raytracing doesn't need the distortion effect.
const bool bShouldRenderDistortion = TranslucencyViewsToRender != ETranslucencyView::RayTracing;
#if RHI_RAYTRACING
if (EnumHasAnyFlags(TranslucencyViewsToRender, ETranslucencyView::RayTracing))
{ RenderRayTracingTranslucency(GraphBuilder, SceneColorTexture);
EnumRemoveFlags(TranslucencyViewsToRender, ETranslucencyView::RayTracing);
}#endif
// Render all remaining translucency views.
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_Translucency));
RenderTranslucency(GraphBuilder, SceneColorTexture, SceneDepthTexture, HairDatas, &SeparateTranslucencyTextures, TranslucencyViewsToRender);
AddServiceLocalQueuePass(GraphBuilder);
TranslucencyViewsToRender = ETranslucencyView::None;
...
}
```
## Pass Event String
static const TCHAR* TranslucencyPassToString(ETranslucencyPass::Type TranslucencyPass)
{
switch (TranslucencyPass)
{ case ETranslucencyPass::TPT_StandardTranslucency:
return TEXT("Standard");
case ETranslucencyPass::TPT_TranslucencyAfterDOF:
return TEXT("AfterDOF");
case ETranslucencyPass::TPT_TranslucencyAfterDOFModulate:
return TEXT("AfterDOFModulate");
case ETranslucencyPass::TPT_AllTranslucency:
return TEXT("All");
} checkNoEntry();
return TEXT("");
}
##
半透明渲染分为可分离与单Pass渲染。
- 可分离渲染调用RenderViewTranslucencyInner()渲染内部之后调用AddEndSeparateTranslucencyTimerPass()完成渲染。
- 单Pass渲染完所有物体。调用RenderViewTranslucencyInner()渲染内部之后调用AddEndSeparateTranslucencyTimerPass()完成渲染。
Shader变量使用FTranslucentBasePassParameters。
MeshDrawPass名为。
```c++
EMeshPass::TranslucencyStandard
EMeshPass::TranslucencyAfterDOF
EMeshPass::TranslucencyAfterDOFModulate
EMeshPass::TranslucencyAfterMotionBlur
EMeshPass::TranslucencyAll
```
最后在AddPass()调用RenderViewTranslucencyInner()进行实际MeshDraw渲染
```c++
if (bRenderInParallel)
{
GraphBuilder.AddPass(
RDG_EVENT_NAME("Translucency(%s Parallel) %dx%d",
TranslucencyPassToString(TranslucencyPass),
int32(View.ViewRect.Width() * ViewportScale),
int32(View.ViewRect.Height() * ViewportScale)),
PassParameters,
ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
[&SceneRenderer, &View, PassParameters, ViewportScale, Viewport, TranslucencyPass](FRHICommandListImmediate& RHICmdList)
{
FRDGParallelCommandListSet ParallelCommandListSet(RHICmdList, GET_STATID(STAT_CLP_Translucency), SceneRenderer, View, FParallelCommandListBindings(PassParameters), ViewportScale);
RenderViewTranslucencyInner(RHICmdList, SceneRenderer, View, Viewport, ViewportScale, TranslucencyPass, &ParallelCommandListSet, PassParameters->InstanceCullingDrawParams);
});
}
else
{
GraphBuilder.AddPass(
RDG_EVENT_NAME("Translucency(%s) %dx%d",
TranslucencyPassToString(TranslucencyPass),
int32(View.ViewRect.Width() * ViewportScale),
int32(View.ViewRect.Height() * ViewportScale)),
PassParameters,
ERDGPassFlags::Raster,
[&SceneRenderer, &View, ViewportScale, Viewport, TranslucencyPass, PassParameters](FRHICommandListImmediate& RHICmdList)
{
RenderViewTranslucencyInner(RHICmdList, SceneRenderer, View, Viewport, ViewportScale, TranslucencyPass, nullptr, PassParameters->InstanceCullingDrawParams);
});
}
```
最后使用FBasePassMeshProcessor。
```c++
DrawDynamicMeshPass(View, RHICmdList,
[&View, &DrawRenderState, TranslucencyPass](FDynamicPassMeshDrawListContext* DynamicMeshPassContext)
{
FBasePassMeshProcessor PassMeshProcessor(
View.Family->Scene->GetRenderScene(),
View.GetFeatureLevel(),
&View,
DrawRenderState,
DynamicMeshPassContext,
FBasePassMeshProcessor::EFlags::CanUseDepthStencil,
TranslucencyPass);
const uint64 DefaultBatchElementMask = ~0ull;
for (int32 MeshIndex = 0; MeshIndex < View.ViewMeshElements.Num(); MeshIndex++)
{ const FMeshBatch& MeshBatch = View.ViewMeshElements[MeshIndex];
PassMeshProcessor.AddMeshBatch(MeshBatch, DefaultBatchElementMask, nullptr);
}});
```
## 透明物体判断
IsTranslucentBlendMode()位于MaterialShared.h
```c++
inline bool IsTranslucentBlendMode(enum EBlendMode BlendMode)
{
return BlendMode != BLEND_Opaque && BlendMode != BLEND_Masked;
}
```
位于`bool FBasePassMeshProcessor::ShouldDraw(const FMaterial& Material)`
```c++
const bool bIsTranslucent = IsTranslucentBlendMode(BlendMode);
if (bTranslucentBasePass)
{
if (bIsTranslucent && !Material.IsDeferredDecal())
{
switch (TranslucencyPassType)
{
case ETranslucencyPass::TPT_StandardTranslucency:
bShouldDraw = !Material.IsTranslucencyAfterDOFEnabled();
break;
case ETranslucencyPass::TPT_TranslucencyAfterDOF:
bShouldDraw = Material.IsTranslucencyAfterDOFEnabled();
break;
// only dual blended or modulate surfaces need background modulation
case ETranslucencyPass::TPT_TranslucencyAfterDOFModulate:
bShouldDraw = Material.IsTranslucencyAfterDOFEnabled() && (Material.IsDualBlendingEnabled(GetFeatureLevelShaderPlatform(FeatureLevel)) || BlendMode == BLEND_Modulate);
break;
case ETranslucencyPass::TPT_AllTranslucency:
bShouldDraw = true;
break;
}
}
}
```
## 为什么MultiDraw无法同时渲染透明与不透明材质
首先透明会使用FBasePassMeshProcessor进行渲染不同BasePass会有不同的设置针对图元创建MeshProcessor时是以每个图元为单位进行的。
```c++
void FMaterialRenderProxy::UpdateUniformExpressionCacheIfNeeded(ERHIFeatureLevel::Type InFeatureLevel) const
{
if (!UniformExpressionCache[InFeatureLevel].bUpToDate)
{ // Don't cache uniform expressions if an entirely different FMaterialRenderProxy is going to be used for rendering
const FMaterial* Material = GetMaterialNoFallback(InFeatureLevel);
if (Material)
{ FMaterialRenderContext MaterialRenderContext(this, *Material, nullptr);
MaterialRenderContext.bShowSelection = GIsEditor;
EvaluateUniformExpressions(UniformExpressionCache[InFeatureLevel], MaterialRenderContext);
}
}
}
```

View File

@@ -0,0 +1,18 @@
---
title: 时间抗锯齿TemporalAA与上采样
date: 2022-11-04 09:45:21
excerpt:
tags:
rating: ⭐
---
## 网址
- 文档:[https://docs.unrealengine.com/5.0/en-US/screen-percentage-with-temporal-upscale-in-unreal-engine/](https://docs.unrealengine.com/5.0/en-US/screen-percentage-with-temporal-upscale-in-unreal-engine/)
- 论坛上介绍的一种方法:[https://forums.unrealengine.com/unreal-engine/feedback-for-epic/1821872-gen-5-temporal-anti-aliasing](https://forums.unrealengine.com/unreal-engine/feedback-for-epic/1821872-gen-5-temporal-anti-aliasing)
### 步骤
UE5中大概率不适用了
- r.temporalAA.Algorithm 1   //切换到gen5
- r.temporalAA.Upsampling 1   //开启上采样效果
- r.upscale.quality 5         //选择上采样算法5(最猛的一种)
- r.tonemapper.sharpen 2     // 最后再加一点点后处理锐化抵消边缘模糊感
- r.TemporalAA.R11G11B10History=1 //TAA缓存格式

View File

@@ -0,0 +1,28 @@
---
title: 给Pass传入SceneColor
date: 2022-10-10 15:11:46
excerpt:
tags: Rendering
rating: ⭐
---
## 添加变量
```c++
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColorTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorTextureSampler)
```
传入变量
```c++
PassParameters->SceneColorTexture = SceneColorTexture.Target;
PassParameters->SceneColorTextureSampler = TStaticSamplerState<>::GetRHI();
```
## Shader
```c++
Texture2D       SceneColorTexture;
SamplerState    SceneColorTextureSampler;
```
```c++
float4 SceneColor = SceneColorTexture.SampleLevel(SceneColorTextureSampler, SceneBufferUV, 0);
```

View File

@@ -0,0 +1,9 @@
---
title: 薄膜干涉材质
date: 2023-03-14 17:36:48
excerpt:
tags:
rating: ⭐
---
https://80.lv/articles/building-an-iridescence-shader-in-ue4/