5.5 Shader Graph


文档摘要

5.5 Shader Graph 5.5 Shader Graph:可视化着色器编程的艺术 5.5.1 Shader Graph 简介:释放创造力的可视化工具 Shader Graph是Unity官方推出的可视化着色器编辑器,它集成在Unity编辑器中,无需编写代码即可创建自定义着色器。它采用节点式编程的范式,用户通过拖拽、连接各种功能节点,构建出复杂的着色器逻辑。 Shader Graph的核心优势: 可视化编程: 摆脱了代码的束缚,使用图形化的界面进行着色器开发,降低了学习门槛,加速了开发流程。 实时预览: 在Shader Graph编辑器中,可以实时预览着色器的效果,方便快速迭代和调整。

5.5 Shader Graph

5.5 Shader Graph:可视化着色器编程的艺术

5.5.1 Shader Graph 简介:释放创造力的可视化工具

Shader Graph是Unity官方推出的可视化着色器编辑器,它集成在Unity编辑器中,无需编写代码即可创建自定义着色器。它采用节点式编程的范式,用户通过拖拽、连接各种功能节点,构建出复杂的着色器逻辑。

Shader Graph的核心优势:

  • 可视化编程: 摆脱了代码的束缚,使用图形化的界面进行着色器开发,降低了学习门槛,加速了开发流程。

  • 实时预览: 在Shader Graph编辑器中,可以实时预览着色器的效果,方便快速迭代和调整。

  • 跨平台兼容性: Shader Graph生成的着色器具有良好的跨平台兼容性,可以运行在Unity支持的各种平台上。

  • 易于学习和使用: 直观的操作界面和丰富的节点库,使得Shader Graph易于上手,即使没有编程经验的用户也能快速入门。

  • 强大的扩展性: Shader Graph支持自定义节点和子图,可以扩展其功能,满足更高级的需求。

Shader Graph的应用场景:

  • 自定义材质效果: 创建独特的表面材质,例如金属、玻璃、布料、卡通风格等。

  • 视觉特效: 制作各种视觉特效,例如火焰、水流、粒子效果、溶解效果等。

  • 后处理效果: 实现图像后处理效果,例如色彩校正、模糊、景深、风格化滤镜等。

  • 交互式效果: 创建与用户交互的着色器效果,例如点击波纹、动态光照、环境交互等。

5.5.2 Shader Graph 的基本概念:节点、连接与属性

理解Shader Graph的基本概念是掌握其使用的关键。Shader Graph的核心组成部分包括:

  • 节点 (Nodes): Shader Graph的基本构建块,每个节点代表一个特定的操作或功能。节点可以是简单的数学运算、纹理采样、颜色操作,也可以是复杂的光照模型、噪声生成器等。

  • 连接 (Connections): 节点之间通过连接线进行数据传递。输出端口 (Output Port) 将数据传递到输入端口 (Input Port)。数据的流向决定了着色器的逻辑。

  • 属性 (Properties): 属性是着色器的可调节参数,可以在材质面板中进行修改。属性可以是颜色、数值、纹理、向量等。通过属性,可以方便地控制着色器的外观和行为。

  • 主节点 (Master Node): Shader Graph的最终输出节点,决定了着色器的输出类型和渲染管线。例如, lit Master Node 用于创建表面着色器,Unlit Master Node 用于创建非光照着色器,Post-Processing Master Node 用于创建后处理着色器。

  • 关键字 (Keywords): 关键字用于控制着色器的编译变体,可以根据不同的条件编译不同的代码分支,实现更灵活的效果和优化。

节点类型:

Shader Graph提供了丰富的节点库,大致可以分为以下几类:

  • Input 节点 (输入节点): 用于获取外部输入数据,例如属性、纹理、时间、摄像机参数等。

  • Math 节点 (数学节点): 用于进行各种数学运算,例如加减乘除、三角函数、向量运算、矩阵运算等。

  • Texture 节点 (纹理节点): 用于采样和操作纹理,例如纹理采样、纹理坐标变换、纹理混合等。

  • Color 节点 (颜色节点): 用于创建和操作颜色,例如颜色常量、颜色混合、颜色空间转换等。

  • Lighting 节点 (光照节点): 用于计算光照效果,例如漫反射、镜面反射、环境光、阴影等。

  • Utility 节点 (实用节点): 提供各种实用功能,例如条件判断、数据转换、噪声生成、时间操作等。

  • Artistic 节点 (艺术节点): 提供一些艺术效果相关的节点,例如卡通渲染、风格化效果等。

  • Channel 节点 (通道节点): 用于提取和操作颜色的通道,例如红绿蓝通道、Alpha通道等。

5.5.3 创建第一个 Shader Graph 着色器:颜色渐变

让我们通过一个简单的例子来创建第一个 Shader Graph 着色器:一个颜色渐变着色器。

步骤 1:创建 Shader Graph 资源

  1. 在 Unity 项目窗口中,右键单击,选择 Create > Shader Graph > URP > Lit Shader Graph (或者根据你的渲染管线选择合适的 Shader Graph 类型,例如 Unlit Graph 如果不需要光照)。

  2. 将新创建的 Shader Graph 资源命名为 "ColorGradientShader"。

  3. 双击 "ColorGradientShader" 资源,打开 Shader Graph 编辑器。

步骤 2:添加颜色属性

  1. 在 Shader Graph 编辑器的 Blackboard (黑板) 窗口中,点击 + 按钮,选择 Color

  2. 将新创建的颜色属性命名为 "ColorA"。

  3. 再次点击 + 按钮,选择 Color,命名为 "ColorB"。

  4. 确保两个颜色属性的 Mode 都设置为 DefaultDefault 值可以根据需要设置。

步骤 3:添加 Lerp 节点

  1. 在 Shader Graph 编辑器中,右键单击空白区域,选择 Create Node > Math > Lerp

    • Lerp (线性插值) 节点 用于在两个值之间进行插值。它的输入包括:

      • A: 插值的起始值。

      • B: 插值的结束值。

      • T: 插值参数,范围通常在 0 到 1 之间。当 T 为 0 时,输出为 A;当 T 为 1 时,输出为 B;当 T 在 0 到 1 之间时,输出为 A 和 B 之间的线性插值结果。

步骤 4:添加 UV 节点

  1. 右键单击空白区域,选择 Create Node > Input > Geometry > UV

    • UV 节点 输出网格模型的 UV 坐标。UV 坐标是纹理在模型表面上的映射坐标,范围通常在 0 到 1 之间。

步骤 5:连接节点

  1. 将 "ColorA" 属性节点的输出端口拖拽连接到 "Lerp" 节点的 "A" 输入端口。

  2. 将 "ColorB" 属性节点的输出端口拖拽连接到 "Lerp" 节点的 "B" 输入端口。

  3. 将 "UV" 节点的 "Out" 输出端口的 R (红色通道) 拖拽连接到 "Lerp" 节点的 "T" 输入端口。

    • 这里我们使用 UV 坐标的红色通道作为插值参数 T。由于 UV 坐标的 U 分量 (红色通道) 通常在水平方向上从 0 变化到 1,因此我们可以利用它来实现水平方向的颜色渐变。
  4. 将 "Lerp" 节点的 "Out" 输出端口拖拽连接到 "Lit Master" 节点的 "Color" 输入端口。

步骤 6:保存 Shader Graph

  1. 点击 Shader Graph 编辑器左上角的 Save Asset 按钮,保存 Shader Graph 资源。

步骤 7:创建材质并应用到物体

  1. 在 Unity 项目窗口中,右键单击,选择 Create > Material

  2. 将新创建的材质命名为 "ColorGradientMaterial"。

  3. 在 "ColorGradientMaterial" 材质的 Inspector 面板中,将 Shader 下拉菜单设置为 Shader Graphs > ColorGradientShader (或者你 Shader Graph 资源所在的路径)。

  4. 现在材质的 Inspector 面板中会显示 "ColorA" 和 "ColorB" 颜色属性,你可以调整它们的颜色。

  5. 将 "ColorGradientMaterial" 材质拖拽到场景中的一个物体上 (例如 Cube)。

运行效果:

你会看到物体表面呈现出从 "ColorA" 颜色到 "ColorB" 颜色的水平渐变效果。

Mermaid 图表:

代码详解 (ShaderLab 代码片段,仅供参考,Shader Graph 内部生成):

虽然 Shader Graph 的核心优势在于可视化编程,但了解其背后生成的 ShaderLab 代码有助于更深入地理解着色器的工作原理。以下是上面 ColorGradientShader 可能生成的 ShaderLab 代码片段 (简化版,实际生成代码会更复杂,包含各种平台兼容性处理):

Shader "Shader Graphs/ColorGradientShader" { Properties { _ColorA ("Color A", Color) = (1,1,1,1) _ColorB ("Color B", Color) = (1,1,1,1) } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } LOD 100 Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; CBUFFER_START(UnityPerMaterial) float4 _ColorA; float4 _ColorB; CBUFFER_END Varyings vert (Attributes input) { Varyings output; output.vertex = TransformObjectToHClip(input.positionOS.xyz); output.uv = input.uv; return output; } half4 frag (Varyings input) : SV_Target { half t = input.uv.r; // 使用 UV.r 作为插值参数 half4 color = lerp(_ColorA, _ColorB, t); // 线性插值 return color; } ENDHLSL } } FallBack "Diffuse" }

代码解释:

  • Properties 块: 定义了材质的属性,对应 Shader Graph 中的 "ColorA" 和 "ColorB" 属性。

  • SubShader 块: 包含一个或多个 Pass,定义了渲染过程。

  • Pass 块: 定义了一个渲染 Pass,使用 HLSL 编写着色器代码。

    • #pragma vertex vert: 声明顶点着色器函数为 "vert"。

    • #pragma fragment frag: 声明片元着色器函数为 "frag"。

    • #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl": 包含 URP 渲染管线的核心库,提供内置函数和结构体。

    • struct Attributes: 定义了顶点着色器的输入属性,包括顶点位置和 UV 坐标。

    • struct Varyings: 定义了顶点着色器到片元着色器的输出数据,包括 UV 坐标和裁剪空间顶点位置。

    • CBUFFER_START/CBUFFER_END(UnityPerMaterial): 定义了常量缓冲区,用于传递材质属性数据。

    • vert 函数 (顶点着色器): 将模型空间顶点位置转换为裁剪空间顶点位置,并将 UV 坐标传递给片元着色器。

    • frag 函数 (片元着色器):

      • half t = input.uv.r;: 获取 UV 坐标的红色通道 (U 分量) 作为插值参数 t

      • half4 color = lerp(_ColorA, _ColorB, t);: 使用 lerp 函数在 _ColorA_ColorB 之间进行线性插值,插值参数为 t

      • return color;: 返回计算得到的颜色。

5.5.4 进阶实践:纹理采样与凹凸贴图

接下来,我们创建一个稍微复杂一些的 Shader Graph 着色器,使用纹理采样和凹凸贴图来增强表面细节。

步骤 1:创建 Shader Graph 资源 (Unlit Graph)

  1. 在 Unity 项目窗口中,右键单击,选择 Create > Shader Graph > URP > Unlit Graph (这里使用 Unlit Graph 简化光照计算,专注于纹理效果)。

  2. 命名为 "TextureBumpShader"。

  3. 双击打开 Shader Graph 编辑器。

步骤 2:添加纹理属性和采样节点

  1. 在 Blackboard 窗口中,点击 + 按钮,选择 Texture 2D。命名为 "MainTexture"。

  2. 再次点击 + 按钮,选择 Texture 2D。命名为 "NormalMap"。

  3. 右键单击空白区域,选择 Create Node > Input > Texture > Sample Texture 2D

  4. 创建两个 Sample Texture 2D 节点。分别命名为 "MainTexture Sample" 和 "NormalMap Sample"。

  5. 将 "MainTexture" 属性节点拖拽连接到 "MainTexture Sample" 节点的 "Texture" 输入端口。

  6. 将 "NormalMap" 属性节点拖拽连接到 "NormalMap Sample" 节点的 "Texture" 输入端口。

步骤 3:添加 UV 节点

  1. 右键单击空白区域,选择 Create Node > Input > Geometry > UV

  2. 将 "UV" 节点的 "Out" 输出端口连接到 "MainTexture Sample" 和 "NormalMap Sample" 节点的 "UV" 输入端口。

步骤 4:连接到 Master Node

  1. 将 "MainTexture Sample" 节点的 "RGBA" 输出端口连接到 "Unlit Master" 节点的 "Color" 输入端口。

  2. 将 "NormalMap Sample" 节点的 "Normal" 输出端口连接到 "Unlit Master" 节点的 "Normal Tangent Space" 输入端口。

步骤 5:保存 Shader Graph

  1. 点击 Save Asset

步骤 6:创建材质并应用

  1. 创建材质 "TextureBumpMaterial"。

  2. 设置 Shader 为 "Shader Graphs > TextureBumpShader"。

  3. 在材质的 Inspector 面板中,分别为 "MainTexture" 和 "NormalMap" 属性指定纹理。

    • MainTexture: 选择一个颜色纹理。

    • NormalMap: 选择一个法线贴图 (Normal Map) 纹理。确保纹理类型设置为 "Normal Map"。

运行效果:

物体表面会显示 "MainTexture" 纹理,并且根据 "NormalMap" 纹理模拟出凹凸不平的表面细节。

Mermaid 图表:

代码详解 (ShaderLab 代码片段,仅供参考):

Shader "Shader Graphs/TextureBumpShader" { Properties { _MainTexture ("Main Texture", 2D) = "white" {} _NormalMap ("Normal Map", 2D) = "bump" {} } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } LOD 100 Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; float4 tangentOS : TANGENT; float3 normalOS : NORMAL; }; struct Varyings { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 tangentWS : TEXCOORD1; float3 bitangentWS : TEXCOORD2; float3 normalWS : TEXCOORD3; }; CBUFFER_START(UnityPerMaterial) sampler2D _MainTexture; float4 _MainTexture_ST; sampler2D _NormalMap; float4 _NormalMap_ST; CBUFFER_END Varyings vert (Attributes input) { Varyings output; output.vertex = TransformObjectToHClip(input.positionOS.xyz); output.uv = TRANSFORM_TEX(input.uv, _MainTexture); // 应用纹理 tiling 和 offset output.tangentWS = TransformTangentToWorld(input.tangentOS.xyz); output.bitangentWS = cross(output.normalWS, output.tangentWS) * input.tangentOS.w; output.normalWS = TransformNormalToWorld(input.normalOS); return output; } half4 frag (Varyings input) : SV_Target { half4 mainTexColor = tex2D(_MainTexture, input.uv); half3 normalMap = UnpackNormalmapRGB(tex2D(_NormalMap, input.uv)); // 解包法线贴图 half3 normalTS = normalMap; // 法线贴图在切线空间 half3 normalWS = TransformTangentToWorldNormal(normalTS, half3x3(input.tangentWS, input.bitangentWS, input.normalWS)); // 切线空间法线转世界空间法线 return mainTexColor; // 这里简化了,没有使用法线进行光照计算,因为是 Unlit Graph } ENDHLSL } } FallBack "Diffuse" }

代码解释 (重点部分):

  • sampler2D _MainTexture; sampler2D _NormalMap;: 声明纹理采样器。

  • TRANSFORM_TEX(input.uv, _MainTexture);: 应用纹理的 Tiling 和 Offset 属性。

  • tex2D(_MainTexture, input.uv);: 采样主纹理。

  • UnpackNormalmapRGB(tex2D(_NormalMap, input.uv));: 采样并解包法线贴图。法线贴图通常使用 RGB 编码切线空间法线信息,需要解包才能得到正确的法线向量。

  • TransformTangentToWorldNormal(normalTS, half3x3(input.tangentWS, input.bitangentWS, input.normalWS));: 将切线空间法线转换为世界空间法线。这对于后续的光照计算非常重要 (虽然在这个 Unlit 示例中没有用到,但在 Lit Shader Graph 中会用到)。

5.5.5 Shader Graph 的优化与最佳实践

为了创建高性能的 Shader Graph 着色器,需要遵循一些优化和最佳实践:

  • 减少不必要的节点: 避免使用冗余的节点和计算,精简 Shader Graph 结构。

  • 使用简单的数学运算: 复杂的数学运算会增加 GPU 的计算负担。尽量使用简单的数学运算,例如加减乘除,避免使用昂贵的三角函数、指数函数等。

  • 优化纹理采样: 纹理采样是着色器中常见的性能瓶颈。

    • Mipmap: 启用纹理的 Mipmap 功能,可以减少远处物体的纹理采样次数,提高性能。

    • 纹理压缩: 使用合适的纹理压缩格式,例如 DXT (Windows)、ETC (Android)、ASTC (iOS/Android),可以减小纹理大小,降低内存带宽和加载时间。

    • 避免过度采样: 尽量减少纹理采样次数,可以通过预计算、缓存等方式减少采样。

  • 合理使用关键字 (Keywords): 关键字可以控制着色器的编译变体,但过多的关键字会导致着色器变体数量爆炸,增加编译时间和包体大小。合理规划和使用关键字,只在必要时使用。

  • 使用 Subgraph (子图): 将复杂的 Shader Graph 逻辑封装成 Subgraph,可以提高 Shader Graph 的可读性和可维护性,并实现代码复用。

  • 性能测试与分析: 使用 Unity 的 Profiler 工具对 Shader Graph 着色器进行性能测试和分析,找出性能瓶颈,并进行针对性优化。

  • 平台适配: 不同平台的 GPU 性能和特性有所不同。针对目标平台进行 Shader Graph 优化,例如在移动平台可以适当降低着色器复杂度,使用低精度数据类型 (half)。

5.5.6 总结与展望

Shader Graph 作为一个强大的可视化着色器编辑器,极大地降低了着色器开发的门槛,让更多人能够参与到视觉效果的创作中。通过本章节的学习,我们了解了 Shader Graph 的基本概念、节点类型、创建流程,并进行了简单的代码实践。

总结 Shader Graph 的关键点:

  • 可视化编程: 无需代码,图形化界面操作。

  • 节点式构建: 通过连接节点构建着色器逻辑。

  • 实时预览: 快速迭代和调整。

  • 丰富的节点库: 满足各种着色器需求。

  • 强大的扩展性: 支持自定义节点和子图。

展望未来:

Shader Graph 仍在不断发展和完善,未来将会有更多强大的功能和节点加入,例如:

  • 更高级的光照模型: 例如基于物理的渲染 (PBR) 光照模型、全局光照 (Global Illumination) 等。

  • 更强大的视觉特效节点: 例如粒子效果、体积渲染、光线追踪等。

  • 更智能的优化工具: 自动优化 Shader Graph 性能。

  • 更便捷的资源共享平台: 方便用户分享和使用自定义 Shader Graph 资源。

Shader Graph 的出现,标志着着色器开发进入了一个新的时代,它将继续推动游戏和图形技术的创新发展,为我们带来更加绚丽多彩的视觉体验。希望本章节的内容能够帮助你入门 Shader Graph,开启你的可视化着色器编程之旅!


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U