Init
This commit is contained in:
37
03-UnrealEngine/Rendering/AIGC/4DGaussians.md
Normal file
37
03-UnrealEngine/Rendering/AIGC/4DGaussians.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-03-11 13:32:22
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- https://github.com/hustvl/4DGaussians
|
||||
- 使用的渲染器为**diff_gaussian_rasterization**。
|
||||
- https://github.com/yzslab/gaussian-splatting-lightning
|
||||
- 使用内置渲染器,在项目目录下的**internal/renderers**中,但还是基于**diff-gaussian-rasterization**。
|
||||
|
||||
**diff_gaussian_rasterization**: https://github.com/graphdeco-inria/diff-gaussian-rasterization ,具体可以参考[[GaussianViewer]],里面一样基于此渲染器。
|
||||
|
||||
问题:
|
||||
1. 4DGaussians
|
||||
1. 数据与3DGaussians的区别在哪?主要是load_ply()
|
||||
# hustvl/4DGaussians
|
||||
- scene/gaussian_model.py:场景管理
|
||||
- load_ply():点云文件读取。
|
||||
- load_model():载入AI模型?
|
||||
|
||||
# 与毛同学的沟通记录
|
||||
比对2个仓库
|
||||
- https://github.com/hustvl/4DGaussians
|
||||
- 以及3DGaussion
|
||||
|
||||
录制: 毛钟楷的个人会议室
|
||||
录制文件:https://meeting.tencent.com/v2/cloud-record/share?id=917807b7-2772-4891-b33c-3e61a71904a9&from=3
|
||||
|
||||
# UE5 运行神经网络模型
|
||||
- https://zhuanlan.zhihu.com/p/665593759
|
||||
- https://github.com/microsoft/OnnxRuntime-UnrealEngine
|
||||
- https://www.youtube.com/watch?v=oWYphpV6A40
|
||||
- https://youtu.be/LX1w_etaftY?si=iF1f8-7TtqI_q4VI
|
||||
- How to use LibTorch and Tokenizers in Unreal Engine 5:https://www.youtube.com/watch?v=dvGWUh4SPBY
|
180
03-UnrealEngine/Rendering/AIGC/GaussianSplattingViewer.md
Normal file
180
03-UnrealEngine/Rendering/AIGC/GaussianSplattingViewer.md
Normal file
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: GaussianSplattingViewer
|
||||
date: 2023-12-29 19:35:16
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# 前言
|
||||
- https://github.com/limacv/GaussianSplattingViewer
|
||||
使用GLFW创建的程序。
|
||||
# main.py
|
||||
主要逻辑位于main()中,大致逻辑如下:
|
||||
1. 获取前文设置的全部变量。
|
||||
2. 创建imgui用于控制变量。
|
||||
3. 创建GLFW渲染窗口**windows**。
|
||||
4. 调用**imgui.integrations.glfw**中的**GlfwRenderer**,并且将结果渲染到这个**windows**中。
|
||||
5. 获取tk(tkinter)并且赋值给root,之后调用withdraw()。应该是用于绘制选择文件窗口的。
|
||||
6. 绑定glfw的set_cursor_pos_callback、set_mouse_button_callback、set_scroll_callback、set_key_callback、set_window_size_callback事件。
|
||||
7. 创建**renderer_ogl**的**OpenGLRenderer**渲染器对象,并将其加入g_renderer_list全局渲染器列表。
|
||||
8. 创建**renderer_cuda**的**CUDARenderer**渲染器对象,如果成功,将其加入g_renderer_list全局渲染器列表。
|
||||
9. 按照之前设置的渲染器index选择用于渲染的渲染器,并赋值给**g_renderer**。
|
||||
10. 高斯数据处理
|
||||
1. gaussians = util_gau.naive_gaussian(),创建写死的高斯数据。
|
||||
2. update_activated_renderer_state(gaussians)
|
||||
11. 开始进入渲染循环
|
||||
1. 调用glfw、GlfwRenderer、imgui循环相关函数。
|
||||
2. 清屏。
|
||||
3. 更新摄像机Location & Intrin。
|
||||
4. imgui菜单控制逻辑。调整各种参数、打开Ply点云文件。**载入逻辑位于util_gau.py的load_ply()**
|
||||
1. 文件载入之后会进行一次高斯数据更新update_gaussian_data()以及排序sort_and_update()
|
||||
5. 摄像机更新。
|
||||
6. 缩放更新。
|
||||
7. 如果修改了Shading则更新渲染模式set_render_mod()
|
||||
8. 如果点击了sort Gaussians按钮,则进行一次排序sort_and_update()
|
||||
9. 如果勾选了g_auto_sort,则进行一次排序sort_and_update()
|
||||
10. 保存图片按钮逻辑。
|
||||
11. imgui、GlfwRenderer渲染函数调用;glfw更换前后缓存。
|
||||
|
||||
## 渲染器函数
|
||||
### renderer_ogl.py
|
||||
> 渲染模式为:"Gaussian Ball", "Billboard", "Depth", "SH:0", "SH:0~1", "SH:0~2", "SH:0~3 (default)。
|
||||
|
||||
`_sort_gaussian`
|
||||
```python
|
||||
def _sort_gaussian(gaus: util_gau.GaussianData, view_mat):
|
||||
xyz = gaus.xyz
|
||||
xyz_view = view_mat[None, :3, :3] @ xyz[..., None] + view_mat[None, :3, 3, None]
|
||||
depth = xyz_view[:, 2, 0]
|
||||
index = np.argsort(depth)
|
||||
index = index.astype(np.int32).reshape(-1, 1)
|
||||
return index
|
||||
```
|
||||
|
||||
`__init__`
|
||||
1. 载入Shader。
|
||||
2. 定义面片顶点数据。
|
||||
3. 设置属性通道为Position,并将顶点数据塞入VAO。
|
||||
4. 设置渲染属性:
|
||||
1. 禁用剔除。
|
||||
2. 开启BlendMode,gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA。也就是线性插值。
|
||||
|
||||
`update_gaussian_data`
|
||||
1. 传入当前高斯数据。
|
||||
2. 调用下面的flat函数,赋值给gaussian_data。
|
||||
3. 传递vao、buffer_id、gaussian_data、bind_idx到Shader中。
|
||||
4. 调用 `util.set_uniform_1int(self.program, gaus.sh_dim, "sh_dim")`
|
||||
|
||||
`sort_and_update`:排序并且更新Shader中的数据。
|
||||
`draw`:绘制函数。
|
||||
1. 传递VAO点云数据数组到VertexShader。
|
||||
2. 取得点云数。
|
||||
3. 绘制与点云数目一样多的面片Instance。
|
||||
|
||||
```python
|
||||
def flat(self) -> np.ndarray:
|
||||
ret = np.concatenate([self.xyz, self.rot, self.scale, self.opacity, self.sh], axis=-1)
|
||||
return np.ascontiguousarray(ret)
|
||||
```
|
||||
|
||||
#### VertexShader
|
||||
1. 根据gl_InstanceID、total_dim计算当前面片Instance的点云数据开始index。
|
||||
2. 根据开始index,从g_data[]取得g_pos数据,并且转换成屏幕空间坐标。
|
||||
3. 执行early culling。将不在屏幕内的点云数据都塞到Vec4(-100, -100, -100,1)。
|
||||
4. 根据开始index,从g_data[]取得g_rot。
|
||||
5. 根据开始index,从g_data[]取得g_scale。
|
||||
6. 根据开始index,从g_data[]取得g_opacity。
|
||||
7. 调用computeCov3D() => computeCov2D(),计算协方差矩阵。
|
||||
```c++
|
||||
mat3 cov3d = computeCov3D(g_scale * scale_modifier, g_rot);
|
||||
vec2 wh = 2 * hfovxy_focal.xy * hfovxy_focal.z;
|
||||
vec3 cov2d = computeCov2D(g_pos_view,
|
||||
hfovxy_focal.z,
|
||||
hfovxy_focal.z,
|
||||
hfovxy_focal.x,
|
||||
hfovxy_focal.y,
|
||||
cov3d,
|
||||
view_matrix);
|
||||
|
||||
// Invert covariance (EWA algorithm)
|
||||
float det = (cov2d.x * cov2d.z - cov2d.y * cov2d.y);
|
||||
if (det == 0.0f)
|
||||
gl_Position = vec4(0.f, 0.f, 0.f, 0.f);
|
||||
|
||||
float det_inv = 1.f / det;
|
||||
conic = vec3(cov2d.z * det_inv, -cov2d.y * det_inv, cov2d.x * det_inv);
|
||||
|
||||
vec2 quadwh_scr = vec2(3.f * sqrt(cov2d.x), 3.f * sqrt(cov2d.z)); // screen space half quad height and width
|
||||
vec2 quadwh_ndc = quadwh_scr / wh * 2; // in ndc space
|
||||
g_pos_screen.xy = g_pos_screen.xy + position * quadwh_ndc;
|
||||
coordxy = position * quadwh_scr;
|
||||
gl_Position = g_pos_screen;
|
||||
```
|
||||
8. alpha = g_opacity;
|
||||
9. if (render_mod == -1)则计算深度,最后输出color为1/Depth的灰度值。"Depth"渲染模式。
|
||||
```c++
|
||||
// Covert SH to color
|
||||
int sh_start = start + SH_IDX;
|
||||
vec3 dir = g_pos.xyz - cam_pos;
|
||||
dir = normalize(dir);
|
||||
color = SH_C0 * get_vec3(sh_start);
|
||||
|
||||
if (sh_dim > 3 && render_mod >= 1) // 1 * 3
|
||||
{
|
||||
float x = dir.x;
|
||||
float y = dir.y;
|
||||
float z = dir.z;
|
||||
color = color - SH_C1 * y * get_vec3(sh_start + 1 * 3) + SH_C1 * z * get_vec3(sh_start + 2 * 3) - SH_C1 * x * get_vec3(sh_start + 3 * 3);
|
||||
|
||||
if (sh_dim > 12 && render_mod >= 2) // (1 + 3) * 3
|
||||
{
|
||||
float xx = x * x, yy = y * y, zz = z * z;
|
||||
float xy = x * y, yz = y * z, xz = x * z;
|
||||
color = color +
|
||||
SH_C2_0 * xy * get_vec3(sh_start + 4 * 3) +
|
||||
SH_C2_1 * yz * get_vec3(sh_start + 5 * 3) +
|
||||
SH_C2_2 * (2.0f * zz - xx - yy) * get_vec3(sh_start + 6 * 3) +
|
||||
SH_C2_3 * xz * get_vec3(sh_start + 7 * 3) +
|
||||
SH_C2_4 * (xx - yy) * get_vec3(sh_start + 8 * 3);
|
||||
|
||||
if (sh_dim > 27 && render_mod >= 3) // (1 + 3 + 5) * 3
|
||||
{
|
||||
color = color +
|
||||
SH_C3_0 * y * (3.0f * xx - yy) * get_vec3(sh_start + 9 * 3) +
|
||||
SH_C3_1 * xy * z * get_vec3(sh_start + 10 * 3) +
|
||||
SH_C3_2 * y * (4.0f * zz - xx - yy) * get_vec3(sh_start + 11 * 3) +
|
||||
SH_C3_3 * z * (2.0f * zz - 3.0f * xx - 3.0f * yy) * get_vec3(sh_start + 12 * 3) +
|
||||
SH_C3_4 * x * (4.0f * zz - xx - yy) * get_vec3(sh_start + 13 * 3) +
|
||||
SH_C3_5 * z * (xx - yy) * get_vec3(sh_start + 14 * 3) +
|
||||
SH_C3_6 * x * (xx - 3.0f * yy) * get_vec3(sh_start + 15 * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
color += 0.5f;
|
||||
```
|
||||
|
||||
#### PixelShader
|
||||
1. if (render_mod == -2)则直接显示当前颜色,并且结束渲染。"Billboard"渲染模式
|
||||
2. 计算`power = -0.5f * (conic.x * coordxy.x * coordxy.x + conic.z * coordxy.y * coordxy.y) - conic.y * coordxy.x * coordxy.y;`,丢弃power大于0的像素。
|
||||
3. 计算float opacity = `min(0.99f, alpha * exp(power))`;,丢弃opacity小于1/255的像素。
|
||||
4. FragColor = vec4(color, opacity);
|
||||
5. if (render_mod == -3)则将透明度低于0.22的像素都隐藏。"Gaussian Ball"渲染模式
|
||||
|
||||
"Depth", "SH:0", "SH:0~1", "SH:0~2", "SH:0~3 (default)。
|
||||
|
||||
### renderer_cuda.py
|
||||
略
|
||||
|
||||
## 相关函数
|
||||
**update_activated_renderer_state**:更新渲染器状态。包括更新**高斯数据**、**摄像机缩放&高斯点云排序**、摄像机位移、渲染比例、渲染Mode。
|
||||
```python
|
||||
def update_activated_renderer_state(gaus: util_gau.GaussianData):
|
||||
g_renderer.update_gaussian_data(gaus)
|
||||
g_renderer.sort_and_update(g_camera)
|
||||
g_renderer.set_scale_modifier(g_scale_modifier)
|
||||
g_renderer.set_render_mod(g_render_mode - 3)
|
||||
g_renderer.update_camera_pose(g_camera)
|
||||
g_renderer.update_camera_intrin(g_camera)
|
||||
g_renderer.set_render_reso(g_camera.w, g_camera.h)
|
||||
```
|
310
03-UnrealEngine/Rendering/AIGC/GaussianViewer.md
Normal file
310
03-UnrealEngine/Rendering/AIGC/GaussianViewer.md
Normal file
@@ -0,0 +1,310 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-01-01 18:57:57
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- https://github.com/graphdeco-inria/gaussian-splatting/tree/main/gaussian_renderer
|
||||
基于Sibr渲染器制作的3D高斯查看器。
|
||||
|
||||
# 项目结构
|
||||
- [x] gaussian
|
||||
- render - sibr_gaussian
|
||||
- apps - SIBR_gaussianViewer_app
|
||||
- [x] diff-gaussian-rasterization(CUDA)
|
||||
# render - sibr_gaussian
|
||||
- picojson:JSON库
|
||||
- rapidxml:XML库
|
||||
- **nanoflann**:是一个c++11标准库,用于构建具有不同拓扑(R2,R3(点云),SO(2)和SO(3)(2D和3D旋转组))的KD树。
|
||||
|
||||
## GaussianSurfaceRenderer
|
||||
>主要用于渲染椭圆体,估计是用于Debug用的。
|
||||
|
||||
### GaussianData
|
||||
- GaussianData():通过构造函数形参接受CPU端读取的高斯数据,再通过调用glCreateBuffers()、glNamedBufferStorage()创建GL缓存对象并且初始化,并使用GLuint进行记录(index)。
|
||||
- render:给Shader绑定GL缓存,并且绘制数组实例。
|
||||
|
||||
### GaussianSurfaceRenderer
|
||||
- GaussianSurfaceRenderer():初始化相关变量。
|
||||
- 初始化VS/Frag Shader。
|
||||
- rayOrigin、MVP、alpha_limit、stage变量
|
||||
- 创建idTexture、colorTexture贴图变量以及过滤器
|
||||
- 创建fbo对象以及depthBuffer之后调用makeFBO()正式创建FBO
|
||||
- 创建清屏Shader。
|
||||
- makeFBX():创建idTexture、colorTexture、depthBuffer FBO,用于将顶点数据传递到FragShader中。
|
||||
- process():整个渲染过程逻辑处理。
|
||||
1. 清屏。
|
||||
2. 判断如果分辨率与FBO大小不同,则重新创建FBO。
|
||||
3. 获取绘制Buffer的Index,调用glDrawBuffers() 绘制colorTexture、idTexture。
|
||||
4. 开启深度测试关闭Blend模式。
|
||||
5. 给Shader绑定相关`_paramMVP`、`_paramCamPos`、`_paramLimit`、`_paramStage`变量,并且调用GaussianData.render()进行一次**不透明物体**的渲染。以小方盒的形式绘制点云数据。
|
||||
6. 调用glDrawBuffers() 绘制colorTexture。
|
||||
7. 关闭深度测试,开启透明Blend模式。
|
||||
8. GaussianData.render()进行一次**透明物体**的渲染,融合模式**additive blendnig**。以小方盒的形式绘制点云数据。
|
||||
9. 开启深度测试,关闭Blend模式。
|
||||
10. 将结果显示在屏幕上?
|
||||
|
||||
## GaussianView
|
||||
继承自sibr::ViewBase,用与调用渲染器以及显示结果。
|
||||
|
||||
### GaussianView
|
||||
- GaussianView():
|
||||
- 初始化_pointbasedrenderer渲染器
|
||||
- 初始化_copyRenderer渲染器
|
||||
- 载入图片并且加入debug模式(应该sibr自带的那个多视角图片debug模式)
|
||||
- 载入*.ply点云文件,函数为loadPly()。
|
||||
- CUDA相关处理,应该是为了计算3D高斯结果所需的数据。
|
||||
- 生成GaussianData指针变量gData。
|
||||
- 初始化3D高斯渲染器对象_gaussianRenderer。
|
||||
- 创建GL缓存对象imageBuffer。
|
||||
- CUDA插值操作。
|
||||
- 绑定3个geomBufferFunc、binningBufferFunc、imgBufferFunc仿函数,用来调整CUDA渲染时的缓存大小(创建或者回收内存空间)
|
||||
- onRenderIBR():View的渲染函数。
|
||||
- Ellipsoids(椭圆体渲染):使用_gaussianRenderer->process() 进行渲染。(OpenGL)
|
||||
- Initial Points:`_pointbasedrenderer->process()`渲染点。
|
||||
- Splats:使用CudaRasterizer::Rasterizer::forward()进行渲染。最后通过_copyRenderer->process()复制回imageBuffer缓存。
|
||||
- onGUI():GUI相关逻辑。
|
||||
|
||||
CUDA文件位于`SIBR_viewers\extlibs\CudaRasterizer\CudaRasterizer\cuda_rasterizer\rasterizer_impl.cu`以及`forward.cu`,这些为核心逻辑。
|
||||
## Shader
|
||||
可以理解为将点云渲染成一个个的椭圆体,每个椭圆体的颜色与点云数据中的颜色相关。
|
||||
### VertexShader
|
||||
1. 取得IndexID。
|
||||
2. 使用IndexID从传入Shader的Buffer中获取的椭圆体中心、alpha、ellipsoidScale、q(四元数rotation),之后将rotation转成3x3矩阵 ellipsoidRotation。
|
||||
3. 取得当前顶点Index并获得坐标。再乘以椭圆体旋转值并加上椭圆体中心坐标,取得最终的WorldPos(当前顶点的世界坐标)。
|
||||
4. 使用IndexID从传入Shader的Buffer中取得**辐射照度?辐射强度?** 数据。
|
||||
5. 将不符合要求的顶点堆到vec4(0,0,0,0)点。
|
||||
6. 输出顶点数据到FragShader。
|
||||
### FragShader
|
||||
1. 计算摄像机=>当前顶点世界坐标的方向向量dir。
|
||||
2. 调用closestEllipsoidIntersection(),计算与椭圆体的相交的坐标与相交点的法线。
|
||||
1. 计算椭圆体空间的localRayOrigin与localRayDirection
|
||||
2. 计算椭圆与直线相交的方程。
|
||||
3. 计算摄像机朝向的椭圆体的外表面。如果是内表面最终颜色值 * 0.4。
|
||||
4. 将相交的世界坐标乘以MVP矩阵,得到摄像机View坐标下的的世界坐标。
|
||||
5. 计算深度缓存。
|
||||
6. 计算Alpha。
|
||||
7. 渲染`out_color = vec4(align * colorVert, a);` 也就是colorTexture
|
||||
8. 渲染`out_id = boxID;`也就是idTexture
|
||||
|
||||
# CudaRasterizer
|
||||
**本人没学过CUDA,以下仅仅是对代码的猜测。**
|
||||
额外需要了解Tile渲染方式(具体可以看**Tiled-Based Deferred Rendering(TBDR)**) https://zhuanlan.zhihu.com/p/547943994
|
||||
|
||||
- 屏幕分成`16 * 16`的tile,每个tile进行单独计算。之后对每个像素进行计算。
|
||||
- 取得对应tile中Start与End的位置,对已经排序完的高斯点进行计算,求微分。
|
||||
- 计算当前像素的透明度T
|
||||
- 2D协方差 => power => alpha。
|
||||
- 每次循环都进行`float test_T = T * (1 - alpha)`,当test_T极小时(不透明)则停止循环。
|
||||
- T = test_T。
|
||||
- 计算当前像素的颜色,也就是计算各个方向接受的辐射照度。
|
||||
- `for (int ch = 0; ch < CHANNELS; ch++)`
|
||||
`C[ch] += features[collected_id[j] * CHANNELS + ch] * alpha * T;`
|
||||
- 计算最终贡献值
|
||||
- 如果当前像素在范围中则输出
|
||||
- `final_T[pix_id]`最终透明度。
|
||||
- `n_contrib[pix_id]`最终贡献值。
|
||||
- `out_color[ch * H * W + pix_id]`最终颜色。`C[ch] + T * bg_color[ch]`
|
||||
|
||||
对屏幕分Tile
|
||||
![[ScreenSpaceTile.jpg]]
|
||||
|
||||
以此减少需要遍历的点云数量。
|
||||
![[TileRange.jpg|500]]
|
||||
|
||||
每个点云相当于空间中当前位置空间的辐射强度分布。
|
||||
![[GS_radiation.jpg]]
|
||||
|
||||
一个像素的渲染会计算这个像素范围内所有的点云的辐射强度、透明度,最后求微分。下图两条横线内相当于一个像素的范围。
|
||||
![[一个像素需要计算范围内所有电源的辐射强度.png|500]]
|
||||
|
||||
## rasterizer_impl.cu
|
||||
- getHigherMsb()
|
||||
- checkFrustum():判断点云是否在视锥内,返回一个bool数组。
|
||||
- duplicateWithKeys()
|
||||
- identifyTileRanges():确定每个Tile的工作起点与终点。
|
||||
- markVisible():标记高斯点云是否处于可视状态。
|
||||
- GeometryState::fromChunk():计算数据块的指针偏移,并且返回创建的GeometryState结构体对象。
|
||||
- ImageState::fromChunk():计算数据块的指针偏移,并且返回创建的ImageState结构体对象。
|
||||
- BinningState::fromChunk():计算数据块的指针偏移,并且返回创建的BinningState结构体对象。
|
||||
- forward():前向渲染可微分光栅化的高斯。具体见下文。
|
||||
- backward():生成优化所需的梯度数据,并传递到forward()。**该项目中目前未被调用**
|
||||
|
||||
相关数据结构体定义在rasterizer_impl.h中:
|
||||
```c++
|
||||
struct GeometryState
|
||||
{
|
||||
size_t scan_size;
|
||||
float* depths;
|
||||
char* scanning_space;
|
||||
bool* clamped;
|
||||
int* internal_radii;
|
||||
float2* means2D;
|
||||
float* cov3D;
|
||||
float4* conic_opacity;
|
||||
float* rgb;
|
||||
uint32_t* point_offsets;
|
||||
uint32_t* tiles_touched;
|
||||
|
||||
static GeometryState fromChunk(char*& chunk, size_t P);
|
||||
};
|
||||
|
||||
struct ImageState
|
||||
{
|
||||
uint2* ranges;
|
||||
uint32_t* n_contrib;
|
||||
float* accum_alpha;
|
||||
|
||||
static ImageState fromChunk(char*& chunk, size_t N);
|
||||
};
|
||||
|
||||
struct BinningState
|
||||
{
|
||||
size_t sorting_size;
|
||||
uint64_t* point_list_keys_unsorted;
|
||||
uint64_t* point_list_keys;
|
||||
uint32_t* point_list_unsorted;
|
||||
uint32_t* point_list;
|
||||
char* list_sorting_space;
|
||||
|
||||
static BinningState fromChunk(char*& chunk, size_t P);
|
||||
};
|
||||
```
|
||||
|
||||
### forward()
|
||||
1. 创建相关变量:GeometryState、ImageState、minn、maxx。
|
||||
2. FORWARD::preprocess()
|
||||
3. 计算所有tile的高斯点云总量。
|
||||
4. 根据需要需要渲染的高斯点云总量来调整CUDA buffer大小。
|
||||
5. 创建BinningState。
|
||||
6. duplicateWithKeys()
|
||||
7. getHigherMsb()
|
||||
8. 对高斯点运行排序。
|
||||
9. cudaMemset(imgState.ranges, 0, tile_grid.x * tile_grid.y * sizeof(uint2));
|
||||
10. 调用identifyTileRanges(),确定每个Tile的工作起点与终点。
|
||||
11. 取得点云颜色数组。
|
||||
12. FORWARD::render()
|
||||
|
||||
## forward.cu
|
||||
### preprocess()
|
||||
在光栅化之前,对每个高斯进行初始化处理。
|
||||
- 只处理在视锥中并且在盒子中的高斯。
|
||||
- 使用投影矩阵对点云的点进行变换,并进行归一化,赋予给新变量p_proj。
|
||||
- 计算协方差矩阵cov3D。
|
||||
- 计算2D屏幕空间的协方差矩阵cov
|
||||
- Invert covariance
|
||||
- Compute extent in screen space (by finding eigenvalues of 2D covariance matrix). Use extent to compute a bounding rectangle of screen-space tiles that this Gaussian overlaps with. Quit if rectangle covers 0 tiles.
|
||||
- 如果没有颜色数据则从球谐函数中计算辐射照度。
|
||||
- 存储当前数据。
|
||||
- `depths[idx]`
|
||||
- `radii[idx]`
|
||||
- `points_xy_image[idx]`
|
||||
- `conic_opacity[idx]`
|
||||
- `tiles_touched[idx]`
|
||||
|
||||
```c++
|
||||
// Invert covariance (EWA algorithm)
|
||||
float det = (cov.x * cov.z - cov.y * cov.y);
|
||||
if (det == 0.0f)
|
||||
return;
|
||||
float det_inv = 1.f / det;
|
||||
float3 conic = { cov.z * det_inv, -cov.y * det_inv, cov.x * det_inv };
|
||||
|
||||
// Compute extent in screen space (by finding eigenvalues of
|
||||
// 2D covariance matrix). Use extent to compute a bounding rectangle
|
||||
// of screen-space tiles that this Gaussian overlaps with. Quit if
|
||||
// rectangle covers 0 tiles.
|
||||
float mid = 0.5f * (cov.x + cov.z);
|
||||
float lambda1 = mid + sqrt(max(0.1f, mid * mid - det));
|
||||
float lambda2 = mid - sqrt(max(0.1f, mid * mid - det));
|
||||
float my_radius = ceil(3.f * sqrt(max(lambda1, lambda2)));
|
||||
float2 point_image = { ndc2Pix(p_proj.x, W), ndc2Pix(p_proj.y, H) };
|
||||
uint2 rect_min, rect_max;
|
||||
|
||||
if (rects == nullptr) // More conservative
|
||||
{
|
||||
getRect(point_image, my_radius, rect_min, rect_max, grid);
|
||||
}
|
||||
else // Slightly more aggressive, might need a math cleanup
|
||||
{
|
||||
const int2 my_rect = { (int)ceil(3.f * sqrt(cov.x)), (int)ceil(3.f * sqrt(cov.z)) };
|
||||
rects[idx] = my_rect;
|
||||
getRect(point_image, my_rect, rect_min, rect_max, grid);
|
||||
}
|
||||
|
||||
if ((rect_max.x - rect_min.x) * (rect_max.y - rect_min.y) == 0)
|
||||
return;
|
||||
```
|
||||
|
||||
### render()
|
||||
对所有Tile进行并行计算。针对CUDA核心数量创建对应的Block以及对应数据。`int collected_id[BLOCK_SIZE]、float2 collected_xy[BLOCK_SIZE]、float4 collected_conic_opacity[BLOCK_SIZE]`。
|
||||
|
||||
递归所有的Block,计算透明度、Color以及贡献值(用于计算平均值)。
|
||||
|
||||
```c++
|
||||
// Iterate over batches until all done or range is complete
|
||||
for (int i = 0; i < rounds; i++, toDo -= BLOCK_SIZE)
|
||||
{
|
||||
// End if entire block votes that it is done rasterizing
|
||||
int num_done = __syncthreads_count(done);
|
||||
if (num_done == BLOCK_SIZE)
|
||||
break;
|
||||
|
||||
// Collectively fetch per-Gaussian data from global to shared
|
||||
int progress = i * BLOCK_SIZE + block.thread_rank();
|
||||
if (range.x + progress < range.y)
|
||||
{ int coll_id = point_list[range.x + progress];
|
||||
collected_id[block.thread_rank()] = coll_id;
|
||||
collected_xy[block.thread_rank()] = points_xy_image[coll_id];
|
||||
collected_conic_opacity[block.thread_rank()] = conic_opacity[coll_id];
|
||||
} block.sync();
|
||||
|
||||
// Iterate over current batch
|
||||
for (int j = 0; !done && j < min(BLOCK_SIZE, toDo); j++)
|
||||
{ // Keep track of current position in range
|
||||
contributor++;
|
||||
|
||||
// Resample using conic matrix (cf. "Surface
|
||||
// Splatting" by Zwicker et al., 2001)
|
||||
float2 xy = collected_xy[j];
|
||||
float2 d = { xy.x - pixf.x, xy.y - pixf.y };
|
||||
float4 con_o = collected_conic_opacity[j];
|
||||
float power = -0.5f * (con_o.x * d.x * d.x + con_o.z * d.y * d.y) - con_o.y * d.x * d.y;
|
||||
if (power > 0.0f)
|
||||
continue;
|
||||
|
||||
// Eq. (2) from 3D Gaussian splatting paper.
|
||||
// Obtain alpha by multiplying with Gaussian opacity // and its exponential falloff from mean. // Avoid numerical instabilities (see paper appendix).float alpha = min(0.99f, con_o.w * exp(power));
|
||||
if (alpha < 1.0f / 255.0f)
|
||||
continue;
|
||||
float test_T = T * (1 - alpha);
|
||||
if (test_T < 0.0001f)
|
||||
{ done = true;
|
||||
continue;
|
||||
}
|
||||
// Eq. (3) from 3D Gaussian splatting paper.
|
||||
for (int ch = 0; ch < CHANNELS; ch++)
|
||||
C[ch] += features[collected_id[j] * CHANNELS + ch] * alpha * T;
|
||||
|
||||
T = test_T;
|
||||
|
||||
// Keep track of last range entry to update this
|
||||
// pixel. last_contributor = contributor;
|
||||
}}
|
||||
```
|
||||
|
||||
```c++
|
||||
// All threads that treat valid pixel write out their final
|
||||
// rendering data to the frame and auxiliary buffers.
|
||||
if (inside)
|
||||
{
|
||||
final_T[pix_id] = T;
|
||||
n_contrib[pix_id] = last_contributor;
|
||||
for (int ch = 0; ch < CHANNELS; ch++)
|
||||
out_color[ch * H * W + pix_id] = C[ch] + T * bg_color[ch];
|
||||
}
|
||||
```
|
||||
# apps - SIBR_gaussianViewer_app
|
||||
调用`gaussianviewer/renderer/GaussianView.hpp`封装的App。
|
79
03-UnrealEngine/Rendering/AIGC/Sibr相关笔记.md
Normal file
79
03-UnrealEngine/Rendering/AIGC/Sibr相关笔记.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: 未命名
|
||||
date: 2023-12-29 16:20:43
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# 前言
|
||||
- 文档:https://sibr.gitlabpages.inria.fr
|
||||
- 代码:https://gitlab.inria.fr/sibr/sibr_core
|
||||
- 案例代码
|
||||
- [renderer/SimpleView.hpp](https://gitlab.inria.fr/mbenadel/sibr_simple/-/blob/master/renderer/SimpleView.hpp)&[renderer/SimpleView.cpp](https://gitlab.inria.fr/mbenadel/sibr_simple/-/blob/master/renderer/SimpleView.cpp)
|
||||
- [renderer/SimpleRenderer.hpp](https://gitlab.inria.fr/mbenadel/sibr_simple/-/blob/master/renderer/SimpleRenderer.hpp)&[renderer/SimpleRenderer.cpp](https://gitlab.inria.fr/mbenadel/sibr_simple/-/blob/master/renderer/SimpleRenderer.cpp)
|
||||
- [Simple SIBR Project](https://gitlab.inria.fr/sibr/projects/simple)
|
||||
- [SIBR/OptiX integration example](https://sibr.gitlabpages.inria.fr/docs/0.9.6/optixPage.html)
|
||||
- [Tensorflow/OpenGL Interop for SIBR](https://sibr.gitlabpages.inria.fr/docs/0.9.6/tfgl_interopPage.html)
|
||||
- Shader:需要将你自己编写的Shader放入**renderer/shaders**文件夹中
|
||||
- 关键词:
|
||||
- Structure-from-Motion (SfM)
|
||||
- Multi-View Stereo (MVS)
|
||||
|
||||
## 功能
|
||||
https://sibr.gitlabpages.inria.fr/docs/0.9.6/projects.html
|
||||
|
||||
- [Sample algorithms & toolboxes](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_samples.html)
|
||||
- [Dataset Preprocessing Tools](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_dataset_tools.html) ([https://gitlab.inria.fr/sibr/sibr_core](https://gitlab.inria.fr/sibr/sibr_core))
|
||||
- [Unstructured Lumigraph Rendering (ULR)](https://sibr.gitlabpages.inria.fr/docs/0.9.6/ulrPage.html) ([https://gitlab.inria.fr/sibr/sibr_core](https://gitlab.inria.fr/sibr/sibr_core))
|
||||
- [Our algorithms](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_ours.html)
|
||||
- [Exploiting Repetitions for IBR of Facades](https://sibr.gitlabpages.inria.fr/docs/0.9.6/facade_repetitionsPage.html) ([https://gitlab.inria.fr/sibr/projects/facades-repetitions/facade_repetitions](https://gitlab.inria.fr/sibr/projects/facades-repetitions/facade_repetitions)) (Exploiting Repetitions for IBR of Facades (paper reference :[http://www-sop.inria.fr/reves/Basilic/2018/RBDD18/](http://www-sop.inria.fr/reves/Basilic/2018/RBDD18/)))
|
||||
- [Deep Blending for Free-Viewpoint Image-Based Rendering – Scalable Inside-Out Image-Based Rendering](https://sibr.gitlabpages.inria.fr/docs/0.9.6/inside_out_deep_blendingPage.html) ([https://gitlab.inria.fr/sibr/projects/inside_out_deep_blending](https://gitlab.inria.fr/sibr/projects/inside_out_deep_blending)) (Deep Blending for Free-Viewpoint Image-Based Rendering, paper references: [http://www-sop.inria.fr/reves/Basilic/2018/HPPFDB18/](http://www-sop.inria.fr/reves/Basilic/2018/HPPFDB18/) , [http://visual.cs.ucl.ac.uk/pubs/deepblending/](http://visual.cs.ucl.ac.uk/pubs/deepblending/) ; Scalable Inside-Out Image-Based Rendering, paper references: [http://www-sop.inria.fr/reves/Basilic/2016/HRDB16](http://www-sop.inria.fr/reves/Basilic/2016/HRDB16) , [http://visual.cs.ucl.ac.uk/pubs/insideout/](http://visual.cs.ucl.ac.uk/pubs/insideout/) )
|
||||
- [Multi-view relighting using a geometry-aware network](https://sibr.gitlabpages.inria.fr/docs/0.9.6/outdoorRelightingPage.html) ([https://gitlab.inria.fr/sibr/projects/outdoor_relighting](https://gitlab.inria.fr/sibr/projects/outdoor_relighting)) (Multi-view Relighting Using a Geometry-Aware Network; paper reference ([https://www-sop.inria.fr/reves/Basilic/2019/PGZED19/](https://www-sop.inria.fr/reves/Basilic/2019/PGZED19/)) )
|
||||
- [Image-Based Rendering of Cars using Semantic Labels and Approximate Reflection Flow](https://sibr.gitlabpages.inria.fr/docs/0.9.6/semantic_reflectionsPage.html) ([https://gitlab.inria.fr/sibr/projects/semantic-reflections/semantic_reflections](https://gitlab.inria.fr/sibr/projects/semantic-reflections/semantic_reflections)) (Image-Based Rendering of Cars using Semantic Labels and Approximate Reflection Flow (paper reference : [http://www-sop.inria.fr/reves/Basilic/2020/RPHD20/](http://www-sop.inria.fr/reves/Basilic/2020/RPHD20/)))
|
||||
- [Depth Synthesis and Local Warps for plausible image-based navigation - Bayesian approach for selective image-based rendering using superpixels](https://sibr.gitlabpages.inria.fr/docs/0.9.6/spixelwarpPage.html) ([https://gitlab.inria.fr/sprakash/spixelwarp](https://gitlab.inria.fr/sprakash/spixelwarp)) (Depth Synthesis and Local Warps for plausible image-based navigation, paper reference: [http://www-sop.inria.fr/reves/Basilic/2013/CDSD13/](http://www-sop.inria.fr/reves/Basilic/2013/CDSD13/) ; Bayesian approach for selective image-based rendering using superpixels, paper reference: [http://www-sop.inria.fr/reves/Basilic/2015/ODD15/](http://www-sop.inria.fr/reves/Basilic/2015/ODD15/) ))
|
||||
- [Glossy Probe Reprojection for Interactive Global Illumination](https://sibr.gitlabpages.inria.fr/docs/0.9.6/synthetic_ibrPage.html) ([https://gitlab.inria.fr/sibr/projects/glossy-probes/synthetic_ibr](https://gitlab.inria.fr/sibr/projects/glossy-probes/synthetic_ibr)) (Glossy Probe Reprojection for Interactive Global Illumination (paper reference : [http://www-sop.inria.fr/reves/Basilic/2020/RLPWSD20/](http://www-sop.inria.fr/reves/Basilic/2020/RLPWSD20/)))
|
||||
- [Other algorithms](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_others.html)
|
||||
- [Soft3D](https://sibr.gitlabpages.inria.fr/docs/0.9.6/soft3dPage.html) ([https://gitlab.inria.fr/sibr/projects/soft3d](https://gitlab.inria.fr/sibr/projects/soft3d)) (Soft 3D Reconstruction for View Synthesis (paper reference : [https://ericpenner.github.io/soft3d/](https://ericpenner.github.io/soft3d/)))
|
||||
- [Integrated toolboxes](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_toolbox.html)
|
||||
- [Core framework of FRIBR](https://sibr.gitlabpages.inria.fr/docs/0.9.6/fribrFrameworkPage.html) ([https://gitlab.inria.fr/sibr/fribr_framework](https://gitlab.inria.fr/sibr/fribr_framework)) (Core framework of FRIBR)
|
||||
- [SIBR/OptiX integration example](https://sibr.gitlabpages.inria.fr/docs/0.9.6/optixPage.html) ([https://gitlab.inria.fr/sibr/projects/optix](https://gitlab.inria.fr/sibr/projects/optix)) (SIBR/OptiX integration example)
|
||||
- [Simple SIBR Project](https://sibr.gitlabpages.inria.fr/docs/0.9.6/simplePage.html) ([https://gitlab.inria.fr/sibr/projects/simple](https://gitlab.inria.fr/sibr/projects/simple)) (A simple sample SIBR project for you to base your projects on)
|
||||
- [Tensorflow/OpenGL Interop for SIBR](https://sibr.gitlabpages.inria.fr/docs/0.9.6/tfgl_interopPage.html) ([https://gitlab.inria.fr/sibr/tfgl_interop](https://gitlab.inria.fr/sibr/tfgl_interop)) (Tensorflow GL interoperability dependencies and cuda code)
|
||||
|
||||
- [示例算法和工具箱](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_samples.html)
|
||||
- [数据集预处理工具](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_dataset_tools.html)([https://gitlab.inria.fr/sibr/sibr_core](https://gitlab.inria.fr/sibr/sibr_core))
|
||||
- [非结构化 Lumigraph 渲染 (ULR)](https://sibr.gitlabpages.inria.fr/docs/0.9.6/ulrPage.html) ( [https://gitlab.inria.fr/sibr/sibr_core](https://gitlab.inria.fr/sibr/sibr_core) )
|
||||
- [我们的算法](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_ours.html)
|
||||
- [Exploiting Repetitions for IBR of Facades](https://sibr.gitlabpages.inria.fr/docs/0.9.6/facade_repetitionsPage.html) ( [https://gitlab.inria.fr/sibr/projects/facades-repetitions/facade_repetitions](https://gitlab.inria.fr/sibr/projects/facades-repetitions/facade_repetitions) ) (Exploiting Repetitions for IBR of Facades (论文参考: http: [//www-sop.inria.fr/里夫/巴西利克/2018/RBDD18/](http://www-sop.inria.fr/reves/Basilic/2018/RBDD18/)))
|
||||
- [用于基于自由视点图像的渲染的深度混合 – 可扩展的由内而外基于图像的渲染](https://sibr.gitlabpages.inria.fr/docs/0.9.6/inside_out_deep_blendingPage.html)( [https://gitlab.inria.fr/sibr/projects/inside_out_deep_blending](https://gitlab.inria.fr/sibr/projects/inside_out_deep_blending) ) (用于基于自由视点图像的渲染的深度混合,论文参考:[http://www-sop.inria.fr/reves/Basilic/2018/HPPFDB18/,http](http://www-sop.inria.fr/reves/Basilic/2018/HPPFDB18/) : [//visual.cs.ucl.ac.uk/pubs/deepblending/](http://visual.cs.ucl.ac.uk/pubs/deepblending/);可扩展的由内而外基于图像的渲染,论文参考:[http://www-sop.inria.fr/reves/Basilic/2016/HRDB16,http](http://www-sop.inria.fr/reves/Basilic/2016/HRDB16) : [//visual.cs.ucl.ac.uk/pubs/insideout/](http://visual.cs.ucl.ac.uk/pubs/insideout/))
|
||||
- [使用几何感知网络的多视图重新照明](https://sibr.gitlabpages.inria.fr/docs/0.9.6/outdoorRelightingPage.html)( [https://gitlab.inria.fr/sibr/projects/outdoor_relighting](https://gitlab.inria.fr/sibr/projects/outdoor_relighting) )(使用几何感知网络的多视图重新照明;论文参考 ( [https://www-sop. inria.fr/reves/Basilic/2019/PGZED19/](https://www-sop.inria.fr/reves/Basilic/2019/PGZED19/) ) )
|
||||
- [使用语义标签和近似反射流的基于图像的汽车渲染](https://sibr.gitlabpages.inria.fr/docs/0.9.6/semantic_reflectionsPage.html)([https://gitlab.inria.fr/sibr/projects/semantic-reflections/semantic_reflections](https://gitlab.inria.fr/sibr/projects/semantic-reflections/semantic_reflections))(使用语义标签和近似反射流的基于图像的汽车渲染(论文参考:[http://www-sop.inria.fr/reves/Basilic/2020/RPHD20/](http://www-sop.inria.fr/reves/Basilic/2020/RPHD20/)))
|
||||
- [用于合理的基于图像的导航的深度合成和局部扭曲 - 使用超像素进行选择性基于图像的渲染的贝叶斯方法](https://sibr.gitlabpages.inria.fr/docs/0.9.6/spixelwarpPage.html)([https://gitlab.inria.fr/sprakash/spixelwarp](https://gitlab.inria.fr/sprakash/spixelwarp))(用于合理的基于图像的导航的深度合成和局部扭曲,论文参考:[http://www-sop.inria.fr/reves/Basilic/2013/CDSD13/](http://www-sop.inria.fr/reves/Basilic/2013/CDSD13/);使用超像素进行选择性基于图像渲染的贝叶斯方法,论文参考: http: [//www-sop.inria.fr/里夫/巴西利克/2015/ODD15/](http://www-sop.inria.fr/reves/Basilic/2015/ODD15/)))
|
||||
- [用于交互式全局照明的光泽探针重投影](https://sibr.gitlabpages.inria.fr/docs/0.9.6/synthetic_ibrPage.html)([https://gitlab.inria.fr/sibr/projects/glossy-probes/synthetic_ibr](https://gitlab.inria.fr/sibr/projects/glossy-probes/synthetic_ibr))(用于交互式全局照明的光泽探针重投影(论文参考:[http://www-sop.inria。 fr/reves/Basilic/2020/RLPWSD20/](http://www-sop.inria.fr/reves/Basilic/2020/RLPWSD20/) ))
|
||||
- [其他算法](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_others.html)
|
||||
- [Soft3D](https://sibr.gitlabpages.inria.fr/docs/0.9.6/soft3dPage.html)([https://gitlab.inria.fr/sibr/projects/soft3d](https://gitlab.inria.fr/sibr/projects/soft3d))(用于视图合成的软3D重建(论文参考: https: [//ericpenner.github.io/soft3d/](https://ericpenner.github.io/soft3d/)))
|
||||
- [集成工具箱](https://sibr.gitlabpages.inria.fr/docs/0.9.6/sibr_projects_toolbox.html)
|
||||
- [FRIBR核心框架](https://sibr.gitlabpages.inria.fr/docs/0.9.6/fribrFrameworkPage.html)([https://gitlab.inria.fr/sibr/fribr_framework)(FRIBR](https://gitlab.inria.fr/sibr/fribr_framework)核心框架)
|
||||
- [SIBR/OptiX 集成示例](https://sibr.gitlabpages.inria.fr/docs/0.9.6/optixPage.html)( [https://gitlab.inria.fr/sibr/projects/optix](https://gitlab.inria.fr/sibr/projects/optix) )(SIBR/OptiX 集成示例)
|
||||
- [简单 SIBR 项目](https://sibr.gitlabpages.inria.fr/docs/0.9.6/simplePage.html)( [https://gitlab.inria.fr/sibr/projects/simple](https://gitlab.inria.fr/sibr/projects/simple) )(一个简单的示例 SIBR 项目,供您作为项目的基础)
|
||||
- [SIBR 的 Tensorflow/OpenGL 互操作](https://sibr.gitlabpages.inria.fr/docs/0.9.6/tfgl_interopPage.html)([https://gitlab.inria.fr/sibr/tfgl_interop)(Tensorflow](https://gitlab.inria.fr/sibr/tfgl_interop) GL 互操作性依赖项和 cuda 代码)
|
||||
|
||||
项目结构:
|
||||
- `renderer/`: contains your library code and configuration
|
||||
- `preprocess/`: contains your preprocesses listed by directory, and the configuration CMake file to list them
|
||||
- `apps/`: contains your apps listed by directory, and the configuration CMake file to list them
|
||||
- `documentation/`: contains additional doxygen documentation
|
||||
|
||||
# SIBR数据集创建方式
|
||||
**SIBR**本身定义了一种数据格式
|
||||
|
||||
可以使用**RealityCapture**或者**Colmap**创建原生的SIBR数据集,也可以根据文档使用SFM或者MVS系统创建兼容数据集合。
|
||||
- [如何从 Reality Capture 创建数据集](https://sibr.gitlabpages.inria.fr/docs/0.9.6/HowToCapreal.html)
|
||||
- [如何从 Colmap 创建数据集](https://sibr.gitlabpages.inria.fr/docs/0.9.6/HowToColmap.html)
|
||||
|
||||
官方提供的案例数据集:https://repo-sam.inria.fr/fungraph/sibr-datasets/museum_front27_ulr.zip
|
||||
|
||||
# 运行案例方式
|
||||
下载编译好的版本
|
||||
>SIBR_ulrv2_app_rwdi.exe --path C:/Downloads/museum_front27_ulr/museum_front27/sibr_cm sibr -museum-front
|
@@ -0,0 +1,103 @@
|
||||
## 前言
|
||||
本文仅为使用Ornatrix制作角色头发的流程笔记,只为记录制作流程。但因为本人非专职美术,没有分析过面片头发,也没有制作头发经验,所以本文仅供参考。
|
||||
## Ornatrix的优点
|
||||
插件目前支持max、maya、c4d。拥有大部分xGen的功能(我无法保证所有功能都有),除了制作头发之外,它还可以制作羽毛与编织物。以下是官方介绍视频:
|
||||
https://www.bilibili.com/video/av80061009
|
||||
|
||||
个人觉得优点有下:
|
||||
### 节点式的流程
|
||||

|
||||
|
||||
节点式流程就代表着流程是非线性的,这意味着你可以任意拖动节点位移或者可以随时对节点树中的节点进行修改,并预览效果。同时你可以把做成完成的头发保存为预设,下次再遇到相似的发型就可以直接使用了,以此加快制作进程。
|
||||
|
||||
### 各种使用节点
|
||||
梳理节点:
|
||||
你可以通过画几个箭头就完成对发型的大致梳理。
|
||||
|
||||

|
||||
|
||||
旋转节点:这个节点对于游戏面片头发的制作相当不错。另外Ornatrix可以设置生成面片的段数、UV等其他属性,这也是我放弃xGen转而使用Ornatrix的原因。
|
||||
|
||||

|
||||
使用旋转节点前
|
||||
|
||||

|
||||
使用旋转节点后
|
||||
|
||||
另外还有Frizz、Curl等节点,出个大形的速度非常快。
|
||||
## EPIC模型分析
|
||||
这里本人使用虚幻争霸中的一个角色作为参考,因为本人能力有限,所以使用官方提供的头发材质进行渲染。这样只需要制作出符合高度贴图、id贴图即可。这里我为了方便分析贴图所以给不同的头发进行填色,以此来判断头发组成。(因为本人不知道如何在Maya中解决透明排序的问题,所以结果就凑合地看吧)
|
||||

|
||||

|
||||
|
||||
- 红色为最底层的头发,形状和头皮一样为了防止穿帮。
|
||||
- 蓝色为主要头发,头发相对于红色部分会稍微稀疏一些。
|
||||
- 剩下的黄色、绿色、亮红色为点缀部分。
|
||||
|
||||

|
||||
## 制作流程
|
||||
### 设计发型与分块
|
||||
尽管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
|
||||
```
|
@@ -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)
|
||||

|
||||
1. 导入带有Groom信息的abc缓存文件。
|
||||

|
||||
4. 设置Groom Asset属性。(主要是设置头发宽度)
|
||||
5. 设置Groom材质。(ShadingModel需要设置hair,Groom对象的材质有两种设置方式:1、从ContentBrowser中设置GroomAsset的属性 2、在场景中设置Groom对象的属性)
|
||||

|
||||
6. 将GroomAsset拖入场景中并且attach到场景中的骨骼模型上,并调整位置。(直接放入蓝图的骨骼模型引擎会崩溃)
|
||||

|
||||
7. 设置场景中的Groom物体属性,给它挂载Niagara Particle System,并且设置GroomAssetSystem。
|
||||

|
||||

|
||||
8. 设置Niagara物理参数。
|
||||
|
||||
**最终结果:**
|
||||

|
||||
|
||||
|
||||
**关于头发对齐问题与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,这个就不要更改了。
|
||||
|
||||

|
||||
从这个图中,可以看出一些靠近边缘的区域需要增加头发密度,不然就有可能出现图中这种25岁程序员的头发。
|
||||
|
||||
### Ornatrix导出abc
|
||||

|
||||
导出abc的时候需要在Ornatrix的大纲视图选中最上面的节点,实际上就是选中Ornatrix的形状节点,你可以Maya的关系编辑器中通过查看上下游节点进行查看。
|
||||
|
||||
之后点击“导出当前选择”,选择OrnatrixAlembic文件类型。勾选
|
||||
文件类型特定选项-Export Components-Unreal Engine Export选项,之后就可以导出了。
|
||||
|
||||
### Groom ID
|
||||
Ornatrix中可以通过StandGroup对头发进行分组编辑。Ue4会通过这些StandGroup作为Groom ID数据。你可以对Groom ID的头发使用不同的参数。这样可以实现一个GroomAsset(abc文件)存储头发、睫毛、眉毛多种类型毛发(不同的宽度与材质)。
|
||||
|
||||
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。
|
154
03-UnrealEngine/Rendering/Debug/RDG Debug笔记.md
Normal file
154
03-UnrealEngine/Rendering/Debug/RDG Debug笔记.md
Normal file
@@ -0,0 +1,154 @@
|
||||
---
|
||||
title: RDG Debug笔记
|
||||
date: 2025-06-14 15:44:47
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- [UE5 Render Dependency Graph-实用指南](https://zhuanlan.zhihu.com/p/637889120)
|
||||
|
||||
# Command
|
||||
- r.rdg
|
||||
- AsyncCompute
|
||||
- BreakPoint
|
||||
|
||||
## Reference
|
||||
```c++
|
||||
FAutoConsoleVariableRef CVarRDGDebug(
|
||||
TEXT("r.RDG.Debug"),
|
||||
GRDGDebug,
|
||||
TEXT("Allow to output warnings for inefficiencies found during wiring and execution of the passes.\n")
|
||||
TEXT(" 0: disabled;\n")
|
||||
TEXT(" 1: emit warning once (default);\n")
|
||||
TEXT(" 2: emit warning everytime issue is detected."),
|
||||
ECVF_RenderThreadSafe);
|
||||
|
||||
FAutoConsoleVariableRef CVarRDGClobberResources(
|
||||
TEXT("r.RDG.ClobberResources"),
|
||||
GRDGClobberResources,
|
||||
TEXT("Clears all render targets and texture / buffer UAVs with the requested clear color at allocation time. Useful for debugging.\n")
|
||||
TEXT(" 0:off (default);\n")
|
||||
TEXT(" 1: 1000 on RGBA channels;\n")
|
||||
TEXT(" 2: NaN on RGBA channels;\n")
|
||||
TEXT(" 3: +INFINITY on RGBA channels.\n"),
|
||||
ECVF_Cheat | ECVF_RenderThreadSafe);
|
||||
|
||||
FAutoConsoleVariableRef CVarRDGOverlapUAVs(
|
||||
TEXT("r.RDG.OverlapUAVs"), GRDGOverlapUAVs,
|
||||
TEXT("RDG will overlap UAV work when requested; if disabled, UAV barriers are always inserted."),
|
||||
ECVF_RenderThreadSafe);
|
||||
|
||||
FAutoConsoleVariableRef CVarRDGTransitionLog(
|
||||
TEXT("r.RDG.TransitionLog"), GRDGTransitionLog,
|
||||
TEXT("Logs resource transitions to the console.\n")
|
||||
TEXT(" 0: disabled(default);\n")
|
||||
TEXT(">0: enabled for N frames;\n")
|
||||
TEXT("<0: enabled;\n"),
|
||||
ECVF_RenderThreadSafe);
|
||||
|
||||
TAutoConsoleVariable<FString> CVarRDGDebugPassFilter(
|
||||
TEXT("r.RDG.Debug.PassFilter"), TEXT(""),
|
||||
TEXT("Filters certain debug events to specific passes. Set to 'None' to reset.\n"),
|
||||
ECVF_Default);
|
||||
|
||||
TAutoConsoleVariable<FString> CVarRDGDebugResourceFilter(
|
||||
TEXT("r.RDG.Debug.ResourceFilter"), TEXT(""),
|
||||
TEXT("Filters certain debug events to a specific resource. Set to 'None' to reset.\n"),
|
||||
ECVF_Default);
|
||||
|
||||
FAutoConsoleVariableRef CVarRDGParallelSetup(
|
||||
TEXT("r.RDG.ParallelSetup"), GRDGParallelSetup,
|
||||
TEXT("RDG will setup passes in parallel when prompted by calls to FRDGBuilder::FlushSetupQueue.")
|
||||
TEXT(" 0: pass setup is done synchronously in AddPass;")
|
||||
TEXT(" 1: pass setup is done asynchronously (default);"),
|
||||
ECVF_RenderThreadSafe);
|
||||
```
|
||||
|
||||
# Visualize Texture Integration
|
||||
**vis**
|
||||
使用vis指令,可以打印vis指令帮助信息,以及当前帧使用到的所有的资源信息
|
||||
|
||||
![[RDG_Vis.jpg|1200]]
|
||||
|
||||
**vis [RT Name]**
|
||||
使用vis [RT Name]可以实时预览当前RT的内容,在调试渲染特性的时候可以很方便的Debug
|
||||
|
||||
![[RDG_Vis_RTName.jpg|800]]
|
||||
|
||||
**vis [RT Name] bump**
|
||||
打印当前RT到Saved/Screenshots
|
||||
|
||||
**vis off**
|
||||
关闭当前帧的debug模式
|
||||
|
||||
# r.RDG.ImmediateMode
|
||||
将RDG设置成理解执行模式,方便进行资源绑定Debug以及断点。
|
||||
- RDG立即执行模式:
|
||||
- 立刻执行Pass在其被添加的时候
|
||||
- 产生和延后执行时一样的功能
|
||||
- 如果打断点,会非常容易检查其创建的代码
|
||||
- 由于这个原因,所以不能在AddPass后再去更改Pass参数
|
||||
- 可以利用r.RDG.ImmediateMode在运行时切换
|
||||
- 会消耗更多的内存,可以考虑使用r.Test.SecondaryUpscaleOverride运行在低分辨率下。(r.Test.SecondaryUpscaleOverride 设置的参数越大越模糊)
|
||||
|
||||
![[RDG_Immediate.png|800]]
|
||||
|
||||
- **r.RDG.ImmediateMode 1** / -rdgimmediate : 去除RDG的影响
|
||||
- **r.RHICmdBypass 1** :去除RHI和并行渲染的影响
|
||||
- r.RDG.ParallelExecute=0
|
||||
- r.RDG.ParallelSetup=0
|
||||
|
||||
## RDG Async Compute
|
||||
1.直接设置ComputePassFlags为ERDGPassFlags::AsyncCompute,剩下的工作交给RDG自动判断和设置
|
||||
```c++
|
||||
ERDGPassFlags ComputePassFlags = (GSupportsEfficientAsyncCompute && CVarTSRAsyncCompute.GetValueOnRenderThread() != 0) ? ERDGPassFlags::AsyncCompute : ERDGPassFlags::Compute;
|
||||
|
||||
TShaderMapRef<FTSRClearPrevTexturesCS> ComputeShader(View.ShaderMap);
|
||||
FComputeShaderUtils::AddPass(
|
||||
GraphBuilder,
|
||||
RDG_EVENT_NAME("TSR ClearPrevTextures %dx%d", InputRect.Width(), InputRect.Height()),
|
||||
ComputePassFlags,
|
||||
ComputeShader,
|
||||
PassParameters,
|
||||
FComputeShaderUtils::GetGroupCount(InputRect.Size(), 8 * 2));
|
||||
```
|
||||
|
||||
2.在Render层调整Pass顺序,为RDG提供更多Async Compute Overlap的可能性
|
||||
|
||||
# ValidateShaderParameters
|
||||
ValidateShaderParameters()可以检测RDG绑定的资源是否都存在。
|
||||
|
||||
# -rdgdebug
|
||||
`r.RDG.Debug = 1`来开启。
|
||||
|
||||
# RDG Insight
|
||||
开启RDG Insight Plugin之后即可在UnrealInsight看到额外添加RDG Channel。
|
||||
|
||||
命令行启动游戏:
|
||||
`Game.exe -trace=rdg,defaults`
|
||||
|
||||
# DumpGPU
|
||||
使用DumpGPU指令可以将当前帧的所有Pass和资源信息打印,可以很方便的进行查看。
|
||||
在Console中输入DumpGPU指令,会弹出生成好的html文件:
|
||||
|
||||

|
||||
|
||||
|
||||
执行OpenGPUDumpViewer.bat打开网页,默认只支持chrome浏览器,可以通过修改脚本调用别的浏览器:
|
||||

|
||||
|
||||
|
||||
可以看到各个Pass的信息:
|
||||
|
||||

|
||||
|
||||
|
||||
显示当前帧所有CVar的值:
|
||||
|
||||

|
||||
|
||||
|
||||
比较像素的值:
|
||||
|
||||

|
208
03-UnrealEngine/Rendering/Debug/RenderDoc使用技巧.md
Normal file
208
03-UnrealEngine/Rendering/Debug/RenderDoc使用技巧.md
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
title: RenderDoc使用技巧
|
||||
date: 2022-09-30 11:17:29
|
||||
excerpt:
|
||||
tags: RenderDoc
|
||||
rating: ⭐⭐
|
||||
---
|
||||
# 前言
|
||||
参考:https://zhuanlan.zhihu.com/p/568990608
|
||||
|
||||
>UE5.3需要关闭异步计算,让Renderdoc抓帧正常。输入命令即可:***r.rdg.asynccompute 0***
|
||||
|
||||
**UE5.2之后自动开启Renderdoc**:
|
||||
在DefaultEngine.ini中添加一下配置:
|
||||
```ini
|
||||
[/Script/RenderDocPlugin.RenderDocPluginSettings]
|
||||
renderdoc.AutoAttach=True
|
||||
```
|
||||
|
||||
PS. **开启DX12后无法通过Renderdoc进行单步调试**,此时可以考虑使用Pix,或者切换成DX11。因为DX12的DXIL 着色器不支持着色器调试 https://renderdoc.org/docs/behind_scenes/d3d12_support.html
|
||||
|
||||
具体参考
|
||||
- UE5RayTracing篇-2-——Pix与NSight-Graphics补充 https://zhuanlan.zhihu.com/p/586030650
|
||||
- UE5使用PIX单步调试DX12环境下的Shader https://zhuanlan.zhihu.com/p/654935138
|
||||
|
||||
pix的单步调试需要开启**开发者模式**,具体是在window的设置 - 开发者选项 - **开发人员设置** 。
|
||||
|
||||
## 抓帧技巧
|
||||
- [RenderDoc抓帧steam平台](https://zhuanlan.zhihu.com/p/721764908)
|
||||
- 关闭并行绘制相关命令:
|
||||
- **r.RHICmdBypass=1**
|
||||
- **r.MeshDrawCommands.ParallelPassSetup=0**
|
||||
- **r.MeshDrawCommands.UseCachedCommands=1**
|
||||
- **r.RDG.ImmediateMode=1**
|
||||
|
||||
# UE相关设置
|
||||
开启Renderdoc的PixelDebug功能:
|
||||

|
||||
|
||||
UE5.3 开启调试的需要开启:
|
||||
- r.Shaders.Symbols=1
|
||||
- r.Shaders.Optimize=0
|
||||
- r.DisableEngineAndAppRegistration=1
|
||||
|
||||
## 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 | 如果符号已生成,则将其写入磁盘。 |
|
||||
|
||||
## 其他设置
|
||||
```ini
|
||||
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。
|
||||
|
||||
|
||||
# Renderdoc本身相关
|
||||
## 使用技巧
|
||||
### 修改ms显示耗时
|
||||
|
||||

|
||||
|
||||
### 过滤高耗时DrawCall
|
||||
|
||||
使用$action(duration > 0.8ms) 进行过滤
|
||||
|
||||

|
||||
|
||||
### **调试VS**
|
||||
|
||||
Mesh Viewer中;vs input选中或在preview窗口中鼠标右键选中顶点,在选中顶点行上右键debug thie vertices
|
||||
|
||||

|
||||
|
||||
### **调试PS**
|
||||
|
||||
Texture Viewer中右键选择像素;在Pixel Context中心就是选中的像素,选择需要调试的历史时间,点击“Debug”调试
|
||||
|
||||

|
||||
|
||||
### 修改VS
|
||||
|
||||

|
||||
|
||||
选中dc高亮绘制
|
||||
|
||||

|
||||
|
||||
Pipeline state进入VS
|
||||
|
||||

|
||||
|
||||
修改坐标
|
||||
|
||||

|
||||
|
||||
Texture View中预览位置变化
|
||||
|
||||
### 修改PS
|
||||
|
||||

|
||||
|
||||
进入pipeline state 编辑ps
|
||||
|
||||

|
||||
|
||||
修改前颜色
|
||||
|
||||

|
||||
|
||||
修改base color为红色
|
||||
|
||||

|
||||
|
||||
修改后效果预览
|
||||
|
||||
### 查看深度模板测试结果
|
||||
|
||||

|
||||
|
||||
红色测试不同多,绿色测试通过
|
||||
|
||||
### 查看纹理在那些事件引用
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
PS 资源种点击链接
|
||||
|
||||

|
||||
|
||||
Resource Inspector中右侧查看那些事件使用了此资源
|
||||
|
||||
### 纹理太暗
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### **查看DrawCall耗时**
|
||||
|
||||

|
||||
|
||||
### 查看纹理输入输出
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 重名名纹理
|
||||
|
||||

|
||||
|
||||
### 如何对比数据
|
||||
|
||||
将过滤后的数据导出为文本,使用对比工具进行对比。用于发现dc耗时问题
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
# 命令行启动Renderdoc截帧
|
||||
相关命令可以使用`renderdoccmd.exe -h`来查看:
|
||||
- -d:工作目录
|
||||
- -w:运行到最后再启动。
|
||||
- --opt-hook-children:捕获子进程。
|
||||
- 最后填写运行exe程序路径。
|
||||
```bash
|
||||
renderdoccmd.exe capture -d "C:\Game\ScarletNexus" -w --opt-hook-children C:\Game\ScarletNexus\ScarletNexus.exe
|
||||
```·
|
@@ -0,0 +1,142 @@
|
||||
---
|
||||
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
|
||||
|
||||
个人觉得相比RenderDoc,NSight的优势在于:
|
||||
- 更加注重性能统计与优化,适合图形程序使用。
|
||||
- 拥有Rtx、VR相关工具,可以让你debug Rtx、VR程序。
|
||||
- 2个截帧文件比较功能。
|
||||
|
||||
PS.2020.6.1 附加进程到UE5会直接崩溃,UE4.27虽然可以启动,但依然会有问题。2021.2~2022.3的Shader Profiler的Soucre功能会因为SM不是SM6而只能查看Shader的汇编指令。
|
||||
|
||||

|
||||
|
||||
只能等UE5完全支持SM6后才能使用该功能。(测试版本5.02,开启项目设置里的DirectX12 (SM6,Experimental)功能也不行)
|
||||
|
||||
### 连接到进程
|
||||
NSight有3种
|
||||
1. 启动进程并附加:设置ApplicationExcutable后,点击Launch即可。调试UE4的话还需要设置CommandLineArguments为项目路径,比如D:/UnrealEngine/Project/RtxRendering/RtxRendering.uproject
|
||||

|
||||
2. 手动连接已启动的进程(是由通过NSight Graphics启动的进程才能连接):选择启动的进程,之后点Attach即可。
|
||||

|
||||
3. 远程启动:在远程机器上运行Nsight Remote Monitor程序,之后再本机上输入远程机器的IP即可。
|
||||
|
||||
进入界面后点击Capture for Live Analysis即可截取帧信息(默认的截取按键为F11)。
|
||||
|
||||
### NSight工具
|
||||
NSight有5种工具,需要在Connect to process界面中的左下角选择:
|
||||
- Frame Debugger(基本用这个) 
|
||||
- Frame Profiler 
|
||||
- Generate C++ Capture
|
||||
- GPU Trace Profiler
|
||||
- System Trace
|
||||
|
||||
在选择完工具连接进程之后就会显示对应的界面。部分功能需要开启设备访问权限,没开启会显示:
|
||||

|
||||
|
||||
需要在Nvidia控制面板里进行开启,步骤如下:
|
||||
|
||||

|
||||

|
||||
|
||||
其他平台的步骤可以参考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
|
||||
- Function Summary显示Shader函数的GPU性能占用分布。
|
||||
- Hot Spots显示Shader代码的GPU性能占用分布。
|
||||
- Source 可以打开特定Shader文件,并且显示每行代码的GPU性能占用分布。(NSight2022.3版本无法显示UE5的Shader源码)
|
||||
- All Resource:可以查看渲染说用到的贴图、Buffer以及场景模型的BLAS
|
||||
- Geometry:该功能需要选中绘制命令才能显示对应的多边形。具体操作是:Scrubber中选中Event,右键选择Open Event List,再从Event窗口选择绘制事件即可(Filter中输入Draw就可以找到绘制事件)
|
||||
- PixelHistory:查看一个像素绘制过程的工具。可以通过在Resource中选择绘制的RT之后使用该工具点击想要查看的像素。  
|
||||
- Shader Timeing Heating:Rtx相关工具,可以查看Rtx Shader在屏幕上的资源占用分布图。但有bug。分析UE5的某个分发Ray事件必然会崩。
|
||||
|
||||
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工程。并且显示以下界面:
|
||||

|
||||
|
||||
该功能与RenderDoc保存截帧文件类似,但NSight是截取当前帧的渲染情况并且生成一个不断渲染当前帧的程序以便后续复现与Debug。之后依次点击Build=>Execute=>Connect即可进入Frame Debugger界面。该工具还支持嵌入式Linux、桌面Linux。
|
||||
|
||||
#### GPU Trace
|
||||
NSight的截帧功能,在连接界面可以设置截帧范围(截取的帧数),另一个相比RenderDoc的优势就是比较2个帧文件。启动进程后,点击**Generate GPU Trace Capture**就可以截取帧文件。并进入类似Frame Debugger的界面。
|
||||

|
||||

|
||||
|
||||
同时可以设置为高级模式,以截取更多的信息:
|
||||

|
||||

|
||||
|
||||
#### System Trace
|
||||
需要额外下载Nsight Systems工具才能使用。该工具主要分析CPU与GPU直接交互情况。
|
||||
|
||||
#### 其他Rt Shader相关的功能
|
||||
APIInspector的Rt Shader会显示额外所需的HitTable等数据:
|
||||

|
||||
|
||||
##### Acceleration Structure View
|
||||
首先可以看一下UE中的相关文档:https://docs.unrealengine.com/5.0/en-US/ray-tracing-performance-guide-in-unreal-engine/
|
||||
|
||||
- BLAS:Bottom Level Acceleration Structure Updates。静态模型的BLAS会在模型载入时构建。动态物体与骨骼物体会每帧构建。
|
||||
- TLAS:Top Level Acceleration Structure。TLAS会每帧构建。
|
||||
|
||||
在NSigh中可以从API Inspector或All Resource(只能显示BLAS)中打开并查看。
|
||||
|
||||

|
||||
|
||||
视角控制:
|
||||
- WASD — 向前、向后、向左或向右移动相机
|
||||
- 箭头键- 向前、向后、向左或向右移动相机
|
||||
- E/Q - 向上/向下移动相机
|
||||
- Shift/Ctrl - 更快/更慢地移动相机
|
||||
- 鼠标滚轮——放大/缩小
|
||||
- 鼠标左键 + 拖动- 向前或向后移动相机并向左或向右旋转
|
||||
- 鼠标右键 + 拖动- 旋转相机
|
||||
- 鼠标中间 + 拖动- 跟踪相机(向上、向下、向左、向右移动)
|
||||
- 鼠标左键 + 鼠标右键 + Drag — 跟踪相机(上、下、左、右)
|
||||
- ALT + 鼠标左键 + 拖动- 围绕选定几何体旋转相机
|
||||
- ALT + 鼠标右键 + 拖动- 放大/缩小
|
||||
- 双击或 F - 将相机聚焦在选定的几何体上
|
||||
|
||||
# UE查看Shader代码
|
||||
想要看源代码需要开启:
|
||||
r.Shaders.Optimize=0
|
||||
; When this is enabled, shaders will have extra debugging info. This could change patch sizes, uniqueness, etc and will recompile the shaders
|
||||
r.Shaders.Symbols=1
|
||||
; When this is enabled, ShaderName field of FRHIShader will be populated (Development and Debug builds only)
|
||||
r.Shaders.ExtraData=1
|
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: UE5RayTracing篇-2-——Pix与NSight-Graphics补充
|
||||
date: 2022-11-11 17:53:44
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
## 前言
|
||||
最近继续学习Rtx,所以就总结一下相关debug工具的使用方法。当然如果你是A卡用户大概就需要[【新鲜资讯】Radeon™光线追踪分析器(RRA)1.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(截图键)键来截帧。直接点击摄像机图标是没办法截到场景信息的,所以只能用鼠标点击场景窗口(让窗口获得焦点)之后再按截图键才能截到。
|
||||

|
||||
|
||||
查看渲染过程只需要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`下方会显示时间轴。
|
||||

|
||||
|
||||
Pipeline:显示当前Event的Shader、管线状态以及其他相关数据。
|
||||

|
||||

|
||||
|
||||
想要DebugShader,可以在Pipeline找到对应Shader,并在Shader文件上右键,点击`Open In Debugger`后进行调试。
|
||||

|
||||
|
||||
当然也可以在输入的RT上双击鼠标左键,之后点击`DebugPixel`进行debug。
|
||||

|
||||
|
||||
Debug:debug时需要点击左上方,Overview下面的运行按钮,这里我已经点了。这里我很好奇为啥没有类似Renderdoc的断点功能。
|
||||

|
||||
|
||||
Tools:大概是一个测试性能的工具,可以测试Basic Information、Depth/Stencil、Primitives and Rasterization、Bandwidth、TDR Analysis、ExecuteIndirect。
|
||||

|
||||
|
||||
## NSight For VisualStudio
|
||||
之后测试了一下NSight的VS插件,虽然本质上就是一个帮你填写启动参数的工具,但的确方便。
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
测试完之后就能查看光追的一些参数。
|
||||

|
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2025-07-28 10:06:02
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- DXBC/DXIL 反汇编:HLSL Decompiler:https://zhuanlan.zhihu.com/p/1927111489627599176
|
||||
|
||||
# 正文
|
||||
## 项目背景
|
||||
从 UE5 和 DirectX12 的时代开始,[Shader Model 6.0](https://zhida.zhihu.com/search?content_id=260234833&content_type=Article&match_order=1&q=Shader+Model+6.0&zhida_source=entity)+ 和它的新编译格式 DXIL,已经接替老旧的 [DXBC](https://zhida.zhihu.com/search?content_id=260234833&content_type=Article&match_order=1&q=DXBC&zhida_source=entity) 成为新时代的标配。这意味着,当我们截帧分析基于 UE5 + DX12 的游戏时,看到的 Shader 代码不再是熟悉的 DXBC,而是 DXIL 了。
|
||||
|
||||
对于热衷于深挖渲染管线一探究竟的逆向爱好者来说,这带来了一个痛点:我们急需一个能将 DXIL 反编译回可读性强的 HLSL 源码的工具。
|
||||
## HLSL 反编译的历史
|
||||
在 DXBC 时代(Shader Model 5.1 及之前),开源项目 [3Dmigoto](https://link.zhihu.com/?target=https%3A//github.com/bo3b/3Dmigoto) 旗下的 [HLSLDecompiler](https://link.zhihu.com/?target=https%3A//github.com/bo3b/3Dmigoto/tree/master/HLSLDecompiler) 堪称神器。
|
||||
它甚至被专门 Fork 出来([zxxyye/HLSLDecompiler](https://link.zhihu.com/?target=https%3A//github.com/zxxyye/HLSLDecompiler)),集成到 [RenderDoc](https://zhida.zhihu.com/search?content_id=260234833&content_type=Article&match_order=1&q=RenderDoc&zhida_source=entity) 中作为插件使用,并收获了 300+ Star 和 100+ Fork,足见其受欢迎程度和逆向需求的旺盛。
|
||||
|
||||
## 现在与将来
|
||||
然而,时代变了。[3Dmigoto](https://link.zhihu.com/?target=https%3A//github.com/bo3b/3Dmigoto) 项目本身已经陷入沉寂,其下的 [HLSLDecompiler](https://link.zhihu.com/?target=https%3A//github.com/bo3b/3Dmigoto/tree/master/HLSLDecompiler) 子模块更是多年未曾更新。
|
||||
根据作者在 [Issue #358](https://link.zhihu.com/?target=https%3A//github.com/bo3b/3Dmigoto/issues/358) 中的说法,这个反编译器对 Shader Model 4.0 为止的 DXBC 代码具有良好的支持性,然而对于 Shader Model 5.0+ 的着色器代码已是捉襟见肘,并且大概率不会再有任何更新。
|
||||
实际体验也印证了这点。很多 Shader Model 5.0+ DXBC 的基础指令(甚至包括 `numthreads`)它都翻译不出来,已经完全无法满足现代游戏分析的需求。
|
||||
一言以蔽之,老工具已无法胜任 DXIL 和部分 DXBC 的反编译工作。为了解决这个痛点,我们需要打造一个全新的逆向分析工具:[HLSL-Decompiler](https://link.zhihu.com/?target=https%3A//github.com/YYadorigi/HLSL-Decompiler)。
|
||||
它的目标很明确:高效、准确地将 DXBC / DXIL / [SPIR-V](https://zhida.zhihu.com/search?content_id=260234833&content_type=Article&match_order=1&q=SPIR-V&zhida_source=entity) 反编译回可读的 HLSL 源码,并且能无缝集成到 RenderDoc 中,真正做到「开箱即用」,接续前人未完成的使命。
|
||||
|
||||
## 项目简述
|
||||
### 开源库
|
||||
本项目基于如下开源库。它们权威,高质量,并且正活跃地持续维护。
|
||||
- [DirectXShaderCompiler (DXC)](https://link.zhihu.com/?target=https%3A//github.com/microsoft/DirectXShaderCompiler/tree/main):微软官方提供的现代 HLSL 编译器。其中包括关键组件 [dxbc2dxil](https://link.zhihu.com/?target=https%3A//github.com/microsoft/DirectXShaderCompiler/tree/main/projects/dxilconv/tools/dxbc2dxil),将老式 DXBC 转换为 [LLVM IR](https://zhida.zhihu.com/search?content_id=260234833&content_type=Article&match_order=1&q=LLVM+IR&zhida_source=entity) 形式的 DXIL。
|
||||
- [dxil-spirv](https://link.zhihu.com/?target=https%3A//github.com/HansKristian-Work/dxil-spirv/tree/master):将 DXIL 或其 LLVM IR 形式高效地转换为 SPIR-V。作者同时也是 [SPIRV-Cross](https://link.zhihu.com/?target=https%3A//github.com/KhronosGroup/SPIRV-Cross/tree/main) 的重要贡献者。
|
||||
- [SPIRV-Cross](https://link.zhihu.com/?target=https%3A//github.com/KhronosGroup/SPIRV-Cross/tree/main):KhronosGroup 官方维护的 SPIR-V 反汇编工具,支持从 SPIR-V 到 HLSL 的转换。
|
||||
|
||||
### 工具链
|
||||
该项目的核心就是巧妙地串联起上述库,形成一个高效的反编译流水线。原理如下图所示。
|
||||
|
||||

|
||||
|
||||
### 为什么你需要它?
|
||||
- **分析 UE5 / DX12 游戏渲染管线**:轻松查看截帧得到的 DXIL Shader 源码。
|
||||
- **逆向老旧 DXBC (SM5.1)**:老工具搞不定的,它能搞定。
|
||||
- **研究 Vulkan Shader (SPIR-V)**:方便地将 SPIR-V 转回 HLSL 进行分析或移植。
|
||||
- **RenderDoc 插件集成**:安装即用,分析体验丝滑流畅。
|
||||
- **开源 & 活跃**:基于活跃项目构建,持续维护更新有保障。
|
||||
|
||||
### 项目地址
|
||||
项目已开源在 GitHub:[https://github.com/YYadorigi/HL](https://link.zhihu.com/?target=https%3A//github.com/YYadorigi/HLSL-Decompiler)
|
77
03-UnrealEngine/Rendering/Film/CAD可视化与MovieRenderQueue笔记.md
Normal file
77
03-UnrealEngine/Rendering/Film/CAD可视化与MovieRenderQueue笔记.md
Normal 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 Count:16
|
||||
- Render Warm Up Count:64
|
||||
- Engine Warm Up Count:64
|
||||
|
||||
本人只需要好的反射效果,所以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已经可以了)。
|
32
03-UnrealEngine/Rendering/Film/使用MovieRenderQueue输出视频格式.md
Normal file
32
03-UnrealEngine/Rendering/Film/使用MovieRenderQueue输出视频格式.md
Normal 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
|
||||

|
||||
|
||||
这里我选择第一个网址,之后选择一个FullBuild版本即可。
|
||||

|
||||
|
||||
# 设置编码器
|
||||
在`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**。
|
81
03-UnrealEngine/Rendering/Film/提高光线追踪反射效果.md
Normal file
81
03-UnrealEngine/Rendering/Film/提高光线追踪反射效果.md
Normal 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 Count):1
|
||||
- 临时采样数量(Temporal Sample Count):64
|
||||
- 覆盖抗锯齿模式(Override Anti Aliasing Mode):已启用(Enabled)
|
||||
- 抗锯齿方法(Anti Aliasing Method):无(None)
|
||||
- 渲染预热计数(Render Warm Up Count):120
|
||||
- 引擎预热计数(Engine Warm Up Count):120
|
||||
|
||||
### Console Variable
|
||||

|
||||
- 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
|
||||

|
||||
|
||||
### 需要调节的参数
|
||||
- 灯光的采样数目
|
||||
- 后处理里搜索Sample 反射设置为16 AO 10
|
8
03-UnrealEngine/Rendering/Lighting/PBR拍摄和校准基色纹理流程.md
Normal file
8
03-UnrealEngine/Rendering/Lighting/PBR拍摄和校准基色纹理流程.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: PBR拍摄和校准基色纹理流程
|
||||
date: 2023-08-04 09:41:17
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
https://mp.weixin.qq.com/s?__biz=MzU2MTU4MTc1Ng==&mid=2247782588&idx=2&sn=f0e5fe67a2cff080807064d02b67c991&chksm=fc784b6fcb0fc279902898e0f5b604f252e435dcd5259ff62f9834a490aea27cfafc2979bce2&mpshare=1&scene=23&srcid=0801KJ7U8QP2xaidU0vEFfzh&sharer_sharetime=1691113034153&sharer_shareid=e764b1c22c315c1a3cd64fa5211f1847#rd
|
206
03-UnrealEngine/Rendering/Lighting/Ue4照明技术引导视频笔记.md
Normal file
206
03-UnrealEngine/Rendering/Lighting/Ue4照明技术引导视频笔记.md
Normal 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
|
||||
级联阴影的远阴影开关,可以解决当摄像机处于较远距离时,物体的阴影会消失的问题。
|
||||
|
||||
#### 调整级联阴影以得到更好的效果
|
||||
通过调整上述设置,可以很好地调整阴影的出血值和精度。下一节将尝试调整到最佳成都,以获得更好的精度与阴影。找到一种适合任何特定游戏的平衡将需要耗费时间、精力来进行大量测试。
|
||||
|
||||
以下是默认设置下会出现的问题。
|
||||

|
||||
|
||||
调整后的结果
|
||||

|
||||
##### 对于所有的动态光源
|
||||
###### Shadow Bias
|
||||
控制阴影在场景中的精确程度。默认值是0.5,这是权衡了精度与性能的值。
|
||||
###### Shadow Filter Sharpness
|
||||
控制阴影边缘的锐化程度。
|
||||
|
||||
##### 为什么可移动光源在较远时其遮挡关系会出错
|
||||

|
||||

|
||||
|
||||
为了解释这一点,我们首先需要了解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,后期空间开启人眼自适应。
|
||||

|
||||
<center>直接光照与一次反弹的间接光照</center>
|
||||
|
||||

|
||||
<center>只有一次反弹的间接光照</center>
|
||||
一个较为暴力的手法就是提高光照贴图分辨率,但这不是最好的方法。因为这样做会增大内存消耗。
|
||||
|
||||

|
||||
<center>光照贴图分辨率为1024的结果</center>
|
||||
|
||||
在WorldSettings——LightMass中,可以对以下参数进行设置:
|
||||
##### Indirect Lighting Quality
|
||||
控制间接光照的质量,以获得更佳的效果。
|
||||
##### Indirect Lighting Smoothness
|
||||
控制光照贴图中细节的平滑度。
|
||||
##### Num Indirect Lighting Bounces
|
||||
间接光照光线的反弹次数。
|
||||
|
||||
在对这些参数进行调整后,即使是光照贴图分辨率为512,其效果也比1024的好了。
|
||||

|
||||
<center>光照贴图分辨率为512</center>
|
||||
|
||||

|
||||
<center>光照贴图分辨率为1024</center>
|
244
03-UnrealEngine/Rendering/Lighting/正确的打光流程.md
Normal file
244
03-UnrealEngine/Rendering/Lighting/正确的打光流程.md
Normal 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相等的值)
|
||||

|
||||
#### Num Indirect Lighting Bounce(世界设置)
|
||||
许多用户喜欢把这个值提高到 100 次,不过好在这个对烘焙时长影响不大,但同时对光照质量影响也不大。实际使用这个值设置为 3 - 5 次就足够了。
|
||||
|
||||
下面是对比数据:
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Static Lighting Level Scale(世界设置)
|
||||
改变lightmass的级别。默认值为 1,越小细节越多,最终我们使用 0.1。
|
||||
|
||||
其原理是增加单位的区域的采样点的量。但这样会产生大量噪点。
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Indirect Lighting Smoothness(世界设置)
|
||||
该值大于1是会对LightMap进行平滑处理(反之会进行锐化),过高的数值会导致漏光现象。对于这个属性作者建议设置为0.7~1。
|
||||
|
||||
#### Indirect Lighting Quality(世界设置)
|
||||
增加进行final gathering运算时的采样光线量。提高数值可以减少间接照明时的噪点,但代价是极大地增加构建时间。
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### 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类型,如下图所示:
|
||||

|
||||
|
||||
另外,设置Level的加载方式为使用Blueprint加载:
|
||||

|
||||
|
||||
接着,对不同Level设置不同的光照进行烘焙。烘焙结束后,在Persistent Level Blueprint中,添加Load Stream Level节点对Level进行加载,并设置要加载的Level名称Day,如下图所示:
|
||||

|
||||
|
||||
### 动态实时灯光
|
||||
#### 场景流程
|
||||
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 12–14
|
||||
At sunset 12
|
||||
Just after sunset 9–11
|
||||
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 9–10
|
||||
Night sports 9
|
||||
Fires and burning buildings 9
|
||||
Bright street scenes 8
|
||||
Night street scenes and window displays 7–8
|
||||
Night vehicle traffic 5
|
||||
Fairs and amusement parks 7
|
||||
Christmas tree lights 4–5
|
||||
Floodlit buildings, monuments, and fountains 3–5
|
||||
Distant views of lighted buildings 2
|
||||
Indoor, artificial light
|
||||
Galleries 8–11
|
||||
Sports events, stage shows, and the like 8–9
|
||||
Circuses, floodlit 8
|
||||
Ice shows, floodlit 9
|
||||
Offices and work areas 7–8
|
||||
Home interiors 5–7
|
||||
Christmas tree lights 4–5
|
||||
|
||||
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,适当增加天光的亮度。
|
@@ -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”,演讲者的意思是纯材质,排除混合材质情况。
|
||||

|
||||
|
||||
#### 关于BaseColor
|
||||
Daedalus51提出场景中的BaseColor应该要较为平缓(亮度、对比度与饱和度别太高),这一点也在在Ue4的LightMass文档中的“使用LightMass获取最佳质量”章节中有提到。
|
||||
|
||||
特别是饱和度,饱和度如果过高会让人感觉画面奇怪。1.5章节开头就在调整贴图的饱和度与亮度。
|
||||
|
||||
检查的方法是切换到BaseColor查看模式截图,再使用Photoshop的Histogram查看颜色分布。Daedalus51使用的方法是,使用PS对截图使用平均模糊滤镜,之后观察图片的各个值。
|
||||
|
||||
但从视频中可以看出,这个步骤更多的是靠经验,所以这里还是建议各位仔细看下对应视频。
|
||||
##### 在Ue4中修改贴图
|
||||
一种简单修复方法就是打开TextureAsset,直接修改里面的饱和度、亮度、对比度等选项。(但是作者还是建议使用Photoshop修改源文件再更新Asset)
|
||||
|
||||
## 结语
|
||||
在下一篇文章中,我会总结各个步骤的重要细节,以及各个功能所应用的场景。但是文章只能记录操作步骤与流程,不能记录使用经验,所以本人依然推荐想深入学习光照的人去看一下Daedalus51的视频。
|
||||
|
||||
#### Daedalus51视频中的场景
|
||||
第一章节:
|
||||
|
||||
户外傍晚:
|
||||
使用纯动态光照
|
||||
距离场AO(SSAO好像关闭了)
|
||||
CubeMap天光并且自定义下半球颜色
|
||||
|
||||
其他为后处理
|
||||
|
||||
户外早晨:
|
||||
使用静态光照(Stationary的动态阴影不算的话)
|
||||
CubeMap天光并且自定义下半球颜色
|
||||
|
||||
第二章节:
|
||||
|
||||
洞穴场景(可以看成是室内)。
|
@@ -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 bias(0=>4.6),使得场景达到合适的亮度。
|
||||
7. 删除BP_Sky_Sphere、高度雾与指数雾,添加新的使用Hdr的天空球(参照第二章节中的内容:场景捕获)后进行高级质量烘焙。
|
||||
8. 删除场景中的反射球,使用一个Box反射球作为主反射球,并将它大致放置在场景中间,之后调整Capture Offset数值来校准反射球。
|
||||
9. 关闭玻璃材质属性中的屏幕反射功能(Translucency-Screen Space Reflections),反射球效果依然有效。
|
||||
10. 检查材质是否正确,并进行调整(固有色、贴图等)
|
||||
11. 在观察到地板的不正常反射后,在后处理空间中开启了屏幕空间反射,并调节了反射质量。(r.sss.quality 4,r.sss.Temporal 0)之后发现反射噪点是材质粗糙度造成。
|
||||
12. 调节LightMass属性,Static Lighting Level Scale 1=》0.1,Indirect Lighting Quality 1=》6,Indirect 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与澡波贴图来实现一些边角污垢效果**
|
||||

|
||||

|
||||
|
||||
**解决植被顶点动画模糊的问题**
|
||||
在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**
|
||||

|
||||
|
||||

|
||||
|
||||
**有关体积光照贴图**
|
||||
Unreal会根据场景模型分布情况分配体积采样点(对于重要的地方可以通过添加体积的方式调整采样点间距),**动态物体**会根据这些采样点进行插值着色,来实现GI效果。但LightMass只会对静态物体进行采样,而完全忽略动态物体。
|
||||
|
||||
这里作者又提到了树,因为树不适合使用LightMass烘焙光照,因为树的结构复杂会导致烘焙时间长而且效果很差,但是如果不使用LightMass就不会有GI效果。所以作者想到了设置两组树,第一组静态用于烘焙体积光照贴图,之后在场景中隐藏。另一种动态用于在场景中展示。这样就解决上面说的问题。
|
||||
|
||||
但这样做不能算是个完美的方法,所以作者希望EPIC能开发让一些特定的动态物体影响空间探针的功能。
|
||||
|
||||
**第一次烘焙完的结果**
|
||||

|
||||

|
||||
|
||||
**有关BaseColor**
|
||||
在检查BaseColor的过程中,发现地面的泥土材质的BaseColor太黑了(这个贴图的中间值为40,而达斯·维达那身黑乎乎的材质颜色Vec3(52,52,52)的中间值也至少为50)。具体的做法是调整亮度与饱和度。
|
||||
|
||||
所以作者对以下贴图进行修改:
|
||||
- 调整了泥土贴图,40=》81
|
||||
- 调整了地面的砖石贴图,70=>104
|
||||
- 调整了台阶贴图
|
||||
- 调整了柱子贴图,68=》104
|
||||
- 调整了砖墙贴图,97=》125
|
||||
- 调整了墙体贴图的金属部分,黑色=》中灰色
|
||||
- 等场景中的其他贴图
|
||||
|
||||
作者不建议固有色的中间值低于50或者高于230。
|
||||
|
||||

|
||||
|
||||
>4.5章节作者额外补充一些东西,个人强烈建议大家去看一下,虽然物理灯光因为引擎存在Bug没有讲完。
|
||||
|
||||
首先作者介绍了一下GPULightMass,可以看得出烘焙速度非常快。这次作者演示的场景比较简单,但包含了室内与室外部分,非常适合讲解新的物理灯光系统。
|
||||
|
||||
**使用物理灯光的原因**
|
||||
|
||||
之前版本中Unreal4的灯光使用的是虚幻单位,这是的之前版本的灯光没有一个物理值的衡量标准。你很可能会因为后处理空间、曝光等因素影响而无法设置出正确的亮度值。例如:你已经将一个区域的效果调整的很自然,但是别的区域就会很奇怪(bloom、阴影、光照等效果互相不匹配)。 而前人已经积累了大量的摄影知识与经验,所以我们没有必要光靠感觉来设置灯光亮度与曝光度,而需要使用摄影标准来设置灯光,从而使得场景更加真实。
|
||||
## 环境设置
|
||||
### 天光
|
||||
类型:static 关闭半球固定色
|
||||
### 方向光
|
||||
级联阴影的动态阴影距离设置为12000
|
||||
### 天空球
|
||||
引擎天空球模型+Hdr图+对应材质,具体参考第二篇文章写的内容。
|
||||
### 后处理空间
|
||||
保持默认
|
||||
### LightMass设置
|
||||
静态光照细节缩放等级0.1 光线反弹次数10 天光反弹次数10 间接照明质量4 间接照明平滑0.8 ## 视频中的操作过程 ### 使用摄像机参数来调整曝光度 所以我们能做的第一件事情就是通过sunny16原则来设置相机。 具体步骤为:在后处理空间——曝光中将模式改成手动,之后按照sunny16原则的户外晴天数值来设置Camera属性。
|
||||

|
||||
### 设置天空球亮度
|
||||
但此时画面还是黑乎乎的,这说明场景中的灯光强度太弱了。此时你直接调整天光强度是无效的,因为默认的天光是场景捕获模式。此时设置天空球亮度(设置材质),在视频中亮度提升到68000左右达到了一个不错的效果。这个时候再进行烘焙,模型的样子就不再是黑乎乎的了。 >使用像素检测器,可以检查对应像素的光照强度
|
||||

|
||||

|
||||

|
||||

|
||||
### 设置方向光
|
||||
将方向光亮度设置为75000(阴天中午),再次烘焙光照。 此时场景出了些问题,金属反射不正常(屏幕空间反射)。可能是作者用的版本对于物理灯光的支持不太好。
|
||||
### 设置室内曝光度
|
||||
按照sunny16原则的室内数值来设置Camera属性。但是通过相机参数来调整曝光度有个问题:那就是曝光度是固定死的,无法根据场景亮度进行自适应。这里作者吐槽自适应曝光不能使用物理参数(这个问题目前版本已经解决)
|
||||
|
||||

|
||||
|
||||
作者登录了一个灯光供应商的网站,网站上提供个灯光的详细参数,有亮度、色温等。 之后作者讲解了坎德拉与流明这种光照单位,这里可以参考日天大佬的文章。 之后作者展示了点光源切换坎德拉与流明,但是效果却不一样的问题。(作者使用的版本是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相等的值)
|
||||
|
||||

|
||||
|
||||
#### Export Resolution Scale
|
||||
缩放导出此材质属性时的分辨率。当需要细节时,这对于提高材质分辨率非常有用。
|
||||
|
||||
第一幅图像中使用了一个小光源,但是光照图的分辨率太低,无法捕捉到清晰的半透明阴影。第二幅图像中,材质导出的分辨率过低(由Export Resolution Scale控制),无法捕捉到清晰的阴影。
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
#### Num Indirect Lighting Bounce(世界设置)
|
||||
许多用户喜欢把这个值提高到 100 次,不过好在这个对烘焙时长影响不大,但同时对光照质量影响也不大。实际使用这个值设置为 3 - 5 次就足够了。
|
||||
|
||||
下面是对比数据:
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Static Lighting Level Scale(世界设置)
|
||||
改变lightmass的级别。默认值为 1,越小细节越多,最终我们使用 0.1。
|
||||
|
||||
其原理是增加单位的区域的采样点的量。但这样会产生大量噪点。
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Indirect Lighting Smoothness(世界设置)
|
||||
该值大于1是会对LightMap进行平滑处理(反之会进行锐化),过高的数值会导致漏光现象。对于这个属性作者建议设置为0.7~1。
|
||||
|
||||
#### Indirect Lighting Quality(世界设置)
|
||||
增加进行final gathering运算时的采样光线量。提高数值可以减少间接照明时的噪点,但代价是极大地增加构建时间。
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### 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类型,如下图所示:
|
||||
|
||||

|
||||
|
||||
另外,设置Level的加载方式为使用Blueprint加载:
|
||||
|
||||

|
||||
|
||||
接着,对不同Level设置不同的光照进行烘焙。烘焙结束后,在Persistent Level Blueprint中,添加Load Stream Level节点对Level进行加载,并设置要加载的Level名称Day,如下图所示:
|
||||
|
||||

|
||||
## 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)
|
||||
|
||||

|
||||
默认设置
|
||||
|
||||

|
||||
|
||||
#### 使用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中的参数步数以及射线数都是12,12。你显卡牛逼的话都调成64。吃不消就往下调。反正你会发现你的帧数会有明显变化。没有效果,那么修改完usf文件后,退出引擎重新进一次,然后控制台输入r.SSR.Quality 4。
|
||||
|
||||
##### 抗锯齿
|
||||
在控制台中输入r.PostProcessAAQuality 6
|
154
03-UnrealEngine/Rendering/Lighting/灯光知识与流程学习总结(二)——设置天光与曝光设置.md
Normal file
154
03-UnrealEngine/Rendering/Lighting/灯光知识与流程学习总结(二)——设置天光与曝光设置.md
Normal file
@@ -0,0 +1,154 @@
|
||||
## 天光
|
||||
天光亮度一般来说还是保持为1比较好,但以天光作为主要照明是例外(夜晚场景)。
|
||||
天光有“场景捕获”与“使用CubeMap”两种使用方向。(但是本质上还是围绕着CubeMap,所以作者更加倾向于使用CubeMap。除非你实现了类似CryEngine的昼夜交替系统。)
|
||||
|
||||
Daedalus51在视频1.1最后讲解了Skylight技巧,推荐大家去看。
|
||||
### 调试方法
|
||||
往场景中放入一个Sphere与Cube模型,并赋予Chrome材质(引擎Content中)。假设你已经关闭后处理空间中的屏幕空间反射效果。这样就可以观察天光是否达到你的要求。
|
||||
|
||||
在静态光照下,LightMass会烘焙出GI效果,你可以开启LightingOnly模式查看GI效果。
|
||||
|
||||

|
||||
### 使用CubeMap
|
||||
也就是使用Hdr环境贴图,赋予Hdr后,记得修改CubeMap Resolution(贴图最长边的值,必须为2的幂)。
|
||||
|
||||
### 场景捕获
|
||||
对远景与环境球进行采样,再作为天光数据对场景进行照明。
|
||||
|
||||
如果你希望把Hdr图作为环境球在场景中渲染,那步骤如下:
|
||||
1. 往场景中加入环境球模型,想省事可以使用EngineContent中的SM_SkySphere。
|
||||
2. 使用以下材质(这里我就直接拿日天大佬的图来展示):
|
||||
|
||||

|
||||
另外材质的光照模型选择自发光,勾选双面渲染选项。
|
||||
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章节视频中,他演示一种动态环境球效果:
|
||||

|
||||
|
||||
如果在云层移动速度较慢的情况下,效果还不错。以下是材质实现:
|
||||

|
||||
|
||||
作者在其基础上进行了一定改进,但是环境球的顶部会有图像畸变bug。
|
||||

|
||||
|
||||
## 调整曝光与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
|
||||
|
||||

|
||||
|
||||
Sunny16
|
||||
https://en.wikipedia.org/wiki/Sunny_16_rule
|
||||
|
||||
EV使用推荐
|
||||
|
||||

|
||||
|
||||
### 操作过程
|
||||
#### 调整曝光
|
||||
在设置天光与其他主光源亮度前,需要确定好曝光值。在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。
|
||||

|
||||
|
||||
### 动态环境球与天光捕获问题
|
||||
上文有介绍Daedalus51的动态环境球方案。那么问题来了,天光捕获的是哪一帧的环境球?我们无法确定。那么天光烘焙结果就是不确定的么?那么TrueSky之类的RayMarching材质,天光捕获是否会正确呢?
|
||||
|
||||
因为本人没有TrueSky,而且这个问题也无伤大雅,所以本人就不研究了。
|
||||
#### 个人对于动态天空的意见
|
||||
其实本人不太喜欢Daedalus51的环境球方案,因为会穿帮。个人还是倾向于改造BP_Sky_Sphere。BP_Sky_Sphere的问题主要在云与大气上。
|
||||
|
||||
云的话还是推荐做成RayMarching,计算透明度与阳光散射效果(如果是不运动的云可以预计算效果)
|
||||

|
||||
|
||||
大气效果应该不是物理的不然Unreal4也不会在4.24推出物理大气系统。
|
@@ -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://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
|
||||
控制阴影边缘的锐化程度。
|
||||
#### 调整级联阴影以得到更好的效果
|
||||
通过调整上述设置,可以很好地调整阴影的出血值和精度。下一节将尝试调整到最佳成都,以获得更好的精度与阴影。找到一种适合任何特定游戏的平衡将需要耗费时间、精力来进行大量测试。
|
||||
|
||||
以下是默认设置下会出现的问题。
|
||||

|
||||
|
||||
调整后的结果
|
||||

|
||||
|
||||
#### 远距离阴影(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)
|
||||
### 距离场模型问题
|
||||
因为距离场使用体积贴图来存储空间信息,所以对于巨大物体(比如摩天大楼)应该把模型拆成小块,以减少性能损耗。
|
||||
|
||||
同理,模型的形状也不应该奇形怪状,这样会大大的影响距离场构建质量。
|
||||
|
||||

|
||||
### 植被渲染注意
|
||||
因为植被面数高、结构复杂等因为因素,不适合使用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://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选项
|
43
03-UnrealEngine/Rendering/Lighting/灯关相关学习资料.md
Normal file
43
03-UnrealEngine/Rendering/Lighting/灯关相关学习资料.md
Normal 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
|
@@ -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设置。基础效果差不多后,调整到以下参数,并切换至中级效果进行烘焙,至到最终渲染。
|
||||
|
||||
体积光照贴图密度取决于环境雾精度。如果对性能没有要求可以使用更高的采样与更高的光照贴图分辨率。
|
||||
|
||||

|
||||
再光照模式下查看效果,场景只烘焙了自发光。
|
||||
|
||||

|
||||
光照贴图密度
|
||||
|
||||

|
||||
最终烘焙所用的LightMass设置参数
|
||||
|
||||
**设置主要光源**
|
||||
在设置完场景后,开始规划主要光源。首先需要一个来自外部的明亮光源来提供室内绝大多数的照明,但需要保证室内暗部
|
||||
的灯光依然可见。所以作者选择阴天正午时分的Hdr环境图来充当天光(使用场景捕获+环境球模型+环境球材质)。调整天光材质的亮度,至到其物理亮度达到阴天亮度值(使用Ue4的像素检测工具可以检测物理亮度值)。你可以使用Ue4的像素检测工具检查环境球基础关键区域(天空、亮度云层、暗部云层、太阳),但在检查时需要关闭ToneMappering并保持曝光度为0,
|
||||
以及关闭雾气等处于摄像鱼天空之间的显示效果,不然像素检测工具会给出错误的数值。
|
||||
|
||||
中午时分的天空亮度大约在400(云彩暗部)~10000(云彩亮部) `${cd/m^2}$` 。基于作者想要达到效果,曝光值(EV)定在5~7之间就比较室内渲染。对于外部的环境球可以使用EV10。注意一下HDR亮度:
|
||||

|
||||
|
||||
**自发光光源**
|
||||
作者以相同的方式来设置自发光物体。以下是室内LightingOnly模式下只烘焙自发光的效果(应该还有天光,本人并不赞同使用自发光来代替灯光,除非你的灯光形状非同一般。):
|
||||

|
||||
|
||||
因为场景中的金属物体会反射到环境球,所以下一步就应该添加反射球了。
|
||||
细节光照模式(关闭屏幕空间反射)第一张为只有环境球反射高光效果。第二张为只有反射球效果。
|
||||

|
||||

|
||||
|
||||
**灯光形状与反射捕捉**
|
||||
在几次测试烘焙后,作者开始放置实际光源与反射球。作者更加倾向于以真实环境作为参考来设置灯光属性(例如光源形状与参数)。作者会尽可能地使用Static类型的灯光,如果该灯光会作用于角色等动态物体,那就会使用Stationary类型的灯光。体积光照贴图可以弥补动态光照的不足,但这取决于你对显存与GPU运算能力的把控。
|
||||
|
||||
作者更喜欢使用色温来控制灯光颜色。毕竟大多数灯光厂家都会标注灯光色温,而且Unreal4可以直接通过色温来控制灯光颜色。以下是场景中主光源的设置参数:
|
||||

|
||||
|
||||
**后处理校色与效果打磨**
|
||||
因为本人对校色不了解,这边就直接贴原文了:
|
||||
Once I’m 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. It’s worth pointing out that I avoid adjusting the tone mapper. Combined with color grading, it can get very messy to manage. It’s 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, I’d rather do that because it’s cleaner, easier to control, and easier to revisit later on without being completely lost.
|
||||
|
||||

|
||||
|
||||
- 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 it’s 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里面也可以通过一些简单的方式,通过透明物体去投影,按需求去控制影子的浓度,降低对比度。
|
||||

|
||||
|
||||
使用贴图与定制的贴花材质来区域阴影伪造。
|
||||

|
||||
|
||||
使用后处理材质,控制场景亮度、饱和度。对于使用LightMass烘焙的场景可以在调整曝光度比值与间接光照强度。
|
||||

|
||||
|
||||
一些场景的光线强度分布差距较大时,会出现只能讨好一边的情况,所以我们可以其中一边为标准调整另一边的灯光亮度。在传统的拍电影过程中也能实现,拍电影的灯光功率都非常大,为了平衡室内外的灯光,室内就是打了这么亮的灯光,当然在引擎中或者游戏中我们非常容易的就能实现。
|
||||

|
||||
|
||||
## 问题解答
|
||||
原文地址:
|
||||
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
|
||||
级联阴影的远阴影开关,可以解决当摄像机处于较远距离时,物体的阴影会消失的问题。
|
||||
|
||||
#### 调整级联阴影以得到更好的效果
|
||||
通过调整上述设置,可以很好地调整阴影的出血值和精度。下一节将尝试调整到最佳程度,以获得更好的精度与阴影。找到一种适合任何特定游戏的平衡将需要耗费时间、精力来进行大量测试。
|
||||
|
||||
以下是默认设置下会出现的问题。
|
||||

|
||||
|
||||
调整后的结果
|
||||

|
||||
##### 对于所有的动态光源
|
||||
###### Shadow Bias
|
||||
控制阴影在场景中的精确程度。默认值是0.5,这是权衡了精度与性能的值。
|
||||
###### Shadow Filter Sharpness
|
||||
控制阴影边缘的锐化程度。
|
||||
|
||||
##### 为什么可移动光源在较远时其遮挡关系会出错
|
||||

|
||||

|
||||
|
||||
为了解释这一点,我们首先需要了解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,后期空间开启人眼自适应。
|
||||

|
||||
<center>直接光照与一次反弹的间接光照</center>
|
||||
|
||||

|
||||
<center>只有一次反弹的间接光照</center>
|
||||
一个较为暴力的手法就是提高光照贴图分辨率,但这不是最好的方法。因为这样做会增大内存消耗。
|
||||
|
||||

|
||||
<center>光照贴图分辨率为1024的结果</center>
|
||||
|
||||
在WorldSettings——LightMass中,可以对以下参数进行设置:
|
||||
##### Indirect Lighting Quality
|
||||
控制间接光照的质量,以获得更佳的效果。
|
||||
##### Indirect Lighting Smoothness
|
||||
控制光照贴图中细节的平滑度。
|
||||
##### Num Indirect Lighting Bounces
|
||||
间接光照光线的反弹次数。
|
||||
|
||||
在对这些参数进行调整后,即使是光照贴图分辨率为512,其效果也比1024的好了。
|
||||

|
||||
<center>光照贴图分辨率为512</center>
|
||||
|
||||

|
||||
<center>光照贴图分辨率为1024</center>
|
16
03-UnrealEngine/Rendering/Lighting/解决UE5植被阴影出现噪点的问题.md
Normal file
16
03-UnrealEngine/Rendering/Lighting/解决UE5植被阴影出现噪点的问题.md
Normal 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,草上的噪点就没有了。
|
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ef5ddf87935c2d37bf8db4a6cc61f30e919ffcfbd6a535f51a342beaa2ae1c56
|
||||
size 7776591
|
38
03-UnrealEngine/Rendering/Lighting/音乐控制DMX灯光的简单方法.md
Normal file
38
03-UnrealEngine/Rendering/Lighting/音乐控制DMX灯光的简单方法.md
Normal 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灯光效果:
|
||||

|
||||
|
||||
IsBeatRange是根据指定频率声音的音量进行判断,这里可以根据需要来设置判断条件:
|
||||

|
||||
|
||||
因为逻辑是写在Actor中的,所以搞了按键事件来停止音乐播放。
|
||||

|
||||
|
||||
不过需要注意:启用这个插件之后导入Wav就会变成这个资产。如果想要获得SoundWave资产就必须停用插件并且再次导入。
|
||||

|
||||
## DMX移植
|
||||
只需要在新工程里启用DMX插件,之后将官方的DMX的工程迁移即可。需要注意的是Sequence里的需要使用DMX,里面设置好DMXLibrary以及补丁(这中文翻译槽点满满)即可。
|
||||
|
||||
## 后续工作
|
||||
这个方案有一些缺点:
|
||||
1. AudioAnalysisTools需要播放声音才能获取的数据。
|
||||
2. AudioAnalysisTools无法使用UE自带的Asset,同时会导致无法在导入后生成SoundWave Asset。
|
||||
3. 无法在Sequence停止播放时停下(更好的控制)或者在拖动轨道时执行正确的行为。
|
||||
|
||||
这就需要通过自定义Sequence Track以及修改插件才能完美实现。
|
114
03-UnrealEngine/Rendering/Material/ShaderWorld Debug笔记.md
Normal file
114
03-UnrealEngine/Rendering/Material/ShaderWorld Debug笔记.md
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: 未命名
|
||||
date: 2025-04-10 09:14:41
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 错误日志
|
||||
```c++
|
||||
UATHelper: Packaging (Android (ASTC)): LogInit: Display: LogShaderCompilers: Warning: Failed to compile Material /ShaderWorld/Material/MSPresets/MS_Foliage_Material/MasterMaterials/MA_Impostor.MA_Impostor (MI:/ShaderWorld/Spawnables/BlackAlder/Materials/Impostor/MAI_Impostor_BlackAlder.MAI_Impostor_BlackAlder) for platform SF_VULKAN_ES31_ANDROID, Default Material will be used in game.
|
||||
UATHelper: Packaging (Android (ASTC)): /Engine/Generated/Material.ush:3672:27: error: no matching function for call to 'transpose'
|
||||
UATHelper: Packaging (Android (ASTC)): return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
UATHelper: Packaging (Android (ASTC)): ^~~~~~~~~
|
||||
UATHelper: Packaging (Android (ASTC)): /Engine/Generated/Material.ush:3672:27: note: candidate function not viable: no known conversion from 'FDFMatrix' to 'matrix<float, 1, 1>' for 1st argument
|
||||
UATHelper: Packaging (Android (ASTC)): return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
UATHelper: Packaging (Android (ASTC)): ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
UATHelper: Packaging (Android (ASTC)): /Engine/Generated/Material.ush:3701:37: error: no matching function for call to 'transpose'
|
||||
UATHelper: Packaging (Android (ASTC)): return mul(InWorldVector, (float3x3)transpose(Parameters.InstanceLocalToWorld)) / (temp*temp);
|
||||
UATHelper: Packaging (Android (ASTC)): ^~~~~~~~~
|
||||
UATHelper: Packaging (Android (ASTC)): /Engine/Generated/Material.ush:3701:37: note: candidate function not viable: no known conversion from 'FDFMatrix' to 'matrix<float, 1, 1>' for 1st argument
|
||||
UATHelper: Packaging (Android (ASTC)): return mul(InWorldVector, (float3x3)transpose(Parameters.InstanceLocalToWorld)) / (temp*temp);
|
||||
UATHelper: Packaging (Android (ASTC)): ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
UATHelper: Packaging (Android (ASTC)): /Engine/Generated/Material.ush:3722:27: error: no matching function for call to 'transpose'
|
||||
UATHelper: Packaging (Android (ASTC)): return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
UATHelper: Packaging (Android (ASTC)): ^~~~~~~~~
|
||||
UATHelper: Packaging (Android (ASTC)): /Engine/Generated/Material.ush:3722:27: note: candidate function not viable: no known conversion from 'FDFMatrix' to 'matrix<float, 1, 1>' for 1st argument
|
||||
UATHelper: Packaging (Android (ASTC)): return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
UATHelper: Packaging (Android (ASTC)): ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
```
|
||||
|
||||
|
||||
搜索结果大致是:
|
||||
```c++
|
||||
MaterialFloat3 CustomExpression1(FMaterialVertexParameters Parameters,float3 InWorldVector, FWSVector3 LWCInWorldVector)
|
||||
{
|
||||
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
|
||||
return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
#else
|
||||
return mul(InWorldVector, (MaterialFloat3x3)LWCToFloat(GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal));
|
||||
#endif
|
||||
}
|
||||
|
||||
MaterialFloat3 CustomExpression4(FMaterialVertexParameters Parameters,MaterialFloat3 InWorldVector)
|
||||
{
|
||||
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
|
||||
float3 temp;
|
||||
temp.x = length(TransformLocalVectorToWorld(Parameters, float3(1,0,0)));
|
||||
temp.y = length(TransformLocalVectorToWorld(Parameters, float3(0,1,0)));
|
||||
temp.z = length(TransformLocalVectorToWorld(Parameters, float3(0,0,1)));
|
||||
return mul(InWorldVector, (MaterialFloat3x3)transpose(Parameters.InstanceLocalToWorld)) / (temp*temp);
|
||||
#else
|
||||
return mul(InWorldVector, (MaterialFloat3x3)LWCToFloat(GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal));
|
||||
#endif
|
||||
}
|
||||
|
||||
MaterialFloat3 CustomExpression6(FMaterialVertexParameters Parameters,MaterialFloat3 InWorldVector)
|
||||
{
|
||||
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
|
||||
return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
#else
|
||||
return mul(InWorldVector, (MaterialFloat3x3)LWCToFloat(GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal));
|
||||
#endif
|
||||
}
|
||||
|
||||
MaterialFloat3 CustomExpression6(FMaterialVertexParameters Parameters,MaterialFloat3 InWorldVector)
|
||||
{
|
||||
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
|
||||
return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
#else
|
||||
return mul(InWorldVector, (MaterialFloat3x3)LWCToFloat(GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal));
|
||||
#endif
|
||||
}
|
||||
|
||||
MaterialFloat3 CustomExpression9(FMaterialVertexParameters Parameters,float3 InWorldVector, FWSVector3 LWCInWorldVector)
|
||||
{
|
||||
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
|
||||
return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
|
||||
#else
|
||||
return mul(InWorldVector, (MaterialFloat3x3)LWCToFloat(GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal));
|
||||
#endif
|
||||
}
|
||||
|
||||
MaterialFloat3 CustomExpression12(FMaterialVertexParameters Parameters,MaterialFloat3 InWorldVector)
|
||||
{
|
||||
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
|
||||
float3 temp;
|
||||
temp.x = length(TransformLocalVectorToWorld(Parameters, float3(1,0,0)));
|
||||
temp.y = length(TransformLocalVectorToWorld(Parameters, float3(0,1,0)));
|
||||
temp.z = length(TransformLocalVectorToWorld(Parameters, float3(0,0,1)));
|
||||
return mul(InWorldVector, (MaterialFloat3x3)transpose(Parameters.InstanceLocalToWorld)) / (temp*temp);
|
||||
#else
|
||||
return mul(InWorldVector, (MaterialFloat3x3)LWCToFloat(GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal));
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
## 其他bug
|
||||
- Vulkan & OpenGL Preview 必然崩溃。
|
||||
##
|
||||
```c++
|
||||
#if USE_INSTANCING || USE_INSTANCE_CULLING
|
||||
float3x3 LocalToWorld = DFToFloat3x3(Parameters.InstanceLocalToWorld);
|
||||
float3 LocalPosition = Parameters.InstanceLocalPosition;
|
||||
|
||||
// skip if this instance is hidden
|
||||
if (Parameters.PerInstanceParams.y < 1.f)
|
||||
{
|
||||
return float3(0,0,0);
|
||||
}
|
||||
#else
|
||||
float3x3 LocalToWorld = DFToFloat3x3(GetPrimitiveData(Parameters).LocalToWorld);
|
||||
float3 LocalPosition = WSMultiplyDemote(GetWorldPosition(Parameters), Parameters.LWCData.WorldToLocal);
|
||||
#endif
|
||||
```
|
9
03-UnrealEngine/Rendering/Material/UE创建CubeMap.md
Normal file
9
03-UnrealEngine/Rendering/Material/UE创建CubeMap.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: UE创建CubeMap
|
||||
date: 2023-07-28 15:34:15
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
https://www.youtube.com/watch?v=rQXROveBR14
|
@@ -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配合使用。
|
71
03-UnrealEngine/Rendering/Material/使用Curve进行深度适配的Outline.md
Normal file
71
03-UnrealEngine/Rendering/Material/使用Curve进行深度适配的Outline.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: 使用Curve进行深度适配的Outline
|
||||
date: 2023-07-18 14:47:24
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# 前言
|
||||
在UE5.1发现了若干新节点感觉对后处理描边有很大的作用。
|
||||
![[MultiDraw_OutlineMaterial.png]]
|
||||
|
||||
|
||||
# ControlCurve
|
||||
<iframe src="https://www.desmos.com/calculator/z9o3k0luyb?embed" width="500" height="500" style="border: 1px solid #ccc" frameborder=0></iframe>
|
||||
UE单位为cm,理论上描边宽度控制范围为10cm~10000cm(1000m)。假设标准的角色身高为180cm(110~200cm变化比较轻微)并以此定义一个标准数StandardCharacterHeight,经过观察`10~ ControlWidthRangeMax`范围适合使用CurveTexture。而超过ControlWidthRangeMax = StandardCharacterHeight * 2的区域基本是一个线性区间,所以可以使用一个线性函数来控制。
|
||||
|
||||
以下为原始数据:
|
||||
|
||||
| | | | | | | | |
|
||||
| --- | --- | --- | ---- | --- | ---- | --- | ---- |
|
||||
| x轴 | 15 | 20 | 25 | 30 | 40 | 60 | 100 |
|
||||
| y轴 | -12 | -9 | -7.2 | -6 | -4.5 | -3 | -1.8 |
|
||||
|
||||
经过重映射后:
|
||||
|
||||
| | | | | | | | |
|
||||
| --- | ----- | ---- | ---- | ------ | ----- | ------- | ---- |
|
||||
| x轴 | 0.15 | 0.2 | 0.25 | 0.3 | 0.4 | 0.6 | 1 |
|
||||
| y轴 | 0.667 | 0.75 | 0.8 | 0.8333 | 0.875 | 0.91666 | 0.95 |
|
||||
|
||||
考虑需要让描边变得平滑以及让效果更好的考虑,所以以此为基础数据进行了调节(平移曲线并且调整了几个点),以下去本人设置的曲线:![[MultiDraw_OutlineCurve.png|800]]
|
||||
|
||||
### 线性函数斜率k的计算
|
||||
取得ControlWidthRangeMax上一点(360 0.5)与最大范围x=10000的一点(10000 0.018),即可取得斜率 k = 0.0005/1cm。
|
||||
|
||||
实际使用0.0005的效果不佳,经过调整最终斜率使用0.002。
|
||||
|
||||
### Remap
|
||||
```c++
|
||||
half invLerp(half from, half to, half value)
|
||||
{
|
||||
return (value - from) / (to - from);
|
||||
}
|
||||
half invLerpClamp(half from, half to, half value)
|
||||
{
|
||||
return saturate(invLerp(from,to,value));
|
||||
}
|
||||
// full control remap, but slower
|
||||
half remap(half origFrom, half origTo, half targetFrom, half targetTo, half value)
|
||||
{
|
||||
half rel = invLerp(origFrom, origTo, value);
|
||||
return lerp(targetFrom, targetTo, rel);
|
||||
}
|
||||
```
|
||||
|
||||
## FOV
|
||||
首先确定一下摄像机的FOV范围,CineCamera 默认Current Focal Length 为35,Current Horizontal FOV为37.497356,材质Fov显示为0.6544528(弧度制);编辑器Camera 默认 FOV:90 材质FOV显示1.5707976(弧度制)
|
||||
- CineCamera
|
||||
- Current Horizontal FOV范围 1.375033 ~ 143.130096
|
||||
- 材质 FOV 范围 0.024~2.4
|
||||
- Camera
|
||||
- FOV范围 5~170
|
||||
- 材质 FOV 范围 0.08~2.967
|
||||
|
||||
经过观察FOV<90需要使用曲线进行控制,大致和Depth曲线差不多,再调整一下就好。90 < FOV < 170 时OutlineWidth大致呈线性,所以大致计算出斜率k=3:
|
||||
|
||||
| | | | |
|
||||
| ------------ | --- | --- | --- |
|
||||
| FOV | 90 | 130 | 170 |
|
||||
| OutlineWidth | 1 | 3 | 8 |
|
10
03-UnrealEngine/Rendering/Material/材质距离场Mask.md
Normal file
10
03-UnrealEngine/Rendering/Material/材质距离场Mask.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: 未命名
|
||||
date: 2025-03-12 21:08:01
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
PS. InstancedFoliage是没有距离场的
|
||||
|
||||
![[材质距离场Mask1.png]]![[材质距离场Mask2.png]]![[材质距离场Mask3.png]]
|
48
03-UnrealEngine/Rendering/Material/自定义材质输出节点.md
Normal file
48
03-UnrealEngine/Rendering/Material/自定义材质输出节点.md
Normal 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
|
||||
}
|
||||
```
|
12
03-UnrealEngine/Rendering/Ocean/海洋渲染笔记.md
Normal file
12
03-UnrealEngine/Rendering/Ocean/海洋渲染笔记.md
Normal 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)
|
@@ -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()进行光线追踪计算。其工作流程如下:
|
||||
.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页
|
@@ -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
|
331
03-UnrealEngine/Rendering/RayTracing/UE5RayTracing渲染管线笔记——(1).md
Normal file
331
03-UnrealEngine/Rendering/RayTracing/UE5RayTracing渲染管线笔记——(1).md
Normal 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. 计算最终的半球混合PDF:float 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_RayGen,FRayTracingPrimaryRaysRGS
|
||||
- DebugVisualizationMode == RAY_TRACING_DEBUG_VIZ_INSTANCES || DebugVisualizationMode == RAY_TRACING_DEBUG_VIZ_TRIANGLES;使用SF_RayGen,FRayTracingDebugRGS
|
13
03-UnrealEngine/Rendering/RenderFeature/Debug/DebugView.md
Normal file
13
03-UnrealEngine/Rendering/RenderFeature/Debug/DebugView.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: DebugView
|
||||
date: 2024-03-26 21:48:13
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- NaniteVisualize.cpp
|
||||
- RenderDebugViewMode()
|
||||
- NaniteDebugViews.usf
|
||||
|
||||
UE4.26参考:https://zhuanlan.zhihu.com/p/668782106
|
105
03-UnrealEngine/Rendering/RenderFeature/FRenderCommandFence.md
Normal file
105
03-UnrealEngine/Rendering/RenderFeature/FRenderCommandFence.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: FRenderCommandFence
|
||||
date: 2025-06-25 23:11:25
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
在 Unreal Engine 5 (UE5) 中,`FRenderCommandFence` 是一个关键的**线程同步工具**,主要用于协调**游戏线程(Game Thread)** 和**渲染线程(Render Thread)** 之间的执行顺序。它的核心作用是确保渲染相关的操作(如资源创建、更新或销毁)在特定时间点前完成,避免多线程环境下的竞态条件。
|
||||
|
||||
---
|
||||
|
||||
### 核心作用
|
||||
1. **跨线程同步**
|
||||
- 游戏线程向渲染线程提交渲染命令后,这些命令不会立即执行(渲染线程有自己的任务队列)。
|
||||
- `FRenderCommandFence` 允许游戏线程**阻塞等待**,直到所有已提交的渲染命令(包括栅栏之前的所有命令)在渲染线程中执行完毕。
|
||||
2. **确保资源安全**
|
||||
- 当游戏线程需要操作渲染资源(如纹理、网格)时,必须确保渲染线程不再使用这些资源(例如释放 `UTexture`)。
|
||||
- 通过栅栏同步,可以安全地销毁或修改资源,避免访问无效内存。
|
||||
|
||||
---
|
||||
|
||||
### 关键方法
|
||||
- **`BeginFence(bool bSyncToRHIAndGPU = false)`**
|
||||
- 在渲染命令队列中插入一个“栅栏标记”,表示同步点。调用后游戏线程可以继续执行其他任务。
|
||||
- `bSyncToRHIAndGPU` - 是否等待 RHI 线程或 GPU,否则只等待渲染线程。
|
||||
- **`Wait(bool bProcessGameThreadTasks = false)`**
|
||||
- 阻塞游戏线程,直到渲染线程处理到栅栏位置(即执行完栅栏之前的所有渲染命令)。
|
||||
- `bProcessGameThreadTasks`: 若为 `true`,等待期间允许游戏线程处理其他任务(如消息泵)。
|
||||
- **`IsFenceComplete()`**
|
||||
- 非阻塞检查栅栏是否已完成(渲染线程是否已越过栅栏点)。
|
||||
|
||||
---
|
||||
|
||||
### 典型使用场景
|
||||
|
||||
#### 1. 安全释放渲染资源
|
||||
```c++
|
||||
// 游戏线程代码
|
||||
void ReleaseTexture(UTexture* Texture)
|
||||
{
|
||||
// 将资源释放命令提交到渲染线程
|
||||
ENQUEUE_RENDER_COMMAND(ReleaseTextureCommand)(
|
||||
[Texture](FRHICommandListImmediate& RHICmdList) {
|
||||
Texture->ReleaseResource();
|
||||
}
|
||||
);
|
||||
|
||||
// 创建栅栏并等待释放完成
|
||||
FRenderCommandFence Fence;
|
||||
Fence.BeginFence();
|
||||
Fence.Wait(); // 阻塞直到渲染线程执行完 ReleaseResource()
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 确保渲染操作完成后再继续
|
||||
```c++
|
||||
// 更新动态纹理后确保数据已提交到GPU
|
||||
void UpdateDynamicTexture()
|
||||
{
|
||||
// 提交更新命令到渲染线程
|
||||
ENQUEUE_RENDER_COMMAND(UpdateTexture)(
|
||||
[...](...) { /* 更新纹理数据 */ }
|
||||
);
|
||||
|
||||
FRenderCommandFence Fence;
|
||||
Fence.BeginFence();
|
||||
Fence.Wait(); // 等待纹理更新完成
|
||||
// 此时可以安全读取纹理或进行后续操作
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 异步加载资源时同步
|
||||
```c++
|
||||
void LoadAssetAsync()
|
||||
{
|
||||
StreamableManager.RequestAsyncLoad(..., [this]()
|
||||
{
|
||||
// 资源加载完成后,确保渲染线程初始化完毕
|
||||
FRenderCommandFence Fence;
|
||||
Fence.BeginFence();
|
||||
Fence.Wait(); // 等待渲染线程处理完资源初始化
|
||||
OnAssetLoaded();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 注意事项
|
||||
1. **性能影响**
|
||||
`Wait()` 会阻塞游戏线程,过度使用可能导致帧率下降。应仅在必要时同步(如资源卸载)。
|
||||
2. **替代方案**
|
||||
- 对于非紧急任务,可用 `AsyncTask` 或 `TaskGraph` 异步处理。
|
||||
- 使用 `FGraphEvent` 实现无阻塞等待。
|
||||
3. **与 `FlushRenderingCommands()` 的区别**
|
||||
`FlushRenderingCommands()` 会强制**立即刷新**整个渲染命令队列(更重操作),而 `FRenderCommandFence` 只等待到特定同步点,更轻量可控。
|
||||
|
||||
---
|
||||
|
||||
### 总结
|
||||
|
||||
`FRenderCommandFence` 是 UE5 多线程架构中的**安全阀**,通过它开发者可以:
|
||||
✅ 确保渲染操作在指定时间点前完成
|
||||
✅ 安全操作渲染资源(创建/更新/销毁)
|
||||
✅ 避免游戏线程与渲染线程的竞态条件
|
9
03-UnrealEngine/Rendering/RenderFeature/Fur制作方案.md
Normal file
9
03-UnrealEngine/Rendering/RenderFeature/Fur制作方案.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Fur制作方案
|
||||
date: 2023-08-15 11:17:42
|
||||
excerpt:
|
||||
tags: Fur 毛发
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
https://xbdev.net/directx3dx/specialX/Fur/
|
20
03-UnrealEngine/Rendering/RenderFeature/LOD相关代码笔记.md
Normal file
20
03-UnrealEngine/Rendering/RenderFeature/LOD相关代码笔记.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: LOD相关代码笔记
|
||||
date: 2023-12-28 15:13:44
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# UE5中的带有LOD名称的相关代码
|
||||
- Plugin
|
||||
- Mesh LOD Toolset
|
||||
- `Engine\Plugins\Experimental\MeshLODToolset\Source\MeshLODToolset\Public\Graphs\GenerateStaticMeshLODProcess.h`
|
||||
- Proxy LOD Plugin
|
||||
- `Engine\Plugins\Experimental\ProxyLODPlugin\Source\ProxyLOD\PrivateProxyLODPlugin.cpp`
|
||||
- Skeletal Mesh Simplifier
|
||||
- `Engine\Plugins\Experimental\SkeletalReduction\Source\Private\SkeletalMeshReductionPlugin.cpp`
|
||||
|
||||
# UGenerateStaticMeshLODProcess
|
||||
- FGenerateStaticMeshLODAssetOperatorOp
|
||||
- FGenerateStaticMeshLODAssetOperatorOp::CalculateResult()
|
||||
- void FGenerateMeshLODGraph::EvaluateResult
|
@@ -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显示不正常时就可以使用这个工具来查找问题。
|
||||
|
||||

|
||||
|
||||
注意:
|
||||
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,并对每个采样点进行更多的光线追踪采样。(牺牲密度来提高精度) 之后使用插值的方式来填补周围像素。
|
||||

|
||||
|
||||
Lumen还使用**世界空间的辐射度缓存**,也就是探针(数量较少),这些探针将会重复运用于多个像素。使用`r.Lumen.RadianceCache.Visualize 1`可以显示出探针。与VolumeLightMap不同,Lumn并不使用这些探针直接渲染GI效果,而是从像素开始RayTracing。
|
||||

|
||||
|
||||
Lumen与降噪器效果比较:
|
||||

|
||||
|
||||
### 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会去实际计算反射结果,以提供更准确的效果。
|
||||

|
||||
|
||||
此时多重反弹的反射效果与天光依然由表面缓存提供。
|
||||
|
||||
#### 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
|
||||
- Future:Lumen Reflections
|
||||
- Bring Over Remaining Feature From Ray Traced Reflection
|
||||
|
||||
## 最佳实践
|
||||
- Emissive
|
||||
1. 使用Lumen时,不要使用自发光材质模型代替灯光。(容易产生大量噪点)
|
||||
2. 使用较大的自发光材质模型做出一些效果。比如夜晚房间中的电视机效果。以及场景中微弱补光。
|
||||
3. 使用较小的自发光材质模型+灯光。
|
||||
- BaseColor
|
||||
1. BaseColor与GI影响很大。
|
||||
2. 非常暗以及糟乱BaseColor会产生很烂的GI效果。
|
||||
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. 把错误的模型拆开。
|
||||
|
||||
## 灯光矫正
|
124
03-UnrealEngine/Rendering/RenderFeature/Lumen学习笔记(2)——原理笔记.md
Normal file
124
03-UnrealEngine/Rendering/RenderFeature/Lumen学习笔记(2)——原理笔记.md
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: Lumen学习笔记(2)——代码分析
|
||||
date: 2023-02-12 10:50:24
|
||||
excerpt:
|
||||
tags: Lumen
|
||||
rating: ⭐
|
||||
---
|
||||
## 前言
|
||||
本文的内容为丛越的知乎文章的知识点浓缩。
|
||||
|
||||
- **SIGGRAPH2022 Lumen**:https://advances.realtimerendering.com/s2022/SIGGRAPH2022-Advances-Lumen-Wright%20et%20al.pdf
|
||||
- **UE5.1 Lumen Indirect Diffuse Lighting技术分析 - GOOD REAL的文章 - 知乎**:https://zhuanlan.zhihu.com/p/696232022
|
||||
## 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**。
|
||||

|
||||
|
||||
## 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、Radiance(Direct Lighting) 以及最终的渲染画面。
|
||||

|
||||
|
||||
## Lumen Scene
|
||||
可以在`ViewMode-Lumen-LumenScene`中查看。
|
||||
Lumen Scene 包含了 DF 描述的场景几何表达以及 Surface Cache 描述的场景材质表达,是一个完整的系统。
|
||||
|
||||

|
||||
|
||||
## 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**。
|
||||
|
||||

|
||||
|
||||
那么 Lumen 又是如何对 Global DF 进行 Lighting 的呢?这就要引入 [[#Voxel Lighting]]。
|
||||
|
||||
### Screen Space Probe放置方法
|
||||
首先每隔 16 个像素均匀的放置,然后进行自适应放置,检查 Probe 网格中的像素是否可被插值,即 4 个 Corner Probe 是否被当前像素遮挡,如果是则在当前像素位置上增加一个 Probe,如此迭代,每次迭代增加一倍分辨率,直到插值差值失败的像素很少或达到指定的分辨率阈值。如下图所示,橘红色为差值失败的像素:
|
||||

|
||||
|
||||
## Voxel Lighting
|
||||
Lumen的**Voxel Lighting**存储的是较粗颗粒度的**空间光照信息**(应该6轴向的光照信息)。从采样**Radiance Cache**获得。为了精确,采用了和**Surface Cache**一样的世界坐标6轴向对齐方案。最后通过采样3轴向的光照数据,再根据Ray方向进行权重插值取得结果。
|
||||

|
||||
为了进一步优化性能减少内存,Lumen 还为 Voxel Lighting 生成了 Clipmap,用于覆盖不同范围,这样可以根据采样点所在的 Clipmap 获取光照。通过 Clipmap,Voxel 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 中的稀疏分布:
|
||||

|
||||
当 **Global DF Tracing 失败时**,可以快速查找 Screen Space Probe 周围的 8 个 Word Space Probe,在 Ray 方向上**对 Word Space Probe Radiance Cache 采样,三线性插值混合这些 Radiance**,从而得到最终的 Distant Lighting,这种方式使光照更加稳定,大大缓解了噪声。此外,还可以与采样的 Voxel Lighting 进行插值混合,使光照过渡更加平滑。下图是 Radiance Cache 对比图,左图关闭,右图开启:
|
||||

|
||||
|
||||
## Importance Sampling
|
||||
Lumen 引入了业界降噪常用的方法:重要性采样(Importance Sampling),通过使用上一帧的 Radiance Cache 作为生成当前帧的光线方向的引导,使光线尽可能朝向光源方向追踪,这样就可以在不增加光线预算的情况下实现了降噪,如下所示:
|
||||

|
||||
|
||||
## Shadow
|
||||
阴影的本质就是光照的可见性。Lumen 为 Surface Cache 标记 Shadow Mask,这样在 Lighting 时直接乘以这个 Mask 即可。Shadow Mask 可以直接使用之前已经生成的Virtual Shadow Map。
|
||||
|
||||
但是这并不够完整,原因在于 **(Virtual)Shadow 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 的对比图,降噪效果明显:
|
||||

|
||||
|
||||
## 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**。
|
||||

|
||||
|
||||

|
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Lumen学习笔记(3)——代码分析
|
||||
date: 2023-02-10 15:32:29
|
||||
excerpt:
|
||||
tags: Lumen
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# Lumen
|
||||
Lumen的逻辑开始于DeferredShadingRenderer.cpp的`RenderLumenSceneLighting()`
|
||||
|
||||
|
@@ -0,0 +1,172 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-12-28 21:56:29
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
相关论文地址:
|
||||
- Reflective Shadow Maps: https://users.soe.ucsc.edu/~pang/160/s13/proposal/mijallen/proposal/media/p203-dachsbacher.pdf
|
||||
- Light Propagation Volumes: https://ericpolman.com/
|
||||
- VXGI:
|
||||
- https://on-demand.gputechconf.com/gtc/2014/preseintations/S4552-rt-voxel-based-global-illumination-gpus.pdf
|
||||
- https://docs.nvidia.com/gameworks/content/gameworkslibrary/visualfx/vxgi/product.html
|
||||
- https://developer.download.nvidia.com/assets/gameworks/downloads/secure/GDC18_Slides/Advances%20in%20Real-Time%20Voxel-Based%20GI%20-%20GDC%202018.pdf?HAwkc19IdhKMFWUs8jSk-If6y4QowlgUUrY8F9SL1-4qnhWWo-u5_gptg4ITWjMJCd1JrFbGmBw4-ztQCJ4M9Bh61am6lFCerKQVApEjN-HQ4chiSb5MLRLY41TyIse6_XW2DpNAe0_4inrYkZqxVb2iGiyZQhxwzfZ40dByP-X8jRYfWhJVsC1aSIqZ8BkooGkjUoUUGw2r7AAxyWVQole9a4je_76g04KtUwlONQ==
|
||||
- Lumen:
|
||||
- [https://youtu.be/2GYXuM10riw](https://youtu.be/2GYXuM10riw)
|
||||
- https://www.advances.realtimerendering.com/s2021/Radiance%20Caching%20for%20real-time%20Global%20Illumination%20(SIGGRAPH%202021).pptx
|
||||
- SSGI: https://www.ea.com/frostbite/news/stochastic-screen-space-reflections
|
||||
- DDGI:
|
||||
- https://zhuanlan.zhihu.com/p/404520592
|
||||
- https://developer.download.nvidia.com/video/gputechconf/gtc/2019/presentation/s9900-irradiance-fields-rtx-diffuse-global-illumination-for-local-and-cloud-graphics.pdf
|
||||
# HIZ Tracing
|
||||
|
||||
![[HierachicalTracing2.png]]
|
||||
|
||||
![[HierachicalTracing3.png]]
|
||||
|
||||
![[HierachicalTracing1.png]]# Reflective Shadow Maps(RSM,2005)
|
||||
论文地址:https://users.soe.ucsc.edu/~pang/160/s13/proposal/mijallen/proposal/media/p203-dachsbacher.pdf
|
||||
|
||||
Let's inject light in. (Photon Mapping?)
|
||||
解决如何把“光”注入到场景中。
|
||||
|
||||
**Cool Ideas**
|
||||
- Easy to be implemented
|
||||
- Photon Injection with RSM
|
||||
- Cone sampling in mipmap
|
||||
- Low-res Indirect illumination with error check
|
||||
**Cons**
|
||||
- Single bounce
|
||||
- No visibility check for indirect illumination
|
||||
|
||||
# Light Propagation Volumeshj
|
||||
|
||||
**"Freeze" the Radiance in Voxel**
|
||||
Light Injection
|
||||
- Pre-subdivide the scene into a 3D grid
|
||||
- For each grid cell, find enclosed virtual light sources
|
||||
- Sum up their directional radiance distribution
|
||||
- Project to first 2 orders of SHs (4 in total)
|
||||
|
||||
# Sparse Voxel Octree for Real-time Global Illumination (SVOGI)
|
||||
|
||||
## Shading with Cone Tracing in Voxel Tree
|
||||
Pass 2 from the camera
|
||||
- Emit some cones based on diffuse+specular BRDF
|
||||
- Query in octree based on the (growing) size of the cone
|
||||
|
||||
# VXGI(Nvidia UE4 Plugins)
|
||||
**Problems in VXGI**
|
||||
Incorrect Occlusion(opacity)
|
||||
- naively combine the opacity with alpha blending.
|
||||
Light Leaking
|
||||
- when occlusion wall is much smaller than voxel size
|
||||
|
||||
# SSGI
|
||||
SIGGRAPH2015:Advances in Real-Time Rendering course
|
||||
|
||||
**Radiance Sampling in Screen Space**
|
||||
For each fragment:
|
||||
- **Step 1**: compute many reflection
|
||||
rays
|
||||
- **Step 2**: march along ray direction
|
||||
**(in depth gbuffer)**
|
||||
- **Step3**: use color of hit point as
|
||||
indirect lighting
|
||||
![[SSGI1.png]]
|
||||
|
||||
![[SSGI2.png]]
|
||||
中间的RayCast使用RayMarching进行。但使用LinearRayMarching相对比较消耗资源,所以采用HierachicalTracing。
|
||||
|
||||
![[SSGI3.png]]
|
||||
最低层级
|
||||
![[HierachicalTracing1.png]]
|
||||
层级+1,相当于RayMarching2个像素。
|
||||
![[HierachicalTracing2.png]]
|
||||
层级+2,相当于RayMarching4个像素。此时RayHit。
|
||||
![[HierachicalTracing3.png]]
|
||||
回退当前HiZ像素的上一层级。
|
||||
![[HierachicalTracing4.png]]
|
||||
回退当前HiZ像素的上上一层级。
|
||||
![[HierachicalTracing5.png]]
|
||||
找到RayHit位置。
|
||||
![[HierachicalTracing6.png]]
|
||||
|
||||
## Ray Reuse among Neighbor Pixels
|
||||
- Store **hitpoint data**
|
||||
- Assume visibility is the same between neighbors
|
||||
- Regard **ray to neighbor's hitpoint** as valid
|
||||
|
||||
![[ConeTracingWithMipmapFiltering1.png]]
|
||||
|
||||
# Lumen
|
||||
## Phase1: Fast Ray Track in Any Hardward
|
||||
Signed Distance Field(SDF)
|
||||
1. 它是均匀的。
|
||||
2. 在空间上是连续的。
|
||||
|
||||
### Cone Tracing with SDF(ie. Soft Shadow)
|
||||
|
||||
## Phase2:Radiance Injection and Cacheing
|
||||
![[MeshCard1.png]]
|
||||
MeshCard的目的是为了将直接光照存储在模型上(Surface Cache)
|
||||
|
||||
![[GenerateSurfaceCache1.png]]
|
||||
![[GenerateSurfaceCache2.png]]
|
||||
|
||||
最终目的是通过SurfaceCache这4张图渲染出SurfaceCache FinalLighting
|
||||
![[LightingCachePipeline1.png]]
|
||||
1. 计算SurfaceCache DirectLighting
|
||||
2. 通过1计算体素光照。
|
||||
3. 通过体素光照来计算间接照明。
|
||||
4. 最终计算Surface Cache FinalLighting。
|
||||
|
||||
以此进行循环。![[DirectLighting1.png]]
|
||||
|
||||
针对多个光源会渲染对应数量的cache,之后累加在一起。
|
||||
![[MultiLightSurfaceCache.png]]
|
||||
|
||||
### Voxel Lighting to Sample
|
||||
![[VoxelLightingToSample.png]]
|
||||
对于近处的物体可以准确拿到Hit到物体的上一点的Radiance;对于远处的物体,会以相机坐标轴生成一个Voxel形式的表达,之后通过Global SDF拿到对应的Radiance。
|
||||
***PS. 该Voxel存储的数据为:每个面对应方向上被其他直接照明照亮的亮度。***
|
||||
|
||||
![[VoxelClipmap.png]]![[BuildVoxelFaces.png]]
|
||||
其Voxel的计算是基于SDF的。
|
||||
|
||||
![[InjectLightIntoClipmap.png]]
|
||||
|
||||
![[IndirectLighting.png]]
|
||||
在SurfaceCache中 8x8的tile中(行与列间隔4个像素,放置2个探针),进行4次空间Voxel采样。
|
||||
之后进行球谐插值:
|
||||
![[IndirectLighting_SHLerp.png]]![[Per-PixelIndirectLighting.png]]![[CombineLighting.png]]
|
||||
|
||||
## Phase3:Build a lot of probes with Different Kinds
|
||||
![[ScreenProbeStructure.png]]
|
||||
|
||||
每隔 16 * 16 个像素采样一个ScreenSpaceProbe。采样的内容是Radiance与HitDistance,以8面体(Octahedron Mapping)的方式进行存储。
|
||||
|
||||
![[ScreenProbePlacement.png]]![[PlaneDistanceWeightingOfProbeInterpolation.png]]![[DetectNon-InterpolatableCases.png]]![[ScreenProbeAtlas.png]]
|
||||
将重采样的结果(部分区域的屏幕空间探针因为实际空间距离太远,进行插值没有意义,所以需要额外填充探针进行重采样)存在Atlas下面的空出来的区域。
|
||||
|
||||
![[ScreenProbeJitter.png]]
|
||||
|
||||
### 重要性采样
|
||||
![[ApproximateRadianceImportanceFromLastFrameProbes.png]]![[AccumulateNormalDistributionNearby.png]]![[NearbyNormalAccumulation.png]]![[StructuredImportanceSampling.png]]![[FixBudgetImportanceSampling.png]]
|
||||
|
||||
### Denoise
|
||||
![[Denoise_SpatialFilteringForProbe.png]]![[Denoise_GatherRadianceFromNeightbors.png]]![[ClampDistanceMismatching.png]]
|
||||
|
||||
### WorldSpace Probes and Ray Connecting
|
||||
![[WorldSapceRadianceCache.png]]![[WorldSpaceRadianceCache.png]]![[ConnectingRays.png]]![[ConnectingRays2.png]]![[ConnectingRay3.png]]![[PlacementAndCacheing.png]]
|
||||
|
||||
## Phase4:Shading Full Pixels with Screen Space Probes
|
||||
在ScreenSpaceProbes将场景Radiance都收集好。
|
||||
![[ConvertProbeRadianceTo3rdOrderSphericalHarmonic.png]]
|
||||
|
||||
![[FinalIntegarationWithSH.png]]
|
||||
|
||||
## 性能问题
|
||||
![[LumenTrackMethod.png]]
|
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: 未命名
|
||||
date: 2025-03-27 16:31:21
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- [游戏引擎随笔 0x29:UE5 Lumen 源码解析(一)原理篇](https://zhuanlan.zhihu.com/p/499713106)
|
||||
- [游戏引擎随笔 0x30:UE5 Lumen 源码解析(二)Surface Cache 篇](https://zhuanlan.zhihu.com/p/516141543)
|
||||
- [游戏引擎随笔 0x31:UE5 Lumen 源码解析(三)光影篇](https://zhuanlan.zhihu.com/p/517756126)
|
||||
- [游戏引擎随笔 0x32:UE5 Lumen 源码解析(四)Radiosity 篇](https://zhuanlan.zhihu.com/p/522165652)
|
||||
- [游戏引擎随笔 0x33:UE5 Lumen 源码解析(五)Voxel Lighting 篇](https://zhuanlan.zhihu.com/p/525521811)
|
||||
- [游戏引擎随笔 0x34:UE5 Lumen 源码解析(六)Importance Sampling 篇](https://zhuanlan.zhihu.com/p/531442379)
|
||||
|
||||
# LumenScreenProbeGather
|
9
03-UnrealEngine/Rendering/RenderFeature/Mesh形变算法(FFD).md
Normal file
9
03-UnrealEngine/Rendering/RenderFeature/Mesh形变算法(FFD).md
Normal 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
|
82
03-UnrealEngine/Rendering/RenderFeature/Nanite学习笔记.md
Normal file
82
03-UnrealEngine/Rendering/RenderFeature/Nanite学习笔记.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Nanite学习笔记
|
||||
date: 2022-09-20 17:07:44
|
||||
excerpt:
|
||||
tags: Nanite
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- UE5.4 Nanite源码阅读(一):NaniteStreaming:https://zhuanlan.zhihu.com/p/29306422635
|
||||
- UE5.4 Nanite源码阅读(二):NaniteCulling:https://zhuanlan.zhihu.com/p/30057230872
|
||||
- UE5.4 Nanite源码阅读(三):NaniteRasterize:https://zhuanlan.zhihu.com/p/30655592664
|
||||
- UE5.4 Nanite源码阅读(四):NaniteShading:https://zhuanlan.zhihu.com/p/30662515655
|
||||
|
||||
# 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)窗口。
|
||||

|
||||
|
||||
# 对启用了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关卡1(Import LOD Level 1)** 。
|
||||
4. 在 **LOD设置(LOD Settings)** 分段下,将 **最低LOD(Minimum LOD)** 设置为 **1** 。这会使得Nanite生成的回退网格体被忽略。
|
||||
|
||||
复杂碰撞是一种特殊情况。使用 **通用设置(General Settings)** 下 **用于碰撞的LOD(LOD 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中会增加不必要的开销。
|
File diff suppressed because it is too large
Load Diff
123
03-UnrealEngine/Rendering/RenderFeature/UE5 3DGaussians 插件笔记.md
Normal file
123
03-UnrealEngine/Rendering/RenderFeature/UE5 3DGaussians 插件笔记.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: UE5 3DGaussians 插件笔记
|
||||
date: 2023-12-22 11:44:33
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# c++
|
||||
插件的c++部分主要实现了
|
||||
- FThreeDGaussians——可以理解为一个场景或者根节点
|
||||
- FThreeDGaussiansTree——类似BVH的空间切分树
|
||||
- FThreeDGaussiansData——具体数据
|
||||
- ply点云文件导入,流程如下
|
||||
- FThreeDGaussiansImporterModule::PluginButtonClicked()
|
||||
- LoadPly(),载入`TArray<FThreeDGaussian>`数据。
|
||||
- 进行排序
|
||||
- 初始化一个`TArray<FThreeDGaussianSortPair> unsorted`并且进行排序。
|
||||
- 取得各种排序用参数DO_SPLIT_BY_3D_MORTON_ORDER、DO_SPLIT_BY_DISTANCE、MAX_TEXTURE_WIDHT、MAX_NUM_PARTICLES
|
||||
- 采用莫顿码分割法、距离排序法。
|
||||
- 莫顿码分割法:使用莫顿码进行排序,之后进行空间分割,构建一个三维加速结构。当当前区域点云数量小于MAX_NUM_PARTICLES后调用CreateDatum()。
|
||||
- 距离排序法:根据Position上三个分量中最大绝对值进行排序,之后调用CreateDatum()。
|
||||
- CreateDatum()
|
||||
- Sort3dMortonOrder()排序。
|
||||
- CreateExr()创建Exr Texture文件。
|
||||
- 将上一步创建的文件导入UE。
|
||||
- CreateActorBpSubclass(),创建3DGaussians蓝图Actor,并且查找SetData函数并且将数据塞入。
|
||||
|
||||
## FThreeDGaussians代码
|
||||
```c++
|
||||
struct FThreeDGaussiansData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FThreeDGaussiansData() {}
|
||||
FThreeDGaussiansData(const TArray<UTexture2D*>& textures, const FVector3f& in_minPos, const FVector3f& in_maxPos)
|
||||
{
|
||||
minPos = in_minPos;
|
||||
maxPos = in_maxPos;
|
||||
textureWidth = textures[0]->GetSizeX();
|
||||
position = textures[0];
|
||||
rotation = textures[1];
|
||||
scaleAndOpacity = textures[2];
|
||||
|
||||
for (int i = 3; i < textures.Num(); i++)
|
||||
{
|
||||
sh.Add(textures[i]);
|
||||
}
|
||||
}
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") FVector3f minPos = FVector3f::Zero();
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") FVector3f maxPos = FVector3f::Zero();
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") int32 textureWidth = -1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") UTexture2D* position;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") UTexture2D* rotation;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") UTexture2D* scaleAndOpacity;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") TArray<UTexture2D*> sh;
|
||||
};
|
||||
|
||||
/** 类似BVH的控件数据结构 */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FThreeDGaussiansTree
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FThreeDGaussiansTree() {}
|
||||
|
||||
// Axis for split (x=0, y=1, z=2)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") int32 splitAxis = -1;
|
||||
// max value of the position of gaussian in child0 or leaf0 in "splitAxis" axis
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") float splitValue = 0.0f;
|
||||
|
||||
// index of child tree node (Index of TArray<FThreeDGaussiansTree> tree)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") int32 childIndex0 = -1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") int32 childIndex1 = -1;
|
||||
|
||||
// index of child data node (Index of TArray<FThreeDGaussiansData> data)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") int32 leafIndex0 = -1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") int32 leafIndex1 = -1;
|
||||
};
|
||||
|
||||
/* 作为3D高斯数据的载荷 */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FThreeDGaussians
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FThreeDGaussians() {}
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") TArray<FThreeDGaussiansData> data;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "3D Gaussians") TArray<FThreeDGaussiansTree> tree;
|
||||
};
|
||||
```
|
||||
|
||||
# BP_3D_Gaussians_Base
|
||||
- BeginPlay:判断三维加速结构是否还子节点,如果有则开启Tick进行排序。
|
||||
- Tick:根据摄像机位置对三维加速结构进行排序。
|
||||
- ConstructionScript:
|
||||
1. 添加Niagara粒子组件,一个FThreeDGaussiansData生成一个粒子组件。
|
||||
2. 设置Niagara资产:NS_3D_Gaussians_sh0_mesh(勾选mesh选项)、NS_3D_Gaussians_sh0(SH角度)、NS_3D_Gaussians_sh1、NS_3D_Gaussians_sh2、NS_3D_Gaussians_sh3
|
||||
3. 设置粒子材质属性:
|
||||
1. AlbedoTint
|
||||
2. 剔除设置:CropEnabled、CropTranslation、CropRotation、CropExtent
|
||||
3. 数据贴图(FThreeDGaussiansData):texture_width、texture_position、texture_rotation、texture_scaleAndOpacity。
|
||||
4. SH数据贴图(FThreeDGaussiansData):根据角度设置Niagara里texture_sh_X的贴图。
|
||||
5. 社会中剔除空间 CropTranslations、CropRotators、CropExtents、KillTranslations、KillRotators、KillExtents。
|
||||
|
||||
|
||||
# 实现思路
|
||||
## 4D高斯
|
||||
1. 实现一个Niagara Module实现对Texture2DArray贴图采样。
|
||||
2. ~~使用Niagara Cache~~。
|
||||
3. 考虑 TextureStream机制以此节约显存。
|
||||
|
||||
## 使用RVT实现3D高斯 LOD思路
|
||||
AI数据侧:
|
||||
1. 确定点云数据是否可以划分成四叉树的数据结构,也就是将一堆点云按照一个**距离阈值** 进行分割,最终形成一个四叉树。
|
||||
1. 确定是否可以生成金字塔结构贴图(直接写入到Mipmap结构里),或者生成多张基于2的幕长度贴图。
|
||||
|
||||
UE侧:
|
||||
目前已经测试过SVT可以放入到Niagara Texture Sampler中。同时也可以将SVT放到Texture2DArray中。
|
||||
1. 将3D高斯各种贴图制作成SVT之后塞入Texture2DArray,在Niagara中采样。
|
||||
2. 在Niagara中根据Niagara 粒子ID对SVT进行采样。
|
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: VirtualTexture学习笔记
|
||||
date: 2023-07-29 12:26:09
|
||||
excerpt:
|
||||
tags: VirtualTexture
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# 前言
|
||||
学习文章:
|
||||
- https://zhuanlan.zhihu.com/p/138484024
|
||||
|
||||
**Streaming VT** = 显存优化,但需要额外消耗一些性能。
|
||||
**Runtime VT** = 性能优化,可以预先将材质缓存到一张巨大的虚拟RT上,一般用于地形。
|
||||
|
||||
# VirtualTexture
|
||||
## 1. Software Virtual Texture
|
||||
在MegaTexture的基础上,id Software进一步提出了Virtual Texture的概念,这个概念取自于Virtual Memory,与虚拟内存类似的是,一个很大的Texture将不会全部加载到内存中,而是根据实际需求,将需要的部分加载。与虚拟内存不同的是,它不会阻塞执行,可以使用更高的mipmap来暂时显示,它对基于block的压缩贴图有很好的支持。 基本思路是,会将纹理的mipmap chain分割为相同大小的tile或page,这里的纹理叫虚纹理,然后通过某种映射,映射到一张内存中存在的纹理,这里的纹理是物理纹理,在游戏视野发生变化的时候,一部分物理纹理会被替换出去,一部分物理纹理会被加载。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
这样的机制不仅仅减少了带宽消耗和内存(显存)消耗,也带来了其他好处,比如有利于合批,而不用因为使用不同的Texture而打断合批,这样可以根据需求来组织几何使得更利于Culling,当然合批的好处是states change 变少。LightMap也可以预计算到一张大的Virtual Texture上,用来合批。
|
||||
|
||||
**其关键在于地址映射**,比如四叉树映射:
|
||||

|
||||
|
||||
# RuntimeVirtualTexture
|
||||
其主要目的是Cache材质计算结果。可以参考李文磊的视频:
|
||||
https://www.bilibili.com/video/BV1KK411L7Rg/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
|
||||
|
||||
使用步骤:
|
||||
1. 创建RuntimeVirtualTexture、RuntimeVirtualTextureActor、Landscape资产。
|
||||
2. 在RuntimeVirtualTextureActor指定RuntimeVirtualTexture与Landscape资产,并且复制Landscape的Bound。
|
||||
3. 在Landscape的RenderToVirtualTexture中指定RuntimeVirtualTexture资产。
|
||||
4. 修改Landscape的材质,分别添加VirtualTexture Input、Export节点。 顺便也可以修改VirtualTexturePassType(某些需求只需要将结果渲染到VT中,无需在MainPass中渲染)。
|
||||
5. 此时Landscape的材质大概率显示不正确,这时需要添加VirtualTextureOutputLevel来计算UV(为了匹配非VT情况,可以加入RuntimeVirtualTextureReplace节点进行判断。
|
||||
|
||||
# UDIMs Virtual Texture(多象限虚拟贴图)
|
||||
https://docs.unrealengine.com/5.0/en-US/streaming-virtual-texturing-in-unreal-engine/
|
||||
参看UDIM Support章节。大致的方法就是把多个贴图按照`BaseName.####.[Support Image Format]`进行命名,####代表了坐标。将这些贴图放到同一目录,之后只需要导入1001坐标的贴图即可自动全部导入。
|
||||

|
||||
工作流可以参考:
|
||||
- https://www.youtube.com/watch?v=SkUW4JSYrEo
|
||||
- https://www.youtube.com/watch?v=HhT9oEB7xks
|
||||
|
||||
# 其他
|
||||
## 贴图优化
|
||||
具体参看:Managing the Texture Streaming Pool:https://www.youtube.com/watch?v=uk3W8Zhahdg
|
||||
大致步骤:
|
||||
- 使用Tools - Audit - Statistics查看贴图占用显存以及场景中的使用数量。
|
||||
- 通过ViewMode - OptimizationViewModes - RequiredTextureResolution 查看贴图在场景中所需要的占用比例是否合适。
|
||||
- 使用批量编辑功能修改Texture资产的MaximumTextureSize到指定Mipmap层级的分辨率即可。
|
||||
|
||||
另一个种方式就是使用SVT。
|
179
03-UnrealEngine/Rendering/RenderFeature/VirualShadowMap优化笔记.md
Normal file
179
03-UnrealEngine/Rendering/RenderFeature/VirualShadowMap优化笔记.md
Normal 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. 植被问题。
|
||||

|
||||
|
||||
## 解决方法
|
||||
1.让场景模型启用Nanite。
|
||||

|
||||
|
||||
可以通过ViewMode - VirualShadowMap - Cache模式来显示缓存失效情况,,以此来找到非Nanaite物体。
|
||||
|
||||

|
||||
|
||||
之后使用Show > Visualize > 仅绘制导致VSM失效的几何体(Draw only Geometry Causing VSM Invalidation)来精准查找未开启Nanite的物体。
|
||||

|
||||
|
||||
2. 关闭远景天空球(SM_Skybox_Mesh)、雾气面片与近景3个人模型的 Cast Shadow选项。尝试启用远景天空球的Nanite选项。
|
||||
3. 植被分两种情况:有/无WorldPositionOffset。无的话直接转成Nanite即可。有的话建议去除 Cast Shadow选项,并且勾选Contact Shadow。
|
10
03-UnrealEngine/Rendering/RenderFeature/体积渲染相关文章.md
Normal file
10
03-UnrealEngine/Rendering/RenderFeature/体积渲染相关文章.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: 未命名
|
||||
date: 2025-05-03 18:12:31
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- 知乎文章
|
||||
- 烟雾的体积渲染方法:https://zhuanlan.zhihu.com/p/656758416
|
68
03-UnrealEngine/Rendering/RenderFeature/平面反射.md
Normal file
68
03-UnrealEngine/Rendering/RenderFeature/平面反射.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-07-19 22:09:12
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
```c++
|
||||
for (auto It = SceneCaptureComponent->HiddenActors.CreateConstIterator(); It; ++It)
|
||||
{
|
||||
AActor* Actor = *It;
|
||||
|
||||
if (Actor)
|
||||
{
|
||||
for (UActorComponent* Component : Actor->GetComponents())
|
||||
{
|
||||
if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(Component))
|
||||
{
|
||||
View->HiddenPrimitives.Add(PrimComp->ComponentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SceneCaptureComponent->PrimitiveRenderMode == ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList)
|
||||
{
|
||||
View->ShowOnlyPrimitives.Emplace();
|
||||
|
||||
for (auto It = SceneCaptureComponent->ShowOnlyComponents.CreateConstIterator(); It; ++It)
|
||||
{
|
||||
// If the primitive component was destroyed, the weak pointer will return NULL.
|
||||
UPrimitiveComponent* PrimitiveComponent = It->Get();
|
||||
if (PrimitiveComponent)
|
||||
{
|
||||
View->ShowOnlyPrimitives->Add(PrimitiveComponent->ComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto It = SceneCaptureComponent->ShowOnlyActors.CreateConstIterator(); It; ++It)
|
||||
{
|
||||
AActor* Actor = *It;
|
||||
|
||||
if (Actor)
|
||||
{
|
||||
for (UActorComponent* Component : Actor->GetComponents())
|
||||
{
|
||||
if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(Component))
|
||||
{
|
||||
View->ShowOnlyPrimitives->Add(PrimComp->ComponentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (SceneCaptureComponent->ShowOnlyComponents.Num() > 0 || SceneCaptureComponent->ShowOnlyActors.Num() > 0)
|
||||
{
|
||||
static bool bWarned = false;
|
||||
|
||||
if (!bWarned)
|
||||
{
|
||||
UE_LOG(LogRenderer, Log, TEXT("Scene Capture has ShowOnlyComponents or ShowOnlyActors ignored by the PrimitiveRenderMode setting! %s"), *SceneCaptureComponent->GetPathName());
|
||||
bWarned = true;
|
||||
}
|
||||
}
|
||||
|
||||
ViewFamily.Views.Add(View);
|
||||
```
|
45
03-UnrealEngine/Rendering/RenderFeature/深度相关.md
Normal file
45
03-UnrealEngine/Rendering/RenderFeature/深度相关.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: 深度相关
|
||||
date: 2024-09-09 16:49:08
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 相关代码
|
||||
位于common.ush
|
||||
```c++
|
||||
#define NearDepthValue (HAS_INVERTED_Z_BUFFER ? 1.0f : 0.0f)
|
||||
#define FarDepthValue (HAS_INVERTED_Z_BUFFER ? 0.0f : 1.0f)
|
||||
```
|
||||
|
||||
可以通过**DeviceZ == FarDepthValue**来判断是否处于最远端。
|
||||
```c++
|
||||
float DeviceZ = DepthReadDisabled ? FarDepthValue : LookupDeviceZ(UvBuffer);
|
||||
#endif
|
||||
|
||||
if (DeviceZ == FarDepthValue)
|
||||
{
|
||||
// Get the light disk luminance to draw
|
||||
LuminanceScale = SkyAtmosphere.SkyLuminanceFactor;
|
||||
#if SOURCE_DISK_ENABLED
|
||||
if (SourceDiskEnabled > 0)
|
||||
{
|
||||
PreExposedL += GetLightDiskLuminance(WorldPos, WorldDir, 0);
|
||||
#if SECOND_ATMOSPHERE_LIGHT_ENABLED
|
||||
PreExposedL += GetLightDiskLuminance(WorldPos, WorldDir, 1);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RENDERSKY_ENABLED==0
|
||||
// We should not render the sky and the current pixels are at far depth, so simply early exit.
|
||||
// We enable depth bound when supported to not have to even process those pixels.
|
||||
OutLuminance = PrepareOutput(float3(0.0f, 0.0f, 0.0f), float3(1.0f, 1.0f, 1.0f));
|
||||
|
||||
//Now the sky pass can ignore the pixel with depth == far but it will need to alpha clip because not all RHI backend support depthbound tests.
|
||||
// And the depthtest is already setup to avoid writing all the pixel closer than to the camera than the start distance (very good optimisation).
|
||||
// Since this shader does not write to depth or stencil it should still benefit from EArlyZ even with the clip (See AMD depth-in-depth documentation)
|
||||
clip(-1.0f);
|
||||
return;
|
||||
#endif
|
||||
```
|
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: 未命名
|
||||
date: 2025-03-20 18:07:58
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- [UE5 MeshShader初探](https://zhuanlan.zhihu.com/p/30320015352)
|
||||
- [UE5 MeshShader完整实践](https://zhuanlan.zhihu.com/p/30893647975)
|
77
03-UnrealEngine/Rendering/RenderingPipeline/GPUScene.md
Normal file
77
03-UnrealEngine/Rendering/RenderingPipeline/GPUScene.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-08-15 12:02:42
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- [UE5渲染--GPUScene与合并绘制](https://zhuanlan.zhihu.com/p/614758211)
|
||||
|
||||
# 相关类型
|
||||
- Primitive:
|
||||
- C++的数据类型_FPrimitiveUniformShaderParameters_(PrimitiveUniformShaderParameters.h)
|
||||
- Shader的数据FPrimitiveSceneData(SceneData.ush);
|
||||
- Instance:
|
||||
- C++的数据类型FInstanceSceneShaderData(InstanceUniformShaderParameters.h)
|
||||
- Shader的数据FInstanceSceneData(SceneData.ush);
|
||||
- Payload:
|
||||
- C++数据类型FPackedBatch、FPackedItem(InstanceCullingLoadBalancer.h)
|
||||
- Shader的数据类型FPackedInstanceBatch、FPackedInstanceBatchItem(InstanceCullingLoadBalancer.ush)
|
||||
|
||||
# DeferredShadingRenderer.cpp
|
||||
```c++
|
||||
Scene->GPUScene.Update(GraphBuilder, GetSceneUniforms(), *Scene, ExternalAccessQueue);
|
||||
|
||||
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
||||
{
|
||||
FViewInfo& View = Views[ViewIndex];
|
||||
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
|
||||
|
||||
Scene->GPUScene.UploadDynamicPrimitiveShaderDataForView(GraphBuilder, *Scene, View, ExternalAccessQueue);
|
||||
|
||||
Scene->GPUScene.DebugRender(GraphBuilder, *Scene, GetSceneUniforms(), View);
|
||||
}
|
||||
```
|
||||
|
||||
## GPUScene数据更新
|
||||
```c++
|
||||
// 函数调用关系
|
||||
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
|
||||
{
|
||||
void FGPUScene::Update(FRDGBuilder& GraphBuilder, FScene& Scene, FRDGExternalAccessQueue& ExternalAccessQueue)
|
||||
{
|
||||
void FGPUScene::UpdateInternal(FRDGBuilder& GraphBuilder, FScene& Scene, FRDGExternalAccessQueue& ExternalAccessQueue)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2、更新PrimitiveData
|
||||
|
||||

|
||||
|
||||
接下来,对标记为dirty的Primitive进行更新,更新逻辑为找到该Primitive在PrimitiveData Buffer里对应offset位置,对其进行更新。使用模板FUploadDataSourceAdapterScenePrimitives调用UploadGeneral()。
|
||||
|
||||

|
||||
|
||||
初始化5类Buffer的上传任务TaskContext
|
||||
|
||||
后面代码都是在对这个TaskContext进行初始化,然后启动任务,进行数据的上传。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
2.2.1、并行更新Primitive数据,将需要更新的PrimitiveCopy到Upload Buffer里,后续通过ComputeShader进行显存的上传然后更新到目标Buffer里;
|
||||
|
||||

|
||||
|
||||
2.2.2、同理InstanceSceneData以及InstancePayloadData数据的处理。
|
||||
|
||||
2.2.3、同理InstanceBVHUploader,LightmapUploader。
|
||||
|
||||
2.2.4、最后都会调用每个Uploader的***End()方法进行GPU显存的更新***。
|
||||
|
||||
总结:数据的上传都一样,将需要更新的数据Copy到对应的UploadBuffer对应的位置里,然后通过对应的ComputeShader进行更新到目标Buffer,在函数FRDGAsyncScatterUploadBuffer::End()里实现,截图:
|
||||
|
||||

|
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-10-16 14:39:40
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
|
||||
# Stencil相关
|
||||
BasePass:绘制Stencil
|
||||
=>
|
||||
CopyStencilToLightingChannels
|
||||
=>
|
||||
ClearStencil (SceneDepthZ) :清空深度缓存中的Stencil
|
||||
|
||||
## BasePass
|
||||
BasePass中进行LIGHTING_CHANNELS、DISTANCE_FIELD_REPRESENTATION、贴花方面的Mask Bit计算,设置到深度缓存的Stencil上。
|
||||
```c++
|
||||
template<bool bDepthTest, ECompareFunction CompareFunction>
|
||||
void SetDepthStencilStateForBasePass_Internal(FMeshPassProcessorRenderState& InDrawRenderState, ERHIFeatureLevel::Type FeatureLevel)
|
||||
{
|
||||
const static bool bStrataDufferPassEnabled = Strata::IsStrataEnabled() && Strata::IsDBufferPassEnabled(GShaderPlatformForFeatureLevel[FeatureLevel]);
|
||||
if (bStrataDufferPassEnabled)
|
||||
{
|
||||
SetDepthStencilStateForBasePass_Internal<bDepthTest, CompareFunction, GET_STENCIL_BIT_MASK(STRATA_RECEIVE_DBUFFER_NORMAL, 1) | GET_STENCIL_BIT_MASK(STRATA_RECEIVE_DBUFFER_DIFFUSE, 1) | GET_STENCIL_BIT_MASK(STRATA_RECEIVE_DBUFFER_ROUGHNESS, 1) | GET_STENCIL_BIT_MASK(DISTANCE_FIELD_REPRESENTATION, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)>(InDrawRenderState);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetDepthStencilStateForBasePass_Internal<bDepthTest, CompareFunction, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | GET_STENCIL_BIT_MASK(DISTANCE_FIELD_REPRESENTATION, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)>(InDrawRenderState);
|
||||
}
|
||||
}
|
||||
```
|
816
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Lighting.md
Normal file
816
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Lighting.md
Normal file
@@ -0,0 +1,816 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2025-02-11 11:30:34
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# FSortedLightSetSceneInfo
|
||||
有序的光源集合相关定义:
|
||||
```c++
|
||||
/** Data for a simple dynamic light. */
|
||||
class FSimpleLightEntry
|
||||
{
|
||||
public:
|
||||
FVector3f Color;
|
||||
float Radius;
|
||||
float Exponent;
|
||||
float InverseExposureBlend = 0.0f;
|
||||
float VolumetricScatteringIntensity;
|
||||
bool bAffectTranslucency;
|
||||
};
|
||||
|
||||
struct FSortedLightSceneInfo
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
// Note: the order of these members controls the light sort order!
|
||||
// Currently bHandledByLumen is the MSB and LightType is LSB /** The type of light. */ uint32 LightType : LightType_NumBits;
|
||||
/** Whether the light has a texture profile. */
|
||||
uint32 bTextureProfile : 1;
|
||||
/** Whether the light uses a light function. */
|
||||
uint32 bLightFunction : 1;
|
||||
/** Whether the light uses lighting channels. */
|
||||
uint32 bUsesLightingChannels : 1;
|
||||
/** Whether the light casts shadows. */
|
||||
uint32 bShadowed : 1;
|
||||
/** Whether the light is NOT a simple light - they always support tiled/clustered but may want to be selected separately. */
|
||||
uint32 bIsNotSimpleLight : 1;
|
||||
/* We want to sort the lights that write into the packed shadow mask (when enabled) to the front of the list so we don't waste slots in the packed shadow mask. */
|
||||
uint32 bDoesNotWriteIntoPackedShadowMask : 1;
|
||||
/**
|
||||
* True if the light doesn't support clustered deferred, logic is inverted so that lights that DO support clustered deferred will sort first in list
|
||||
* Super-set of lights supporting tiled, so the tiled lights will end up in the first part of this range.
|
||||
*/
|
||||
uint32 bClusteredDeferredNotSupported : 1;
|
||||
/** Whether the light should be handled by Lumen's Final Gather, these will be sorted to the end so they can be skipped */
|
||||
uint32 bHandledByLumen : 1;
|
||||
} Fields;
|
||||
/** Sort key bits packed into an integer. */
|
||||
int32 Packed;
|
||||
} SortKey;
|
||||
|
||||
const FLightSceneInfo* LightSceneInfo;
|
||||
int32 SimpleLightIndex;
|
||||
|
||||
/** Initialization constructor. */
|
||||
explicit FSortedLightSceneInfo(const FLightSceneInfo* InLightSceneInfo)
|
||||
: LightSceneInfo(InLightSceneInfo),
|
||||
SimpleLightIndex(-1)
|
||||
{
|
||||
SortKey.Packed = 0;
|
||||
SortKey.Fields.bIsNotSimpleLight = 1;
|
||||
}
|
||||
explicit FSortedLightSceneInfo(int32 InSimpleLightIndex)
|
||||
: LightSceneInfo(nullptr),
|
||||
SimpleLightIndex(InSimpleLightIndex)
|
||||
{
|
||||
SortKey.Packed = 0;
|
||||
SortKey.Fields.bIsNotSimpleLight = 0;
|
||||
}};
|
||||
|
||||
/**
|
||||
* Stores info about sorted lights and ranges.
|
||||
* The sort-key in FSortedLightSceneInfo gives rise to the following order:
|
||||
* [SimpleLights,Clustered,UnbatchedLights,LumenLights] * Note that some shadowed lights can be included in the clustered pass when virtual shadow maps and one pass projection are used. */struct FSortedLightSetSceneInfo
|
||||
{
|
||||
int32 SimpleLightsEnd;
|
||||
int32 ClusteredSupportedEnd;
|
||||
|
||||
/** First light with shadow map or */
|
||||
int32 UnbatchedLightStart;
|
||||
|
||||
int32 LumenLightStart;
|
||||
|
||||
FSimpleLightArray SimpleLights;
|
||||
TArray<FSortedLightSceneInfo, SceneRenderingAllocator> SortedLights;
|
||||
};
|
||||
```
|
||||
|
||||
## 开始获取有序光源集合
|
||||
UE的光源分配由`FDeferredShadingSceneRenderer::Render`内的`bComputeLightGrid`变量决定的,bComputeLightGrid的赋值逻辑如下:
|
||||
```c++
|
||||
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) {
|
||||
...
|
||||
bool bComputeLightGrid = false;
|
||||
|
||||
if (RendererOutput == ERendererOutput::FinalSceneColor)
|
||||
{
|
||||
if (bUseVirtualTexturing)
|
||||
{
|
||||
// Note, should happen after the GPU-Scene update to ensure rendering to runtime virtual textures is using the correctly updated scene
|
||||
FVirtualTextureSystem::Get().EndUpdate(GraphBuilder, MoveTemp(VirtualTextureUpdater), FeatureLevel);
|
||||
}
|
||||
|
||||
#if RHI_RAYTRACING
|
||||
GatherRayTracingWorldInstancesForView(GraphBuilder, ReferenceView, RayTracingScene, InitViewTaskDatas.RayTracingRelevantPrimitives);
|
||||
#endif // RHI_RAYTRACING
|
||||
|
||||
bool bAnyLumenEnabled = false;
|
||||
|
||||
{
|
||||
if (bUseGBuffer)
|
||||
{
|
||||
bComputeLightGrid = bRenderDeferredLighting;
|
||||
}
|
||||
else
|
||||
{
|
||||
bComputeLightGrid = ViewFamily.EngineShowFlags.Lighting;
|
||||
}
|
||||
|
||||
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
||||
{
|
||||
FViewInfo& View = Views[ViewIndex];
|
||||
bAnyLumenEnabled = bAnyLumenEnabled
|
||||
|| GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen
|
||||
|| GetViewPipelineState(View).ReflectionsMethod == EReflectionsMethod::Lumen;
|
||||
}
|
||||
|
||||
bComputeLightGrid |= (
|
||||
ShouldRenderVolumetricFog() ||
|
||||
VolumetricCloudWantsToSampleLocalLights(Scene, ViewFamily.EngineShowFlags) ||
|
||||
ViewFamily.ViewMode != VMI_Lit ||
|
||||
bAnyLumenEnabled ||
|
||||
VirtualShadowMapArray.IsEnabled() ||
|
||||
ShouldVisualizeLightGrid());
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
获取有序的光源集合
|
||||
```c++
|
||||
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) {
|
||||
...
|
||||
// 有序的光源集合.
|
||||
FSortedLightSetSceneInfo& SortedLightSet = *GraphBuilder.AllocObject<FSortedLightSetSceneInfo>();
|
||||
{
|
||||
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, SortLights);
|
||||
RDG_GPU_STAT_SCOPE(GraphBuilder, SortLights);
|
||||
ComputeLightGridOutput = GatherLightsAndComputeLightGrid(GraphBuilder, bComputeLightGrid, SortedLightSet);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
PS. 简单光源都可以被分块或分簇渲染,但对于非简单光源,只有满足以下条件的光源才可被分块或分簇渲染:
|
||||
- 没有使用光源的附加特性(TextureProfile、LightFunction、LightingChannel)。
|
||||
- 没有开启阴影。
|
||||
- 非平行光或矩形光。
|
||||
|
||||
另外,是否支持分块渲染,还需要光源场景代理的`IsTiledDeferredLightingSupported`返回true,长度为0的点光源才支持分块渲染。
|
||||
|
||||
## GatherLightsAndComputeLightGrid
|
||||
```c++
|
||||
FComputeLightGridOutput FDeferredShadingSceneRenderer::GatherLightsAndComputeLightGrid(FRDGBuilder& GraphBuilder, bool bNeedLightGrid, FSortedLightSetSceneInfo& SortedLightSet)
|
||||
{
|
||||
SCOPED_NAMED_EVENT(GatherLightsAndComputeLightGrid, FColor::Emerald);
|
||||
FComputeLightGridOutput Result = {};
|
||||
|
||||
bool bShadowedLightsInClustered = ShouldUseClusteredDeferredShading()
|
||||
&& CVarVirtualShadowOnePassProjection.GetValueOnRenderThread()
|
||||
&& VirtualShadowMapArray.IsEnabled();
|
||||
|
||||
const bool bUseLumenDirectLighting = ShouldRenderLumenDirectLighting(Scene, Views[0]);
|
||||
|
||||
GatherAndSortLights(SortedLightSet, bShadowedLightsInClustered, bUseLumenDirectLighting);
|
||||
|
||||
if (!bNeedLightGrid)
|
||||
{
|
||||
SetDummyForwardLightUniformBufferOnViews(GraphBuilder, ShaderPlatform, Views);
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool bAnyViewUsesForwardLighting = false;
|
||||
bool bAnyViewUsesLumen = false;
|
||||
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
||||
{
|
||||
const FViewInfo& View = Views[ViewIndex];
|
||||
bAnyViewUsesForwardLighting |= View.bTranslucentSurfaceLighting || ShouldRenderVolumetricFog() || View.bHasSingleLayerWaterMaterial || VolumetricCloudWantsToSampleLocalLights(Scene, ViewFamily.EngineShowFlags) || ShouldVisualizeLightGrid();
|
||||
bAnyViewUsesLumen |= GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen || GetViewPipelineState(View).ReflectionsMethod == EReflectionsMethod::Lumen;
|
||||
}
|
||||
|
||||
const bool bCullLightsToGrid = GLightCullingQuality
|
||||
&& (IsForwardShadingEnabled(ShaderPlatform) || bAnyViewUsesForwardLighting || IsRayTracingEnabled() || ShouldUseClusteredDeferredShading() ||
|
||||
bAnyViewUsesLumen || ViewFamily.EngineShowFlags.VisualizeMeshDistanceFields || VirtualShadowMapArray.IsEnabled());
|
||||
|
||||
// Store this flag if lights are injected in the grids, check with 'AreLightsInLightGrid()'
|
||||
bAreLightsInLightGrid = bCullLightsToGrid;
|
||||
|
||||
Result = ComputeLightGrid(GraphBuilder, bCullLightsToGrid, SortedLightSet);
|
||||
|
||||
return Result;
|
||||
}
|
||||
```
|
||||
|
||||
- GatherAndSortLights:收集与排序当前场景中所有的可见光源(当前View)。
|
||||
- ComputeLightGrid:是在锥体空间(frustum space)裁剪局部光源和反射探针到3D格子中,构建每个视图相关的光源列表和格子。
|
||||
|
||||
# RenderLights() -> RenderLight()
|
||||
|
||||
## InternalRenderLight()
|
||||
|
||||
## DeferredLightVertexShaders
|
||||
```c++
|
||||
// 输入参数.
|
||||
struct FInputParams
|
||||
{
|
||||
float2 PixelPos;
|
||||
float4 ScreenPosition;
|
||||
float2 ScreenUV;
|
||||
float3 ScreenVector;
|
||||
};
|
||||
|
||||
// 派生参数.
|
||||
struct FDerivedParams
|
||||
{
|
||||
float3 CameraVector;
|
||||
float3 WorldPosition;
|
||||
};
|
||||
|
||||
// 获取派生参数.
|
||||
FDerivedParams GetDerivedParams(in FInputParams Input, in float SceneDepth)
|
||||
{
|
||||
FDerivedParams Out;
|
||||
#if LIGHT_SOURCE_SHAPE > 0
|
||||
// With a perspective projection, the clip space position is NDC * Clip.w
|
||||
// With an orthographic projection, clip space is the same as NDC
|
||||
float2 ClipPosition = Input.ScreenPosition.xy / Input.ScreenPosition.w * (View.ViewToClip[3][3] < 1.0f ? SceneDepth : 1.0f);
|
||||
Out.WorldPosition = mul(float4(ClipPosition, SceneDepth, 1), View.ScreenToWorld).xyz;
|
||||
Out.CameraVector = normalize(Out.WorldPosition - View.WorldCameraOrigin);
|
||||
#else
|
||||
Out.WorldPosition = Input.ScreenVector * SceneDepth + View.WorldCameraOrigin;
|
||||
Out.CameraVector = normalize(Input.ScreenVector);
|
||||
#endif
|
||||
return Out;
|
||||
}
|
||||
|
||||
Texture2D<uint> LightingChannelsTexture;
|
||||
|
||||
uint GetLightingChannelMask(float2 UV)
|
||||
{
|
||||
uint2 IntegerUV = UV * View.BufferSizeAndInvSize.xy;
|
||||
return LightingChannelsTexture.Load(uint3(IntegerUV, 0)).x;
|
||||
}
|
||||
|
||||
float GetExposure()
|
||||
{
|
||||
return View.PreExposure;
|
||||
}
|
||||
```
|
||||
|
||||
向往文章中的SetupLightDataForStandardDeferred()变为InitDeferredLightFromUniforms()。位于LightDataUniform.ush。
|
||||
```c++
|
||||
FDeferredLightData InitDeferredLightFromUniforms(uint InLightType)
|
||||
{
|
||||
const bool bIsRadial = InLightType != LIGHT_TYPE_DIRECTIONAL;
|
||||
|
||||
FDeferredLightData Out;
|
||||
Out.TranslatedWorldPosition = GetDeferredLightTranslatedWorldPosition();
|
||||
Out.InvRadius = DeferredLightUniforms.InvRadius;
|
||||
Out.Color = DeferredLightUniforms.Color;
|
||||
Out.FalloffExponent = DeferredLightUniforms.FalloffExponent;
|
||||
Out.Direction = DeferredLightUniforms.Direction;
|
||||
Out.Tangent = DeferredLightUniforms.Tangent;
|
||||
Out.SpotAngles = DeferredLightUniforms.SpotAngles;
|
||||
Out.SourceRadius = DeferredLightUniforms.SourceRadius;
|
||||
Out.SourceLength = bIsRadial ? DeferredLightUniforms.SourceLength : 0;
|
||||
Out.SoftSourceRadius = DeferredLightUniforms.SoftSourceRadius;
|
||||
Out.SpecularScale = DeferredLightUniforms.SpecularScale;
|
||||
Out.ContactShadowLength = abs(DeferredLightUniforms.ContactShadowLength);
|
||||
Out.ContactShadowLengthInWS = DeferredLightUniforms.ContactShadowLength < 0.0f;
|
||||
Out.ContactShadowCastingIntensity = DeferredLightUniforms.ContactShadowCastingIntensity;
|
||||
Out.ContactShadowNonCastingIntensity = DeferredLightUniforms.ContactShadowNonCastingIntensity;
|
||||
Out.DistanceFadeMAD = DeferredLightUniforms.DistanceFadeMAD;
|
||||
Out.ShadowMapChannelMask = DeferredLightUniforms.ShadowMapChannelMask;
|
||||
Out.ShadowedBits = DeferredLightUniforms.ShadowedBits;
|
||||
Out.bInverseSquared = bIsRadial && DeferredLightUniforms.FalloffExponent == 0; // Directional lights don't use 'inverse squared attenuation'
|
||||
Out.bRadialLight = bIsRadial;
|
||||
Out.bSpotLight = InLightType == LIGHT_TYPE_SPOT;
|
||||
Out.bRectLight = InLightType == LIGHT_TYPE_RECT;
|
||||
|
||||
Out.RectLightData.BarnCosAngle = DeferredLightUniforms.RectLightBarnCosAngle;
|
||||
Out.RectLightData.BarnLength = DeferredLightUniforms.RectLightBarnLength;
|
||||
Out.RectLightData.AtlasData.AtlasMaxLevel = DeferredLightUniforms.RectLightAtlasMaxLevel;
|
||||
Out.RectLightData.AtlasData.AtlasUVOffset = DeferredLightUniforms.RectLightAtlasUVOffset;
|
||||
Out.RectLightData.AtlasData.AtlasUVScale = DeferredLightUniforms.RectLightAtlasUVScale;
|
||||
|
||||
Out.HairTransmittance = InitHairTransmittanceData();
|
||||
return Out;
|
||||
}
|
||||
```
|
||||
|
||||
### DeferredLightPixelMain
|
||||
```c++
|
||||
void DeferredLightPixelMain(
|
||||
#if LIGHT_SOURCE_SHAPE > 0
|
||||
float4 InScreenPosition : TEXCOORD0,
|
||||
#else
|
||||
float2 ScreenUV : TEXCOORD0,
|
||||
float3 ScreenVector : TEXCOORD1,
|
||||
#endif
|
||||
float4 SVPos : SV_POSITION,
|
||||
out float4 OutColor : SV_Target0
|
||||
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
|
||||
, out float3 OutOpaqueRoughRefractionSceneColor : SV_Target1
|
||||
, out float3 OutSubSurfaceSceneColor : SV_Target2
|
||||
#endif
|
||||
)
|
||||
{
|
||||
const float2 PixelPos = SVPos.xy;
|
||||
OutColor = 0;
|
||||
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
|
||||
OutOpaqueRoughRefractionSceneColor = 0;
|
||||
OutSubSurfaceSceneColor = 0;
|
||||
#endif
|
||||
|
||||
// Convert input data (directional/local light)
|
||||
// 计算屏幕UV
|
||||
FInputParams InputParams = (FInputParams)0;
|
||||
InputParams.PixelPos = SVPos.xy;
|
||||
#if LIGHT_SOURCE_SHAPE > 0
|
||||
InputParams.ScreenPosition = InScreenPosition;
|
||||
InputParams.ScreenUV = InScreenPosition.xy / InScreenPosition.w * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;
|
||||
InputParams.ScreenVector = 0;
|
||||
#else
|
||||
InputParams.ScreenPosition = 0;
|
||||
InputParams.ScreenUV = ScreenUV;
|
||||
InputParams.ScreenVector = ScreenVector;
|
||||
#endif
|
||||
|
||||
#if STRATA_ENABLED
|
||||
|
||||
FStrataAddressing StrataAddressing = GetStrataPixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Strata.MaxBytesPerPixel);
|
||||
FStrataPixelHeader StrataPixelHeader = UnpackStrataHeaderIn(Strata.MaterialTextureArray, StrataAddressing, Strata.TopLayerTexture);
|
||||
|
||||
BRANCH
|
||||
if (StrataPixelHeader.BSDFCount > 0 // This test is also enough to exclude sky pixels
|
||||
#if USE_LIGHTING_CHANNELS
|
||||
//灯光通道逻辑
|
||||
&& (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)
|
||||
#endif
|
||||
)
|
||||
{
|
||||
//通过SceneDepth获取的CameraVector以及当前像素的世界坐标
|
||||
const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
|
||||
const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);
|
||||
|
||||
//设置获取光源各种信息
|
||||
FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);
|
||||
UpdateLightDataColor(LightData, InputParams, DerivedParams);//根据当前世界坐标计算LightData.Color *= 大气&云&阴影的衰减值 * IES灯亮度(非IES灯数值为1)
|
||||
|
||||
float3 V =-DerivedParams.CameraVector;
|
||||
float3 L = LightData.Direction; // Already normalized
|
||||
float3 ToLight = L;
|
||||
float LightMask = 1;
|
||||
if (LightData.bRadialLight)
|
||||
{
|
||||
LightMask = GetLocalLightAttenuation(DerivedParams.TranslatedWorldPosition, LightData, ToLight, L);
|
||||
}
|
||||
|
||||
if (LightMask > 0)
|
||||
{
|
||||
FShadowTerms ShadowTerms = { StrataGetAO(StrataPixelHeader), 1.0, 1.0, InitHairTransmittanceData() };
|
||||
float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);
|
||||
|
||||
float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8);
|
||||
const uint FakeShadingModelID = 0;
|
||||
const float FakeContactShadowOpacity = 1.0f;
|
||||
float4 PrecomputedShadowFactors = StrataReadPrecomputedShadowFactors(StrataPixelHeader, PixelPos, SceneTexturesStruct.GBufferETexture);
|
||||
GetShadowTerms(SceneDepth, PrecomputedShadowFactors, FakeShadingModelID, FakeContactShadowOpacity,
|
||||
LightData, DerivedParams.TranslatedWorldPosition, L, LightAttenuation, Dither, ShadowTerms);
|
||||
|
||||
FStrataDeferredLighting StrataLighting = StrataDeferredLighting(
|
||||
LightData,
|
||||
V,
|
||||
L,
|
||||
ToLight,
|
||||
LightMask,
|
||||
ShadowTerms,
|
||||
Strata.MaterialTextureArray,
|
||||
StrataAddressing,
|
||||
StrataPixelHeader);
|
||||
|
||||
OutColor += StrataLighting.SceneColor;
|
||||
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
|
||||
OutOpaqueRoughRefractionSceneColor += StrataLighting.OpaqueRoughRefractionSceneColor;
|
||||
OutSubSurfaceSceneColor += StrataLighting.SubSurfaceSceneColor;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#else // STRATA_ENABLED
|
||||
//取得屏幕空间数据(FGbufferData、AO)
|
||||
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InputParams.ScreenUV);
|
||||
// Only light pixels marked as using deferred shading
|
||||
BRANCH if (ScreenSpaceData.GBuffer.ShadingModelID > 0
|
||||
#if USE_LIGHTING_CHANNELS
|
||||
&& (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)
|
||||
#endif
|
||||
)
|
||||
{
|
||||
//通过SceneDepth获取的CameraVector以及当前像素的世界坐标
|
||||
const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
|
||||
const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);
|
||||
|
||||
//设置获取光源各种信息
|
||||
FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);
|
||||
UpdateLightDataColor(LightData, InputParams, DerivedParams);//根据当前世界坐标计算LightData.Color *= 大气&云&阴影的衰减值 * IES灯亮度(非IES灯数值为1)
|
||||
|
||||
|
||||
#if USE_HAIR_COMPLEX_TRANSMITTANCE
|
||||
//针对ShadingModel Hair(同时需要CustomData.a > 0)计算头发散射结果
|
||||
if (ScreenSpaceData.GBuffer.ShadingModelID == SHADINGMODELID_HAIR && ShouldUseHairComplexTransmittance(ScreenSpaceData.GBuffer))
|
||||
{
|
||||
LightData.HairTransmittance = EvaluateDualScattering(ScreenSpaceData.GBuffer, DerivedParams.CameraVector, -DeferredLightUniforms.Direction);
|
||||
}
|
||||
#endif
|
||||
//计算当前像素的抖动值
|
||||
float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8);
|
||||
|
||||
float SurfaceShadow = 1.0f;
|
||||
|
||||
float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);//根绝是否开启VSM 分别从VirtualShadowMap 或者 LightAttenuationTexture(上一阶段渲染的ShadowProjction) 获取灯光衰减值。
|
||||
float4 Radiance = GetDynamicLighting(DerivedParams.TranslatedWorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, LightAttenuation, Dither, uint2(InputParams.PixelPos), SurfaceShadow);
|
||||
|
||||
OutColor += Radiance;
|
||||
}
|
||||
|
||||
#endif // STRATA_ENABLED
|
||||
|
||||
// RGB:SceneColor Specular and Diffuse
|
||||
// A:Non Specular SceneColor Luminance
|
||||
// So we need PreExposure for both color and alpha
|
||||
OutColor.rgba *= GetExposure();
|
||||
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
|
||||
// Idem
|
||||
OutOpaqueRoughRefractionSceneColor *= GetExposure();
|
||||
OutSubSurfaceSceneColor *= GetExposure();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
#### GetLightAttenuationFromShadow() => GetPerPixelLightAttenuation()
|
||||
原文:https://zhuanlan.zhihu.com/p/23216110797
|
||||
有提到阴影模糊问题。
|
||||
FDeferredLightPS::FParameters GetDeferredLightPSParameters()可以看到该Sampler的模式是Point模式。
|
||||
```c++
|
||||
float4 GetPerPixelLightAttenuation(float2 UV)
|
||||
{
|
||||
return DecodeLightAttenuation(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler, UV, 0));
|
||||
}
|
||||
```
|
||||
|
||||
之后可以仿照GetPerPixelLightAttenuation写一个针对ToonShadow的函数:
|
||||
```c++
|
||||
//对卡通阴影进行降采样抗锯齿
|
||||
float4 GetPerPixelLightAttenuationToonAA(float2 UV)
|
||||
{
|
||||
int texture_x, texture_y;
|
||||
LightAttenuationTexture.GetDimensions(texture_x, texture_y);
|
||||
|
||||
float2 texelSize = float2(1.0 / texture_x, 1.0 / texture_y);
|
||||
|
||||
float2 sampleOffsets[4] = {
|
||||
float2(-1.5, 0.5),
|
||||
float2( 0.5, 0.5),
|
||||
float2(-1.5, -1.5),
|
||||
float2( 0.5, -1.5)
|
||||
};
|
||||
|
||||
float4 shadowSum = float4(0,0,0,0);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float2 sampleUV = UV + sampleOffsets[i] * texelSize;
|
||||
shadowSum += DecodeLightAttenuation(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler_Toon, sampleUV, 0));
|
||||
}
|
||||
return shadowSum * 0.25;
|
||||
}
|
||||
|
||||
//获取卡通灯光衰减
|
||||
float4 GetLightAttenuationFromShadowToonAA(in FInputParams InputParams, float SceneDepth, float3 TranslatedWorldPosition)
|
||||
{
|
||||
float4 LightAttenuation = float4(1, 1, 1, 1);
|
||||
|
||||
#if USE_VIRTUAL_SHADOW_MAP_MASK
|
||||
if (VirtualShadowMapId != INDEX_NONE)
|
||||
{
|
||||
float ShadowMask = GetVirtualShadowMapMaskForLight(ShadowMaskBits[InputParams.PixelPos], uint2(InputParams.PixelPos), SceneDepth, VirtualShadowMapId, TranslatedWorldPosition);
|
||||
return ShadowMask.xxxx;
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
return GetPerPixelLightAttenuationToonAA(InputParams.ScreenUV);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GetDynamicLighting() => GetDynamicLightingSplit()
|
||||
```c++
|
||||
FDeferredLightingSplit GetDynamicLightingSplit(
|
||||
float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID,
|
||||
FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos,
|
||||
inout float SurfaceShadow)
|
||||
{
|
||||
FLightAccumulator LightAccumulator = AccumulateDynamicLighting(TranslatedWorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, LightData, LightAttenuation, Dither, SVPos, SurfaceShadow);
|
||||
return LightAccumulator_GetResultSplit(LightAccumulator);
|
||||
}
|
||||
```
|
||||
|
||||
LightAccumulator_GetResultSplit():针对Subsurface,`RetDiffuse.a = In.ScatterableLightLuma;` 或者 `RetDiffuse.a = Luminance(In.ScatterableLight);`
|
||||
```c++
|
||||
FDeferredLightingSplit LightAccumulator_GetResultSplit(FLightAccumulator In)
|
||||
{
|
||||
float4 RetDiffuse;
|
||||
float4 RetSpecular;
|
||||
|
||||
if (VISUALIZE_LIGHT_CULLING == 1)
|
||||
{
|
||||
// a soft gradient from dark red to bright white, can be changed to be different
|
||||
RetDiffuse = 0.1f * float4(1.0f, 0.25f, 0.075f, 0) * In.EstimatedCost;
|
||||
RetSpecular = 0.1f * float4(1.0f, 0.25f, 0.075f, 0) * In.EstimatedCost;
|
||||
}
|
||||
else
|
||||
{
|
||||
RetDiffuse = float4(In.TotalLightDiffuse, 0);
|
||||
RetSpecular = float4(In.TotalLightSpecular, 0);
|
||||
|
||||
//针对Subsurface会额外对RetDiffuse的Alpha设置数值 ScatterableLight的亮度数值
|
||||
if (SUBSURFACE_CHANNEL_MODE == 1 )
|
||||
{
|
||||
if (View.bCheckerboardSubsurfaceProfileRendering == 0)
|
||||
{
|
||||
// RGB accumulated RGB HDR color, A: specular luminance for screenspace subsurface scattering
|
||||
RetDiffuse.a = In.ScatterableLightLuma;
|
||||
}
|
||||
}
|
||||
else if (SUBSURFACE_CHANNEL_MODE == 2)
|
||||
{
|
||||
// RGB accumulated RGB HDR color, A: view independent (diffuse) luminance for screenspace subsurface scattering
|
||||
// 3 add, 1 mul, 2 mad, can be optimized to use 2 less temporary during accumulation and remove the 3 add
|
||||
RetDiffuse.a = Luminance(In.ScatterableLight);
|
||||
// todo, need second MRT for SUBSURFACE_CHANNEL_MODE==2
|
||||
}
|
||||
}
|
||||
|
||||
FDeferredLightingSplit Ret;
|
||||
Ret.DiffuseLighting = RetDiffuse;
|
||||
Ret.SpecularLighting = RetSpecular;
|
||||
|
||||
return Ret;
|
||||
}
|
||||
```
|
||||
#### AccumulateDynamicLighting
|
||||
```c++
|
||||
FLightAccumulator AccumulateDynamicLighting(
|
||||
float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion, uint ShadingModelID,
|
||||
FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos,
|
||||
inout float SurfaceShadow)
|
||||
{
|
||||
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
|
||||
|
||||
half3 V = -CameraVector;
|
||||
half3 N = GBuffer.WorldNormal;
|
||||
//针对开启CLEAR_COAT_BOTTOM_NORMAL的清漆ShadingModel进行Normal处理
|
||||
BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
|
||||
{
|
||||
const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 4) - (512.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
|
||||
N = OctahedronToUnitVector(oct1);
|
||||
}
|
||||
|
||||
float3 L = LightData.Direction; // Already normalized
|
||||
float3 ToLight = L;
|
||||
float3 MaskedLightColor = LightData.Color;//灯光颜色
|
||||
float LightMask = 1;
|
||||
// 获取辐射光源的衰减值,衰减方法根据LightData.bInverseSquared,会分别使用新版衰减方法InverseSquared 或者 旧方法。如果是SpotLight与RectLight就乘以SpotLight、RectLight对应的形状衰减数值。
|
||||
if (LightData.bRadialLight)
|
||||
{
|
||||
LightMask = GetLocalLightAttenuation( TranslatedWorldPosition, LightData, ToLight, L );
|
||||
MaskedLightColor *= LightMask;
|
||||
}
|
||||
|
||||
LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost
|
||||
|
||||
BRANCH
|
||||
if( LightMask > 0 )//如果不是完全死黑就计算阴影部分逻辑
|
||||
{
|
||||
FShadowTerms Shadow;
|
||||
Shadow.SurfaceShadow = AmbientOcclusion;//GBuffer中的AO
|
||||
Shadow.TransmissionShadow = 1;
|
||||
Shadow.TransmissionThickness = 1;
|
||||
Shadow.HairTransmittance.OpaqueVisibility = 1;
|
||||
const float ContactShadowOpacity = GBuffer.CustomData.a;//TODO:修正ToonStandard对应的逻辑
|
||||
//
|
||||
GetShadowTerms(GBuffer.Depth, GBuffer.PrecomputedShadowFactors, GBuffer.ShadingModelID, ContactShadowOpacity,
|
||||
LightData, TranslatedWorldPosition, L, LightAttenuation, Dither, Shadow);
|
||||
SurfaceShadow = Shadow.SurfaceShadow;
|
||||
|
||||
LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms
|
||||
|
||||
#if SHADING_PATH_MOBILE
|
||||
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
|
||||
|
||||
FDirectLighting Lighting = (FDirectLighting)0;
|
||||
|
||||
half NoL = max(0, dot(GBuffer.WorldNormal, L));
|
||||
#if TRANSLUCENCY_NON_DIRECTIONAL
|
||||
NoL = 1.0f;
|
||||
#endif
|
||||
Lighting = EvaluateBxDF(GBuffer, N, V, L, NoL, Shadow);
|
||||
|
||||
Lighting.Specular *= LightData.SpecularScale;
|
||||
|
||||
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
||||
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
||||
#else // SHADING_PATH_MOBILE
|
||||
BRANCH
|
||||
if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
|
||||
{
|
||||
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);//判断是否需要SubsurfaceProfile计算
|
||||
#if NON_DIRECTIONAL_DIRECT_LIGHTING // 非平行直接光
|
||||
float Lighting;
|
||||
if( LightData.bRectLight )//面光源
|
||||
{
|
||||
FRect Rect = GetRect( ToLight, LightData );
|
||||
Lighting = IntegrateLight( Rect );
|
||||
}
|
||||
else //点光源以及胶囊光源
|
||||
{
|
||||
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
|
||||
Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
|
||||
}
|
||||
|
||||
float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;//Lambert照明 * 积分结果
|
||||
LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
|
||||
#else
|
||||
FDirectLighting Lighting;
|
||||
if (LightData.bRectLight)//面光源
|
||||
{
|
||||
FRect Rect = GetRect( ToLight, LightData );
|
||||
const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
|
||||
|
||||
#if REFERENCE_QUALITY
|
||||
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
|
||||
#else
|
||||
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
|
||||
#endif
|
||||
}
|
||||
else //点光源以及胶囊光源
|
||||
{
|
||||
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
|
||||
|
||||
#if REFERENCE_QUALITY
|
||||
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
|
||||
#else
|
||||
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
|
||||
#endif
|
||||
}
|
||||
|
||||
Lighting.Specular *= LightData.SpecularScale;
|
||||
|
||||
//累加Diffuse + Specular光照结果(Diffuse项还会作为散射进行计算,根绝散射模式不同赋予 FLightAccumulator.ScatterableLightLuma 或者 FLightAccumulator.ScatterableLight)
|
||||
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
||||
//散射项计算
|
||||
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
||||
|
||||
LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light)
|
||||
#endif
|
||||
}
|
||||
#endif // SHADING_PATH_MOBILE
|
||||
}
|
||||
return LightAccumulator;
|
||||
}
|
||||
```
|
||||
|
||||
光源新衰减公式,相关计算位于`GetLocalLightAttenuation()`:
|
||||
$$Falloff = \frac{saturate(1-(distance/lightRadius)^4)^2}{distance^2 + 1}$$
|
||||
|
||||
光源旧衰减公式,相关函数位于DynamicLightingCommon.ush中的`RadialAttenuation()`
|
||||
$$Falloff = (1 - saturate(length(WorldLightVector)))^ {FalloffExponent}$$
|
||||
##### GetShadowTerms()
|
||||
```c++
|
||||
void GetShadowTerms(float SceneDepth, half4 PrecomputedShadowFactors, uint ShadingModelID, float ContactShadowOpacity, FDeferredLightData LightData, float3 TranslatedWorldPosition, half3 L, half4 LightAttenuation, float Dither, inout FShadowTerms Shadow)
|
||||
{
|
||||
float ContactShadowLength = 0.0f;
|
||||
const float ContactShadowLengthScreenScale = GetTanHalfFieldOfView().y * SceneDepth;
|
||||
|
||||
BRANCH
|
||||
if (LightData.ShadowedBits)
|
||||
{
|
||||
// 重新映射ShadowProjection结果
|
||||
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
|
||||
|
||||
// LightAttenuation: Light function + per-object shadows in z, per-object SSS shadowing in w,
|
||||
// Whole scene directional light shadows in x, whole scene directional light SSS shadows in y
|
||||
// Get static shadowing from the appropriate GBuffer channel
|
||||
#if ALLOW_STATIC_LIGHTING
|
||||
half UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, half4(1, 1, 1, 1));
|
||||
half StaticShadowing = lerp(1, dot(PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap);
|
||||
#else
|
||||
half StaticShadowing = 1.0f;
|
||||
#endif
|
||||
|
||||
if (LightData.bRadialLight || SHADING_PATH_MOBILE)//RadialLight或者是移动端使用以下逻辑。bRadialLight一般是 PointLight or SpotLight。径向衰减(radial attenuation):指光照强度随距离光源的远近而衰减的特性(通常遵循平方反比定律)。
|
||||
{
|
||||
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
|
||||
|
||||
Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;//RadialLight灯光的阴影项计算不受AO影响,赋值Light function + per-object的ShadowProjection
|
||||
// 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;//per-object SSS shadowing
|
||||
|
||||
Shadow.TransmissionThickness = LightAttenuation.w;//per-object SSS shadowing
|
||||
}
|
||||
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);//根据计算出动态阴影的衰减值来插值ShadowProject与静态阴影。x:方向光阴影
|
||||
// Fade between SSS dynamic shadowing and static shadowing based on distance
|
||||
Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);// w:per-object SSS shadowing
|
||||
|
||||
Shadow.SurfaceShadow *= LightAttenuation.z;//Light function + per-object shadows in 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);
|
||||
}
|
||||
|
||||
FLATTEN
|
||||
if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
|
||||
{
|
||||
ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
|
||||
}
|
||||
}
|
||||
|
||||
#if SUPPORT_CONTACT_SHADOWS //接触阴影相关逻辑
|
||||
|
||||
#if STRATA_ENABLED == 0
|
||||
if (LightData.ShadowedBits < 2 && (ShadingModelID == SHADINGMODELID_HAIR))
|
||||
{
|
||||
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
|
||||
}
|
||||
// World space distance to cover eyelids and eyelashes but not beyond
|
||||
if (ShadingModelID == SHADINGMODELID_EYE)
|
||||
{
|
||||
ContactShadowLength = 0.5;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MATERIAL_CONTACT_SHADOWS
|
||||
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
|
||||
#endif
|
||||
|
||||
BRANCH
|
||||
if (ContactShadowLength > 0.0)
|
||||
{
|
||||
float StepOffset = Dither - 0.5;
|
||||
bool bHitCastContactShadow = false;
|
||||
bool bHairNoShadowLight = ShadingModelID == SHADINGMODELID_HAIR && !LightData.ShadowedBits;
|
||||
float HitDistance = ShadowRayCast( TranslatedWorldPosition, L, ContactShadowLength, 8, StepOffset, bHairNoShadowLight, bHitCastContactShadow );//通过RayMarching来计算是否HitContactShadow以及HitDistance。
|
||||
|
||||
if ( HitDistance > 0.0 )
|
||||
{
|
||||
float ContactShadowOcclusion = bHitCastContactShadow ? LightData.ContactShadowCastingIntensity : LightData.ContactShadowNonCastingIntensity;
|
||||
|
||||
#if STRATA_ENABLED == 0
|
||||
// Exponential attenuation is not applied on hair/eye/SSS-profile here, as the hit distance (shading-point to blocker) is different from the estimated
|
||||
// thickness (closest-point-from-light to shading-point), and this creates light leaks. Instead we consider first hit as a blocker (old behavior)
|
||||
BRANCH
|
||||
if (ContactShadowOcclusion > 0.0 &&
|
||||
IsSubsurfaceModel(ShadingModelID) &&
|
||||
ShadingModelID != SHADINGMODELID_HAIR &&
|
||||
ShadingModelID != SHADINGMODELID_EYE &&
|
||||
ShadingModelID != SHADINGMODELID_SUBSURFACE_PROFILE)
|
||||
{
|
||||
// Reduce the intensity of the shadow similar to the subsurface approximation used by the shadow maps path
|
||||
// Note that this is imperfect as we don't really have the "nearest occluder to the light", but this should at least
|
||||
// ensure that we don't darken-out the subsurface term with the contact shadows
|
||||
float Density = SubsurfaceDensityFromOpacity(ContactShadowOpacity);
|
||||
ContactShadowOcclusion *= 1.0 - saturate( exp( -Density * HitDistance ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
float ContactShadow = 1.0 - ContactShadowOcclusion;
|
||||
//根据是否命中赋予对应的ContactShadow亮度数值,之后乘以Shadow.SurfaceShadow与Shadow.TransmissionShadow。
|
||||
Shadow.SurfaceShadow *= ContactShadow;
|
||||
Shadow.TransmissionShadow *= ContactShadow;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
Shadow.HairTransmittance = LightData.HairTransmittance;
|
||||
Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow;
|
||||
}
|
||||
```
|
@@ -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
|
||||
|
1334
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Shadow.md
Normal file
1334
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Shadow.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,413 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-09-25 14:59:32
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
可以使用DrawDynamicMeshPass(),实现在插件中使用MeshDraw绘制Pass。
|
||||
|
||||
参考文章:
|
||||
- ***UE5,为HairStrands添加自定义深度与模板***:https://zhuanlan.zhihu.com/p/689578355
|
||||
|
||||
# MeshDraw
|
||||
推荐学习:
|
||||
- CustomDepth
|
||||
- RenderBasePassInternal()
|
||||
- RenderAnisotropyPass()
|
||||
|
||||
Shader推荐:
|
||||
- DepthOnlyVertexShader.usf
|
||||
- DepthOnlyPixelShader.usf
|
||||
|
||||
## BasePass
|
||||
### DrawBasePass()
|
||||
该函数在FDeferredShadingSceneRenderer::RenderBasePassInternal()中调用。
|
||||
|
||||
DrawNaniteMaterialPass() => SubmitNaniteIndirectMaterial()
|
||||
## PSO
|
||||
- RDG 04 Graphics Pipeline State Initializer https://zhuanlan.zhihu.com/p/582020846
|
||||
|
||||
- FGraphicsPipelineStateInitializer
|
||||
- FRHIDepthStencilState* DepthStencilState
|
||||
- FRHIBlendState* BlendState
|
||||
- FRHIRasterizerState* RasterizerState
|
||||
- EPrimitiveType PrimitiveType
|
||||
- FBoundShaderStateInput BoundShaderState.VertexDeclarationRHI
|
||||
- FBoundShaderStateInput BoundShaderState.VertexShaderRHI
|
||||
- FBoundShaderStateInput BoundShaderState.PixelShaderRHI
|
||||
- ……
|
||||
|
||||
// 应用
|
||||
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit,0);
|
||||
|
||||
## FMeshPassProcessorRenderState
|
||||
- FMeshPassProcessorRenderState
|
||||
- FRHIBlendState* BlendState
|
||||
- FRHIDepthStencilState* DepthStencilState
|
||||
- FExclusiveDepthStencil::Type DepthStencilAccess
|
||||
- FRHIUniformBuffer* ViewUniformBuffer
|
||||
- FRHIUniformBuffer* InstancedViewUniformBuffer
|
||||
- FRHIUniformBuffer* PassUniformBuffer
|
||||
- FRHIUniformBuffer* NaniteUniformBuffer
|
||||
- uint32 StencilRef = 0;
|
||||
|
||||
### FRHIBlendState
|
||||
使用***FBlendStateInitializerRHI()*** 进行初始化。
|
||||
它定义了8个渲染对象,一般我们只用第一组,它的七个参数分别是:
|
||||
- Color
|
||||
- Color Write Mask
|
||||
- Color Blend 混合类型
|
||||
- Color Src 混合因子
|
||||
- Color Dest 混合因子
|
||||
- Alpha
|
||||
- Alpha Blend 混合类型
|
||||
- Alpha Src 混合因子
|
||||
- Alpha Dest 混合因子
|
||||
|
||||
```c++
|
||||
FRHIBlendState* CopyBlendState = TStaticBlendState<CW_RGB, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
|
||||
```
|
||||
|
||||
颜色写入蒙版:
|
||||
```c++
|
||||
enum EColorWriteMask
|
||||
{
|
||||
CW_RED = 0x01,
|
||||
CW_GREEN = 0x02,
|
||||
CW_BLUE = 0x04,
|
||||
CW_ALPHA = 0x08,
|
||||
|
||||
CW_NONE = 0,
|
||||
CW_RGB = CW_RED | CW_GREEN | CW_BLUE,
|
||||
CW_RGBA = CW_RED | CW_GREEN | CW_BLUE | CW_ALPHA,
|
||||
CW_RG = CW_RED | CW_GREEN,
|
||||
CW_BA = CW_BLUE | CW_ALPHA,
|
||||
|
||||
EColorWriteMask_NumBits = 4,
|
||||
};
|
||||
```
|
||||
|
||||
#### 混合运算
|
||||
混合运算符对于颜色混合方程和Alpha混合方程效果是一样的,这里就只用颜色混合方程来做讲解。
|
||||
|
||||
| BlendOperation | 颜色混合方程 |
|
||||
| -------------------- | ---------------------------------------------------------------------------------- |
|
||||
| BO_Add | $$C=C_{src} \otimes F_{src} + C_{dst} \otimes F_{dst}Csrc⊗Fsrc+Cdst⊗Fdst$$ |
|
||||
| BO_Subtract | $$C = C_{src} \otimes F_{src} - C_{dst} \otimes F_{dst}C=Csrc⊗Fsrc−Cdst⊗Fdst$$ |
|
||||
| BO_ReverseSubtract | $$C = C_{dst} \otimes F_{dst} - C_{src} \otimes F_{src}C=Cdst⊗Fdst−Csrc⊗Fsrc$$ |
|
||||
| BO_Min | $$C = Min(C_{src} , C_{dst} )C=Min(Csrc,Cdst)$$ |
|
||||
| BO_Max | $$C = Max(C_{src} , C_{dst} )C=Max(Csrc,Cdst)$$ |
|
||||
| BO_Min和BO_Max忽略了混合因子 | |
|
||||
|
||||
#### 混合因子
|
||||
|
||||
| BlendFactor | 颜色混合因子 | Alpha混合因子 |
|
||||
| ----------------------------- | ---------------------------------------------------------------- | ------------------------ |
|
||||
| BF_Zero | $$F = (0,0,0)F=(0,0,0)$$ | $$F=0F=0$$ |
|
||||
| BF_One | $$F=(1,1,1)F=(1,1,1)$$ | $$F=1F=1$$ |
|
||||
| BF_SourceColor | $$F=(r_{src},g_{src},b_{src})F=(rsrc,gsrc,bsrc)$$ | – |
|
||||
| BF_InverseSourceColor | $$F=(1-r_{src},1-g_{src},1-b_{src})F=(1−rsrc,1−gsrc,1−bsrc)$$ | – |
|
||||
| BF_SourceAlpha | $$F=(a_{src},a_{src},a_{src})F=(asrc,asrc,asrc)$$ | $$F=a_{src}F=asrc$$ |
|
||||
| BF_InverseSourceAlpha | $$F=(1-a_{src},1-a_{src},1-a_{src})F=(1−asrc,1−asrc,1−asrc)$$ | $$F=1-a_{src}F=1−asrc$$ |
|
||||
| BF_DestAlpha | $$F=(a_{dst},a_{dst},a_{dst})F=(adst,adst,adst)$$ | $$F=a_{dst}F=adst$$ |
|
||||
| BF_InverseDestAlpha | $$F=(1-a_{dst},1-a_{dst},1-a_{dst})F=(1−adst,1−adst,1−adst)$$ | $$F=1-a_{dst}F=1−adst$$ |
|
||||
| BF_DestColor | $$F=(r_{dst},g_{dst},b_{dst})F=(rdst,gdst,bdst)$$ | – |
|
||||
| BF_InverseDestColor | $$F=(1-r_{dst},1-g_{dst},1-b_{dst})F=(1−rdst,1−gdst,1−bdst)$$ | – |
|
||||
| BF_ConstantBlendFactor | F=(r,g,b)F=(r,g,b) | F=aF=a |
|
||||
| BF_InverseConstantBlendFactor | F=(1-r,1-g,1-b)F=(1−r,1−g,1−b) | F=1-aF=1−a |
|
||||
| BF_Source1Color | 未知 | 未知 |
|
||||
| BF_InverseSource1Color | 未知 | 未知 |
|
||||
| BF_Source1Alpha | 未知 | 未知 |
|
||||
| BF_InverseSource1Alpha | 未知 | 未知 |
|
||||
|
||||
最后四个选项没有在DirectX中找到对应的选项,没有继续探究,前面的应该足够一般使用了。
|
||||
### FRHIDepthStencilState
|
||||
```c++
|
||||
TStaticDepthStencilState<
|
||||
bEnableDepthWrite, // 是否启用深度写入
|
||||
DepthTest, // 深度测试比较函数
|
||||
bEnableFrontFaceStencil, // (正面)启用模板
|
||||
FrontFaceStencilTest, // (正面)模板测试操作
|
||||
FrontFaceStencilFailStencilOp, //(正面)模板测试失败时如何更新模板缓冲区
|
||||
FrontFaceDepthFailStencilOp, //(正面)深度测试失败时如何更新模板缓冲区
|
||||
FrontFacePassStencilOp, //(正面)通过模板测试时如何更新模板缓冲区
|
||||
bEnableBackFaceStencil, // (背面)启用模板
|
||||
BackFaceStencilTest, // (背面)模板失败操作
|
||||
BackFaceStencilFailStencilOp, //(背面)模板测试失败时如何更新模板缓冲区
|
||||
BackFaceDepthFailStencilOp, //(背面)深度测试失败时如何更新模板缓冲区
|
||||
BackFacePassStencilOp, //(背面)通过模板测试时如何更新模板红冲去
|
||||
StencilReadMask, // 模板读取Mask
|
||||
StencilWriteMask // 模板写入Mask
|
||||
>
|
||||
```
|
||||
|
||||
```c++
|
||||
//一般使用这个
|
||||
TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI();
|
||||
//CustomStencil中使用的
|
||||
TStaticDepthStencilState<true, CF_DepthNearOrEqual, true, CF_Always, SO_Keep, SO_Keep, SO_Replace, false, CF_Always, SO_Keep, SO_Keep, SO_Keep, 255, 255>::GetRHI()
|
||||
```
|
||||
|
||||
#### DepthTest
|
||||
深度测试比较函数。
|
||||
```c++
|
||||
enum ECompareFunction
|
||||
{
|
||||
CF_Less,
|
||||
CF_LessEqual,
|
||||
CF_Greater,
|
||||
CF_GreaterEqual,
|
||||
CF_Equal,
|
||||
CF_NotEqual,
|
||||
CF_Never, // 总是返回false
|
||||
CF_Always, // 总是返回true
|
||||
|
||||
ECompareFunction_Num,
|
||||
ECompareFunction_NumBits = 3,
|
||||
|
||||
// Utility enumerations
|
||||
CF_DepthNearOrEqual = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_GreaterEqual : CF_LessEqual),
|
||||
CF_DepthNear = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_Greater : CF_Less),
|
||||
CF_DepthFartherOrEqual = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_LessEqual : CF_GreaterEqual),
|
||||
CF_DepthFarther = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_Less : CF_Greater),
|
||||
};
|
||||
```
|
||||
|
||||
```c++
|
||||
enum EStencilOp
|
||||
{
|
||||
SO_Keep,
|
||||
SO_Zero,
|
||||
SO_Replace,
|
||||
SO_SaturatedIncrement,
|
||||
SO_SaturatedDecrement,
|
||||
SO_Invert,
|
||||
SO_Increment,
|
||||
SO_Decrement,
|
||||
|
||||
EStencilOp_Num,
|
||||
EStencilOp_NumBits = 3,
|
||||
};
|
||||
```
|
||||
|
||||
### CustomStencil
|
||||
#### InitCustomDepthStencilContext()
|
||||
根据当前平台是否支持使用ComputeShader直接输出结果(bComputeExport)、以及是否写入Stencil缓存,以此来创建不同的资源。最终输出FCustomDepthContext。
|
||||
```c++
|
||||
struct FCustomDepthContext
|
||||
{
|
||||
FRDGTextureRef InputDepth = nullptr;
|
||||
FRDGTextureSRVRef InputStencilSRV = nullptr;
|
||||
FRDGTextureRef DepthTarget = nullptr;
|
||||
FRDGTextureRef StencilTarget = nullptr;
|
||||
bool bComputeExport = true;
|
||||
};
|
||||
```
|
||||
|
||||
#### EmitCustomDepthStencilTargets()
|
||||
根据bComputeExport,分别使用RDG的ComputeShader与PixelShader输出DepthStencil。
|
||||
- CS使用FComputeShaderUtils::AddPass()
|
||||
- PS使用NaniteExportGBuffer.usf的**EmitCustomDepthStencilPS()**,FPixelShaderUtils::AddFullscreenPass()
|
||||
|
||||
以**FEmitCustomDepthStencilPS**(NaniteExportGBuffer.usf)为例,额外输入的Nanite相关变量:
|
||||
- FSceneUniformParameters Scene
|
||||
- StructuredBuffer`<`FPackedView`>` InViews
|
||||
- ByteAddressBuffer VisibleClustersSWHW?
|
||||
- FIntVector4, PageConstants
|
||||
- Texture2D`<`UlongType`>`, VisBuffer64
|
||||
- ByteAddressBuffer MaterialSlotTable
|
||||
|
||||
#### FinalizeCustomDepthStencil()
|
||||
替换输出的Depth&Stencil。
|
||||
|
||||
# FViewInfo
|
||||
FViewInfo& ViewInfo
|
||||
- WriteView.bSceneHasSkyMaterial |= bSceneHasSkyMaterial;
|
||||
- WriteView.bHasSingleLayerWaterMaterial |= bHasSingleLayerWaterMaterial;
|
||||
- WriteView.bHasCustomDepthPrimitives |= bHasCustomDepthPrimitives;
|
||||
- WriteView.bHasDistortionPrimitives |= bHasDistortionPrimitives;
|
||||
- WriteView.bUsesCustomDepth |= bUsesCustomDepth;
|
||||
- WriteView.bUsesCustomStencil |= bUsesCustomStencil;
|
||||
|
||||
- FRelevancePacket::Finalize()
|
||||
|
||||
相关性:
|
||||
- 相关性定义
|
||||
- FStaticMeshBatchRelevance
|
||||
- FMaterialRelevance
|
||||
- View相关计算
|
||||
- FViewInfo::Init()
|
||||
- FRelevancePacket
|
||||
- FRelevancePacket::Finalize()
|
||||
|
||||
# 相关宏定义
|
||||
- SCOPE_CYCLE_COUNTER(STAT_BasePassDrawTime);:
|
||||
- DECLARE_CYCLE_STAT_EXTERN(TEXT("Base pass drawing"),STAT_BasePassDrawTime,STATGROUP_SceneRendering, RENDERCORE_API);
|
||||
- DEFINE_STAT(STAT_BasePassDrawTime);
|
||||
- DEFINE_GPU_STAT(NaniteBasePass);
|
||||
- DECLARE_GPU_STAT_NAMED_EXTERN(NaniteBasePass, TEXT("Nanite BasePass"));
|
||||
- GET_STATID(STAT_CLP_BasePass)
|
||||
- FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_BasePass), View, FParallelCommandListBindings(PassParameters));
|
||||
- DECLARE_CYCLE_STAT(TEXT("BasePass"), STAT_CLP_BasePass, STATGROUP_ParallelCommandListMarkers);
|
||||
|
||||
# NaniteMeshDraw
|
||||
`Engine\Source\Runtime\Renderer\Private\Nanite\`NaniteMaterials.h & NaniteMaterials.cpp
|
||||
|
||||
PS.使用的Shader必须是`FNaniteGlobalShader`的子类。
|
||||
|
||||
以下是Nanite物体的CustomDepth绘制过程:
|
||||
```c++
|
||||
bool FSceneRenderer::RenderCustomDepthPass(
|
||||
FRDGBuilder& GraphBuilder,
|
||||
FCustomDepthTextures& CustomDepthTextures,
|
||||
const FSceneTextureShaderParameters& SceneTextures,
|
||||
TConstArrayView<Nanite::FRasterResults> PrimaryNaniteRasterResults,
|
||||
TConstArrayView<Nanite::FPackedView> PrimaryNaniteViews)
|
||||
{
|
||||
if (!CustomDepthTextures.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if any of the views have custom depth and if any of them have Nanite that is rendering custom depth
|
||||
// 构建NaniteDrawLists,用于后面的绘制
|
||||
bool bAnyCustomDepth = false;
|
||||
TArray<FNaniteCustomDepthDrawList, SceneRenderingAllocator> NaniteDrawLists;
|
||||
NaniteDrawLists.AddDefaulted(Views.Num());
|
||||
uint32 TotalNaniteInstances = 0;
|
||||
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
|
||||
{
|
||||
FViewInfo& View = Views[ViewIndex];
|
||||
if (View.ShouldRenderView() && View.bHasCustomDepthPrimitives)
|
||||
{
|
||||
if (PrimaryNaniteRasterResults.IsValidIndex(ViewIndex))
|
||||
{
|
||||
const FNaniteVisibilityResults& VisibilityResults = PrimaryNaniteRasterResults[ViewIndex].VisibilityResults;
|
||||
|
||||
// Get the Nanite instance draw list for this view. (NOTE: Always use view index 0 for now because we're not doing
|
||||
// multi-view yet).
|
||||
NaniteDrawLists[ViewIndex] = BuildNaniteCustomDepthDrawList(View, 0u, VisibilityResults);
|
||||
|
||||
TotalNaniteInstances += NaniteDrawLists[ViewIndex].Num();
|
||||
}
|
||||
bAnyCustomDepth = true;
|
||||
}
|
||||
}
|
||||
|
||||
SET_DWORD_STAT(STAT_NaniteCustomDepthInstances, TotalNaniteInstances);
|
||||
..
|
||||
if (TotalNaniteInstances > 0)
|
||||
{
|
||||
RDG_EVENT_SCOPE(GraphBuilder, "Nanite CustomDepth");
|
||||
|
||||
const FIntPoint RasterTextureSize = CustomDepthTextures.Depth->Desc.Extent;
|
||||
FIntRect RasterTextureRect(0, 0, RasterTextureSize.X, RasterTextureSize.Y);
|
||||
if (Views.Num() == 1)
|
||||
{
|
||||
const FViewInfo& View = Views[0];
|
||||
if (View.ViewRect.Min.X == 0 && View.ViewRect.Min.Y == 0)
|
||||
{
|
||||
RasterTextureRect = View.ViewRect;
|
||||
}
|
||||
}
|
||||
|
||||
const bool bWriteCustomStencil = IsCustomDepthPassWritingStencil();
|
||||
|
||||
Nanite::FSharedContext SharedContext{};
|
||||
SharedContext.FeatureLevel = Scene->GetFeatureLevel();
|
||||
SharedContext.ShaderMap = GetGlobalShaderMap(SharedContext.FeatureLevel);
|
||||
SharedContext.Pipeline = Nanite::EPipeline::Primary;
|
||||
|
||||
// TODO: If !bWriteCustomStencil, we could copy off the depth and rasterize depth-only (probable optimization)
|
||||
//初始化Nanite::FRasterContext RasterContext。
|
||||
Nanite::FRasterContext RasterContext = Nanite::InitRasterContext(
|
||||
GraphBuilder,
|
||||
SharedContext,
|
||||
ViewFamily,
|
||||
RasterTextureSize,
|
||||
RasterTextureRect,
|
||||
false, // bVisualize
|
||||
Nanite::EOutputBufferMode::VisBuffer,
|
||||
true, // bClearTarget
|
||||
nullptr, // RectMinMaxBufferSRV
|
||||
0, // NumRects
|
||||
nullptr, // ExternalDepthBuffer
|
||||
true // bCustomPass
|
||||
);
|
||||
|
||||
//用于构建FCustomDepthContext结构体,创建对应的贴图资源。主要包含了InputDepth、InputStencilSRV、DepthTarget、StencilTarget以及bComputeExport(是否使用ComputeShader输出)。
|
||||
Nanite::FCustomDepthContext CustomDepthContext = Nanite::InitCustomDepthStencilContext(
|
||||
GraphBuilder,
|
||||
CustomDepthTextures,
|
||||
bWriteCustomStencil);
|
||||
|
||||
Nanite::FConfiguration CullingConfig = { 0 };//Nanite剔除设置,用的较多的是bUpdateStreaming、bPrimaryContext、bTwoPassOcclusion,设置为true。
|
||||
CullingConfig.bUpdateStreaming = true;
|
||||
|
||||
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
|
||||
{
|
||||
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
|
||||
|
||||
FViewInfo& View = Views[ViewIndex];
|
||||
|
||||
if (!View.ShouldRenderView() || NaniteDrawLists[ViewIndex].Num() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//创建Nanite渲染器
|
||||
auto NaniteRenderer = Nanite::IRenderer::Create(
|
||||
GraphBuilder,
|
||||
*Scene,
|
||||
View,
|
||||
GetSceneUniforms(),
|
||||
SharedContext,
|
||||
RasterContext,
|
||||
CullingConfig,
|
||||
View.ViewRect,
|
||||
/* PrevHZB = */ nullptr
|
||||
);
|
||||
|
||||
NaniteRenderer->DrawGeometry(
|
||||
Scene->NaniteRasterPipelines[ENaniteMeshPass::BasePass],
|
||||
PrimaryNaniteRasterResults[ViewIndex].VisibilityResults,
|
||||
*Nanite::FPackedViewArray::Create(GraphBuilder, PrimaryNaniteViews[ViewIndex]),
|
||||
NaniteDrawLists[ViewIndex]
|
||||
);
|
||||
|
||||
Nanite::FRasterResults RasterResults;
|
||||
NaniteRenderer->ExtractResults( RasterResults );
|
||||
|
||||
// Emit depth
|
||||
Nanite::EmitCustomDepthStencilTargets(
|
||||
GraphBuilder,
|
||||
*Scene,
|
||||
View,
|
||||
RasterResults.PageConstants,
|
||||
RasterResults.VisibleClustersSWHW,
|
||||
RasterResults.ViewsBuffer,
|
||||
RasterContext.VisBuffer64,
|
||||
CustomDepthContext
|
||||
);
|
||||
}
|
||||
|
||||
Nanite::FinalizeCustomDepthStencil(GraphBuilder, CustomDepthContext, CustomDepthTextures);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- Nanite::InitRasterContext():初始化Nanite::FRasterContext RasterContext。
|
||||
- Nanite::InitCustomDepthStencilContext():位于NaniteMaterials.cpp,用于构建FCustomDepthContext结构体,创建对应的贴图资源。主要包含了InputDepth、InputStencilSRV、DepthTarget、StencilTarget以及bComputeExport(是否使用ComputeShader输出)。
|
||||
- auto NaniteRenderer = Nanite::IRenderer::Create():创建Nanite渲染器
|
||||
- NaniteRenderer->DrawGeometry():Nanite物体绘制
|
||||
- NaniteRenderer->ExtractResults( RasterResults ):提取渲染结果。
|
||||
- ***Nanite::EmitCustomDepthStencilTargets()***:使用提取到的Nanite::FRasterResults RasterResults,输出CustomDepth。结果存储在 FCustomDepthContext& CustomDepthContext中。
|
||||
- FDepthExportCS / FEmitCustomDepthStencilPS
|
||||
- Nanite::FinalizeCustomDepthStencil():将CustomDepthContext中的结果输出到CustomDepthTextures上。
|
||||
|
||||
## BasePass Nanite
|
||||
1. Render()中向RenderBasePass()传入const TArrayView<Nanite::FRasterResults>& NaniteRasterResults。
|
||||
2. 调用Lambda RenderNaniteBasePass(),本质上是调用Nanite::DrawBasePass()进行渲染。
|
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-11-14 12:19:36
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
探索思路:
|
||||
|
||||
# 问题解决方法
|
||||
添加相关性overrider,将bDynamicRelevance设置成true。
|
||||
```c++
|
||||
FPrimitiveViewRelevance FOutlineSkeletalMeshSceneProxy::GetViewRelevance(const FSceneView* View) const
|
||||
{
|
||||
FPrimitiveViewRelevance Result = FSkeletalMeshSceneProxy::GetViewRelevance(View);
|
||||
Result.bDynamicRelevance = true;
|
||||
return Result;
|
||||
}
|
||||
```
|
@@ -0,0 +1,154 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-09-23 19:47:03
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# ShadowPassSwitch
|
||||
## Cpp
|
||||
MaterialExpressionShadowReplace.h
|
||||
|
||||
MaterialExpressions.cpp
|
||||
```c++
|
||||
int32 UMaterialExpressionShadowReplace::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
|
||||
{
|
||||
if (!Default.GetTracedInput().Expression)
|
||||
{
|
||||
return Compiler->Errorf(TEXT("Missing input Default"));
|
||||
}
|
||||
else if (!Shadow.GetTracedInput().Expression)
|
||||
{
|
||||
return Compiler->Errorf(TEXT("Missing input Shadow"));
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32 Arg1 = Default.Compile(Compiler);
|
||||
const int32 Arg2 = Shadow.Compile(Compiler);
|
||||
return Compiler->ShadowReplace(Arg1, Arg2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
MaterialCompiler.h => **HLSLMaterialTranslator.cpp**
|
||||
```c++
|
||||
int32 FHLSLMaterialTranslator::ShadowReplace(int32 Default, int32 Shadow)
|
||||
{
|
||||
return GenericSwitch(TEXT("GetShadowReplaceState()"), Shadow, Default);
|
||||
}
|
||||
```
|
||||
|
||||
```c++
|
||||
int32 FHLSLMaterialTranslator::GenericSwitch(const TCHAR* SwitchExpressionText, int32 IfTrue, int32 IfFalse)
|
||||
{
|
||||
if (IfTrue == INDEX_NONE || IfFalse == INDEX_NONE)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
// exactly the same inputs on both sides - no need to generate anything extra
|
||||
if (IfTrue == IfFalse)
|
||||
{
|
||||
return IfTrue;
|
||||
}
|
||||
|
||||
FMaterialUniformExpression* IfTrueExpression = GetParameterUniformExpression(IfTrue);
|
||||
FMaterialUniformExpression* IfFalseExpression = GetParameterUniformExpression(IfFalse);
|
||||
if (IfTrueExpression &&
|
||||
IfFalseExpression &&
|
||||
IfTrueExpression->IsConstant() &&
|
||||
IfFalseExpression->IsConstant())
|
||||
{
|
||||
FMaterialRenderContext DummyContext(nullptr, *Material, nullptr);
|
||||
FLinearColor IfTrueValue;
|
||||
FLinearColor IfFalseValue;
|
||||
IfTrueExpression->GetNumberValue(DummyContext, IfTrueValue);
|
||||
IfFalseExpression->GetNumberValue(DummyContext, IfFalseValue);
|
||||
if (IfTrueValue == IfFalseValue)
|
||||
{
|
||||
// If both inputs are wired to == constant values, avoid adding the runtime switch
|
||||
// This will avoid breaking various offline checks for constant values
|
||||
return IfTrue;
|
||||
}
|
||||
}
|
||||
|
||||
// Both branches of '?:' need to be the same type
|
||||
const EMaterialValueType ResultType = GetArithmeticResultType(IfTrue, IfFalse);
|
||||
const FString IfTrueCode = CoerceParameter(IfTrue, ResultType);
|
||||
const FString IfFalseCode = CoerceParameter(IfFalse, ResultType);
|
||||
|
||||
if (IsLWCType(ResultType))
|
||||
{
|
||||
AddLWCFuncUsage(ELWCFunctionKind::Other);
|
||||
return AddCodeChunk(ResultType, TEXT("LWCSelect(%s, %s, %s)"), SwitchExpressionText, *IfTrueCode, *IfFalseCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddCodeChunk(ResultType, TEXT("(%s ? (%s) : (%s))"), SwitchExpressionText, *IfTrueCode, *IfFalseCode);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
>可以看得出最终会编译成(%s ? (%s) : (%s)),也就是 GetShadowReplaceState() ? True的Shader : False时的Shader。
|
||||
|
||||
## Shader
|
||||
### Common.ush
|
||||
```c++
|
||||
// NOTE: The raytraced implementation of the ShadowPassSwitch node is kept in RayTracingShaderUtils.ush as it needs to access per ray information.
|
||||
#if RAYHITGROUPSHADER == 0
|
||||
// Experimental way to allow adjusting the OpacityMask for shadow map rendering of masked materials.
|
||||
// This is exposed via the ShadowPassSwitch material node. This can also be accessed with a Custom
|
||||
// material node. If this turns out to be very useful we can expose as MaterialFunction
|
||||
// and potentially expose other queries as well (e.g. SkeletalMesh, HitProxy, ).
|
||||
// @return 0:no, 1:yes
|
||||
bool GetShadowReplaceState()
|
||||
{
|
||||
#if SHADOW_DEPTH_SHADER
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
float IsShadowDepthShader()
|
||||
{
|
||||
return GetShadowReplaceState() ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
#endif // RAYHITGROUPSHADER == 0
|
||||
```
|
||||
|
||||
可以看得出主要通过**SHADOW_DEPTH_SHADER**宏进行判断,而渲染阴影的Shader ShadowDepthVertexShader.usf&ShadowDepthPixelShader.usf都定义该宏为1。
|
||||
|
||||
### NaniteRasterizer.usf
|
||||
```c++
|
||||
// This must be defined before including Common.ush (see GetShadowReplaceState)
|
||||
#define SHADOW_DEPTH_SHADER DEPTH_ONLY
|
||||
```
|
||||
在NaniteCullRaster.cpp中DEPTH_ONLY设置为1。
|
||||
|
||||
### RayTracingShaderUtils.ush
|
||||
光追相关
|
||||
```c++
|
||||
#ifndef RAYHITGROUPSHADER
|
||||
#error "This header should only be included in raytracing contexts"
|
||||
#endif
|
||||
|
||||
#ifndef PATH_TRACING // Path Tracing has a similar implemental with a slightly different set of flags
|
||||
|
||||
#include "RayTracingCommon.ush"
|
||||
|
||||
static int CurrentPayloadInputFlags = 0;
|
||||
|
||||
bool GetShadowReplaceState()
|
||||
{
|
||||
return (CurrentPayloadInputFlags & RAY_TRACING_PAYLOAD_INPUT_FLAG_SHADOW_RAY) != 0;
|
||||
}
|
||||
|
||||
float IsShadowDepthShader()
|
||||
{
|
||||
return GetShadowReplaceState() ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
#endif
|
||||
```
|
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: PSO Precache机制笔记
|
||||
date: 2025-02-08 20:42:16
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- [UE5.3] PSO Cache&PreCache 源码阅读:https://zhuanlan.zhihu.com/p/679832250
|
||||
- [UE5.3] PSO Cache&PreCache 源码阅读(二):https://zhuanlan.zhihu.com/p/681803986
|
||||
- Unreal Engine 5.2 MeshPass拓展:https://zhuanlan.zhihu.com/p/671423486
|
||||
- 优化UE5的PSO卡顿:FileCache,PreCache和异步PSO https://zhuanlan.zhihu.com/p/1898646962561094034
|
||||
# 执行链
|
||||
- FGraphEventArray UPrimitiveComponent::PrecachePSOs()
|
||||
- void UMaterialInterface::InitDefaultMaterials()
|
||||
- UMaterial::PrecachePSOs
|
||||
|
||||
UMaterial::PrecachePSOs => **MaterialResource->CollectPSOs()** => PrecacheMaterialPSOs() => PrecachePSOs() => CollectPSOs() => CollectPSOInitializers()
|
||||
|
||||
# CollectPSOInitializers()
|
||||
接口IPSOCollector::CollectPSOInitializers()
|
||||
|
||||
## 其他MeshProcessor实现
|
||||
|
@@ -0,0 +1,324 @@
|
||||
## 前言
|
||||
>RDG = Rendering Dependency Graph
|
||||
|
||||
RDG主要包含两个重要组件,一个是FRDGBuilder,负责构建Render graph的资源及添加pass等,构建RenderGraph。另一个是FRDGResource,RenderGraph的资源类,所有资源都由它派生。
|
||||
|
||||
官方入门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是四字节对齐的;
|
||||
- FVector2D,FIntPoint是8字节对齐的;
|
||||
- FVector和FVector 4是16字节对齐的。
|
||||
|
||||
>作者(Author):papalqi 链接(URL):https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
|
||||
|
||||
**所以我们需要根据位宽对变量与资源进行排序:**
|
||||

|
||||
|
||||

|
||||
|
||||
进行手动位宽对齐,以减少额外的内存与带宽占用。
|
||||
|
||||
### 资源的初始化与绑定
|
||||
其中资源分为使用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设置变量的步骤:
|
||||

|
||||
|
||||
>当增加一个RGPass它必须带有Shader参数,可以是任何Shader参数比如UnifromBuffer,Texture等。且参数使用" 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:
|
||||

|
||||

|
||||
|
||||
**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。**
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 绑定RenderTarget
|
||||
如需使用RenderTarget,只要在FParameter结构体声明中加一行RENDER_TARGET_BINDING_SLOTS()即可。
|
||||

|
||||
|
||||
之后就可以进行RT的绑定。
|
||||
|
||||
**绑定储存颜色数据的RT**
|
||||

|
||||
颜色数据RT绑定时需要注意数组下标号。
|
||||
|
||||
**绑定深度模板缓存**
|
||||

|
||||
|
||||
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。如下图。
|
||||
|
||||

|
||||
|
||||
>Check out GRenderTargetPool.CreateUntrackedElement()to get a TRefCountPtr<IPooledRenderTarget>if need to register a different from RHI resource (for instance the very old FRenderTarget)
|
||||
|
||||

|
||||
|
||||
## 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);
|
||||
});
|
||||
```
|
@@ -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是FScreenVertexShaderVS,usf为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;
|
||||
```
|
@@ -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);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 如何使用
|
||||
直接在蓝图中调用即可
|
||||
|
||||

|
||||
|
||||
注意RenderTarget的格式需要与UAV的格式一一对应。
|
||||
|
||||

|
||||
|
||||
结果:
|
||||
|
||||

|
208
03-UnrealEngine/Rendering/RenderingPipeline/ShaderModel添加.md
Normal file
208
03-UnrealEngine/Rendering/RenderingPipeline/ShaderModel添加.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 参考文章
|
||||
- UE4:https://zhuanlan.zhihu.com/p/446587397
|
||||
- UE5.2 https://zhuanlan.zhihu.com/p/658700282
|
||||
- UE5.1 https://zhuanlan.zhihu.com/p/565837897
|
||||
|
||||
# 备注
|
||||
添加的定义:
|
||||
- MATERIAL_SHADINGMODEL_TOONSTANDARD
|
||||
- SHADINGMODELID_TOONSTANDARD
|
||||
- PIXEL_INSPECTOR_SHADINGMODELID_TOONSTANDARD
|
||||
|
||||
添加的功能:
|
||||
- Material
|
||||
- 材质编辑器引脚
|
||||
- 相关引脚控制
|
||||
- ShaderModel
|
||||
- ViewMode - BufferVisualization
|
||||
- ShaderModel可视化
|
||||
- ToonBuffer可视化
|
||||
- PixelInspector
|
||||
# c++部分
|
||||
## 添加ShaderModel
|
||||
- EngineTypes.h 在EMaterialShadingModel中添加ShaderModel枚举
|
||||
|
||||
## 材质宏添加数值
|
||||
ShaderMaterial.h给材质宏`MATERIAL_SHADINGMODEL_TOONSTANDARD`添加数值。
|
||||
```c++
|
||||
uint8 MATERIAL_SHADINGMODEL_TOONSTANDARD : 1;
|
||||
```
|
||||
之后再ShaderGenerationUtil.cpp的ApplyFetchEnvironment()与DetermineUsedMaterialSlots()添加对应代码。
|
||||
|
||||
## 添加材质编辑器引脚
|
||||
- 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);
|
||||
}
|
||||
```
|
||||
|
||||
## 控制引脚
|
||||
### Material.cpp(控制引脚开启)
|
||||
在IsPropertyActive_Internal()中修改:
|
||||
```c++
|
||||
case MP_CustomData0:
|
||||
Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Hair, MSM_Cloth, MSM_Eye, MSM_SubsurfaceProfile, MSM_ToonStandard, MSM_PreintegratedSkin });
|
||||
break;
|
||||
case MP_CustomData1:
|
||||
Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Eye, MSM_ToonStandard, MSM_PreintegratedSkin });
|
||||
break;
|
||||
```
|
||||
|
||||
### MaterialShared.cpp(修改引脚显示名称)
|
||||
在MaterialShared.cpp的GetAttributeOverrideForMaterial()中修改:
|
||||
```c++
|
||||
case MP_CustomData0:
|
||||
CustomPinNames.Add({ MSM_ClearCoat, "Clear Coat" });
|
||||
CustomPinNames.Add({ MSM_Hair, "Backlit" });
|
||||
CustomPinNames.Add({ MSM_Cloth, "Cloth" });
|
||||
CustomPinNames.Add({ MSM_Eye, "Iris Mask" });
|
||||
CustomPinNames.Add({ MSM_SubsurfaceProfile, "Curvature" });
|
||||
CustomPinNames.Add({ MSM_ToonStandard, "ToonDataA" });
|
||||
CustomPinNames.Add({ MSM_PreintegratedSkin, "ToonDataA" });
|
||||
return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 0"));
|
||||
case MP_CustomData1:
|
||||
CustomPinNames.Add({ MSM_ClearCoat, "Clear Coat Roughness" });
|
||||
CustomPinNames.Add({ MSM_Eye, "Iris Distance" });
|
||||
CustomPinNames.Add({ MSM_ToonStandard, "ToonDataB" });
|
||||
CustomPinNames.Add({ MSM_PreintegratedSkin, "ToonDataB" });
|
||||
return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 1"));
|
||||
```
|
||||
|
||||
### ShaderMaterialDerivedHelpers.cpp(CustomData数据传递)
|
||||
在CalculateDerivedMaterialParameters()中修改代码,让CustomData渲染到GBuffer上:
|
||||
```c++
|
||||
// Only some shader models actually need custom data.
|
||||
Dst.WRITES_CUSTOMDATA_TO_GBUFFER = (Dst.USES_GBUFFER && (Mat.MATERIAL_SHADINGMODEL_SUBSURFACE || Mat.MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || Mat.MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || Mat.MATERIAL_SHADINGMODEL_CLEAR_COAT || Mat.MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || Mat.MATERIAL_SHADINGMODEL_HAIR || Mat.MATERIAL_SHADINGMODEL_CLOTH || Mat.MATERIAL_SHADINGMODEL_EYE));
|
||||
```
|
||||
|
||||
## PixelInspectorResult
|
||||
在PixelInspectorResult.h中添加`PIXEL_INSPECTOR_SHADINGMODELID_TOONSTANDARD`宏定义。
|
||||
之后在PixelInspectorResult.cpp的DecodeShadingModel()函数中添加定义。
|
||||
|
||||
### PixelInspectorDetailsCustomization(修改菜单显示屏蔽属性用)
|
||||
|
||||
|
||||
# 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设置一个显示颜色。
|
||||
|
||||
## Definitions.ush(定义材质宏)
|
||||
位于`Engine\Shaders\Private\Definitions.ush`。
|
||||
|
||||
## BasePassCommon.ush
|
||||
- ~~修改#define WRITES_CUSTOMDATA_TO_GBUFFER宏,在最后的条件判断中添加新增加的ShaderModel~~。**UE5.1将该逻辑转移到C++中**
|
||||
|
||||
## 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.ush,BxDF实现。
|
||||
- DeferredLightingCommon.ush,延迟光照实现。
|
||||
# 相关文件
|
||||
- UMaterialInterface、UMaterial、UMaterialInstance、FMaterial、FMaterialResource、FMateialRenderProxy、FMaterialInstanceResource、FDefaultMaterialInstance
|
||||
- MaterialShader.cpp
|
||||
- HLSLMaterialTranslator.cpp
|
||||
- MaterialHLSLEmitter:塞入ShaderModel宏
|
||||
- Material.cpp:开启引脚
|
||||
- MaterialAttributeDefinitionMap.cpp
|
||||
- ShaderMaterialDerivedHelpers.cpp
|
||||
- ShaderGenerationUtil.cpp
|
||||
- ShadingCommon.ush
|
||||
|
||||
# Material编辑器相关代码
|
||||
- FMaterialInstanceBasePropertyOverrides
|
||||
- FMaterialInstanceParameterDetails
|
||||
|
||||
|
||||
https://zhuanlan.zhihu.com/p/565776677
|
||||
https://www.cnblogs.com/timlly/p/15109132.html
|
@@ -0,0 +1,171 @@
|
||||
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中有用到)
|
||||
- 1041:GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;
|
||||
- 1059~1072:使用法线(清漆ShaderModel还会计算的底层法线)计算BentNormal以及GBufferAO。(使用SphericalGaussian)
|
||||
|
||||
### 1081~1146:
|
||||
#### 1086~1116:计算DiffuseColorForIndirect
|
||||
DiffuseColorForIndirect(DiffuseDir只在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~1146:SingleLayerWater 覆盖颜色操作
|
||||
```c#
|
||||
GBuffer.DiffuseColor *= BaseMaterialCoverageOverWater;
|
||||
DiffuseColor *= BaseMaterialCoverageOverWater;
|
||||
```
|
||||
### 1148~1211
|
||||
1. 使用ForwardDirectLighting的DiffuseLighting与SpecularLighting累加,Color,THIN_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~1349:SingleLayerWater光照计算
|
||||
计算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~1529:GBuffer相关
|
||||
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;
|
||||
|
||||
### 1553:FinalizeVirtualTextureFeedback
|
||||
|
||||
# UE5
|
||||
|
||||
## Lumen相关
|
||||
- GetSkyLighting()
|
||||
- Lumen
|
||||
- GetTranslucencyGIVolumeLighting()
|
||||
- SkyLighting
|
||||
- GetEffectiveSkySHDiffuse()
|
||||
- GetVolumetricLightmapSkyBentNormal()
|
||||
- GetSkyBentNormalAndOcclusion()
|
||||
|
||||
**GetSkyLighting()** 演示了采样SkyLight与Lumen的方法。
|
||||
|
||||
### SkyLighting
|
||||
GetEffectiveSkySHDiffuse()是一个宏,会根据平台指向下面2个函数:
|
||||
```c++
|
||||
/**
|
||||
* Computes sky diffuse lighting from the SH irradiance map.
|
||||
* This has the SH basis evaluation and diffuse convolution weights combined for minimal ALU's - see "Stupid Spherical Harmonics (SH) Tricks"
|
||||
*/
|
||||
float3 GetSkySHDiffuse(float3 Normal)
|
||||
{
|
||||
float4 NormalVector = float4(Normal, 1.0f);
|
||||
float3 Intermediate0, Intermediate1, Intermediate2;
|
||||
Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector);
|
||||
Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector);
|
||||
Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector);
|
||||
|
||||
float4 vB = NormalVector.xyzz * NormalVector.yzzx;
|
||||
Intermediate1.x = dot(SkyIrradianceEnvironmentMap[3], vB);
|
||||
Intermediate1.y = dot(SkyIrradianceEnvironmentMap[4], vB);
|
||||
Intermediate1.z = dot(SkyIrradianceEnvironmentMap[5], vB);
|
||||
|
||||
float vC = NormalVector.x * NormalVector.x - NormalVector.y * NormalVector.y;
|
||||
Intermediate2 = SkyIrradianceEnvironmentMap[6].xyz * vC;
|
||||
|
||||
// max to not get negative colors
|
||||
return max(0, Intermediate0 + Intermediate1 + Intermediate2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes sky diffuse lighting from the SH irradiance map.
|
||||
* This has the SH basis evaluation and diffuse convolution weights combined for minimal ALU's - see "Stupid Spherical Harmonics (SH) Tricks"
|
||||
* Only does the first 3 components for speed.
|
||||
*/
|
||||
float3 GetSkySHDiffuseSimple(float3 Normal)
|
||||
{
|
||||
float4 NormalVector = float4(Normal, 1);
|
||||
|
||||
float3 Intermediate0;
|
||||
Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector);
|
||||
Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector);
|
||||
Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector);
|
||||
// max to not get negative colors
|
||||
return max(0, Intermediate0);
|
||||
}
|
||||
```
|
304
03-UnrealEngine/Rendering/RenderingPipeline/UE4 ToneMapping.md
Normal file
304
03-UnrealEngine/Rendering/RenderingPipeline/UE4 ToneMapping.md
Normal 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 System(ACES)
|
||||
>是一套颜色编码系统,或者说是一个新的颜色空间。它是一个通用的数据交换格式,一方面可以不同的输入设备转成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_A32B32G32R32F;LinearNoToneCurve、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 Transform(RRT)及 Output Device Transform(ODT)渲染的图像。
|
||||
- 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
|
||||
|
||||

|
@@ -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为Value,DefineValue为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
|
122
03-UnrealEngine/Rendering/RenderingPipeline/UE4渲染用贴图资源实时更换.md
Normal file
122
03-UnrealEngine/Rendering/RenderingPipeline/UE4渲染用贴图资源实时更换.md
Normal 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界面中有SubsufaceProfile(SubsufaceProfile类,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;
|
||||
```
|
||||
|
58
03-UnrealEngine/Rendering/RenderingPipeline/UE5大世界坐标转换.md
Normal file
58
03-UnrealEngine/Rendering/RenderingPipeline/UE5大世界坐标转换.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-08-11 14:30:00
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- 官方文档:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/large-world-coordinates-rendering-in-unreal-engine-5
|
||||
- 相关代码文件:
|
||||
- LargeWorldCoordinates.ush
|
||||
- Math/DoubleFloat.ush(UE5.3不存在)
|
||||
- DoubleFloat.h(UE5.3不存在)
|
||||
|
||||
## 相关HLSL类型
|
||||
|
||||
| HLSL类型 | 说明 |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FDFScalar` `FDFVector2/3/4` | 这些类型是float1/2/3/4的更高精度对应项。在内部由两个floatN矢量构成:High和Low。 |
|
||||
| `FDFMatrix` | 类似于float4x4,但包含一个额外的float3 PreTranslation坐标。将矢量乘以此类型时,该矢量会首先使用PreTranslation进行转译,然后再应用float4x4矩阵。此类型适合变换为世界空间(LocalToWorld)。 |
|
||||
| `FDFInverseMatrix` | 类似于float4x4,但包含一个额外的float3 PostTranslation坐标。将矢量乘以此类型时,该矢量会在应用float4x4矩阵之后使用PostTranslation进行转译。此类型适合变换为世界空间(LocalToWorld)。 |
|
||||
|
||||
运算符带有前缀"DF",并有多种变体,可平衡精度和性能。变体在函数名称中用前缀或后缀来标记。例如:
|
||||
- `DFFast*` 是一个后缀。例如, `DFFastAdd` 是一个后缀变体。此函数更快,但精度更低。使用此变体可获得最高速度,但会牺牲精度。对于基础运算符,每个函数的文档中都提供了论文参考资料,给出了关于其精度极限的详细说明。
|
||||
- `DF*Demote` 是一个前缀。例如,DFFastAddDemote是一个前缀变体。此变体会返回32位结果,而不是双精度值。`DF*Demote` 的函数可用,且类似于 `LWCToFloat` 函数。使用 `DF*Demote` 函数可免除不必要的计算,效率更高。这是首选的截断方法。
|
||||
|
||||
# FLWCVector及相关类型
|
||||
在虚幻引擎中,有多个复合类型使用专用于大型世界坐标的不同底层数学类型结构。 这些类型带有"FLWC"前缀,可在 `LargeWorldCoordinates.ush` 源文件中找到。大部分着色器现在使用DoubleFloat类型。但是,一些系统仍使用FLWC类型。
|
||||
可用的HLSL类型有:
|
||||
|
||||
|HLSL类型|说明|
|
||||
|---|---|
|
||||
|`FLWCScalar` `FLWCVector2/3/4`|这些类型类似于float1/2/3/4,但是,它们包含额外的图块坐标。因此,FLWCVector2由float2图块和float2偏移组成。表示的值由以下公式计算:图块 * TileSize + 偏移,其中TileSize表示一个被定义为256k的常量值。|
|
||||
|`FLWCMatrix` `FLWCInverseMatrix`|这些类型类似于float4x4,但是,它们都包含额外的float3图块坐标。|
|
||||
|`FLWCMatrix`|乘以矩阵后将图块坐标添加到结果。|
|
||||
|`FLWCInverseMatrix`|在乘以矩阵之前添加图块坐标。|
|
||||
|`FLWCMatrix`|此类型适合变换为世界空间(LocalToWorld)。|
|
||||
|`FLWCInverseMatrix`|此类型适合从世界空间进行变换(WorldToLocal)。|
|
||||
`LWCAdd` 或 `LWCRsqrt` 等运算可以在这些类型上执行。接受LWC输入值的运算将返回LWC输出( `LWCAdd` ),而其他运算会返回常规浮点输出( `LWCRsqrt` )。将坐标变小的运算提供了返回常规浮点的途径。
|
||||
|
||||
>FLWC类型提供的精度相较于DoubleFloat库更低。对于靠近图块边缘的矢量,固定图块大小意味着 **图块(Tile)** 组件中的位不被使用,而 **偏移(Offset)** 只能存储截断的坐标。在虚幻引擎5.4之前,这会导致物体在世界中移动时精度时高时低,主要表现为物体抖动和振动。DoubleFloat数学类型通过删除固定大小解决了该问题,因为无论值的量级如何,这两个组件始终会存储尽可能高的精度。这在概念上类似于定点与浮点算术之间的差异。
|
||||
|
||||
# 材质
|
||||
**基于坐标的世界空间位置**节点(例如WorldPosition、Object、Camera、Actor和Particle)以及TransformPosition节点会使用LWC类型的输出,因此这些Input节点输出类型改变后续计算节点类型。
|
||||
可以考虑将这些节点的 **世界位置原点类型(World Position Origin Type)** 属性设置为使用 **摄像机相对世界位置(Camera Relative World Position)** 实现最优精度和性能。(LWC => float)
|
||||
|
||||
# 从UE4到UE5的转换指南
|
||||
View & ResolvedView =>`PrimaryView`
|
||||
|
||||
着色器代码中的许多变量已被切换为LWC变体。以下似乎一些重要的示例:
|
||||
- `SceneData.ush` 、 `FPrimitiveSceneData` 和 `FInstanceSceneData` 有各种更新的矩阵和位置矢量。
|
||||
- 光源位置和反射捕获位置。
|
||||
- 全局摄像机统一数据更改了各种矩阵和偏移(例如PreViewTranslation)。例如,在SceneView和FNaniteView中。
|
||||
|
||||
TranslatedWorldSpace是虚幻引擎着色器中使用的现有引擎概念。它在之前旨在通过相对于摄像机原点工作来提高精度。但是,此行为对LWC很有用,因为在TranslatedWorldSpace中运行时,可以使用浮点数。我们建议不要将WorldPosition转换为双精度值,而是重构函数以改用转译的世界空间,这会带来出色的性能,同时保留高精度。例如:
|
||||
```cpp
|
||||
float3 TranslatedWorldPosition = mul(Input.LocalPosition, PrimaryView.LocalToTranslatedWorld);
|
||||
```
|
141
03-UnrealEngine/Rendering/RenderingPipeline/Ue4后处理逻辑简析.md
Normal file
141
03-UnrealEngine/Rendering/RenderingPipeline/Ue4后处理逻辑简析.md
Normal 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
|
16
03-UnrealEngine/Rendering/RenderingPipeline/Ue4延迟渲染流程笔记.md
Normal file
16
03-UnrealEngine/Rendering/RenderingPipeline/Ue4延迟渲染流程笔记.md
Normal file
@@ -0,0 +1,16 @@
|
||||
#### 渲染循环发起以及渲染函数
|
||||
渲染更新由UGameEngine::Tick()发起。
|
||||
```
|
||||
UGameEngine::Tick
|
||||
|
|
||||
-RedrawViewports()
|
||||
|
|
||||
-GameViewport->Viewport->Draw
|
||||
|
|
||||
-EnqueueBeginRenderFrame()
|
||||
SetRequiresVsync()
|
||||
EnqueueEndRenderFrame()
|
||||
```
|
||||
|
||||
#### FDeferredShadingSceneRenderer
|
||||
FDeferredShadingSceneRenderer继承自FSceneRenderer,从Render函数中可以了解到延迟渲染的整个过程。每个Pass的渲染流程。
|
@@ -0,0 +1,579 @@
|
||||
---
|
||||
title: VirtualTexture学习笔记
|
||||
date: 2024-02-20 18:26:49
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
- UE4 Runtime Virtual Texture 实现机制及源码解析:https://zhuanlan.zhihu.com/p/143709152
|
||||
- **UE Virtual Texture图文浅析**:https://zhuanlan.zhihu.com/p/642580472
|
||||
|
||||
## 相关概念
|
||||
- Virtual Texture:虚拟纹理,以下简称 VT
|
||||
- Runtime Virtual Texture:UE4 运行时虚拟纹理系统,以下简称 RVT
|
||||
- VT feedback:存储当前屏幕像素对应的 VT Page 信息,用于加载 VT 数据。
|
||||
- VT Physical Texture:虚拟纹理对应的物理纹理资源
|
||||
- PageTable:虚拟纹理页表,用来寻址 VT Physical Texture Page 数据。
|
||||
- PageTable Texture:包含虚拟纹理页表数据的纹理资源,通过此纹理资源可查询 Physical Texture Page 信息。有些 VT 系统也叫 Indirection Texture,由于本文分析 UE4 VT 的内容,这里采用 UE4 术语。
|
||||
- PageTable Buffer:包含虚拟纹理页表数据内容的 GPU Buffer 资源。
|
||||
|
||||
### 地址映射
|
||||
地址映射在Virtual Texture是一个很重要的环节,就是如何将一个Virtual Texture的texel映射到Physical Texture的texel上,这里还需要适配当高分辨率的page没有加载的情况,需要得到已经加载的对应低分辨率的page地址。
|
||||
#### 四叉树映射
|
||||

|
||||
这里每个四叉树的节点的内容存的就是bias和scale,这样就可以将虚拟纹理的地址转换成物理纹理的地址了,假如没有找到,也可以用父节点的地址来得到低分辨率的。但是这里要找到对应的节点需要搜索这个四叉树,搜索的速度取决于树的高度,也就是mipmap的层级,在差的低mip的地址上效率会比较差。
|
||||
### Feedback Rendering
|
||||
在Virtual Texture中一个很重要的事情是要有一个可以决定加载哪些page的策略,这个策略就是要有一个叫Feedback Rendering的过程。这个可以设计为一个单独的pass,或者跟Pre-Z或者GBuffer同时。渲染生成的这张texture里面存的就是虚纹理的page坐标,mip level和可能的虚纹理id(用来应对多虚纹理的情况)。
|
||||

|
||||
|
||||
可以看到上图,由于page的变化在屏幕空间是比较低的,所以Feedback的RT是不需要全分辨率的,低分辨率渲染就可以了。对于半透明物体或者alpha test的物体,在Feedback Rendering的过程中只能当作是不透明物体来渲染,那样就会在屏幕像素上随机产生当前像素的可能结果。与之相类似的,如果一个屏幕像素用到了两个page,也会是随机出现一种在最后的结果RT上。这样虽然可以让所有需要的page都加载,但是,可能会遇到另外一个问题,那就是可能会发生这一帧加载的page,下一帧的时候被卸载掉了,然后再下一帧又要加载,这样会导致物理纹理一直在置换,即便屏幕像素并未改变,物理纹理的page也无法稳定下来。为了解决这个问题,需要设计一个调色板,对于半透明物体,间隔出现全透明或者不透明,对于多page的情况,则需要设计为间隔出现不同page的结果,这样就能同时加载所有page,并且保持稳定。但是,如果出现了多层半透明物体的叠加或者多个page的情况,如何设计一个合理的调色板变成了一个问题。这里可以考虑为每个像素匹配一个linked list,这个需要额外的硬件支持,叫structured append and consume buffers。
|
||||
|
||||
接着就是对这个Feedback的结果进行分析了,分析需要将Feedback的结果读回CPU,这里可以使用compute shader解析这个结果,然后输出到一个更简单的buffer上去:
|
||||

|
||||
|
||||
这样可以使回读操作更快,处理page更新也能更快。对于如何更新page,也需要策略,我们需要尽量不阻塞执行,异步的加载page,但是对于完全不存在任何一个mip的page,我们还是需要同步加载防止显示出错。在异步的过程中,我们需要对需要加载page设置优先级,比如需要加载的mip level和已经存在的mip level相差越大的优先级越高,被越多像素要求加载的page的优先级越高,这里需要设计一个完整的加载策略。
|
||||
|
||||
### Texture Poping
|
||||
由于page是异步加载的,这是有延时的,当加载的mip比当前显示的差很远的时候,我们渲染会使用新加载的更清晰的mip,这样我们会看到非常明显的跳变。假如我们用了软实现的Tri-linear Filtering,那么当加载的mip level跟当前显示的mip level相差很大的时候,需要做一个delay,等待中间的mip page的加载,然后再去更新。对于没有Tri-linear Filtering的实现,就得逐渐更新page,使得过度平滑。一个可能的方法是,upsample低分辨率的mip,直到高分辨率的mip加载。但是,这样仍然会出现跳变,由于采样的位置其实发生了突变。
|
||||

|
||||
|
||||
上图可以看到,当分辨率增加2倍之后,结果会发生很大的不同。解决的方案是,先把upsample的低分辨率page加载到一个物理纹理的page,当高分辨率的加载好了,插值过度那个物理纹理的page,这样采样的位置没有发生改变,只是每个像素的颜色在渐变,就不会有跳变出现了。
|
||||
|
||||
# UE5VirtualTexture相关实现
|
||||
为读向往大佬文章的学习笔记。
|
||||
|
||||
## VT 系统概述
|
||||
从原理上来说,VT 系统主要由 2 大阶段构成,VT 数据准备和 VT 运行时采样阶段。
|
||||
1. VT 数据准备阶段:
|
||||
1. 生成 VT feedback 数据
|
||||
2. 生成 VT 纹理数据,并更新到指定 VT Physical Texture 对应的 Page
|
||||
3. 根据 feedback 数据生成并更新 PageTable 数据
|
||||
2. VT 运行时采样阶段:
|
||||
1. 根据屏幕空间坐标以及相关信息生成 VT Physical Texture UV
|
||||
2. 对 VT Physical Texture 执行采样
|
||||
|
||||
UE4 的 RVT 基本上也是按照这个原理和流程来实现的,本文就按照这个顺序来详细讲解。在讲解之前,为了便于后续理解,先来了解下 UE5 RVT 的实现机制。
|
||||
|
||||
## UE5 RVT 实现机制概述
|
||||
IVirtualTexture 是 UE5 VT 最重要的接口,它是如何产生 VT 数据的接口,主要有两个抽象函数
|
||||
- RequestPageData,请求页面数据
|
||||
- ProducePageData,产生页面数据
|
||||
|
||||
在UE5中其子类有:
|
||||
- FLightmapPreviewVirtualTexture
|
||||
- FNullVirtualTextureProducer
|
||||
- FRuntimeVirtualTextureProducer
|
||||
- FUploadingVirtualTexture
|
||||
- FVirtualTextureAddressRedirect
|
||||
- FVirtualTextureLevelRedirector
|
||||
|
||||
对于 RVT 来说,实现此接口的是 FRuntimeVirtualTextureProducer,也就是作为运行时产生 Page 纹理数据的类,对于 SVT 来说,实现此接口的是 FUploadingVirtualTexture,用于从磁盘中流送上传 Page 纹理数据。
|
||||
FVirtualTextureSystem 是全局单件类,包含了 UE5 VT 系统中大部分核心逻辑和流程,驱动这个系统工作的是 Update 函数,分别在 PC/Console Pipeline 的 FDeferredShadingSceneRenderer::Render 和 Mobile Pipeline 的 FMobileSceneRenderer::Render 中调用.
|
||||
|
||||
在 VT 中只有 Diffuse 是远远不够的,在实际的着色过程中经常需要其它的纹理数据来进行光照计算,比如 Normal、Roughness、Specular 等等,UE4 的 RVT 使用了 Layer 的概念,每个 Layer 代表不同的 Physical Texture,在 UE4 中可以支持底色(Diffuse)、法线(Normal)、Roughness(粗糙度)、高光度(Specular)、遮罩(Mask)等不同内容的 VT,这些数据以 Pack 的方式保存在多张 Physical Texture 的不同通道中,在使用时通过 Unpack 以及组合的形式解算出来进行光照计算。这些 Physical Texture 的寻址信息保存在同一个 VT 上的 PageTable Texture 的不同颜色通道中,下文会详细描述。
|
||||
|
||||
UE4 RVT 中所使用的 GPU 资源有以下 3 种:
|
||||
- PageTable Buffer 用于在 CPU 端只写的 PageTable 数据。
|
||||
- PageTable Texture 用于在采样 VT 时获取指定 VT Physical Texture Page 数据,此资源数据不是在 CPU 端填充,而是由 PageTable Buffer 通过 RHICommandList 在 GPU 上填充。
|
||||
- VT Physical Texture 实际存储纹理数据的资源,通过 VT feedback 从磁盘或运行时动态生成纹理数据,并在 VT 数据准备阶段中更新到 VT Physical Texture 中。
|
||||
|
||||
其中 VT Physical Texture 资源包含在 FVirtualTexturePhysicalSpace 类中,PageTable Buffer/Texture 包含在 FVirtualTextureSpace 类中。
|
||||
|
||||
FVirtualTextureSystem的会提交请求最终会调用**FRuntimeVirtualTextureProducer::ProducePageData()** ,最后会在**FRuntimeVirtualTextureFinalizer::Finalize()** 中 调用 **RuntimeVirtualTexture::RenderPages()** 函数渲染到 VT Physical Texture 上。
|
||||
|
||||
## UE5中相关类
|
||||
- **FVirtualTextureSystem**:单例类,用于全局控制VT流程。
|
||||
- URuntimeVirtualTexture(UObject)
|
||||
- FRuntimeVirtualTextureRenderResource
|
||||
- UVirtualTexture(UObject)
|
||||
- UVirtualTexture2D(UTexture2D)
|
||||
|
||||
# UE5 VirtualHeightfieldMesh简述
|
||||
https://zhuanlan.zhihu.com/p/575398476
|
||||
|
||||
## 可能的相关类
|
||||
- VirtualHeightfieldMesh
|
||||
- UVirtualHeightfieldMeshComponent
|
||||
- **UHeightfieldMinMaxTexture**
|
||||
- BuildTexture()
|
||||
- FVirtualHeightfieldMeshSceneProxy
|
||||
- FVirtualHeightfieldMeshRendererExtension
|
||||
- AddWork()
|
||||
- **SubmitWork()**
|
||||
- FVirtualTextureFeedbackBuffer 参考[[#Pass1的补充VirtualTextureFeedback]]
|
||||
- UNiagaraDataInterfaceLandscape
|
||||
- UNiagaraDataInterfaceVirtualTexture(**NiagaraDataInterfaceVirtualTextureTemplate.ush**)
|
||||
- GetAttributesValid()
|
||||
- SampleRVTLayer()
|
||||
- SampleRVT()
|
||||
- URuntimeVirtualTextureComponent
|
||||
|
||||
## VirtualHeightfieldMesh
|
||||
首先是MinMaxTexture。全称**UHeightfieldMinMaxTexture**(下简称MinMaxTexture),可以说是整个VHM中最重要的部分之一。它是离线生成的,目的主要是以下几个:
|
||||
1. 用作Instance的剔除(遮挡剔除查询+Frustum剔除)
|
||||
2. 用作决定VHM的LOD
|
||||
3. 用作平滑VHM的顶点位置
|
||||
|
||||
其中比较关键的几个成员变量为:
|
||||
- TObjectPtr<class UTexture2D> Texture:BGRA8格式、贴图大小与RVT的Tile数量一致、有全部mipmap。每个像素存储RVT一个Tile中的最小值以及最大值,各为16bit、encode在RGBA的4个通道上。
|
||||
- TObjectPtr<class UTexture2D> LodBiasTexture:G8格式、贴图大小与RTV的Tile数量一致、无mipmap。每个像素存储了Texture对应像素周围3x3blur之后的结果。
|
||||
- TObjectPtr<class UTexture2D> LodBiasMinMaxTexture:BGRA8格式、贴图大小与RTV的Tile数量一致、有全部mipmap。类似于HZB、每个像素存储LodBiasTexture的最小值以及最大值,各为8bit、存在RG两个通道上。
|
||||
- int32 MaxCPULevels:表示共需要在CPU端存储多少层level的数据。
|
||||
- TArray`<FVector2D>` TextureData:CPU端存储Texture贴图的数据,共MaxCPULevels层mipmap。
|
||||
|
||||
### TextureData的获取
|
||||
因此要生成MinMaxTexture、最关键的就是要得到TextureData,其入口函数为位于**HeightfieldMinMaxTextureBuilder.cpp**的**VirtualHeightfieldmesh::BuildMinMaxTexture**中。由于Texture存储的是RVT中每个Tile中最小最大值,因此不难想象到其大致流程可以分为以下几步:
|
||||
1. 遍历RVT的每个Tile并绘制到一张中间贴图上,然后计算这张中间纹理的最小最大值、存储至目标贴图对应的位置上;
|
||||
2. 为目标贴图生成mipmap;
|
||||
3. 将目标贴图回读至CPU、得到TextureData。
|
||||
|
||||
将Tile绘制到一张中间贴图使用的是自带的***RuntimeVirtualTexture::RenderPagesStandAlone***函数;计算最小最大值是通过Downsample的方式计算而成。如下图所示为2x2Tiles、4TileSize的RVT,计算Tile0的最小最大值的示意过程图:
|
||||

|
||||
|
||||
Downsample的ComputeShader为**TMinMaxTextureCS**。遍历计算完每个Tile的最小最大值后,同样通过Downsample为目标贴图生成全mipmap。
|
||||
|
||||
最后为了将贴图回读到CPU,先是通过CopyTexture的方式将目标贴图的各个mipmap复制到各自带有CPUReadback Flag的贴图后,再通过MapStagingSurface/UnmapStagingSurface的方式复制到CPU内存上。由于是比较常规的操作,就不过多介绍了。
|
||||
|
||||
至此也就得到了带有所有mipmap的CPU端的TextureData,接着将此作为参数调用UHeightfieldMinMaxTexture::BuildTexture以生成剩下的内容(即Texture、LodBiasTexture、LodBiasMinMaxTexture)。
|
||||
|
||||
### FVirtualHeightfieldMeshSceneProxy
|
||||
至此离线生成的MinMaxTexture介绍完毕,后面都是实时渲染内容的介绍部分。所有内容都围绕着VHM的SceneProxy也就是**FVirtualHeightfieldMeshSceneProxy**展开。
|
||||
|
||||
#### 遮挡剔除
|
||||
> 关于硬件的遮挡剔除用法可以参考DX12的官方sample[[8]](https://zhuanlan.zhihu.com/p/575398476#ref_8)
|
||||
|
||||
首先是遮挡剔除部分,VHM做了Tile级别且带有LOD的遮挡剔除。VHM的SceneProxy重写了函数GetOcclusionQueries,函数实现只是简单地返回OcclusionVolumes:
|
||||

|
||||
OcclusionVolumes的构建在函数BuildOcclusionVolumes中,其基本思路为取MinMaxTexture中**CPU端的TextureData**的数据、获得每个Tile的高度最小最大值来创建该Tile的Bounds信息。
|
||||
|
||||
可以看到OcclusionVolumes是带有Lod的。当然实际上这里的代码的LodIndex不一定从0开始,因为Component中有一项成员变量**NumOcclusionLod**、表示创建多少层mipmap的OcclusionVolumes。另外有一点需要注意的是,NumOcclusionLod默认值为0、也就是说VHM的遮挡剔除默认没有开启。
|
||||

|
||||
|
||||
由于VHM需要在ComputePass中动态地构建Instance绘制的IndirectArgs、因此SceneProxy还重写了函数AcceptOcclusionResults,用以获取遮挡剔除的结果。具体是将UE返回的遮挡剔除的结果存在贴图OcclusionTexture上、以便能够作为SRV在后续的Pass中访问:
|
||||
```cpp
|
||||
void FVirtualHeightfieldMeshSceneProxy::AcceptOcclusionResults(FSceneView const* View, TArray<bool>* Results, int32 ResultsStart, int32 NumResults)
|
||||
{
|
||||
// 由于构建IndirectArgs跟SceneProxy不在同一个地方,因此用了一个全局变量来保存遮挡剔除的结果
|
||||
FOcclusionResults& OcclusionResults = GOcclusionResults.Emplace(FOcclusionResultsKey(this, View));
|
||||
OcclusionResults.TextureSize = OcclusionGridSize;
|
||||
OcclusionResults.NumTextureMips = NumOcclusionLods;
|
||||
|
||||
// 创建贴图,并将遮挡剔除结果Copy至贴图上
|
||||
FRHIResourceCreateInfo CreateInfo(TEXT("VirtualHeightfieldMesh.OcclusionTexture"));
|
||||
OcclusionResults.OcclusionTexture = RHICreateTexture2D(OcclusionGridSize.X, OcclusionGridSize.Y, PF_G8, NumOcclusionLods, 1, TexCreate_ShaderResource, CreateInfo);
|
||||
bool const* Src = Results->GetData() + ResultsStart;
|
||||
FIntPoint Size = OcclusionGridSize;
|
||||
for (int32 MipIndex = 0; MipIndex < NumOcclusionLods; ++MipIndex)
|
||||
{
|
||||
uint32 Stride;
|
||||
uint8* Dst = (uint8*)RHILockTexture2D(OcclusionResults.OcclusionTexture, MipIndex, RLM_WriteOnly, Stride, false);
|
||||
for (int Y = 0; Y < Size.Y; ++Y)
|
||||
{
|
||||
for (int X = 0; X < Size.X; ++X)
|
||||
{
|
||||
Dst[Y * Stride + X] = *(Src++) ? 255 : 0;
|
||||
}
|
||||
}
|
||||
RHIUnlockTexture2D(OcclusionResults.OcclusionTexture, MipIndex, false);
|
||||
|
||||
Size.X = FMath::Max(Size.X / 2, 1);
|
||||
Size.Y = FMath::Max(Size.Y / 2, 1);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 整体思路
|
||||
至此就开始真正的VHM的Mesh的数据构建了。为了后续的代码细节能够更加易懂,这里再说明一下VHM构建mesh的整体思路:假设我们有一个工作队列为QueueBuffer,每一项工作就是从QueueBuffer中取出一项工作(更准确地说,取出一个Quad)、对这个Quad判断是否需要进行细化、如果需要细分则将这个Quad细分为4个Quad并塞入QueueBuffer中。
|
||||
|
||||
重复这个取出→处理→放回的过程,直到QueueBuffer中没有工作为止。示意图如下:
|
||||
|
||||

|
||||
|
||||
### RVT相关代码(Pass1:CollectQuad)
|
||||
如果不能细分,那么就会增加一个Instance、将其Instance的数据写入RWQuadBuffer中。RWQuadBuffer将会用在后续的CullInstance Pass中,以真正地构建IndirectArgs:
|
||||
```c++
|
||||
// 无法继续细分的情况
|
||||
// 用以后续对RVT进行采样
|
||||
uint PhysicalAddress = PageTableTexture.Load(int3(Pos, Level));
|
||||
|
||||
InterlockAdd(RWQuadInfo.Write, 1, Write);
|
||||
RWQuadBuffer[Write] = Pack(InitQuadRenderItem(Pos, Level, PhysicalAddress, bCull | bOcclude));
|
||||
```
|
||||
|
||||
> 其中的RWQuadInfo是我编的变量名、实际的代码中并不存在。或者说实际上这里的变量名是RWIndirectArgsBuffer,但是并不是前面所说的用以绘制的IndirectArgs。为了不让大家混淆,这里改了下变量名
|
||||
> 另外也能由此看出的是,VHM也许曾经想过利用IndirectArgs数组来绘制(即DXSample中将符合条件的生成IndirectArgs放进数组中)。但是最后改成的是一个IndirectArgs但是Instance的绘制方式
|
||||
|
||||
PS. PageTableTexture的类型为**RHITextuire**。相关Shader代码位于**VirtualHeightfieldMesh.usf**
|
||||
|
||||
#### Pass1的补充VirtualTextureFeedback
|
||||
不再继续进行细分后、说明后续就要对该Level的RVT进行采样,因此需要处理对应的Feedback信息、让虚幻可以加载对应的Page。shader代码如下图所示:
|
||||

|
||||
|
||||
c++中则要将这个RWFeedbackBuffer喂给虚幻的函数**SubmitVirtualTextureFeedbackBuffer**:
|
||||

|
||||
|
||||
### 相关代码段
|
||||
```c++
|
||||
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
|
||||
{
|
||||
...
|
||||
|
||||
// Sample height from virtual texture.
|
||||
VTUniform Uniform = VTUniform_Unpack(VHM.VTPackedUniform);
|
||||
Uniform.vPageBorderSize -= .5f * VHM.PhysicalTextureSize.y; // Half texel offset is used in VT write and in sampling because we want texel locations to match landscape vertices.
|
||||
VTPageTableUniform PageTableUniform = VTPageTableUniform_Unpack(VHM.VTPackedPageTableUniform0, VHM.VTPackedPageTableUniform1);
|
||||
VTPageTableResult VTResult0 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, floor(SampleLevel));
|
||||
float2 UV0 = VTComputePhysicalUVs(VTResult0, 0, Uniform);
|
||||
float Height0 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV0, 0);
|
||||
VTPageTableResult VTResult1 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, ceil(SampleLevel));
|
||||
float2 UV1 = VTComputePhysicalUVs(VTResult1, 0, Uniform);
|
||||
float Height1 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV1, 0);
|
||||
float Height = lerp(Height0.x, Height1.x, frac(SampleLevel));
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
渲染线程创建VT的相关逻辑:
|
||||
```c++
|
||||
void FVirtualHeightfieldMeshSceneProxy::CreateRenderThreadResources()
|
||||
{
|
||||
if (RuntimeVirtualTexture != nullptr)
|
||||
{
|
||||
if (!bCallbackRegistered)
|
||||
{
|
||||
GetRendererModule().AddVirtualTextureProducerDestroyedCallback(RuntimeVirtualTexture->GetProducerHandle(), &OnVirtualTextureDestroyedCB, this);
|
||||
bCallbackRegistered = true;
|
||||
}
|
||||
|
||||
//URuntimeVirtualTexture* RuntimeVirtualTexture;
|
||||
if (RuntimeVirtualTexture->GetMaterialType() == ERuntimeVirtualTextureMaterialType::WorldHeight)
|
||||
{
|
||||
AllocatedVirtualTexture = RuntimeVirtualTexture->GetAllocatedVirtualTexture();
|
||||
NumQuadsPerTileSide = RuntimeVirtualTexture->GetTileSize();
|
||||
|
||||
if (AllocatedVirtualTexture != nullptr)
|
||||
{
|
||||
// Gather vertex factory uniform parameters.
|
||||
FVirtualHeightfieldMeshVertexFactoryParameters UniformParams;
|
||||
UniformParams.PageTableTexture = AllocatedVirtualTexture->GetPageTableTexture(0);
|
||||
UniformParams.HeightTexture = AllocatedVirtualTexture->GetPhysicalTextureSRV(0, false);
|
||||
UniformParams.HeightSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
|
||||
UniformParams.LodBiasTexture = LodBiasTexture ? LodBiasTexture->GetResource()->TextureRHI : GBlackTexture->TextureRHI;
|
||||
UniformParams.LodBiasSampler = TStaticSamplerState<SF_Point>::GetRHI();
|
||||
UniformParams.NumQuadsPerTileSide = NumQuadsPerTileSide;
|
||||
|
||||
FUintVector4 PackedUniform;
|
||||
AllocatedVirtualTexture->GetPackedUniform(&PackedUniform, 0);
|
||||
UniformParams.VTPackedUniform = PackedUniform;
|
||||
FUintVector4 PackedPageTableUniform[2];
|
||||
AllocatedVirtualTexture->GetPackedPageTableUniform(PackedPageTableUniform);
|
||||
UniformParams.VTPackedPageTableUniform0 = PackedPageTableUniform[0];
|
||||
UniformParams.VTPackedPageTableUniform1 = PackedPageTableUniform[1];
|
||||
|
||||
const float PageTableSizeX = AllocatedVirtualTexture->GetWidthInTiles();
|
||||
const float PageTableSizeY = AllocatedVirtualTexture->GetHeightInTiles();
|
||||
UniformParams.PageTableSize = FVector4f(PageTableSizeX, PageTableSizeY, 1.f / PageTableSizeX, 1.f / PageTableSizeY);
|
||||
|
||||
const float PhysicalTextureSize = AllocatedVirtualTexture->GetPhysicalTextureSize(0);
|
||||
UniformParams.PhysicalTextureSize = FVector2f(PhysicalTextureSize, 1.f / PhysicalTextureSize);
|
||||
|
||||
UniformParams.VirtualHeightfieldToLocal = FMatrix44f(UVToLocal);
|
||||
UniformParams.VirtualHeightfieldToWorld = FMatrix44f(UVToWorld); // LWC_TODO: Precision loss
|
||||
|
||||
UniformParams.MaxLod = AllocatedVirtualTexture->GetMaxLevel();
|
||||
UniformParams.LodBiasScale = LodBiasScale;
|
||||
|
||||
// Create vertex factory.
|
||||
VertexFactory = new FVirtualHeightfieldMeshVertexFactory(GetScene().GetFeatureLevel(), UniformParams);
|
||||
VertexFactory->InitResource(FRHICommandListImmediate::Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# RVT生成相关
|
||||
|
||||
# RVT相关操作总结
|
||||
CPU端创建:
|
||||
```c++
|
||||
|
||||
```
|
||||
|
||||
作为UniformParameter传递到GPU端:
|
||||
```c++
|
||||
AllocatedVirtualTexture = RuntimeVirtualTexture->GetAllocatedVirtualTexture();
|
||||
|
||||
//PageTableTexture、Texture&Sampler
|
||||
FVirtualHeightfieldMeshVertexFactoryParameters UniformParams;
|
||||
UniformParams.PageTableTexture = AllocatedVirtualTexture->GetPageTableTexture(0);
|
||||
UniformParams.HeightTexture = AllocatedVirtualTexture->GetPhysicalTextureSRV(0, false);
|
||||
UniformParams.HeightSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
|
||||
|
||||
//VTPackedUniform&VTPackedPageTableUniform
|
||||
FUintVector4 PackedUniform;
|
||||
AllocatedVirtualTexture->GetPackedUniform(&PackedUniform, 0);
|
||||
UniformParams.VTPackedUniform = PackedUniform;
|
||||
FUintVector4 PackedPageTableUniform[2];
|
||||
AllocatedVirtualTexture->GetPackedPageTableUniform(PackedPageTableUniform);
|
||||
UniformParams.VTPackedPageTableUniform0 = PackedPageTableUniform[0];
|
||||
UniformParams.VTPackedPageTableUniform1 = PackedPageTableUniform[1];
|
||||
|
||||
//PageTableSize
|
||||
const float PageTableSizeX = AllocatedVirtualTexture->GetWidthInTiles();
|
||||
const float PageTableSizeY = AllocatedVirtualTexture->GetHeightInTiles();
|
||||
UniformParams.PageTableSize = FVector4f(PageTableSizeX, PageTableSizeY, 1.f / PageTableSizeX, 1.f / PageTableSizeY);
|
||||
|
||||
//PhysicalTextureSize
|
||||
const float PhysicalTextureSize = AllocatedVirtualTexture->GetPhysicalTextureSize(0);
|
||||
UniformParams.PhysicalTextureSize = FVector2f(PhysicalTextureSize, 1.f / PhysicalTextureSize);
|
||||
|
||||
//Local <=> World Matrix
|
||||
UniformParams.VirtualHeightfieldToLocal = FMatrix44f(UVToLocal);
|
||||
UniformParams.VirtualHeightfieldToWorld = FMatrix44f(UVToWorld); // LWC_TODO: Precision loss
|
||||
|
||||
//MaxLod
|
||||
UniformParams.MaxLod = AllocatedVirtualTexture->GetMaxLevel();
|
||||
```
|
||||
|
||||
GPU端采样:
|
||||
```c++
|
||||
VTUniform Uniform = VTUniform_Unpack(VHM.VTPackedUniform);
|
||||
Uniform.vPageBorderSize -= .5f * VHM.PhysicalTextureSize.y; // Half texel offset is used in VT write and in sampling because we want texel locations to match landscape vertices.
|
||||
VTPageTableUniform PageTableUniform = VTPageTableUniform_Unpack(VHM.VTPackedPageTableUniform0, VHM.VTPackedPageTableUniform1);
|
||||
VTPageTableResult VTResult0 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, floor(SampleLevel));
|
||||
float2 UV0 = VTComputePhysicalUVs(VTResult0, 0, Uniform);
|
||||
float Height0 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV0, 0);
|
||||
VTPageTableResult VTResult1 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, ceil(SampleLevel));
|
||||
float2 UV1 = VTComputePhysicalUVs(VTResult1, 0, Uniform);
|
||||
float Height1 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV1, 0);
|
||||
float Height = lerp(Height0.x, Height1.x, frac(SampleLevel));
|
||||
```
|
||||
|
||||
**NiagaraDataInterfaceVirtualTextureTemplate.ush**中的代码:
|
||||
```c++
|
||||
//其他相关VT操作函数位于VirtualTextureCommon.ush
|
||||
|
||||
float4 SampleRVTLayer_{ParameterName}(float2 SampleUV, Texture2D InTexture, Texture2D<uint4> InPageTable, uint4 InTextureUniforms)
|
||||
{
|
||||
VTPageTableResult PageTable = TextureLoadVirtualPageTableLevel(InPageTable, VTPageTableUniform_Unpack({ParameterName}_PageTableUniforms[0], {ParameterName}_PageTableUniforms[1]), SampleUV, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, 0.0f);
|
||||
return TextureVirtualSample(InTexture, {ParameterName}_SharedSampler, PageTable, 0, VTUniform_Unpack(InTextureUniforms));
|
||||
}
|
||||
|
||||
void SampleRVT_{ParameterName}(in float3 WorldPosition, out bool bInsideVolume, out float3 BaseColor, out float Specular, out float Roughness, out float3 Normal, out float WorldHeight, out float Mask)
|
||||
{
|
||||
bInsideVolume = false;
|
||||
BaseColor = float3(0.0f, 0.0f, 0.0f);
|
||||
Specular = 0.5f;
|
||||
Roughness = 0.5f;
|
||||
Normal = float3(0.0f, 0.0f, 1.0f);
|
||||
WorldHeight = 0.0f;
|
||||
Mask = 1.0f;
|
||||
|
||||
// Get Sample Location
|
||||
FLWCVector3 LWCWorldPosition = MakeLWCVector3({ParameterName}_SystemLWCTile, WorldPosition);
|
||||
FLWCVector3 LWCUVOrigin = MakeLWCVector3({ParameterName}_SystemLWCTile, {ParameterName}_UVUniforms[0].xyz);
|
||||
|
||||
float2 SampleUV = VirtualTextureWorldToUV(LWCWorldPosition, LWCUVOrigin, {ParameterName}_UVUniforms[1].xyz, {ParameterName}_UVUniforms[2].xyz);
|
||||
|
||||
// Test to see if we are inside the volume, but still take the samples as it will clamp to the edge
|
||||
bInsideVolume = all(SampleUV >- 0.0f) && all(SampleUV < 1.0f);
|
||||
|
||||
// Sample Textures
|
||||
float4 LayerSample[3];
|
||||
LayerSample[0] = ({ParameterName}_ValidLayersMask & 0x1) != 0 ? SampleRVTLayer_{ParameterName}(SampleUV, {ParameterName}_VirtualTexture0, {ParameterName}_VirtualTexture0PageTable, {ParameterName}_VirtualTexture0TextureUniforms) : 0;
|
||||
LayerSample[1] = ({ParameterName}_ValidLayersMask & 0x2) != 0 ? SampleRVTLayer_{ParameterName}(SampleUV, {ParameterName}_VirtualTexture1, {ParameterName}_VirtualTexture1PageTable, {ParameterName}_VirtualTexture1TextureUniforms) : 0;
|
||||
LayerSample[2] = ({ParameterName}_ValidLayersMask & 0x4) != 0 ? SampleRVTLayer_{ParameterName}(SampleUV, {ParameterName}_VirtualTexture2, {ParameterName}_VirtualTexture2PageTable, {ParameterName}_VirtualTexture2TextureUniforms) : 0;
|
||||
|
||||
// Sample Available Attributes
|
||||
switch ( {ParameterName}_MaterialType )
|
||||
{
|
||||
case ERuntimeVirtualTextureMaterialType_BaseColor:
|
||||
{
|
||||
BaseColor = LayerSample[0].xyz;
|
||||
break;
|
||||
}
|
||||
|
||||
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Roughness:
|
||||
{
|
||||
BaseColor = VirtualTextureUnpackBaseColorSRGB(LayerSample[0]);
|
||||
Roughness = LayerSample[1].y;
|
||||
Normal = VirtualTextureUnpackNormalBGR565(LayerSample[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_DEPRECATED:
|
||||
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Specular:
|
||||
{
|
||||
BaseColor = LayerSample[0].xyz;
|
||||
Specular = LayerSample[1].x;
|
||||
Roughness = LayerSample[1].y;
|
||||
Normal = VirtualTextureUnpackNormalBC3BC3(LayerSample[0], LayerSample[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Specular_YCoCg:
|
||||
{
|
||||
BaseColor = VirtualTextureUnpackBaseColorYCoCg(LayerSample[0]);
|
||||
Specular = LayerSample[2].x;
|
||||
Roughness = LayerSample[2].y;
|
||||
Normal = VirtualTextureUnpackNormalBC5BC1(LayerSample[1], LayerSample[2]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Specular_Mask_YCoCg:
|
||||
{
|
||||
BaseColor = VirtualTextureUnpackBaseColorYCoCg(LayerSample[0]);
|
||||
Specular = LayerSample[2].x;
|
||||
Roughness = LayerSample[2].y;
|
||||
Normal = VirtualTextureUnpackNormalBC5BC1(LayerSample[1], LayerSample[2]);
|
||||
Mask = LayerSample[2].w;
|
||||
break;
|
||||
}
|
||||
|
||||
case ERuntimeVirtualTextureMaterialType_WorldHeight:
|
||||
{
|
||||
WorldHeight = VirtualTextureUnpackHeight(LayerSample[0], {ParameterName}_WorldHeightUnpack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
RVT的UV计算逻辑在VirtualTextureWorldToUV()中:
|
||||
```glsl
|
||||
float2 VirtualTextureWorldToUV(in float3 WorldPos, in float3 Origin, in float3 U, in float3 V)
|
||||
{
|
||||
float3 P = WorldPos - Origin;
|
||||
return saturate(float2(dot(P, U), dot(P, V)));
|
||||
}
|
||||
```
|
||||
从代码可以看出,根据当前像素的世界空间位置以及 RVT Volume 原点(Volume 左下角)、Volume 边界大小的 UV 范围(经过世界旋转变换的 XY 轴乘以 Volume 缩放-即 Volume 大小-的倒数,这些计算在 **URuntimeVirtualTexture::Initialize()** 中完成),求出当前像素在 RVT 中的 UV 坐标。
|
||||
|
||||
TextureComputeVirtualMipLevel() 函数计算 RVT 的 mipLevel,为了实现较好的混合效果,这里根据当前帧 Id 生成交错的随机 noise 扰动 level。
|
||||
```cpp
|
||||
int TextureComputeVirtualMipLevel(
|
||||
in out VTPageTableResult OutResult,
|
||||
float2 dUVdx, float2 dUVdy, float MipBias,
|
||||
float2 SvPositionXY,
|
||||
VTPageTableUniform PageTableUniform)
|
||||
{
|
||||
OutResult.dUVdx = dUVdx * PageTableUniform.SizeInPages;
|
||||
OutResult.dUVdy = dUVdy * PageTableUniform.SizeInPages;
|
||||
|
||||
// Always compute mip level using MipLevelAniso2D, even if VIRTUAL_TEXTURE_ANISOTROPIC_FILTERING is disabled
|
||||
// This way the VT mip level selection will come much closer to HW mip selection, even if we're not sampling the texture using anisotropic filtering const float ComputedLevel = MipLevelAniso2D(OutResult.dUVdx, OutResult.dUVdy, PageTableUniform.MaxAnisoLog2);
|
||||
|
||||
const float GlobalMipBias = GetGlobalVirtualTextureMipBias();
|
||||
#if VIRTUAL_TEXTURE_MANUAL_TRILINEAR_FILTERING
|
||||
const float Noise = 0.f;
|
||||
#else
|
||||
const float Noise = GetStochasticMipNoise(SvPositionXY);
|
||||
#endif
|
||||
|
||||
const float MipLevel = ComputedLevel + MipBias + GlobalMipBias + Noise;
|
||||
const float MipLevelFloor = floor(MipLevel);
|
||||
OutResult.MipLevelFrac = MipLevel - MipLevelFloor;
|
||||
|
||||
return (int)MipLevelFloor + int(PageTableUniform.vPageTableMipBias);
|
||||
}
|
||||
```
|
||||
|
||||
TextureLoadVirtualPageTableInternal 函数代码如下:
|
||||
```cpp
|
||||
void TextureLoadVirtualPageTableInternal(
|
||||
in out VTPageTableResult OutResult,
|
||||
Texture2D<uint4> PageTable0, Texture2D<uint4> PageTable1,
|
||||
VTPageTableUniform PageTableUniform,
|
||||
float2 UV, int vLevel)
|
||||
{
|
||||
OutResult.UV = UV * PageTableUniform.SizeInPages;
|
||||
|
||||
const uint vLevelClamped = clamp(vLevel, 0, int(PageTableUniform.MaxLevel));
|
||||
uint vPageX = (uint(OutResult.UV.x) + PageTableUniform.XOffsetInPages) >> vLevelClamped;
|
||||
uint vPageY = (uint(OutResult.UV.y) + PageTableUniform.YOffsetInPages) >> vLevelClamped;
|
||||
|
||||
OutResult.PageTableValue[0] = PageTable0.Load(int3(vPageX, vPageY, vLevelClamped));
|
||||
OutResult.PageTableValue[1] = PageTable1.Load(int3(vPageX, vPageY, vLevelClamped));
|
||||
|
||||
#if VIRTUAL_TEXTURE_MANUAL_TRILINEAR_FILTERING
|
||||
// Second page table for trilinear.
|
||||
const uint vLevelClamped2 = clamp(vLevel + 1, 0, int(PageTableUniform.MaxLevel));
|
||||
const uint vPageX2 = (uint(OutResult.UV.x) + PageTableUniform.XOffsetInPages) >> vLevelClamped2;
|
||||
const uint vPageY2 = (uint(OutResult.UV.y) + PageTableUniform.YOffsetInPages) >> vLevelClamped2;
|
||||
|
||||
OutResult.PageTableValue[2] = PageTable0.Load(int3(vPageX2, vPageY2, vLevelClamped2)); OutResult.PageTableValue[3] = PageTable1.Load(int3(vPageX2, vPageY2, vLevelClamped2));
|
||||
// Alternate requests to both mip levels
|
||||
if ((View.FrameNumber & 1u) == 0u)
|
||||
{ vLevel += 1; vPageX = vPageX2; vPageY = vPageY2; }#endif
|
||||
|
||||
// PageTableID packed in upper 4 bits of 'PackedPageTableUniform', which is the bit position we want it in for PackedRequest as well, just need to mask off extra bits
|
||||
OutResult.PackedRequest = PageTableUniform.ShiftedPageTableID;
|
||||
OutResult.PackedRequest |= vPageX;
|
||||
OutResult.PackedRequest |= vPageY << 12;
|
||||
|
||||
// Feedback always encodes vLevel+1, and subtracts 1 on the CPU side.
|
||||
// This allows the CPU code to know when we requested a negative vLevel which indicates that we don't have sufficient virtual texture resolution. const uint vLevelPlusOneClamped = clamp(vLevel + 1, 0, int(PageTableUniform.MaxLevel + 1));
|
||||
OutResult.PackedRequest |= vLevelPlusOneClamped << 24;
|
||||
}```
|
||||
|
||||
这个函数主要 2个作用,一是生成用于寻址 VT Physical Texture 的 PageTableValue,另一个是生成 feedback Request 数据,具体有以下几个步骤:
|
||||
|
||||
1. 根据 UV 寻址模式修正虚拟纹理坐标
|
||||
2. 根据当前 VT 的 Page 数量和上一步修正过的虚拟纹理坐标计算出 VT 坐标对应的 Page 坐标。
|
||||
3. 通过 Page 坐标加上 Page 的 XY 偏移,再根据 mipLevel,计算出 PageTable Texture 的 UV 坐标,然后使用这个 UV 坐标和 mipLevel 采样 PageTable Texture 得到在 Physical Texture 上的信息,保存在 PageTableValue 中,在接下来的流程中使用。
|
||||
4. 将第 3 步计算好的 PageTable Texture 的 Page 坐标和 mipLevel 保存在 VTPageTableResult 中,最后通过 StoreVirtualTextureFeedback 函数写入到 VT feedback Buffer 中。
|
||||
|
||||
***TextureVirtualSample***
|
||||
采样所需的 VTPageTableResult 数据准备完毕,在 TextureVirtualSample 函数中就是执行真正的 Physical Texture 采样逻辑,代码如下:
|
||||
```c++
|
||||
MaterialFloat4 TextureVirtualSample(
|
||||
Texture2D Physical, SamplerState PhysicalSampler,
|
||||
VTPageTableResult PageTableResult, uint LayerIndex,
|
||||
VTUniform Uniform)
|
||||
{
|
||||
const float2 pUV = VTComputePhysicalUVs(PageTableResult, LayerIndex, Uniform);
|
||||
return Physical.SampleGrad(PhysicalSampler, pUV, PageTableResult.dUVdx, PageTableResult.dUVdy);
|
||||
}
|
||||
```
|
||||
|
||||
这个函数很简单,只有 2 个函数调用,第一行 VTComputePhysicalUVs 用于生成 Physical Texture UV 坐标,第二行用于执行渐变采样,所以这里重点是如何生成 Physical Texture UV 坐标,VTComputePhysicalUVs 函数代码如下:
|
||||
```cpp
|
||||
float2 VTComputePhysicalUVs(in out VTPageTableResult PageTableResult, uint LayerIndex, VTUniform Uniform)
|
||||
{
|
||||
const uint PackedPageTableValue = PageTableResult.PageTableValue[LayerIndex / 4u][LayerIndex & 3u];
|
||||
|
||||
// See packing in PageTableUpdate.usf
|
||||
const uint vLevel = PackedPageTableValue & 0xf;
|
||||
const float UVScale = 1.0f / (float)(1 << vLevel);
|
||||
const float pPageX = (float)((PackedPageTableValue >> 4) & ((1 << Uniform.PageCoordinateBitCount) - 1));
|
||||
const float pPageY = (float)(PackedPageTableValue >> (4 + Uniform.PageCoordinateBitCount));
|
||||
|
||||
const float2 vPageFrac = frac(PageTableResult.UV * UVScale);
|
||||
const float2 pUV = float2(pPageX, pPageY) * Uniform.pPageSize + (vPageFrac * Uniform.vPageSize + Uniform.vPageBorderSize);
|
||||
|
||||
const float ddxyScale = UVScale * Uniform.vPageSize;
|
||||
PageTableResult.dUVdx *= ddxyScale;
|
||||
PageTableResult.dUVdy *= ddxyScale;
|
||||
return pUV;
|
||||
}
|
||||
```
|
||||
|
||||
VT还存在一个反馈机制,具体可以参考:[[#Pass1的补充VirtualTextureFeedback]]
|
||||
```c++
|
||||
/** GPU fence pool. Contains a fence array that is kept in sync with the FeedbackItems ring buffer. Fences are used to know when a transfer is ready to Map() without stalling. */
|
||||
/** GPU 栅栏池。其中包含一个与 FeedbackItems 环形缓冲区保持同步的栅栏数组。栅栏用于了解传输何时准备就绪,可在不停滞的情况下进行 Map()。 */
|
||||
class FFeedbackGPUFencePool* Fences;
|
||||
```
|
||||
|
||||
# 使用RVT实现3D高斯 LOD思路
|
||||
AI数据侧:
|
||||
1. 确定点云数据是否可以划分成四叉树的数据结构,也就是将一堆点云按照一个**距离阈值** 进行分割,最终形成一个四叉树。
|
||||
1. 确定是否可以生成金字塔结构贴图(直接写入到Mipmap结构里),或者生成多张基于2的幕长度贴图。
|
||||
|
||||
UE侧:
|
||||
目前已经测试过SVT可以放入到Niagara Texture Sampler中。同时也可以将SVT放到Texture2DArray中。
|
||||
1. 将3D高斯各种贴图制作成SVT之后塞入Texture2DArray,在Niagara中采样。
|
||||
2. 在Niagara中根据Niagara 粒子ID对SVT进行采样。
|
@@ -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);
|
||||
}
|
||||
```
|
170
03-UnrealEngine/Rendering/RenderingPipeline/Yivanlee卡通渲染笔记.md
Normal file
170
03-UnrealEngine/Rendering/RenderingPipeline/Yivanlee卡通渲染笔记.md
Normal 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
|
||||
- ToonDataA:R ShadowOffset GBA 未使用
|
||||
- ToonDataB:RGB OutlineColor A OutlineMask
|
||||
- ToonDataC:RGB IDMap A OutlineWidth
|
||||
- PreIntegratedToonBRDF: R 为NoL 预积分Ramp G 为GGX高光预积分值。
|
||||
- PreIntegratedToonSkinBRDF:RGB为皮肤预积分颜色。
|
||||
- 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*/
|
||||
```
|
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: 剖析虚幻渲染体系(09)- 材质体系
|
||||
date: 2024-02-04 21:44:37
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
https://www.cnblogs.com/timlly/p/15109132.html
|
@@ -0,0 +1,215 @@
|
||||
---
|
||||
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::EmitPreshaderOrConstant:PreshaderHeader = &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);
|
||||
}
|
||||
```
|
||||
|
||||
和UMaterialExpressionStrataLegacyConversion::Compile()无关。
|
||||
GetSubsurfaceProfileParameterName()
|
||||
__SubsurfaceProfile
|
||||
|
||||
1. 在FHLSLMaterialTranslator::Translate() => NumericParameter()会往MaterialCompilationOutput.UniformExpressionSet以及DefaultUniformValues添加默认值以及Offset。最终会调用AddUniformExpression()
|
||||
|
||||
## 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;//UE5转移至UMaterialInstance中
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
## UMaterialInstance
|
||||
```c++
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = MaterialInstance)
|
||||
uint8 bOverrideSubsurfaceProfile:1;
|
||||
```
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,737 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-02-04 12:57:56
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
原文地址:https://www.cnblogs.com/timlly/p/15156626.html
|
||||
|
||||
# 概念
|
||||
- FRenderResource:是渲染线程的渲染资源基础父类,实际的数据和逻辑由子类实现。可以认为是渲染线程中承载**CPU相关相关渲染的载体**。
|
||||
- 比如输入的顶点数据、顶点Index数据、贴图数据等。
|
||||
- FRHIResource:抽象了GPU侧的资源,也是众多RHI资源类型的父类。可以认为是承载**显卡API相关资源的载体**。
|
||||
- 比如TextureSampler、TextureObject等。
|
||||
- FRHICommand:其父类为**FRHICommandBase**结构体。其含有**FRHICommandBase* Next**用来保存下一个Command的指针,所以存储他的结构为**链表**。
|
||||
- 含有接口:void void ExecuteAndDestruct(FRHICommandListBase& CmdList, FRHICommandListDebugContext& DebugContext)。执行完就销毁。
|
||||
- UE使用**FRHICOMMAND_MACRO**宏来快速定义各种RHICommand。主要功能包含:
|
||||
- 数据和资源的设置、更新、清理、转换、拷贝、回读。
|
||||
- 图元绘制。
|
||||
- Pass、SubPass、场景、ViewPort等的开始和结束事件。
|
||||
- 栅栏、等待、广播接口。
|
||||
- 光线追踪。
|
||||
- Slate、调试相关的命令。
|
||||
- FRHICommandList:是**RHI的指令队列**,用来管理、执行一组FRHICommand的对象。
|
||||
- 其子类有**FRHICommandListImmediate**(立即执行队列)、FRHIComputeCommandList_RecursiveHazardous与TRHIComputeCommandList_RecursiveHazardous(命令列表的递归使用)
|
||||
- IRHICommandContext:是RHI的命令上下文接口类,定义了一组图形API相关的操作。在可以并行处理命令列表的平台上,它是一个单独的对象类。
|
||||
- 主要的接口函数有:
|
||||
- 派发ComputeShader
|
||||
- 渲染查询(可见性?)
|
||||
- 相关开始/结束函数。
|
||||
- 设置数据(Viewport、GraphicsPipelineState等)
|
||||
- 设置ShadserParameter
|
||||
- 绘制图元
|
||||
- 纹理拷贝/更新
|
||||
- Raytracing
|
||||
- IRHICommandContext的接口和FRHICommandList的接口高度相似且重叠。IRHICommandContext还有许多子类:
|
||||
- IRHICommandContextPSOFallback:不支持真正的图形管道的RHI命令上下文。
|
||||
- FNullDynamicRHI:空实现的动态绑定RHI。
|
||||
- FOpenGLDynamicRHI:OpenGL的动态RHI。
|
||||
- FD3D11DynamicRHI:D3D11的动态RHI。
|
||||
- FMetalRHICommandContext:Metal平台的命令上下文。
|
||||
- FD3D12CommandContextBase:D3D12的命令上下文。
|
||||
- FVulkanCommandListContext:Vulkan平台的命令队列上下文。
|
||||
- FEmptyDynamicRHI:动态绑定的RHI实现的接口。
|
||||
- FValidationContext:校验上下文。
|
||||
- IRHICommandContextContainer:IRHICommandContextContainer就是包含了IRHICommandContext对象的类型。相当于存储了一个或一组命令上下文的容器,以支持并行化地提交命令队列,只在D3D12、Metal、Vulkan等现代图形API中有实现。
|
||||
- D3D12存储了FD3D12Adapter* Adapter、FD3D12CommandContext* CmdContext、 FD3D12CommandContextRedirector* CmdContextRedirector。
|
||||
- FDynamicRHI:FDynamicRHI是由动态绑定的RHI实现的接口,它定义的接口和CommandList、CommandContext比较相似。
|
||||
- 代码详见[[#FDynamicRHI]]
|
||||
- FRHICommandListExecutor:负责将**Renderer层的RHI中间指令转译(或直接调用)到目标平台的图形API**,它在RHI体系中起着举足轻重的作用。
|
||||
- FParallelCommandListSet:用于实现并行渲染。使用案例详见[[#FParallelCommandListSet]]。目前5.3只有下面2个子类:
|
||||
- FRDGParallelCommandListSet
|
||||
- FShadowParallelCommandListSet
|
||||
|
||||
## FDynamicRHI
|
||||
```c++
|
||||
class RHI_API FDynamicRHI
|
||||
{
|
||||
public:
|
||||
virtual ~FDynamicRHI() {}
|
||||
|
||||
virtual void Init() = 0;
|
||||
virtual void PostInit() {}
|
||||
virtual void Shutdown() = 0;
|
||||
|
||||
void InitPixelFormatInfo(const TArray<uint32>& PixelFormatBlockBytesIn);
|
||||
|
||||
// ---- RHI接口 ----
|
||||
|
||||
// 下列接口要求FlushType: Thread safe
|
||||
virtual FSamplerStateRHIRef RHICreateSamplerState(const FSamplerStateInitializerRHI& Initializer) = 0;
|
||||
virtual FRasterizerStateRHIRef RHICreateRasterizerState(const FRasterizerStateInitializerRHI& Initializer) = 0;
|
||||
virtual FDepthStencilStateRHIRef RHICreateDepthStencilState(const FDepthStencilStateInitializerRHI& Initializer) = 0;
|
||||
virtual FBlendStateRHIRef RHICreateBlendState(const FBlendStateInitializerRHI& Initializer) = 0;
|
||||
|
||||
// 下列接口要求FlushType: Wait RHI Thread
|
||||
virtual FVertexDeclarationRHIRef RHICreateVertexDeclaration(const FVertexDeclarationElementList& Elements) = 0;
|
||||
virtual FPixelShaderRHIRef RHICreatePixelShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
|
||||
virtual FVertexShaderRHIRef RHICreateVertexShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
|
||||
virtual FHullShaderRHIRef RHICreateHullShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
|
||||
virtual FDomainShaderRHIRef RHICreateDomainShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
|
||||
virtual FGeometryShaderRHIRef RHICreateGeometryShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
|
||||
virtual FComputeShaderRHIRef RHICreateComputeShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
|
||||
|
||||
// FlushType: Must be Thread-Safe.
|
||||
virtual FRenderQueryPoolRHIRef RHICreateRenderQueryPool(ERenderQueryType QueryType, uint32 NumQueries = UINT32_MAX);
|
||||
inline FComputeFenceRHIRef RHICreateComputeFence(const FName& Name);
|
||||
|
||||
virtual FGPUFenceRHIRef RHICreateGPUFence(const FName &Name);
|
||||
virtual void RHICreateTransition(FRHITransition* Transition, ERHIPipeline SrcPipelines, ERHIPipeline DstPipelines, ERHICreateTransitionFlags CreateFlags, TArrayView<const FRHITransitionInfo> Infos);
|
||||
virtual void RHIReleaseTransition(FRHITransition* Transition);
|
||||
|
||||
// FlushType: Thread safe.
|
||||
virtual FStagingBufferRHIRef RHICreateStagingBuffer();
|
||||
virtual void* RHILockStagingBuffer(FRHIStagingBuffer* StagingBuffer, FRHIGPUFence* Fence, uint32 Offset, uint32 SizeRHI);
|
||||
virtual void RHIUnlockStagingBuffer(FRHIStagingBuffer* StagingBuffer);
|
||||
|
||||
// FlushType: Thread safe, but varies depending on the RHI
|
||||
virtual FBoundShaderStateRHIRef RHICreateBoundShaderState(FRHIVertexDeclaration* VertexDeclaration, FRHIVertexShader* VertexShader, FRHIHullShader* HullShader, FRHIDomainShader* DomainShader, FRHIPixelShader* PixelShader, FRHIGeometryShader* GeometryShader) = 0;
|
||||
// FlushType: Thread safe
|
||||
virtual FGraphicsPipelineStateRHIRef RHICreateGraphicsPipelineState(const FGraphicsPipelineStateInitializer& Initializer);
|
||||
|
||||
// FlushType: Thread safe, but varies depending on the RHI
|
||||
virtual FUniformBufferRHIRef RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout& Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation) = 0;
|
||||
virtual void RHIUpdateUniformBuffer(FRHIUniformBuffer* UniformBufferRHI, const void* Contents) = 0;
|
||||
|
||||
// FlushType: Wait RHI Thread
|
||||
virtual FIndexBufferRHIRef RHICreateIndexBuffer(uint32 Stride, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo) = 0;
|
||||
virtual void* RHILockIndexBuffer(FRHICommandListImmediate& RHICmdList, FRHIIndexBuffer* IndexBuffer, uint32 Offset, uint32 Size, EResourceLockMode LockMode);
|
||||
virtual void RHIUnlockIndexBuffer(FRHICommandListImmediate& RHICmdList, FRHIIndexBuffer* IndexBuffer);
|
||||
virtual void RHITransferIndexBufferUnderlyingResource(FRHIIndexBuffer* DestIndexBuffer, FRHIIndexBuffer* SrcIndexBuffer);
|
||||
|
||||
// FlushType: Wait RHI Thread
|
||||
virtual FVertexBufferRHIRef RHICreateVertexBuffer(uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo) = 0;
|
||||
// FlushType: Flush RHI Thread
|
||||
virtual void* RHILockVertexBuffer(FRHICommandListImmediate& RHICmdList, FRHIVertexBuffer* VertexBuffer, uint32 Offset, uint32 SizeRHI, EResourceLockMode LockMode);
|
||||
virtual void RHIUnlockVertexBuffer(FRHICommandListImmediate& RHICmdList, FRHIVertexBuffer* VertexBuffer);
|
||||
// FlushType: Flush Immediate (seems dangerous)
|
||||
virtual void RHICopyVertexBuffer(FRHIVertexBuffer* SourceBuffer, FRHIVertexBuffer* DestBuffer) = 0;
|
||||
virtual void RHITransferVertexBufferUnderlyingResource(FRHIVertexBuffer* DestVertexBuffer, FRHIVertexBuffer* SrcVertexBuffer);
|
||||
|
||||
// FlushType: Wait RHI Thread
|
||||
virtual FStructuredBufferRHIRef RHICreateStructuredBuffer(uint32 Stride, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo) = 0;
|
||||
// FlushType: Flush RHI Thread
|
||||
virtual void* RHILockStructuredBuffer(FRHICommandListImmediate& RHICmdList, FRHIStructuredBuffer* StructuredBuffer, uint32 Offset, uint32 SizeRHI, EResourceLockMode LockMode);
|
||||
virtual void RHIUnlockStructuredBuffer(FRHICommandListImmediate& RHICmdList, FRHIStructuredBuffer* StructuredBuffer);
|
||||
|
||||
// FlushType: Wait RHI Thread
|
||||
virtual FUnorderedAccessViewRHIRef RHICreateUnorderedAccessView(FRHIStructuredBuffer* StructuredBuffer, bool bUseUAVCounter, bool bAppendBuffer) = 0;
|
||||
// FlushType: Wait RHI Thread
|
||||
virtual FUnorderedAccessViewRHIRef RHICreateUnorderedAccessView(FRHITexture* Texture, uint32 MipLevel) = 0;
|
||||
// FlushType: Wait RHI Thread
|
||||
virtual FUnorderedAccessViewRHIRef RHICreateUnorderedAccessView(FRHITexture* Texture, uint32 MipLevel, uint8 Format);
|
||||
|
||||
(......)
|
||||
|
||||
// RHI帧更新,须从主线程调用,FlushType: Thread safe
|
||||
virtual void RHITick(float DeltaTime) = 0;
|
||||
// 阻塞CPU直到GPU执行完成变成空闲. FlushType: Flush Immediate (seems wrong)
|
||||
virtual void RHIBlockUntilGPUIdle() = 0;
|
||||
// 开始当前帧,并确保GPU正在积极地工作 FlushType: Flush Immediate (copied from RHIBlockUntilGPUIdle)
|
||||
virtual void RHISubmitCommandsAndFlushGPU() {};
|
||||
|
||||
// 通知RHI准备暂停它.
|
||||
virtual void RHIBeginSuspendRendering() {};
|
||||
// 暂停RHI渲染并将控制权交给系统的操作, FlushType: Thread safe
|
||||
virtual void RHISuspendRendering() {};
|
||||
// 继续RHI渲染, FlushType: Thread safe
|
||||
virtual void RHIResumeRendering() {};
|
||||
// FlushType: Flush Immediate
|
||||
virtual bool RHIIsRenderingSuspended() { return false; };
|
||||
|
||||
// FlushType: called from render thread when RHI thread is flushed
|
||||
// 仅在FRHIResource::FlushPendingDeletes内的延迟删除之前每帧调用.
|
||||
virtual void RHIPerFrameRHIFlushComplete();
|
||||
|
||||
// 执行命令队列, FlushType: Wait RHI Thread
|
||||
virtual void RHIExecuteCommandList(FRHICommandList* CmdList) = 0;
|
||||
|
||||
// FlushType: Flush RHI Thread
|
||||
virtual void* RHIGetNativeDevice() = 0;
|
||||
// FlushType: Flush RHI Thread
|
||||
virtual void* RHIGetNativeInstance() = 0;
|
||||
|
||||
// 获取命令上下文. FlushType: Thread safe
|
||||
virtual IRHICommandContext* RHIGetDefaultContext() = 0;
|
||||
// 获取计算上下文. FlushType: Thread safe
|
||||
virtual IRHIComputeContext* RHIGetDefaultAsyncComputeContext();
|
||||
|
||||
// FlushType: Thread safe
|
||||
virtual class IRHICommandContextContainer* RHIGetCommandContextContainer(int32 Index, int32 Num) = 0;
|
||||
|
||||
// 直接由渲染线程调用的接口, 以优化RHI调用.
|
||||
virtual FVertexBufferRHIRef CreateAndLockVertexBuffer_RenderThread(class FRHICommandListImmediate& RHICmdList, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo, void*& OutDataBuffer);
|
||||
virtual FIndexBufferRHIRef CreateAndLockIndexBuffer_RenderThread(class FRHICommandListImmediate& RHICmdList, uint32 Stride, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo, void*& OutDataBuffer);
|
||||
|
||||
(......)
|
||||
|
||||
// Buffer Lock/Unlock
|
||||
virtual void* LockVertexBuffer_BottomOfPipe(class FRHICommandListImmediate& RHICmdList, ...);
|
||||
virtual void* LockIndexBuffer_BottomOfPipe(class FRHICommandListImmediate& RHICmdList, ...);
|
||||
|
||||
(......)
|
||||
};
|
||||
```
|
||||
|
||||
以上只显示了部分接口,其中部分接口要求从渲染线程调用,部分须从游戏线程调用。大多数接口在被调用前需刷新指定类型的命令,比如:
|
||||
```c++
|
||||
class RHI_API FDynamicRHI
|
||||
{
|
||||
// FlushType: Wait RHI Thread
|
||||
void RHIExecuteCommandList(FRHICommandList* CmdList);
|
||||
|
||||
// FlushType: Flush Immediate
|
||||
void RHIBlockUntilGPUIdle();
|
||||
|
||||
// FlushType: Thread safe
|
||||
void RHITick(float DeltaTime);
|
||||
};
|
||||
```
|
||||
可以在**FRHICommandListImmediate**的**ExecuteCommandList()**、**BlockUntilGPUIdle()**、**Tick()** 看到调用。
|
||||
|
||||
>需要注意的是,传统图形API(D3D11、OpenGL)除了继承FDynamicRHI,还需要继承**IRHICommandContextPSOFallback**,因为需要借助后者的接口处理PSO的数据和行为,以保证传统和现代API对PSO的一致处理行为。也正因为此,现代图形API(D3D12、Vulkan、Metal)不需要继承**IRHICommandContext**的任何继承体系的类型,单单直接继承**FDynamicRHI**就可以处理RHI层的所有数据和操作。
|
||||
既然现代图形API(D3D12、Vulkan、Metal)的**DynamicRHI**没有继承**IRHICommandContext**的任何继承体系的类型,那么它们是如何实现FDynamicRHI::RHIGetDefaultContext的接口?下面以FD3D12DynamicRHI为例:
|
||||
|
||||
## FParallelCommandListSet
|
||||
```c++
|
||||
//Engine\Source\Runtime\Renderer\Private\DepthRendering.cpp
|
||||
void FDeferredShadingSceneRenderer::RenderPrePass(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneDepthTexture, FInstanceCullingManager& InstanceCullingManager, FRDGTextureRef* FirstStageDepthBuffer)
|
||||
{
|
||||
RDG_EVENT_SCOPE(GraphBuilder, "PrePass %s %s", GetDepthDrawingModeString(DepthPass.EarlyZPassMode), GetDepthPassReason(DepthPass.bDitheredLODTransitionsUseStencil, ShaderPlatform));
|
||||
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderPrePass);
|
||||
RDG_GPU_STAT_SCOPE(GraphBuilder, Prepass);
|
||||
|
||||
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderPrePass, FColor::Emerald);
|
||||
SCOPE_CYCLE_COUNTER(STAT_DepthDrawTime);
|
||||
|
||||
const bool bParallelDepthPass = GRHICommandList.UseParallelAlgorithms() && CVarParallelPrePass.GetValueOnRenderThread();
|
||||
|
||||
RenderPrePassHMD(GraphBuilder, SceneDepthTexture);
|
||||
|
||||
if (DepthPass.IsRasterStencilDitherEnabled())
|
||||
{
|
||||
AddDitheredStencilFillPass(GraphBuilder, Views, SceneDepthTexture, DepthPass);
|
||||
}
|
||||
|
||||
auto RenderDepthPass = [&](uint8 DepthMeshPass)
|
||||
{
|
||||
check(DepthMeshPass == EMeshPass::DepthPass || DepthMeshPass == EMeshPass::SecondStageDepthPass);
|
||||
const bool bSecondStageDepthPass = DepthMeshPass == EMeshPass::SecondStageDepthPass;
|
||||
|
||||
if (bParallelDepthPass)
|
||||
{
|
||||
RDG_WAIT_FOR_TASKS_CONDITIONAL(GraphBuilder, IsDepthPassWaitForTasksEnabled());
|
||||
|
||||
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
|
||||
{
|
||||
FViewInfo& View = Views[ViewIndex];
|
||||
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
|
||||
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
|
||||
|
||||
FMeshPassProcessorRenderState DrawRenderState;
|
||||
SetupDepthPassState(DrawRenderState);
|
||||
|
||||
const bool bShouldRenderView = View.ShouldRenderView() && (bSecondStageDepthPass ? View.bUsesSecondStageDepthPass : true);
|
||||
if (bShouldRenderView)
|
||||
{
|
||||
View.BeginRenderView();
|
||||
|
||||
FDepthPassParameters* PassParameters = GetDepthPassParameters(GraphBuilder, View, SceneDepthTexture);
|
||||
View.ParallelMeshDrawCommandPasses[DepthMeshPass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);
|
||||
|
||||
GraphBuilder.AddPass(
|
||||
bSecondStageDepthPass ? RDG_EVENT_NAME("SecondStageDepthPassParallel") : RDG_EVENT_NAME("DepthPassParallel"),
|
||||
PassParameters,
|
||||
ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
|
||||
[this, &View, PassParameters, DepthMeshPass](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList)
|
||||
{
|
||||
//并行渲染逻辑主要在这里
|
||||
FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_Prepass), View, FParallelCommandListBindings(PassParameters));
|
||||
ParallelCommandListSet.SetHighPriority();
|
||||
View.ParallelMeshDrawCommandPasses[DepthMeshPass].DispatchDraw(&ParallelCommandListSet, RHICmdList, &PassParameters->InstanceCullingDrawParams);
|
||||
});
|
||||
|
||||
RenderPrePassEditorPrimitives(GraphBuilder, View, PassParameters, DrawRenderState, DepthPass.EarlyZPassMode, InstanceCullingManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
···
|
||||
}
|
||||
|
||||
//Engine\Source\Runtime\Renderer\Private\MeshDrawCommands.cpp
|
||||
void FParallelMeshDrawCommandPass::DispatchDraw(FParallelCommandListSet* ParallelCommandListSet, FRHICommandList& RHICmdList, const FInstanceCullingDrawParams* InstanceCullingDrawParams) const
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(ParallelMdcDispatchDraw);
|
||||
if (MaxNumDraws <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FMeshDrawCommandOverrideArgs OverrideArgs;
|
||||
if (InstanceCullingDrawParams)
|
||||
{
|
||||
OverrideArgs = GetMeshDrawCommandOverrideArgs(*InstanceCullingDrawParams);
|
||||
}
|
||||
|
||||
if (ParallelCommandListSet)
|
||||
{
|
||||
const ENamedThreads::Type RenderThread = ENamedThreads::GetRenderThread();
|
||||
|
||||
FGraphEventArray Prereqs;
|
||||
if (ParallelCommandListSet->GetPrereqs())
|
||||
{
|
||||
Prereqs.Append(*ParallelCommandListSet->GetPrereqs());
|
||||
}
|
||||
if (TaskEventRef.IsValid())
|
||||
{
|
||||
Prereqs.Add(TaskEventRef);
|
||||
}
|
||||
|
||||
// Distribute work evenly to the available task graph workers based on NumEstimatedDraws.
|
||||
// Every task will then adjust it's working range based on FVisibleMeshDrawCommandProcessTask results.
|
||||
const int32 NumThreads = FMath::Min<int32>(FTaskGraphInterface::Get().GetNumWorkerThreads(), ParallelCommandListSet->Width);
|
||||
const int32 NumTasks = FMath::Min<int32>(NumThreads, FMath::DivideAndRoundUp(MaxNumDraws, ParallelCommandListSet->MinDrawsPerCommandList));
|
||||
const int32 NumDrawsPerTask = FMath::DivideAndRoundUp(MaxNumDraws, NumTasks);
|
||||
|
||||
for (int32 TaskIndex = 0; TaskIndex < NumTasks; TaskIndex++)
|
||||
{
|
||||
const int32 StartIndex = TaskIndex * NumDrawsPerTask;
|
||||
const int32 NumDraws = FMath::Min(NumDrawsPerTask, MaxNumDraws - StartIndex);
|
||||
checkSlow(NumDraws > 0);
|
||||
|
||||
FRHICommandList* CmdList = ParallelCommandListSet->NewParallelCommandList();
|
||||
|
||||
FGraphEventRef AnyThreadCompletionEvent = TGraphTask<FDrawVisibleMeshCommandsAnyThreadTask>::CreateTask(&Prereqs, RenderThread)
|
||||
.ConstructAndDispatchWhenReady(*CmdList, TaskContext.InstanceCullingContext, TaskContext.MeshDrawCommands, TaskContext.MinimalPipelineStatePassSet,
|
||||
OverrideArgs,
|
||||
TaskContext.InstanceFactor,
|
||||
TaskIndex, NumTasks);
|
||||
|
||||
ParallelCommandListSet->AddParallelCommandList(CmdList, AnyThreadCompletionEvent, NumDraws);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QUICK_SCOPE_CYCLE_COUNTER(STAT_MeshPassDrawImmediate);
|
||||
|
||||
WaitForMeshPassSetupTask(IsInActualRenderingThread() ? EWaitThread::Render : EWaitThread::Task);
|
||||
|
||||
if (TaskContext.bUseGPUScene)
|
||||
{
|
||||
if (TaskContext.MeshDrawCommands.Num() > 0)
|
||||
{
|
||||
TaskContext.InstanceCullingContext.SubmitDrawCommands(
|
||||
TaskContext.MeshDrawCommands,
|
||||
TaskContext.MinimalPipelineStatePassSet,
|
||||
OverrideArgs,
|
||||
0,
|
||||
TaskContext.MeshDrawCommands.Num(),
|
||||
TaskContext.InstanceFactor,
|
||||
RHICmdList);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitMeshDrawCommandsRange(TaskContext.MeshDrawCommands, TaskContext.MinimalPipelineStatePassSet, nullptr, 0, 0, TaskContext.bDynamicInstancing, 0, TaskContext.MeshDrawCommands.Num(), TaskContext.InstanceFactor, RHICmdList);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 普通Pass渲染
|
||||
```c++
|
||||
// 代码为UE5旧版本代码
|
||||
// Engine\Source\Runtime\RHI\Public\RHIResources.h
|
||||
|
||||
// 渲染通道信息.
|
||||
struct FRHIRenderPassInfo
|
||||
{
|
||||
// 渲染纹理信息.
|
||||
struct FColorEntry
|
||||
{
|
||||
FRHITexture* RenderTarget;
|
||||
FRHITexture* ResolveTarget;
|
||||
int32 ArraySlice;
|
||||
uint8 MipIndex;
|
||||
ERenderTargetActions Action;
|
||||
};
|
||||
FColorEntry ColorRenderTargets[MaxSimultaneousRenderTargets];
|
||||
|
||||
// 深度模板信息.
|
||||
struct FDepthStencilEntry
|
||||
{
|
||||
FRHITexture* DepthStencilTarget;
|
||||
FRHITexture* ResolveTarget;
|
||||
EDepthStencilTargetActions Action;
|
||||
FExclusiveDepthStencil ExclusiveDepthStencil;
|
||||
};
|
||||
FDepthStencilEntry DepthStencilRenderTarget;
|
||||
|
||||
// 解析参数.
|
||||
FResolveParams ResolveParameters;
|
||||
|
||||
// 部分RHI可以使用纹理来控制不同区域的采样和/或阴影分辨率
|
||||
FTextureRHIRef FoveationTexture = nullptr;
|
||||
|
||||
// 部分RHI需要一个提示,遮挡查询将在这个渲染通道中使用
|
||||
uint32 NumOcclusionQueries = 0;
|
||||
bool bOcclusionQueries = false;
|
||||
|
||||
// 部分RHI需要知道,在为部分资源转换生成mip映射的情况下,这个渲染通道是否将读取和写入相同的纹理.
|
||||
bool bGeneratingMips = false;
|
||||
|
||||
// 如果这个renderpass应该是多视图,则需要多少视图.
|
||||
uint8 MultiViewCount = 0;
|
||||
|
||||
// 部分RHI的提示,渲染通道将有特定的子通道.
|
||||
ESubpassHint SubpassHint = ESubpassHint::None;
|
||||
|
||||
// 是否太多UAV.
|
||||
bool bTooManyUAVs = false;
|
||||
bool bIsMSAA = false;
|
||||
|
||||
// 不同的构造函数.
|
||||
|
||||
// Color, no depth, optional resolve, optional mip, optional array slice
|
||||
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* ResolveRT = nullptr, uint32 InMipIndex = 0, int32 InArraySlice = -1);
|
||||
// Color MRTs, no depth
|
||||
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction);
|
||||
// Color MRTs, no depth
|
||||
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction, FRHITexture* ResolveTargets[]);
|
||||
// Color MRTs and depth
|
||||
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction, FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
||||
// Color MRTs and depth
|
||||
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction, FRHITexture* ResolveRTs[], FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
||||
// Depth, no color
|
||||
explicit FRHIRenderPassInfo(FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT = nullptr, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
||||
// Depth, no color, occlusion queries
|
||||
explicit FRHIRenderPassInfo(FRHITexture* DepthRT, uint32 InNumOcclusionQueries, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT = nullptr, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
||||
// Color and depth
|
||||
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
||||
// Color and depth with resolve
|
||||
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* ResolveColorRT,
|
||||
FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
||||
// Color and depth with resolve and optional sample density
|
||||
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* ResolveColorRT,
|
||||
FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT, FRHITexture* InFoveationTexture, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
||||
|
||||
enum ENoRenderTargets
|
||||
{
|
||||
NoRenderTargets,
|
||||
};
|
||||
explicit FRHIRenderPassInfo(ENoRenderTargets Dummy);
|
||||
explicit FRHIRenderPassInfo();
|
||||
|
||||
inline int32 GetNumColorRenderTargets() const;
|
||||
RHI_API void Validate() const;
|
||||
RHI_API void ConvertToRenderTargetsInfo(FRHISetRenderTargetsInfo& OutRTInfo) const;
|
||||
|
||||
(......)
|
||||
};
|
||||
|
||||
// Engine\Source\Runtime\RHI\Public\RHICommandList.h
|
||||
|
||||
class RHI_API FRHICommandList : public FRHIComputeCommandList
|
||||
{
|
||||
public:
|
||||
void BeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* Name)
|
||||
{
|
||||
if (InInfo.bTooManyUAVs)
|
||||
{
|
||||
UE_LOG(LogRHI, Warning, TEXT("RenderPass %s has too many UAVs"));
|
||||
}
|
||||
InInfo.Validate();
|
||||
|
||||
// 直接调用RHI的接口.
|
||||
if (Bypass())
|
||||
{
|
||||
GetContext().RHIBeginRenderPass(InInfo, Name);
|
||||
}
|
||||
// 分配RHI命令.
|
||||
else
|
||||
{
|
||||
TCHAR* NameCopy = AllocString(Name);
|
||||
ALLOC_COMMAND(FRHICommandBeginRenderPass)(InInfo, NameCopy);
|
||||
}
|
||||
// 设置在RenderPass内标记.
|
||||
Data.bInsideRenderPass = true;
|
||||
|
||||
// 缓存活动的RT.
|
||||
CacheActiveRenderTargets(InInfo);
|
||||
// 重置子Pass.
|
||||
ResetSubpass(InInfo.SubpassHint);
|
||||
Data.bInsideRenderPass = true;
|
||||
}
|
||||
|
||||
void EndRenderPass()
|
||||
{
|
||||
// 调用或分配RHI接口.
|
||||
if (Bypass())
|
||||
{
|
||||
GetContext().RHIEndRenderPass();
|
||||
}
|
||||
else
|
||||
{
|
||||
ALLOC_COMMAND(FRHICommandEndRenderPass)();
|
||||
}
|
||||
// 重置在RenderPass内标记.
|
||||
Data.bInsideRenderPass = false;
|
||||
// 重置子Pass标记为None.
|
||||
ResetSubpass(ESubpassHint::None);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
它们的使用案例如下:
|
||||
主要是`FRHIRenderPassInfo RenderPassInfo(1, ColorRTs, ERenderTargetActions::DontLoad_DontStore)`与`RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("Test_MultiDrawIndirect"))`
|
||||
```c++
|
||||
bool FRHIDrawTests::Test_MultiDrawIndirect(FRHICommandListImmediate& RHICmdList)
|
||||
{
|
||||
if (!GRHIGlobals.SupportsMultiDrawIndirect)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Probably could/should automatically enable in the outer scope when running RHI Unit Tests
|
||||
// RenderCaptureInterface::FScopedCapture RenderCapture(true /*bEnable*/, &RHICmdList, TEXT("Test_MultiDrawIndirect"));
|
||||
|
||||
static constexpr uint32 MaxInstances = 8;
|
||||
|
||||
// D3D12 does not have a way to get the base instance ID (SV_InstanceID always starts from 0), so we must emulate it...
|
||||
const uint32 InstanceIDs[MaxInstances] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||
FBufferRHIRef InstanceIDBuffer = CreateBufferWithData(EBufferUsageFlags::VertexBuffer, ERHIAccess::VertexOrIndexBuffer, TEXT("Test_MultiDrawIndirect_InstanceID"), MakeArrayView(InstanceIDs));
|
||||
|
||||
FVertexDeclarationElementList VertexDeclarationElements;
|
||||
VertexDeclarationElements.Add(FVertexElement(0, 0, VET_UInt, 0, 4, true /*per instance frequency*/));
|
||||
FVertexDeclarationRHIRef VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(VertexDeclarationElements);
|
||||
|
||||
const uint16 Indices[3] = { 0, 1, 2 };
|
||||
FBufferRHIRef IndexBuffer = CreateBufferWithData(EBufferUsageFlags::IndexBuffer, ERHIAccess::VertexOrIndexBuffer, TEXT("Test_MultiDrawIndirect_IndexBuffer"), MakeArrayView(Indices));
|
||||
|
||||
static constexpr uint32 OutputBufferStride = sizeof(uint32);
|
||||
static constexpr uint32 OutputBufferSize = OutputBufferStride * MaxInstances;
|
||||
FRHIResourceCreateInfo OutputBufferCreateInfo(TEXT("Test_MultiDrawIndirect_OutputBuffer"));
|
||||
FBufferRHIRef OutputBuffer = RHICmdList.CreateBuffer(OutputBufferSize, EBufferUsageFlags::UnorderedAccess | EBufferUsageFlags::SourceCopy, OutputBufferStride, ERHIAccess::UAVCompute, OutputBufferCreateInfo);
|
||||
|
||||
const uint32 CountValues[4] = { 1, 1, 16, 0 };
|
||||
FBufferRHIRef CountBuffer = CreateBufferWithData(EBufferUsageFlags::DrawIndirect | EBufferUsageFlags::UnorderedAccess, ERHIAccess::IndirectArgs, TEXT("Test_MultiDrawIndirect_Count"), MakeArrayView(CountValues));
|
||||
|
||||
const FRHIDrawIndexedIndirectParameters DrawArgs[] =
|
||||
{
|
||||
// IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation
|
||||
{3, 1, 0, 0, 0}, // fill slot 0
|
||||
// gap in slot 1
|
||||
{3, 2, 0, 0, 2}, // fill slots 2, 3 using 1 sub-draw
|
||||
// gap in slot 4
|
||||
{3, 1, 0, 0, 5}, // fill slots 5, 6 using 2 sub-draws
|
||||
{3, 1, 0, 0, 6},
|
||||
{3, 1, 0, 0, 7}, // this draw is expected to never execute
|
||||
};
|
||||
|
||||
const uint32 ExpectedDrawnInstances[MaxInstances] = { 1, 0, 1, 1, 0, 1, 1, 0 };
|
||||
|
||||
FBufferRHIRef DrawArgBuffer = CreateBufferWithData(EBufferUsageFlags::DrawIndirect | EBufferUsageFlags::UnorderedAccess | EBufferUsageFlags::VertexBuffer, ERHIAccess::IndirectArgs,
|
||||
TEXT("Test_MultiDrawIndirect_DrawArgs"), MakeArrayView(DrawArgs));
|
||||
|
||||
FUnorderedAccessViewRHIRef OutputBufferUAV = RHICmdList.CreateUnorderedAccessView(OutputBuffer,
|
||||
FRHIViewDesc::CreateBufferUAV()
|
||||
.SetType(FRHIViewDesc::EBufferType::Typed)
|
||||
.SetFormat(PF_R32_UINT));
|
||||
|
||||
RHICmdList.ClearUAVUint(OutputBufferUAV, FUintVector4(0));
|
||||
|
||||
const FIntPoint RenderTargetSize(4, 4);
|
||||
FRHITextureDesc RenderTargetTextureDesc(ETextureDimension::Texture2D, ETextureCreateFlags::RenderTargetable, PF_B8G8R8A8, FClearValueBinding(), RenderTargetSize, 1, 1, 1, 1, 0);
|
||||
FRHITextureCreateDesc RenderTargetCreateDesc(RenderTargetTextureDesc, ERHIAccess::RTV, TEXT("Test_MultiDrawIndirect_RenderTarget"));
|
||||
FTextureRHIRef RenderTarget = RHICreateTexture(RenderTargetCreateDesc);
|
||||
|
||||
TShaderMapRef<FTestDrawInstancedVS> VertexShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
|
||||
TShaderMapRef<FTestDrawInstancedPS> PixelShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
|
||||
|
||||
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
||||
|
||||
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
||||
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = VertexDeclarationRHI;
|
||||
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
||||
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
||||
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
|
||||
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
|
||||
GraphicsPSOInit.PrimitiveType = EPrimitiveType::PT_TriangleList;
|
||||
|
||||
FRHITexture* ColorRTs[1] = { RenderTarget.GetReference() };
|
||||
FRHIRenderPassInfo RenderPassInfo(1, ColorRTs, ERenderTargetActions::DontLoad_DontStore);
|
||||
|
||||
RHICmdList.Transition(FRHITransitionInfo(OutputBufferUAV, ERHIAccess::UAVCompute, ERHIAccess::UAVGraphics, EResourceTransitionFlags::None));
|
||||
RHICmdList.BeginUAVOverlap(); // Output UAV can be written without syncs between draws (each draw is expected to write into different slots)
|
||||
|
||||
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("Test_MultiDrawIndirect"));
|
||||
RHICmdList.SetViewport(0, 0, 0, float(RenderTargetSize.X), float(RenderTargetSize.Y), 1);
|
||||
|
||||
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
||||
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
|
||||
|
||||
check(InstanceIDBuffer->GetStride() == 4);
|
||||
RHICmdList.SetStreamSource(0, InstanceIDBuffer, 0);
|
||||
|
||||
FRHIBatchedShaderParameters ShaderParameters;
|
||||
ShaderParameters.SetUAVParameter(PixelShader->OutDrawnInstances.GetBaseIndex(), OutputBufferUAV);
|
||||
RHICmdList.SetBatchedShaderParameters(PixelShader.GetPixelShader(), ShaderParameters);
|
||||
|
||||
const uint32 DrawArgsStride = sizeof(DrawArgs[0]);
|
||||
const uint32 CountStride = sizeof(CountValues[0]);
|
||||
|
||||
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
|
||||
DrawArgBuffer, DrawArgsStride*0, // 1 sub-draw with instance index 0
|
||||
CountBuffer, CountStride*0, // count buffer contains 1 in this slot
|
||||
5 // expect to draw only 1 instance due to GPU-side upper bound
|
||||
);
|
||||
|
||||
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
|
||||
DrawArgBuffer, DrawArgsStride*1, // 1 sub-draw with 2 instances at base index 2
|
||||
CountBuffer, CountStride*1, // count buffer contains 1 in this slot
|
||||
4 // expect to draw only 1 instance due to GPU-side upper bound
|
||||
);
|
||||
|
||||
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
|
||||
DrawArgBuffer, DrawArgsStride*2, // 2 sub-draws with 1 instance each starting at base index 5
|
||||
CountBuffer, CountStride*2, // count buffer contains 16 in this slot
|
||||
2 // expect to draw only 2 instances due to CPU-side upper bound
|
||||
);
|
||||
|
||||
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
|
||||
DrawArgBuffer, DrawArgsStride*4, // 1 sub-draw with 1 instance each starting at base index 7
|
||||
CountBuffer, CountStride*3, // count buffer contains 0 in this slot
|
||||
1 // expect to skip the draw due to GPU-side count of 0
|
||||
);
|
||||
|
||||
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
|
||||
DrawArgBuffer, DrawArgsStride*4, // 1 sub-draw with 1 instance each starting at base index 7
|
||||
CountBuffer, CountStride*0, // count buffer contains 1 in this slot
|
||||
0 // expect to skip the draw due to CPU-side count of 0
|
||||
);
|
||||
|
||||
RHICmdList.EndRenderPass();
|
||||
|
||||
RHICmdList.EndUAVOverlap();
|
||||
|
||||
RHICmdList.Transition(FRHITransitionInfo(OutputBufferUAV, ERHIAccess::UAVGraphics, ERHIAccess::CopySrc, EResourceTransitionFlags::None));
|
||||
|
||||
TConstArrayView<uint8> ExpectedOutputView = MakeArrayView(reinterpret_cast<const uint8*>(ExpectedDrawnInstances), sizeof(ExpectedDrawnInstances));
|
||||
bool bSucceeded = FRHIBufferTests::VerifyBufferContents(TEXT("Test_MultiDrawIndirect"), RHICmdList, OutputBuffer, ExpectedOutputView);
|
||||
|
||||
return bSucceeded;
|
||||
}
|
||||
```
|
||||
|
||||
## Subpass
|
||||
先说一下Subpass的由来、作用和特点。
|
||||
|
||||
在传统的多Pass渲染中,每个Pass结束时通常会渲染出一组渲染纹理,部分成为着色器参数提供给下一个Pass采样读取。这种纹理采样方式不受任何限制,可以读取任意的领域像素,使用任意的纹理过滤方式。这种方式虽然使用灵活,但在TBR(Tile-Based Renderer)硬件架构的设备中会有较大的消耗:渲染纹理的Pass通常会将渲染结果存储在On-chip的Tile Memory中,待Pass结束后会写回GPU显存(VRAM)中,写回GPU显存是个耗时耗耗电的操作。
|
||||
|
||||

|
||||
|
||||
_传统多Pass之间的内存存取模型,多次发生于On-Chip和全局存储器之间。_
|
||||
|
||||
如果出现一种特殊的纹理使用情况:上一个Pass渲染处理的纹理,立即被下一个Pass使用,并且下一个Pass只采样像素位置自身的数据,而不需要采样邻域像素的位置。这种情况就符合了Subpass的使用情景。使用Subpass渲染的纹理结果只会存储在Tile Memory中,在Subpass结束后不会写回VRAM,而直接提供Tile Memory的数据给下一个Subpass采样读取。这样就避免了传统Pass结束写回GPU显存以及下一个Pass又从GPU显存读数据的耗时耗电操作,从而提升了性能。
|
||||
|
||||

|
||||
|
||||
_Subpass之间的内存存取模型,都发生在On-Chip内。_
|
||||
|
||||
Subpass的相关代码主要集中在移动端中。UE涉及Subpass的接口和类型如下:
|
||||
```c++
|
||||
// 提供给RHI的Subpass标记.
|
||||
enum class ESubpassHint : uint8
|
||||
{
|
||||
None, // 传统渲染(非Subpass)
|
||||
DepthReadSubpass, // 深度读取Subpass.
|
||||
DeferredShadingSubpass, // 移动端延迟着色Subpass.
|
||||
};
|
||||
|
||||
|
||||
// Engine\Source\Runtime\RHI\Public\RHICommandList.h
|
||||
|
||||
class RHI_API FRHICommandListBase : public FNoncopyable
|
||||
{
|
||||
(......)
|
||||
|
||||
protected:
|
||||
// PSO上下文.
|
||||
struct FPSOContext
|
||||
{
|
||||
uint32 CachedNumSimultanousRenderTargets = 0;
|
||||
TStaticArray<FRHIRenderTargetView, MaxSimultaneousRenderTargets> CachedRenderTargets;
|
||||
FRHIDepthRenderTargetView CachedDepthStencilTarget;
|
||||
|
||||
// Subpass提示标记.
|
||||
ESubpassHint SubpassHint = ESubpassHint::None;
|
||||
uint8 SubpassIndex = 0;
|
||||
uint8 MultiViewCount = 0;
|
||||
bool HasFragmentDensityAttachment = false;
|
||||
} PSOContext;
|
||||
};
|
||||
|
||||
class RHI_API FRHICommandList : public FRHIComputeCommandList
|
||||
{
|
||||
public:
|
||||
void BeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* Name)
|
||||
{
|
||||
(......)
|
||||
|
||||
CacheActiveRenderTargets(InInfo);
|
||||
// 设置Subpass数据.
|
||||
ResetSubpass(InInfo.SubpassHint);
|
||||
Data.bInsideRenderPass = true;
|
||||
}
|
||||
|
||||
void EndRenderPass()
|
||||
{
|
||||
(......)
|
||||
|
||||
// 重置Subpass标记为None.
|
||||
ResetSubpass(ESubpassHint::None);
|
||||
}
|
||||
|
||||
// 下一个Subpass.
|
||||
void NextSubpass()
|
||||
{
|
||||
// 分配或调用RHI接口.
|
||||
if (Bypass())
|
||||
{
|
||||
GetContext().RHINextSubpass();
|
||||
}
|
||||
else
|
||||
{
|
||||
ALLOC_COMMAND(FRHICommandNextSubpass)();
|
||||
}
|
||||
|
||||
// 增加Subpass计数.
|
||||
IncrementSubpass();
|
||||
}
|
||||
|
||||
// 增加subpass计数.
|
||||
void IncrementSubpass()
|
||||
{
|
||||
PSOContext.SubpassIndex++;
|
||||
}
|
||||
|
||||
// 重置Subpass数据.
|
||||
void ResetSubpass(ESubpassHint SubpassHint)
|
||||
{
|
||||
PSOContext.SubpassHint = SubpassHint;
|
||||
PSOContext.SubpassIndex = 0;
|
||||
}
|
||||
};
|
||||
```
|
@@ -0,0 +1,365 @@
|
||||
---
|
||||
title: 剖析虚幻渲染体系(11)- RDG
|
||||
date: 2024-02-04 21:42:54
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
原文地址:https://www.cnblogs.com/timlly/p/15217090.html
|
||||
|
||||
# 概念
|
||||
- FRDGAllocator:简单的C++对象分配器, 用MemStack分配器追踪和销毁物体。
|
||||
- FComputeShaderUtils
|
||||
- Dispatch():派发ComputeShader到RHI命令列表,携带其参数。
|
||||
- DispatchIndirect():派发非直接的ComputeShader到RHI命令列表,,携带其参数。
|
||||
- AddPass():派发计算着色器到**RenderGraphBuilder**, 携带其参数。
|
||||
- ClearUAV():清理UAV。
|
||||
- AddCopyTexturePass():增加拷贝纹理Pass。
|
||||
- AddCopyToResolveTargetPass():增加拷贝到解析目标的Pass。
|
||||
- AddEnqueueCopyPass():增加回读纹理的Pass。
|
||||
- AddPass():无参数的Pass增加。
|
||||
- AddBeginUAVOverlapPass()/AddEndUAVOverlapPass(): 其它特殊Pass。
|
||||
- FRDGResource:RDG资源并不是直接用RHI资源,而是包裹了RHI资源引用,然后针对不同类型的资源各自封装,且增加了额外的信息。
|
||||
- FRDGUniformBuffer、TRDGUniformBuffer
|
||||
- FRDGParentResource:一种由图跟踪分配生命周期的渲染图资源。可能有引用它的子资源(例如视图)
|
||||
- FRDGView
|
||||
- FRDGBuffer、FRDGBufferSRV、FRDGBufferUAV
|
||||
- FRDGViewableResource:一种由图跟踪分配生命周期的RPGResource。可能有引用它的子资源
|
||||
- FRDGTexture
|
||||
- FRDGView
|
||||
- FRDGUnorderedAccessView、FRDGTextureUAV
|
||||
- FRDGShaderResourceView、FRDGTextureSRV
|
||||
- FRDGTextureDesc:创建渲染纹理的描述信息。
|
||||
- FRDGPooledTexture:贴图池里的贴图资源。
|
||||
- FRDGPooledBuffer:池化的缓冲区。
|
||||
- FRHITransition:用于表示RHI中挂起的资源转换的**不透明表面材质**数据结构。
|
||||
- FRDGBarrierBatch:RDG屏障批。
|
||||
- FRDGBarrierBatchBegin
|
||||
- FRDGBarrierBatchEnd
|
||||
- FRDGPass
|
||||
- TRDGLambdaPass
|
||||
- FRDGSentinelPass:哨兵Pass,用于开始/结束。
|
||||
- [[#FRDGBuilder]]
|
||||
|
||||
## RDG基础类型
|
||||
```c++
|
||||
enum class ERDGBuilderFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/** Allows the builder to parallelize execution of passes. Without this flag, all passes execute on the render thread. */
|
||||
AllowParallelExecute = 1 << 0
|
||||
};
|
||||
|
||||
/** Flags to annotate a pass with when calling AddPass. */
|
||||
enum class ERDGPassFlags : uint16
|
||||
{
|
||||
/** Pass doesn't have any inputs or outputs tracked by the graph. This may only be used by the parameterless AddPass function. */
|
||||
None = 0,
|
||||
|
||||
/** Pass uses rasterization on the graphics pipe. */
|
||||
Raster = 1 << 0,
|
||||
|
||||
/** Pass uses compute on the graphics pipe. */
|
||||
Compute = 1 << 1,
|
||||
|
||||
/** Pass uses compute on the async compute pipe. */
|
||||
AsyncCompute = 1 << 2,
|
||||
|
||||
/** Pass uses copy commands on the graphics pipe. */
|
||||
Copy = 1 << 3,
|
||||
|
||||
/** Pass (and its producers) will never be culled. Necessary if outputs cannot be tracked by the graph. */
|
||||
NeverCull = 1 << 4,
|
||||
|
||||
/** Render pass begin / end is skipped and left to the user. Only valid when combined with 'Raster'. Disables render pass merging for the pass. */
|
||||
SkipRenderPass = 1 << 5,
|
||||
|
||||
/** Pass will never have its render pass merged with other passes. */
|
||||
NeverMerge = 1 << 6,
|
||||
|
||||
/** Pass will never run off the render thread. */
|
||||
NeverParallel = 1 << 7,
|
||||
|
||||
ParallelTranslate = 1 << 8,
|
||||
|
||||
/** Pass uses copy commands but writes to a staging resource. */
|
||||
Readback = Copy | NeverCull
|
||||
};
|
||||
|
||||
/** Flags to annotate a render graph buffer. */
|
||||
enum class ERDGBufferFlags : uint8
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/** Tag the buffer to survive through frame, that is important for multi GPU alternate frame rendering. */
|
||||
MultiFrame = 1 << 0,
|
||||
|
||||
/** The buffer is ignored by RDG tracking and will never be transitioned. Use the flag when registering a buffer with no writable GPU flags.
|
||||
* Write access is not allowed for the duration of the graph. This flag is intended as an optimization to cull out tracking of read-only
|
||||
* buffers that are used frequently throughout the graph. Note that it's the user's responsibility to ensure the resource is in the correct
|
||||
* readable state for use with RDG passes, as RDG does not know the exact state of the resource.
|
||||
*/
|
||||
SkipTracking = 1 << 1,
|
||||
|
||||
/** When set, RDG will perform its first barrier without splitting. Practically, this means the resource is left in its initial state
|
||||
* until the first pass it's used within the graph. Without this flag, the resource is split-transitioned at the start of the graph.
|
||||
*/
|
||||
ForceImmediateFirstBarrier = 1 << 2,
|
||||
};
|
||||
|
||||
/** Flags to annotate a render graph texture. */
|
||||
enum class ERDGTextureFlags : uint8
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/** Tag the texture to survive through frame, that is important for multi GPU alternate frame rendering. */
|
||||
MultiFrame = 1 << 0,
|
||||
|
||||
/** The buffer is ignored by RDG tracking and will never be transitioned. Use the flag when registering a buffer with no writable GPU flags.
|
||||
* Write access is not allowed for the duration of the graph. This flag is intended as an optimization to cull out tracking of read-only
|
||||
* buffers that are used frequently throughout the graph. Note that it's the user's responsibility to ensure the resource is in the correct
|
||||
* readable state for use with RDG passes, as RDG does not know the exact state of the resource.
|
||||
*/
|
||||
SkipTracking = 1 << 1,
|
||||
|
||||
/** When set, RDG will perform its first barrier without splitting. Practically, this means the resource is left in its initial state
|
||||
* until the first pass it's used within the graph. Without this flag, the resource is split-transitioned at the start of the graph.
|
||||
*/
|
||||
ForceImmediateFirstBarrier = 1 << 2,
|
||||
|
||||
/** Prevents metadata decompression on this texture. */
|
||||
MaintainCompression = 1 << 3,
|
||||
};
|
||||
ENUM_CLASS_FLAGS(ERDGTextureFlags);
|
||||
|
||||
/** Flags to annotate a view with when calling CreateUAV. */
|
||||
enum class ERDGUnorderedAccessViewFlags : uint8
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// The view will not perform UAV barriers between consecutive usage.
|
||||
SkipBarrier = 1 << 0
|
||||
};
|
||||
ENUM_CLASS_FLAGS(ERDGUnorderedAccessViewFlags);
|
||||
|
||||
/** The set of concrete parent resource types. */
|
||||
enum class ERDGViewableResourceType : uint8
|
||||
{
|
||||
Texture,
|
||||
Buffer,
|
||||
MAX
|
||||
};
|
||||
|
||||
/** The set of concrete view types. */
|
||||
enum class ERDGViewType : uint8
|
||||
{
|
||||
TextureUAV,
|
||||
TextureSRV,
|
||||
BufferUAV,
|
||||
BufferSRV,
|
||||
MAX
|
||||
};
|
||||
|
||||
inline ERDGViewableResourceType GetParentType(ERDGViewType ViewType)
|
||||
{
|
||||
switch (ViewType)
|
||||
{
|
||||
case ERDGViewType::TextureUAV:
|
||||
case ERDGViewType::TextureSRV:
|
||||
return ERDGViewableResourceType::Texture;
|
||||
case ERDGViewType::BufferUAV:
|
||||
case ERDGViewType::BufferSRV:
|
||||
return ERDGViewableResourceType::Buffer;
|
||||
}
|
||||
checkNoEntry();
|
||||
return ERDGViewableResourceType::MAX;
|
||||
}
|
||||
|
||||
enum class ERDGResourceExtractionFlags : uint8
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// Allows the resource to remain transient. Only use this flag if you intend to register the resource back
|
||||
// into the graph and release the reference. This should not be used if the resource is cached for a long
|
||||
// period of time.
|
||||
AllowTransient = 1,
|
||||
};
|
||||
|
||||
enum class ERDGInitialDataFlags : uint8
|
||||
{
|
||||
/** Specifies the default behavior, which is to make a copy of the initial data for replay when
|
||||
* the graph is executed. The user does not need to preserve lifetime of the data pointer.
|
||||
*/
|
||||
None = 0,
|
||||
|
||||
/** Specifies that the user will maintain ownership of the data until the graph is executed. The
|
||||
* upload pass will only use a reference to store the data. Use caution with this flag since graph
|
||||
* execution is deferred! Useful to avoid the copy if the initial data lifetime is guaranteed to
|
||||
* outlive the graph.
|
||||
*/
|
||||
NoCopy = 1 << 0
|
||||
};
|
||||
|
||||
enum class ERDGPooledBufferAlignment : uint8
|
||||
{
|
||||
// The buffer size is not aligned.
|
||||
None,
|
||||
|
||||
// The buffer size is aligned up to the next page size.
|
||||
Page,
|
||||
|
||||
// The buffer size is aligned up to the next power of two.
|
||||
PowerOfTwo
|
||||
};
|
||||
|
||||
/** Returns the equivalent parent resource type for a view type. */
|
||||
inline ERDGViewableResourceType GetViewableResourceType(ERDGViewType ViewType)
|
||||
{
|
||||
switch (ViewType)
|
||||
{
|
||||
case ERDGViewType::TextureUAV:
|
||||
case ERDGViewType::TextureSRV:
|
||||
return ERDGViewableResourceType::Texture;
|
||||
case ERDGViewType::BufferUAV:
|
||||
case ERDGViewType::BufferSRV:
|
||||
return ERDGViewableResourceType::Buffer;
|
||||
default:
|
||||
checkNoEntry();
|
||||
return ERDGViewableResourceType::MAX;
|
||||
}
|
||||
}
|
||||
|
||||
using ERDGTextureMetaDataAccess = ERHITextureMetaDataAccess;
|
||||
|
||||
/** Returns the associated FRHITransitionInfo plane index. */
|
||||
inline int32 GetResourceTransitionPlaneForMetadataAccess(ERDGTextureMetaDataAccess Metadata)
|
||||
{
|
||||
switch (Metadata)
|
||||
{
|
||||
case ERDGTextureMetaDataAccess::CompressedSurface:
|
||||
case ERDGTextureMetaDataAccess::HTile:
|
||||
case ERDGTextureMetaDataAccess::Depth:
|
||||
return FRHITransitionInfo::kDepthPlaneSlice;
|
||||
case ERDGTextureMetaDataAccess::Stencil:
|
||||
return FRHITransitionInfo::kStencilPlaneSlice;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## FRDGBuilder
|
||||
FRDGBuilder是RDG体系的心脏和发动机,也是个大管家,负责收集渲染Pass和参数,编译Pass、数据,处理资源依赖,裁剪和优化各类数据,还有提供执行接口。
|
||||
|
||||
重要函数:
|
||||
- FindExternalTexture():查找外部纹理, 若找不到返回null.
|
||||
- RegisterExternalTexture():注册外部池内RT到RDG, 以便RDG追踪之. 池内RT可能包含两种RHI纹理: MSAA和非MSAA。
|
||||
- RegisterExternalBuffer():注册外部缓冲区到RDG, 以便RDG追踪之.
|
||||
- 资源创建接口:
|
||||
- CreateTexture()
|
||||
- CreateBuffer()
|
||||
- CreateUAV()
|
||||
- CreateSRV()
|
||||
- CreateUniformBuffer()
|
||||
- 分配内存, 内存由RDG管理生命周期。
|
||||
- Alloc()
|
||||
- AllocPOD()
|
||||
- AllocObject()
|
||||
- AllocParameters()
|
||||
- AddPass()
|
||||
- FRDGPassRef AddPass(FRDGEventName&& Name, const ParameterStructType* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda); :增加有参数的LambdaPass。
|
||||
- FRDGPassRef AddPass(FRDGEventName&& Name, const FShaderParametersMetadata* ParametersMetadata, const void* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda); :增加带有实时生成结构体的LambdaPass
|
||||
- FRDGPassRef AddPass(FRDGEventName&& Name, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda);:增加没有参数的LambdaPass
|
||||
- QueueTextureExtraction():在Builder执行末期, 提取池内纹理到指定的指针. 对于RDG创建的资源, 这将延长GPU资源的生命周期,直到执行,指针被填充. 如果指定,纹理将转换为AccessFinal状态, 否则将转换为kDefaultAccessFinal状态。
|
||||
- QueueBufferExtraction():在Builder执行末期, 提取缓冲区到指定的指针。
|
||||
- PreallocateTexture()/PreallocateBuffer():预分配资源. 只对RDG创建的资源, 会强制立即分配底层池内资源, 有效地将其推广到外部资源. 这将增加内存压力,但允许使用GetPooled{Texture, Buffer}查询池中的资源. 主要用于增量地将代码移植到RDG.
|
||||
- GetPooledTexture()/GetPooledBuffer():立即获取底层资源, 只允许用于注册或预分配的资源。
|
||||
- SetTextureAccessFinal()/SetBufferAccessFinal():设置执行之后的状态。
|
||||
- 变量
|
||||
- RDG对象注册表
|
||||
- FRDGPassRegistry Passes;
|
||||
- FRDGTextureRegistry Textures;
|
||||
- FRDGBufferRegistry Buffers;
|
||||
- FRDGViewRegistry Views;
|
||||
- FRDGUniformBufferRegistry UniformBuffers;
|
||||
|
||||
### FRDGBuilder::Compile
|
||||
RDG编译期间的逻辑非常复杂,步骤繁多,先后经历构建生产者和消费者的依赖关系,确定Pass的裁剪等各类标记,调整资源的生命周期,裁剪Pass,处理Pass的资源转换和屏障,处理异步计算Pass的依赖和引用关系,查找并建立分叉和合并Pass节点,合并所有具体相同渲染目标的光栅化Pass等步骤。
|
||||
|
||||
# RDG开发
|
||||
### 注册外部资源
|
||||
如果我们已有非RDG创建的资源,可以在RDG使用么?答案是可以,通过FRDGBuilder::RegisterExternalXXX接口可以完成将外部资源注册到RDG系统中。下面以注册纹理为例:
|
||||
```c++
|
||||
// 在RDG外创建RHI资源.
|
||||
FRHIResourceCreateInfo CreateInfo;
|
||||
FTexture2DRHIRef MyRHITexture = RHICreateTexture2D(1024, 768, PF_B8G8R8A8, 1, 1, TexCreate_CPUReadback, CreateInfo);
|
||||
|
||||
// 将外部创建的RHI资源注册成RDG资源.
|
||||
FRDGTextureRef MyExternalRDGTexture = GraphBuilder.RegisterExternalTexture(MyRHITexture);
|
||||
```
|
||||
需要注意的是,外部注册的资源,RDG无法控制和管理其生命周期,需要保证RDG使用期间外部资源的生命周期处于正常状态,否则将引发异常甚至程序崩溃。
|
||||
|
||||
如果想从RDG资源获取RHI资源的实例,以下代码可达成:
|
||||
```c++
|
||||
FRHITexture* MyRHITexture = MyRDGTexture.GetRHI();
|
||||
```
|
||||
|
||||
用图例展示RHI资源和RDG资源之间的转换关系:
|
||||
- FRHIResource =>FRDGBuilder::RegisterExternalXXX =>FRDGResource
|
||||
- FRDGResource => FRDGResource::GetRHI => FRHIResource
|
||||
|
||||
### 提取资源
|
||||
RDG收集Pass之后并非立即执行,而是延迟执行(包括资源被延迟创建或分配),这就导致了一个问题:如果想将渲染后的资源赋值给某个变量,无法使用立即模式,需要适配延迟执行模式。这种适配延迟执行的资源提取是通过以下接口来实现的:
|
||||
- FRDGBuilder::QueueTextureExtraction
|
||||
- FRDGBuilder::QueueBufferExtraction
|
||||
|
||||
```c++
|
||||
// 创建RDG纹理.
|
||||
FRDGTextureRef MyRDGTexture;
|
||||
|
||||
FRDGTextureDesc MyTextureDesc = FRDGTextureDesc::Create2D(OutputExtent, HistoryPixelFormat, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_UAV);
|
||||
|
||||
MyRDGTexture = GraphBuilder.CreateTexture(MyTextureDesc, "MyRDGTexture", ERDGTextureFlags::MultiFrame);
|
||||
|
||||
// 创建UAV并作为Pass的shader参数.
|
||||
(......)
|
||||
PassParameters->MyRDGTextureUAV = GraphBuilder.CreateUAV(MyRDGTexture);
|
||||
(......)
|
||||
|
||||
// 增加Pass, 以便渲染图像到MyRDGTextureUAV.
|
||||
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("MyCustomPass", ...), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(8, 8));
|
||||
|
||||
// 入队提取资源.
|
||||
TRefCountPtr<IPooledRenderTarget>* OutputRT;
|
||||
GraphBuilder.QueueTextureExtraction(MyRDGTexture, &OutputRT);
|
||||
|
||||
// 对提取的OutputRT进行后续操作.
|
||||
(......)
|
||||
```
|
||||
|
||||
# RDG调试
|
||||
RDG系统存在一些控制台命令,其名称和描述如下:
|
||||
|控制台变量|描述|
|
||||
|---|---|
|
||||
|**r.RDG.AsyncCompute**|控制异步计算策略:0-禁用;1-为异步计算Pass启用标记(默认);2-开启所有使用compute命令列表的计算通道。|
|
||||
|**r.RDG.Breakpoint**|当满足某些条件时,断点到调试器的断点位置。0-禁用,1~4-不同的特殊调试模式。|
|
||||
|**r.RDG.ClobberResources**|在分配时间用指定的清理颜色清除所有渲染目标和纹理/缓冲UAV。用于调试。|
|
||||
|**r.RDG.CullPasses**|RDG是否开启裁剪无用的Pass。0-禁用,1-开启(默认)。|
|
||||
|**r.RDG.Debug**|允许输出在连接和执行过程中发现的效率低下的警告。|
|
||||
|**r.RDG.Debug.FlushGPU**|开启每次Pass执行后刷新指令到GPU。当设置(r.RDG.AsyncCompute=0)时禁用异步计算。|
|
||||
|**r.RDG.Debug.GraphFilter**|将某些调试事件过滤到特定的图中。|
|
||||
|**r.RDG.Debug.PassFilter**|将某些调试事件过滤到特定的Pass。|
|
||||
|**r.RDG.Debug.ResourceFilter**|将某些调试事件过滤到特定的资源。|
|
||||
|**r.RDG.DumpGraph**|将多个可视化日志转储到磁盘。0-禁用,1-显示生产者、消费者Pass依赖,2-显示资源状态和转换,3-显示图形、异步计算的重叠。|
|
||||
|**r.RDG.ExtendResourceLifetimes**|RDG将把资源生命周期扩展到图的全部长度。会增加内存的占用。|
|
||||
|**r.RDG.ImmediateMode**|在创建Pass时执行Pass。当在Pass的Lambda中崩溃时,连接代码的调用堆栈非常有用。|
|
||||
|**r.RDG.MergeRenderPasses**|图形将合并相同的、连续的渲染通道到一个单一的渲染通道。0-禁用,1-开启(默认)。|
|
||||
|**r.RDG.OverlapUAVs**|RDG将在需要时重叠UAV的工作。如果禁用,UAV屏障总是插入。|
|
||||
|**r.RDG.TransitionLog**|输出资源转换到控制台。|
|
||||
|**r.RDG.VerboseCSVStats**|控制RDG的CSV分析统计的详细程度。0-为图形执行生成一个CSV配置文件,1-为图形执行的每个阶段生成一个CSV文件。|
|
||||
|
||||
除了以上列出的RDG控制台,还有一些命令可以显示RDG系统运行过程中的有用信息。
|
||||
`vis`列出所有有效的纹理,输入之后可能显示如下所示的信息:
|
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: 剖析虚幻渲染体系(17)- 实时光线追踪
|
||||
date: 2024-02-05 22:02:57
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
原文地址:https://www.cnblogs.com/timlly/p/16687324.html
|
BIN
03-UnrealEngine/Rendering/Shader/Effect/Ocean/水下逻辑判断.jpg
(Stored with Git LFS)
Normal file
BIN
03-UnrealEngine/Rendering/Shader/Effect/Ocean/水下逻辑判断.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
03-UnrealEngine/Rendering/Shader/Effect/Ocean/水下逻辑判断2.jpg
(Stored with Git LFS)
Normal file
BIN
03-UnrealEngine/Rendering/Shader/Effect/Ocean/水下逻辑判断2.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
76
03-UnrealEngine/Rendering/Shader/Effect/Ocean/海洋SSS效果论文笔记.md
Normal file
76
03-UnrealEngine/Rendering/Shader/Effect/Ocean/海洋SSS效果论文笔记.md
Normal 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
|
||||
- 反转颜色最后渲染到贴图中
|
||||
|
||||
### 技术细节
|
||||

|
||||
|
||||
LT应该是Light Translucency的意思。v代表Vector,f代表float,i代表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项,代表了始终存的各个方向的透射值。材质相关变量。
|
||||
|
||||

|
||||
|
||||
#### iLTPower
|
||||
强度衰减项,直接透射强度。与视口相关。可以通过预计算进行优化。材质相关变量。
|
||||
|
||||

|
||||
|
||||
#### fLTDistortion
|
||||
透射方向形变项,用于模拟光线传输的不规则的效果,类似于毛玻璃的效果。主要的功能是控制法线对于透射光方向的影响。与视口相关。材质相关变量。
|
||||
|
||||

|
||||
|
||||
#### fLTThickness
|
||||
厚底项,预计算的Local坐标的表面厚度值。材质相关变量。
|
||||
|
||||

|
||||
|
||||
#### fLTScale
|
||||
缩放项,用于缩放直接透射效果。视口相关。灯光相关变量。
|
||||
|
||||

|
||||
|
||||
### 最终效果
|
||||

|
||||
|
||||
### GBuffer设计
|
||||
最低的要求是GBufer中使用8位位宽灰度贴图的方式来存储translucency。使用24位位宽以颜色贴图的方式,可以实现材质对于不同光谱的光线不同的散射效果。
|
||||
|
||||
### 技术缺点
|
||||
因为是一种近似技术,所以只适合在凸物体上演示。这种技术对变形物体不起作用,因为需要烘焙厚度贴图。此外,我们可以使用实时AO算法配合倒置法线来计算厚度。
|
||||
|
||||

|
||||
|
||||
PS.该技术的详细描述可以在《GPU PRO2》中找到。
|
202
03-UnrealEngine/Rendering/Shader/Effect/Ocean/海洋论文笔记.md
Normal file
202
03-UnrealEngine/Rendering/Shader/Effect/Ocean/海洋论文笔记.md
Normal 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:
|
||||

|
||||
|
||||
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。
|
||||
|
||||
#### 大浪
|
||||
大浪采用贝塞尔曲线建模完成
|
||||
**之后再叠加大浪**。
|
||||
|
||||

|
||||
|
||||
这是整个波系的局部公式。
|
||||
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
|
252
03-UnrealEngine/Rendering/Shader/Effect/PostProcess 后处理材质笔记.md
Normal file
252
03-UnrealEngine/Rendering/Shader/Effect/PostProcess 后处理材质笔记.md
Normal 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的传入位置
|
||||
比如AOUpsamplePS(c++中为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));
|
||||
}
|
||||
```
|
53
03-UnrealEngine/Rendering/Shader/Effect/UE4剖切效果实现.md
Normal file
53
03-UnrealEngine/Rendering/Shader/Effect/UE4剖切效果实现.md
Normal file
@@ -0,0 +1,53 @@
|
||||
## 前言
|
||||
思路是使用平面方程来判断模型裁切,之后在另一面使用UnLit的自发光材质显示剖面。但Ue4的BlendingMaterialAttributes不能指定UnLit作为ShaderModel。所以可以使用我之前开发的多Pass插件搞定。
|
||||
|
||||
## 外部普通表面材质
|
||||
使用平面方程来做一个Mask即可。
|
||||

|
||||
|
||||
## 内部剖面材质
|
||||
坡面材质需要使用UnLit材质模型,这样就不会有阴影与法线的干扰了。但Unlit不能翻转法线,所以需要再勾选**双面渲染**选项。
|
||||

|
||||
|
||||
## 使用方法说明
|
||||
https://zhuanlan.zhihu.com/p/69139579
|
||||
|
||||
1. 使用上面链接介绍的代码,新建一个蓝图类并且挂载UStrokeStaticMeshComponent。
|
||||
2. 赋予UStrokeStaticMeshComponent所需剖切的模型,以及上文制作好的外部表面材质。
|
||||
3. 赋予SecondPassMaterial内部剖面材质,并勾选UStrokeStaticMeshComponent的NeedSecondPass变量。
|
||||
4. 将蓝图类拖到关卡中,并且设置2个材质的PlaneNormal与PlanePoint。PlanePoint为世界坐标,PlaneNormal需要归一化。
|
||||
|
||||
## 最终效果
|
||||
锯齿是因为垃圾笔记本散热差,把效果开低的关系。
|
||||
|
||||

|
||||
|
||||
## 错误的尝试
|
||||
之前还尝试WorldPositionOffset的思路来做,不过后来发现没有实用性。因为剖面的大小与形状是不确定的,用投射的方式来平移内部可见顶点,会造成多余的顶点平移到平面外的问题。所以只适合拿来做规整模型的剖切效果。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
错误结果:
|
||||

|
||||
|
||||
实现节点如下:
|
||||

|
||||
|
||||
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;
|
||||
```
|
@@ -0,0 +1,57 @@
|
||||
早几年学习后处理Shader的时候,遇到编辑器视口中的渲染结果与视口大小不匹配的问题:
|
||||

|
||||
|
||||
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.xy,zw为倒数值。
|
||||
- MF_ScreenResolution:具体有VisibleResolution(就是ViewSize节点输出结果) 与BufferResolution(ViewProperty的RenderTargetSize)
|
||||
- SceneTexelSize(场景纹素大小):这是一个float2的值,对应着UV,(u,v),uv均为正数。
|
||||
- MF_ScreenAlignedPixelToPixelUVs:RenderTargetSize / TextureSize。 其中TextureSize可以是ViewSize。可以用来实现一些修改View大小后,后处理尺寸不变得效果。
|
||||
- 后处理节点的Size:ClampSceneTextureUV(ViewportUVToSceneTextureUV,SceneTextureId)
|
@@ -0,0 +1,8 @@
|
||||
# 在UE4中实现LowPoly效果
|
||||
|
||||
## 代码
|
||||

|
||||
|
||||
## 效果
|
||||
|
||||

|
8
03-UnrealEngine/Rendering/Shader/Effect/技能CD转圈效果实现.md
Normal file
8
03-UnrealEngine/Rendering/Shader/Effect/技能CD转圈效果实现.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: 技能CD转圈效果实现
|
||||
date: 2022-11-04 09:38:11
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||

|
209
03-UnrealEngine/Rendering/Shader/Effect/海洋、云Demo制作方案.md
Normal file
209
03-UnrealEngine/Rendering/Shader/Effect/海洋、云Demo制作方案.md
Normal 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上的技术分享中有一张分析水体渲染技术非常经典的图,如下。
|
||||
|
||||

|
||||
|
||||
- 漫反射(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方法的思路是,计算视线方向的水体像素深度,然后基于此深度值采样吸收/散射LUT(Absorb/Scatter LUT)纹理,以控制不同深度水体的上色,得到通透的水体质感表现。
|
||||
|
||||

|
||||

|
||||
|
||||
##### 次表面散射近似方法(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,即散射强度,由采样位移值计算得出。
|
||||

|
||||
|
||||
图 《Crest Ocean System》中基于次表面散射近似的水体表现
|
||||

|
||||
|
||||
|
||||
[GDC 2011] 寒霜引擎的Fast SSS方案
|
||||
[GDC 2011]上,Frostbite引擎提出的Fast Approximating Subsurface Scattering方案,也可以用于水体渲染的模拟。
|
||||
|
||||

|
||||
|
||||
### 白沫(Foam/WhiteCap)
|
||||
白沫(Foam),在有些文献中也被称为Whitecap,White Water,是一种复杂的现象。即使白沫下方的材质具有其他颜色,白沫也通常看起来是白色的。出现这种现象的原因是因为白沫是由包含空气的流体薄膜组成的。随着每单位体积薄膜的数量增加,所有入射光都被反射而没有任何光穿透到其下方。这种光学现象使泡沫看起来比材质本身更亮,以至于看起来几乎是白色的。
|
||||

|
||||
|
||||
对于白沫的渲染而言,白沫可被视为水面上的纹理,其可直接由对象交互作用,浪花的飞溅,或气泡与水表面碰撞而产生。
|
||||
|
||||
白沫的渲染方案,按大的渲染思路而言,可以分为两类:
|
||||
- 基于动态纹理(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贴图。
|
||||

|
||||
|
||||
《战争雷霆(War Thunder)》团队在CGDC 2015上对此的改进方案为,取雅克比矩阵小于M的区域作为求解白沫的区域,其中M~0.3...05。
|
||||

|
||||
|
||||
图 《战争雷霆(War Thunder)》选取雅克比矩阵小于M的区域作为求解白沫的区域 [CGDC 2015]
|
||||
|
||||
另外,《盗贼之海(Sea of Thieves)》团队在SIGGRPAPH 2018上提出,可以对雅可比矩阵进行偏移,以获得更多白沫。且可以采用渐进模糊(Progressive Blur)来解决噪点(noisy)问题以及提供风格化的白沫表现。
|
||||

|
||||
!(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噪声来丰富泡沫的表现形式,以模拟海岸上泡沫的褪色现象。
|
||||
- 由于白沫是白色的,因此在R,G和B通道中的每个通道中都放置三张灰度图,然后颜色ramp图将定义三者之间的混合比率,来实现稠密、中等、稀疏三个级别的白沫。要修改白沫表现,美术师只需对ramp图进行颜色校正即可。如下图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
#### 浪尖白沫&岸边白沫:[GDC 2018]《孤岛惊魂5》基于有向距离场的方法
|
||||
GDC 2018上《孤岛惊魂5》团队分享的白沫渲染技术也不失为一种优秀的方案,主要思路是基于单张Noise贴图控制白沫颜色,结合两个offset采样Flow Map控制白沫的流动,并基于有向距离场(Signed Distance Field,SDF)控制岩石和海岸线处白沫的出现,然后根据位移对白沫进行混合。
|
||||
|
||||

|
||||
|
||||
### 水面高光
|
||||

|
||||
|
||||
### 水焦散
|
||||
水焦散的渲染 (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)
|
57
03-UnrealEngine/Rendering/Shader/Effect/皮皮虾材质方案.md
Normal file
57
03-UnrealEngine/Rendering/Shader/Effect/皮皮虾材质方案.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: 皮皮虾材质方案
|
||||
date: 2022-12-06 16:06:06
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
## 效果分析
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
由观察可得皮皮虾由外表面的透明甲壳材质与内部的肉材质组成。结构如下图:
|
||||

|
||||
解决方案:
|
||||
- 外层甲壳材质:
|
||||
- 透明度:以最薄处作为基础的透明度,叠加上 BumpOffset/POM 的HeightTexture的采样结果(深度)乘以参数计算出的透明度。
|
||||
- Roughness:很光滑光滑 0~0.2
|
||||
- Metallic:非金属(我们不需要反射)
|
||||
- 内部肉材质:
|
||||
- 贴图使用 BumpOffset/POM 进行UV偏移。
|
||||
- Roughness:粗糙 1。
|
||||
- Metallic:非金属
|
||||
- 中间层
|
||||
- 中间发光亮片:通过建模实现,内部放上一些小亮片,并且添加独立的自发光材质控制。
|
||||
- 中间雾气效果:通过建模实现,里面放上几个条带,添加类似雾气粒子的材质。
|
||||
|
||||
## 多层材质的距离感
|
||||
可以使用2种方案,主要都是通过偏移UV的方式来实现视差效果:
|
||||
1. BumpOffset
|
||||
2. POM
|
||||
|
||||
POM的会有较大的性能损耗,但与BumpOffset相比在视角较小时依然有着不错的效果。部分效果不佳的地方mask掉,防止穿帮。所以BumpOffset可以作为其下位代替。
|
||||

|
||||
|
||||
如果这2个方法不行就只能考虑使用2个模型,也就是一个外壳一个内部肉(不推荐)
|
||||
|
||||
## 具体方案
|
||||
### 两层模型
|
||||
- 外壳使用DefaultLit、Translucent
|
||||
- 内部使用Subsurface
|
||||
|
||||
缺点:内部模型需要蒙皮。
|
||||
|
||||
### MultiDraw
|
||||
MultiDraw在5.1支持不透明材质渲染透明效果了。所以可以是只用一个模型+HeightTexture配合MultiDraw二次绘制第二个材质效果。
|
||||
|
||||
缺点:有穿帮的危险。
|
||||
|
||||
### BumpOffset/POM
|
||||
使用DefaultLit或者车漆材质尝试 外壳/内部肉(视差),参考龙虾人的眼睛
|
||||
|
||||
1. 实现DefaultLit版本
|
||||
2. 查看车漆材质并且实现
|
||||
|
||||
缺点:5.1版本前无法做到材质物理上的正确。
|
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: 未命名
|
||||
date: 2024-05-11 11:29:03
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
【UE5所有视差技术合集展示-哔哩哔哩】 https://b23.tv/53fZ9bN
|
11
03-UnrealEngine/Rendering/Shader/HLSL操作符笔记.md
Normal file
11
03-UnrealEngine/Rendering/Shader/HLSL操作符笔记.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: HLSL操作符笔记
|
||||
date: 2024-09-09 18:43:48
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
- ceil:向上取整。
|
||||
- floor:向下取整。
|
||||
- step(x,y):x<=y返回1(真),否则返回0
|
||||
- all():判断所有向量是否都不为0,都不为0返回true,否者返回false。
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user