【Unity Shader Graph 使用与特效实现】专栏-直达
在Unity的Shader Graph中,NormalVector节点是一个基础且重要的工具,它允许着色器访问网格的法线矢量信息。法线矢量在计算机图形学中扮演着关键角色,它定义了表面的朝向,是光照计算、材质表现和各种视觉效果的基础。
节点概述
NormalVector节点为着色器编写者提供了获取网格法线数据的便捷途径。无论是顶点法线还是片元法线,这个节点都能让开发者轻松地在不同的坐标空间中操作这些数据。通过简单的参数设置,就可以将法线矢量转换到所需的坐标空间,大大简化了复杂着色器的开发过程。
法线矢量的本质是垂直于表面的单位向量,在三维空间中表示为(x, y, z)坐标。在Shader Graph中,这些数据通常来自3D模型的顶点数据,或者通过法线贴图等技术进行修改和增强。
参数详解
Space参数
Space参数决定了法线矢量输出的坐标空间,这是NormalVector节点最核心的功能。不同的坐标空间适用于不同的着色场景和计算需求。
- Object空间:也称为模型空间,这是法线数据最原始的存储空间。在Object空间中,法线相对于模型本身的坐标系定义,不考虑模型的旋转、缩放或平移变换。当模型发生变换时,Object空间中的法线不会自动更新,需要手动进行相应的变换计算。
- View空间:也称为相机空间或眼睛空间,在这个空间中,所有坐标都是相对于相机的位置和方向定义的。View空间的原点通常是相机的位置,Z轴指向相机的观察方向。这个空间特别适合与视角相关的效果,如边缘光、反射和折射。
- World空间:World空间中的坐标是相对于场景的世界坐标系定义的。无论模型如何移动或旋转,World空间提供了统一的参考框架。这个空间常用于光照计算、阴影生成和全局效果。
- Tangent空间:这是一个特殊的局部空间,主要用于法线贴图。在Tangent空间中,法线是相对于表面本身定义的,Z轴与表面法线对齐,X轴与切向量对齐,Y轴与副法线对齐。这种表示方法使得法线贴图可以在不同朝向的表面上重复使用。
选择正确的坐标空间对着色器的正确性和性能至关重要。错误的空间选择可能导致光照计算错误、视觉效果异常或性能下降。
端口信息

NormalVector节点只有一个输出端口:
- Out:输出类型为Vector 3,表示三维矢量。这个端口输出的是根据Space参数选择在对应坐标空间中的法线矢量。输出值通常是归一化的单位矢量,但在某些情况下(如使用非统一缩放时)可能需要重新归一化。
使用场景与示例
基础光照计算
法线矢量的一个主要应用是光照计算。在Lambert光照模型中,表面亮度取决于光线方向与表面法线之间的夹角。
HLSL// 简化的Lambert光照计算
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 worldNormal = NormalVector节点输出(World空间);
float NdotL = max(0, dot(worldNormal, lightDir));
float3 diffuse = _LightColor0 * NdotL;
在这个示例中,我们首先获取世界空间中的法线矢量和光线方向,然后计算它们的点积。点积结果决定了表面接收到的光照强度,这是大多数基础光照模型的核心计算。
法线贴图应用
法线贴图是现代实时渲染中增强表面细节的关键技术。NormalVector节点在应用法线贴图时起着桥梁作用。
HLSL// 法线贴图应用流程
float3 tangentNormal = tex2D(_NormalMap, uv).xyz * 2 - 1; // 从[0,1]转换到[-1,1]
float3 worldNormal = NormalVector节点输出(World空间);
// 使用TBN矩阵将切线空间法线转换到世界空间
float3x3 TBN = float3x3(IN.tangent.xyz,cross(IN.normal, IN.tangent.xyz) * IN.tangent.w,IN.normal
);
float3 mappedNormal = mul(TBN, tangentNormal);
这个示例展示了如何将切线空间中的法线贴图数据转换到世界空间。首先从法线贴图中采样并调整数值范围,然后使用TBN(切线-副切线-法线)矩阵进行空间转换。
边缘检测与轮廓光
利用View空间中的法线可以创建各种与视角相关的效果,如边缘光和轮廓检测。
HLSL// 边缘光效果
float3 viewNormal = normalize(mul((float3x3)UNITY_MATRIX_V, NormalVector节点输出(World空间)));
float3 viewDir = normalize(UnityWorldToViewPos(IN.worldPos));
float rim = 1 - abs(dot(viewNormal, viewDir));
float rimLight = pow(rim, _RimPower) * _RimIntensity;
在这个示例中,我们首先将世界空间法线转换到View空间,然后计算法线与视角方向的点积。当表面几乎垂直于视角方向时(即边缘处),点积接近0,从而产生边缘光效果。
环境遮挡与全局光照
法线信息对于环境遮挡和全局光照计算也至关重要。
HLSL// 简化的环境遮挡
float3 worldNormal = NormalVector节点输出(World空间);
float ambientOcclusion = 1.0;// 基于法线方向的简单环境光遮蔽
// 这里可以使用更复杂的算法,如SSAO或烘焙的AO贴图
ambientOcclusion *= (worldNormal.y * 0.5 + 0.5); // 模拟顶部光照更多// 应用环境光
float3 ambient = UNITY_LIGHTMODEL_AMBIENT * ambientOcclusion;
这个简单的示例展示了如何用法线方向来模拟环境光遮蔽效果。在实际项目中,通常会结合更复杂的算法或预计算的数据。
高级应用技巧
法线重定向与混合
在某些情况下,需要将法线从一个表面重定向到另一个表面,或者在不同法线源之间进行混合。
HLSL// 法线混合示例
float3 normalA = tex2D(_NormalMapA, uv).xyz;
float3 normalB = tex2D(_NormalMapB, uv).xyz;
float blendFactor = _BlendFactor;// 使用线性插值混合法线
float3 blendedNormal = lerp(normalA, normalB, blendFactor);// 或者使用更精确的球面线性插值
// float3 blendedNormal = normalize(lerp(normalA, normalB, blendFactor));
法线混合是一个复杂的话题,因为简单的线性插值可能不会保持法线的单位长度。在实际应用中,可能需要重新归一化或使用更高级的插值方法。
法线空间转换优化
在性能关键的场景中,法线空间转换可能需要优化。
HLSL// 优化的世界空间法线计算
// 传统方法
float3 worldNormal = normalize(mul(IN.normal, (float3x3)unity_WorldToObject));// 优化方法 - 使用逆转置矩阵(处理非统一缩放)
float3 worldNormal = normalize(mul(transpose((float3x3)unity_WorldToObject), IN.normal));
当模型应用了非统一缩放时,直接使用模型矩阵变换法线会导致错误的结果。在这种情况下,需要使用模型矩阵的逆转置矩阵来正确变换法线。
法线可视化与调试
在开发过程中,可视化法线矢量对于调试着色器非常有用。
HLSL// 法线可视化
float3 worldNormal = NormalVector节点输出(World空间);
// 将法线从[-1,1]范围映射到[0,1]范围以便可视化
float3 normalColor = worldNormal * 0.5 + 0.5;
return float4(normalColor, 1.0);
这个简单的着色器将法线矢量的各个分量映射到颜色通道,从而可以直观地查看法线的方向和分布。
常见问题与解决方案
法线不连续问题
当使用低多边形模型或不当的UV展开时,可能会遇到法线不连续的问题。
- 问题表现:表面出现不自然的硬边或接缝
- 解决方案:
- 确保模型有适当的平滑组设置
- 检查UV展开是否导致法线贴图采样错误
- 考虑使用更高精度的模型或细分表面
性能考量
法线计算可能会成为性能瓶颈,特别是在移动设备或复杂场景中。
- 优化策略:
- 在顶点着色器中计算法线,而不是片元着色器
- 使用更简单的法线计算,如省略归一化步骤(如果对视觉效果影响不大)
- 考虑使用法线贴图的压缩格式以减少内存带宽
法线精度问题
在特定情况下,法线计算可能会遇到精度问题,导致视觉瑕疵。
- 问题表现:闪烁的表面、带状伪影或不准确的光照
- 解决方案:
- 使用更高精度的数据类型(如half改为float)
- 确保法线贴图使用适当的格式和压缩
- 检查法线变换矩阵的精度和正确性
与其他节点的配合使用
NormalVector节点很少单独使用,通常与其他Shader Graph节点结合以实现复杂的效果。
- 与Dot Product节点结合:用于计算光照强度、菲涅尔效应等
- 与Transform节点结合:在不同坐标空间之间转换法线
- 与Normalize节点结合:确保法线保持单位长度
- 与Sample Texture 2D节点结合:应用法线贴图
- 与Fresnel Effect节点结合:创建基于视角的效果
最佳实践
为了确保NormalVector节点的正确使用和最佳性能,建议遵循以下最佳实践:
- 始终考虑法线是否需要归一化,特别是在进行数学运算或空间变换后
- 选择最适合当前计算任务的坐标空间,避免不必要的空间转换
- 在性能敏感的场景中,尽可能在顶点着色器中计算法线相关数据
- 使用适当的数据类型平衡精度和性能
- 定期验证法线计算的正确性,特别是在使用复杂变换或混合时
【Unity Shader Graph 使用与特效实现】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)
在Unity的Shader Graph中,NormalVector节点是一个基础且重要的工具,它允许着色器访问网格的法线矢量信息。法线矢量在计算机图形学中扮演着关键角色,它定义了表面的朝向,是光照计算