UE5 Shader基础学习笔记——13-20 DetailNormal/Smoothstep/Length/CeilFloorRound/DDXDDY/SinCos/Power

本笔记涵盖UE5 Shader的基础知识点,包括Detail Normal Mapping、灰度mask的高级使用、Smoothstep函数应用、Length与Distance的计算技巧、Ceil Floor Round的色阶效果、DDX DDY的法线及Mip计算、Sin Cos的动态效果实现、以及Power运算的应用等。

本系列学习资料来源,Ben Cloward的油管空间,本笔记主要对其中UE的部分进行记录

Lec13 Detail Normal Mapping

使用Quixel的一个岩石资产(PkeeM)作为演示,拉太近之后纹理会比较模糊
在这里插入图片描述
可以使用Detail Normal增加细节,所谓Detail Normal就是再拿一张法线纹理,平铺很多次,进行法线混合覆盖在上面

Ben在视频中使用的DetailNormal如下,RG是法线,B是粗糙度,A是用于颜色Overlay的mask
由于有很多信息,所以在导入UE时使用BC7格式压缩,并且取消SRGB
在这里插入图片描述
使用如下
在这里插入图片描述
此时放大近看可以看到细节
在这里插入图片描述
不过这样是一直在计算DetailNormal,实际使用时为了让其不一直计算,可以在较低的Lod中舍弃这个,或者创建一个基于距离的mask,靠近时再计算

Lec14 Advanced Detail Mapping (etc.灰度mask)

在这节使用一张纹理mask将多个Detail进行映射
整个实现如下图,也就是RGBA四个通道各画一个Mask分别用不同的图

在这里插入图片描述
这样是消耗比较高的,于是可以用UE的TextureArray来改善,创建TextureArray后插入要用的图
在这里插入图片描述
采样TextureArray需要UVW三个参数,UV正常采样,W用于选择贴图
为了分出用什么贴图,可以在mask上使用不同灰度值,这样实现更加节省性能,W用灰度值映射
在这里插入图片描述
在这里插入图片描述
从评论中节选出一些问题

  • TextureArray会只加载一个mip,无法利用Streaming的优势,但对于detail来说没问题,毕竟detail只在特写中需要。Ben关于这个的建议是使用低分辨率的TextureArray,然后应用所有角色,一直在内存中也没事。
    在这里插入图片描述
  • 关于如何在mask中单独设置每个纹理的UV平铺,如果使用了if节点来设置就没有意义了。
    Ben的回复是之前在开发圣歌的时候尝试做过但是这种尝试并不干净,他决定使用一个全局的统一的分辨率控制缩放。Ben提议可以将纹理缩放倍数也编码为灰度颜色读取,但是会在边界上产生一些奇怪的结果,在涉及纹理压缩和低mipmap时尤为明显。
    在圣歌的开发中,Ben让艺术家在一个mask上使用16种材质,texture array设置有16份贴图,分辨率为64*64
    在这里插入图片描述
  • 如果想要使用mask之间过渡,可以采用最开始分通道存储mask的方式
    在这里插入图片描述
  • 也有人提出问题,UE更适合多种材质插槽,使用多种简单的材质更好。Ben觉得使用一个材质可以减少DrawCall。
    在这里插入图片描述

这种用灰度分mask的方法在其他游戏中也会用到,比如原神中根据不同灰度选择不同Ramp
在这里插入图片描述

Lec15 Smoothstep (etc.溶解/高度混合)

关于Smoothstep,可以看看
常用函数SmoothStep的实现原理
Shader实验室: smoothstep函数
推荐使用 Desmos这个网站 来画函数图像

SmoothStep(float edgo0,float edge1, float x);

edge0是样条线插值的下界,edge1是样条线插值的上界,当x小于下界返回0,当x大于上界返回1,介于上界下界之间返回一个平滑的曲线。
在这里插入图片描述
如下图对UV坐标在0.6和0.7之间Smoothstep
在这里插入图片描述

数学上就是这些,下面有一些例子,比如溶解

sin(time)映射到0~1,将噪声图在两个值之间Smoothstep,边缘有0.03厚度进行溶解
在这里插入图片描述
在这里插入图片描述
对溶解做一些改进,再加一组Smoothstep,但是稍微偏移一点,两者相减,溶解就只剩边缘了,有一种扫描的效果
在这里插入图片描述
在这里插入图片描述
还有个例子,Smoothstep也可以用在地形材质的高度混合
Building Worlds In Unreal 学习笔记——15-19 高度图地形专题&地形自动材质专题
首先拉一个渐变范围,然后让smoothstep去调整渐变的范围
在这里插入图片描述
Smoothstep对调整范围很有帮助,因为有最大值最小值可以控制

Lec16 Length & Distance (etc.摇摆/内部假光)

Length输入一个向量计算长度,Distance输入起止点计算长度,下图上下是等效的
在这里插入图片描述

下面这个例子将局部坐标Z值与下面的点计算距离作为遮罩,进行横向移动,达到摇摆的效果
在这里插入图片描述
在这里插入图片描述

下面的例子结构和摇摆的类似,不过这次将移动的sin作为测量距离的点,将距离pow之后作为自发光,得到一种虚假光源的效果
在这里插入图片描述
在这里插入图片描述
和sdf进行Ray Marching有类似的思路,通过描述距离来实现效果 案例学习——Unity体绘制shader初探

Lec17 Ceil Floor Round (etc.色阶)

Ceil 向上取整
Floor 向下取整
Round 四舍五入

举例 半兰伯特分色阶
在这里插入图片描述

Lec18 DDX DDY (etc.面片法线,纹理mip计算)

相邻像素之间求导
在这里插入图片描述
DDX和DDY叉乘可以得到法线,用于隐藏呈现面片感的树叶
UE4 Material 101学习笔记——30-37 植物叶片(透光/mask/面片隐藏/法线调整/AO/渐隐/世界空间色彩/随风舞动)
在这里插入图片描述
接着来DDXDDY还能修复HDR的latlong贴图
在这里插入图片描述
在这里插入图片描述
图片衔接处会有一个像素的白线,产生的原因是边界的不连续,交界处UV从0跳转到1,此时选择Mipmap出错,于是出现这个问题
在这里插入图片描述
解决办法选择Dericative模式作为MipValueMode,将UV坐标作为DDXDDY输入,对于极坐标等也很有帮助
在这里插入图片描述
在这里插入图片描述
在HLSL中,这个功能相当于 tex2Dgrad(s,t,ddx,ddy)函数 使用微分并指定MIP层的二维纹理查找

DDXDDY还能用于将凹凸贴图转为法线贴图,下面这些节点的功能相当于PerturbNormalLQ
在这里插入图片描述

Lec19 Sin Cos (etc.涟漪)

正弦余弦的定义不再赘述
注意在UE中,sin和cos的周期是1,需要自己在参数栏设置成2pi才会是正常的sin,cos的速度
在这里插入图片描述

sin和cos的使用这里ben佬给出了几个例子
第一个调整幅度
在这里插入图片描述

第二个则可以利用径向渐变来实现ripple
在这里插入图片描述
在这里插入图片描述
更进阶的ripple做法
UE4 Material 101学习笔记——13-18 完整的雨水着色器

第三个是用sin进行运动(加个sin就和弹跳动画一样)
在这里插入图片描述
第四个是旋转UV
在这里插入图片描述

Lec20 Power

幂运算的定义也不再赘述
power可以用于对比度调整,可以用于NdotV菲涅尔的调整,又比如power2.2和gamma校正一样
power性能比较昂贵,引擎也会做一些优化,比如二次方自动用硬编码的自身相乘替代

<think> 我们正在分析代码:`float heightFog = saturate((worldPos.y - _HeightMax) / (_HeightMin - _HeightMax));` 该代码用于计算基于高度的雾效因子,通常用于实现高度雾(Height Fog)效果。 解析: 1. 输入参数: - `worldPos.y`:当前像素的世界空间Y坐标(高度) - `_HeightMax`:雾的最大高度(雾的上边界) - `_HeightMin`:雾的最小高度(雾的下边界) 2. 数学表达式: 计算高度雾因子的表达式为: $$ \text{heightFog} = \text{saturate}\left( \frac{\text{worldPos}.y - \_HeightMax}{\_HeightMin - \_HeightMax} \right) $$ 3. 计算步骤: a. 分子:当前高度与最大雾高度的差值(`worldPos.y - _HeightMax`) b. 分母:最小雾高度与最大雾高度的差值(`_HeightMin - _HeightMax`),注意这个值是负数(因为通常`_HeightMin < _HeightMax`,表示雾从低到高逐渐变淡) c. 相除:得到一个从0到1线性变化的因子,表示当前高度在雾层中的位置比例。 - 当`worldPos.y`等于`_HeightMax`时,分子为0,结果为0。 - 当`worldPos.y`等于`_HeightMin`时,分子为`_HeightMin - _HeightMax`(负值),分母也是这个负值,结果为1。 d. `saturate`:将结果限制在[0,1]范围内。 4. 物理意义: - 结果0:表示当前像素在雾的上边界及以上,没有雾(雾效因子为0)。 - 结果1:表示当前像素在雾的下边界及以下,雾效最强(雾效因子为1)。 - 介于0和1之间:表示像素在雾层中间,按高度比例计算雾的浓度。 注意:该表达式假设`_HeightMin < _HeightMax`,即雾从下往上逐渐变淡。 优化点: 1. 避免重复计算: 分母`(_HeightMin - _HeightMax)`是一个常数,可以在CPU端计算一次后传给Shader,或者使用一个中间变量存储其倒数(避免除法运算,改为乘法)。 优化前: ```hlsl float heightFog = saturate((worldPos.y - _HeightMax) / (_HeightMin - _HeightMax)); ``` 优化后(使用预计算的倒数): ```hlsl // 在CPU端计算并传递:_HeightFogInvRange = 1.0 / (_HeightMin - _HeightMax) float heightFog = saturate((worldPos.y - _HeightMax) * _HeightFogInvRange); ``` 这样将除法转换为乘法,提升性能。 2. 边界条件处理: 原代码使用`saturate`来限制范围,但当`_HeightMin`和`_HeightMax`非常接近时,分母接近于0,可能导致数值不稳定。因此,在设置参数时,应确保`_HeightMin`和`_HeightMax`有合理的差值。在Shader中,可以添加安全判断: ```hlsl float range = _HeightMin - _HeightMax; // 如果范围接近于0,则避免除以0 if (abs(range) < 1e-5) { heightFog = (worldPos.y < _HeightMax) ? 1.0 : 0.0; } else { heightFog = saturate((worldPos.y - _HeightMax) / range); } ``` 但分支判断在Shader中可能影响性能,因此更推荐在CPU端保证参数合理性。 3. 高度雾的平滑过渡: 有时我们希望雾的过渡不是线性的,可以使用一个平滑函数(如smoothstep)来替代线性插值: ```hlsl // 使用smoothstep进行平滑过渡,需要两个边缘参数(这里使用0和1,但实际可以调整) float heightFog = smoothstep(0.0, 1.0, saturate((worldPos.y - _HeightMax) * _HeightFogInvRange)); ``` 或者,更常见的做法是使用自定义的过渡曲线(通过纹理查找或数学函数)。 4. 结合其他雾效: 在实际项目中,高度雾通常与距离雾(Distance Fog)结合使用。最终的雾效因子可能是两者的乘积或最小值/最大值,具体取决于美术需求。 5. 世界空间坐标的获取优化: 在之前的问题中,我们通过`worldPos = ComputeWorldSpacePosition(...)`得到世界坐标。如果这个计算是在全屏进行的,可能会成为性能瓶颈。可以考虑以下优化: - 在延迟渲染中,世界坐标可以直接从G-Buffer获取。 - 在顶点着色器中计算世界坐标(如果几何体高度变化不大,如地形)并插值。 6. 使用半精度浮点数: 在移动平台,如果精度要求不高,可以将`worldPos`和高度参数声明为`half`类型。 7. 雾效计算的位置: 如果雾效仅用于天空盒等特定区域,可以提前通过深度测试剔除不需要计算的像素(比如天空盒的深度值通常为1,可以提前跳过)。 示例优化代码: 假设我们在CPU端已经计算了`_HeightFogInvRange`(即1.0/(_HeightMin - _HeightMax))并传递给Shader,那么代码可以简化为: ```hlsl float heightFog = saturate((worldPos.y - _HeightMax) * _HeightFogInvRange); ``` 如果希望有平滑过渡(例如在雾的上下边界附近各过渡0.5米),可以这样: ```hlsl // 假设_HeightFadeRange为过渡区域大小(例如0.5米) float heightFog = smoothstep(0.0, _HeightFadeRange, (worldPos.y - _HeightMax) * _HeightFogInvRange); // 注意:这里需要调整,因为原线性因子是0到1,而过渡区域需要重新映射 // 更合理的做法是: // lowerBound = _HeightMin + _HeightFadeRange // upperBound = _HeightMax - _HeightFadeRange // 然后重新计算因子,但这样会改变雾的范围。另一种方式是在线性因子上使用smoothstep: float rawFactor = (worldPos.y - _HeightMax) * _HeightFogInvRange; float heightFog = smoothstep(0.0, 1.0, saturate(rawFactor)); // 在0和1处平滑 ``` 但实际上,我们通常希望雾在上下边界都有平滑过渡,可以这样: ```hlsl float rawFactor = (worldPos.y - _HeightMax) * _HeightFogInvRange; float heightFog = smoothstep(0.0, 1.0, rawFactor); // 自动将rawFactor夹在0-1并平滑 ``` 因为`smoothstep(a, b, x)`函数在`x<a`时返回0,`x>b`时返回1,在a和b之间平滑插值。所以如果我们希望雾在接近_HeightMax时开始出现(但不要立刻为0)并在接近_HeightMin时完全变为1,可以调整: ```hlsl // 设置两个过渡阈值:比如在_HeightMax附近0.5米内平滑出现,在_HeightMin附近0.5米内平滑达到1。 // 但原公式的线性因子是0在_HeightMax,1在_HeightMin,所以我们可以直接控制平滑的边界: // 定义过渡区域大小(单位:高度差) float _HeightFadeRange = 0.5; // 计算过渡的起始和结束(因为我们是从0到1,过渡区域在两端) float lowerFade = _HeightFadeRange * abs(_HeightFogInvRange); // 转换为因子空间的过渡大小 float heightFog = smoothstep(0.0, 1.0, saturate(rawFactor)); // 整个区间平滑 // 或者,更精细的控制两端: float heightFog = smoothstep(0.0, lowerFade, rawFactor) * (1 - smoothstep(1.0 - lowerFade, 1.0, rawFactor)); // 但这样比较复杂,通常使用一个smoothstep即可。 // 更常见的做法是直接使用一个平滑函数控制整个范围,例如: float heightFog = rawFactor * rawFactor * (3 - 2 * rawFactor); // 三次多项式平滑,相当于smoothstep(0,1,rawFactor)的内部实现 ``` 总结:优化主要集中在预计算和运算简化上,同时根据美术需求调整过渡效果。 相关问题: 1. 如何将高度雾与距离雾结合使用? 2. 在Shader中如何处理高度雾的过渡区域? 3. 雾效计算在Shader的哪个阶段进行更高效? 4. 高度雾的参数(_HeightMin, _HeightMax)该如何根据场景调整? 5. 除了线性高度雾,还有哪些常见的高度雾模型? </think> ### 代码解析:高度雾效因子计算 代码 `float heightFog = saturate((worldPos.y - _HeightMax) / (_HeightMin - _HeightMax));` 用于计算**基于高度的雾效强度因子**,其数学表达为: $$ \text{heightFog} = \text{saturate}\left( \frac{\text{worldPos}.y - \_HeightMax}{\_HeightMin - \_HeightMax} \right) $$ #### 核心原理与功能 1. **参数定义** - `worldPos.y`:像素的世界空间Y坐标(高度值) - `_HeightMax`:雾效的**上边界高度**(雾浓度最低处) - `_HeightMin`:雾效的**下边界高度**(雾浓度最高处) - 隐含要求:`_HeightMin < _HeightMax`(即雾从地面向空中逐渐变淡) 2. **数学映射关系** | 高度位置 | 计算结果 | 物理意义 | |-----------------------|----------|------------------------| | `worldPos.y ≥ _HeightMax` | `0` | 高于雾层,无雾效 | | `worldPos.y ≤ _HeightMin` | `1` | 低于雾层,雾效最强 | | 两边界之间 | `(0,1)` | 雾效随高度升高线性减弱 | 3. **`saturate`的作用** 将计算结果钳制在 `[0, 1]` 范围内,避免无效值: - 当 `_HeightMin = _HeightMax` 时防止除零错误 - 处理高度超出预设范围的情况 --- ### 优化策略与改进方案 #### 🚀 1. 除法优化 → 乘法倒数(性能提升) **问题**:除法指令在Shader中开销较大 **方案**:预计算倒数,将除法转为乘法 ```hlsl // CPU端预计算并传递(避免每帧重复计算) _HeightFogInvRange = 1.0 / (_HeightMin - _HeightMax); // Shader中优化计算 float heightFog = saturate((worldPos.y - _HeightMax) * _HeightFogInvRange); ``` #### 🌈 2. 非线性过渡(视觉优化) **问题**:线性过渡在边界处有视觉断层 **方案**:使用`smoothstep`实现平滑过渡 ```hlsl // 添加过渡区域控制(如0.2表示20%高度区间平滑) float _FadeRange = 0.2; float rawFactor = (worldPos.y - _HeightMax) * _HeightFogInvRange; float heightFog = smoothstep(0, _FadeRange, saturate(rawFactor)); ``` #### ⚙️ 3. 安全边界处理(健壮性) **问题**:参数设置错误时结果异常 **方案**:添加参数校验逻辑 ```hlsl // 检查高度范围有效性 float range = _HeightMin - _HeightMax; if (abs(range) < 1e-5) { // 避免除零 heightFog = (worldPos.y < _HeightMax) ? 1.0 : 0.0; } else { heightFog = saturate((worldPos.y - _HeightMax) / range); } ``` #### 🎨 4. 多维雾效混合(扩展性) **问题**:单一高度雾缺乏层次感 **方案**:结合距离雾与密度函数 ```hlsl // 距离雾因子(基于相机距离) float distanceFog = ...; // 高度雾因子(优化后) float heightFog = ...; // 混合公式(可替换为乘法/最大值等) float finalFog = lerp(distanceFog, heightFog, _BlendWeight); ``` --- ### 应用场景示例 ```mermaid graph TD A[像素着色器] --> B{计算世界坐标} B --> C[高度雾因子 heightFog] C --> D[采样雾色纹理] D --> E[混合原始颜色与雾色] E --> F[输出最终颜色] ``` #### 典型使用代码 ```hlsl // 优化后高度雾计算 float3 ApplyHeightFog(float3 color, float3 worldPos) { float fogFactor = saturate((worldPos.y - _HeightMax) * _HeightFogInvRange); float3 fogColor = SAMPLE_TEXTURE2D(_FogTex, sampler_FogTex, uv).rgb; return lerp(color, fogColor, fogFactor * _FogIntensity); } ``` --- ### 相关问题 1. 如何将高度雾与距离雾(Depth Fog)高效结合? 2. 非线性雾效过渡函数(如指数密度)如何实现? 3. 在开放大世界中如何动态调整 `_HeightMin/_HeightMax`? 4. 雾效计算在移动端有哪些特殊优化技巧? 5. 如何通过噪声纹理增强雾的体积感?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值