9.表面着色器-基本原理和结构
9.1 知识点
回顾表面着色器基本原理
我们在之前已经对表面着色器有一定的认识,下面回顾相关知识。
我们一般会使用以下三种形式来编写 Unity Shader:
- 表面着色器(可控性较低)
- 顶点/片元着色器(重点学习)
- 固定函数着色器(基本已弃用,仅供了解)
表面着色器(Surface Shader)是 Unity 自创的一种着色器代码类型,其本质是对顶点/片元着色器的一层封装。
它需要的代码量较少,很多工作都已由系统帮助完成,但缺点是渲染消耗较大、可控性较低。
优点在于它处理了大量光照细节,我们无需自己编写复杂的光照计算即可实现光照、阴影、反射、折射等效果。
在创建 Shader 时,我们可以选择创建 Standard Surface Shader。
观察该 Shader 文件内部结构时会发现,着色器相关代码位于 SubShader 语句块中 CGPROGRAM 和 ENDCG 之间。
表面着色器的主要特点:
- 直接在 SubShader 语句块中书写着色器逻辑
- 无需关注多个 Pass 的渲染细节,Unity 内部自动处理
- 可使用 CG 或 HLSL 编写 Shader 逻辑
- 代码量少,但可控性较低,性能消耗较高
- 适用于需要处理复杂光照效果的着色器(主机、PC 平台较为适用,移动平台需谨慎考虑性能)
总的来说,表面着色器是在顶点/片元着色器基础上的一次封装,简化了光照、阴影、反射、折射等效果的计算流程,Unity 内部会自动完成这些计算。
默认表面着色器
// 自定义着色器,名为 "Custom/Lesson09_Test"
Shader "Custom/Lesson09_Test"
{
// 定义材质属性,这些属性可以在 Unity 的材质面板中进行调整
Properties
{
// 颜色属性,显示名称为 "Color",默认值为白色 (RGBA: 1,1,1,1)
_Color ("Color", Color) = (1,1,1,1)
// 主纹理属性,显示名称为 "Albedo (RGB)",默认纹理为白色
_MainTex ("Albedo (RGB)", 2D) = "white" {}
// 光泽度属性,显示名称为 "Smoothness",取值范围 0 到 1,默认值为 0.5
_Glossiness ("Smoothness", Range(0,1)) = 0.5
// 金属度属性,显示名称为 "Metallic",取值范围 0 到 1,默认值为 0.0
_Metallic ("Metallic", Range(0,1)) = 0.0
}
// 子着色器,包含实际渲染逻辑
SubShader
{
// 标签,指定该着色器用于渲染不透明物体
Tags { "RenderType"="Opaque" }
// 细节层次 (LOD) 值,用于控制不同情况使用的着色器版本
LOD 200
// 开始 CG 代码块
CGPROGRAM
// 使用基于物理的标准光照模型,并开启所有光照类型的阴影计算
#pragma surface surf Standard fullforwardshadows
// 使用 Shader Model 3.0 目标以获得更好的光照效果
#pragma target 3.0
// 定义主纹理采样器
sampler2D _MainTex;
// 定义输入结构体,用于传递顶点数据到表面着色器
struct Input
{
// 主纹理的 UV 坐标
float2 uv_MainTex;
};
// 光泽度变量,类型为 half(16 位浮点数)
half _Glossiness;
// 金属度变量,类型为 half(16 位浮点数)
half _Metallic;
// 颜色变量,类型为 fixed4(32 位定点数)
fixed4 _Color;
// 为该着色器添加实例化支持。使用该 Shader 的材质需要勾选 'Enable Instancing'。
// 更多信息请参阅:https://docs.unity3d.com/Manual/GPUInstancing.html
// #pragma instancing_options assumeuniformscaling
// 开始实例化缓冲区,用于存储每个实例的属性
UNITY_INSTANCING_BUFFER_START(Props)
// 可在此添加更多实例属性
UNITY_INSTANCING_BUFFER_END(Props)
// 表面着色器函数,用于计算表面的光照属性
void surf (Input IN, inout SurfaceOutputStandard o)
{
// 反照率(基础颜色)由纹理采样结果乘以颜色属性得到
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
// 将颜色的 RGB 分量赋值给表面的反照率属性
o.Albedo = c.rgb;
// 金属度直接取自材质属性
o.Metallic = _Metallic;
// 光滑度直接取自材质属性
o.Smoothness = _Glossiness;
// 将颜色的 Alpha 分量赋值给表面 Alpha 属性
o.Alpha = c.a;
}
// 结束 CG 代码块
ENDCG
}
// 如果当前子着色器不被支持,回退到 "Diffuse" 着色器
FallBack "Diffuse"
}
了解表面着色器的结构
通过对比表面着色器与顶点/片元着色器,我们可以发现两者的主要区别在于表面着色器中没有显式的 Pass。
表面着色器的关键部分包括:
- 编译指令
- 结构体
- 自定义函数
编写完表面着色器后,Unity 会自动将其编译为顶点/片元着色器,并自动加入光照处理、反射、折射、阴影、雾效等相关计算代码。
查看示例表面着色器结构
Shader "Custom/Lesson09_Shader"
{
// 材质属性面板参数
Properties
{
_Color ("Color", Color) = (1,1,1,1) // 基础颜色参数
_MainTex ("Albedo (RGB)", 2D) = "white" {} // 主纹理(漫反射颜色)
_BumpMap("BumpMap", 2D) = "white"{} // 法线贴图纹理
_BumpScale("BumpScale", Range(0,1)) = 1 // 法线强度调节参数(0-1 范围)
}
SubShader
{
// 渲染标签设置
Tags
{
"RenderType"="Opaque" // 标记为不透明物体
}
CGPROGRAM
// 表面着色器编译指令:
// surf - 表面处理函数名称
// Lambert - 使用兰伯特光照模型
#pragma surface surf Lambert
#pragma target 3.0 // 使用 Shader Model 3.0 特性
// 声明属性对应的变量
sampler2D _MainTex; // 主纹理采样器
sampler2D _BumpMap; // 法线贴图采样器
float _BumpScale; // 法线强度系数
fixed4 _Color; // 颜色参数
// 输入结构体(自动填充顶点数据)
struct Input
{
// 自动匹配的 UV 坐标(uv_ + 纹理变量名)
float2 uv_MainTex; // 主纹理 UV 坐标
float2 uv_BumpMap; // 法线贴图 UV 坐标
};
// 表面着色器处理函数
void surf(Input IN, inout SurfaceOutput o)
{
// 主纹理采样,获取漫反射颜色和透明度
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb * _Color; // 组合纹理颜色与参数颜色
o.Alpha = tex.a * _Color.a; // 计算最终透明度
/* 法线贴图处理流程 */
// 1. 从法线贴图获取切线空间法线(UnpackNormal 用于解码压缩格式)
float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
// 2. 调整法线强度:通过缩放 XY 分量实现
tangentNormal.xy *= _BumpScale;
// 3. 重新计算 Z 分量,保持法线向量归一化
// 根据单位向量性质:x² + y² + z² = 1
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 4. 将处理后的法线赋值给输出结构
o.Normal = tangentNormal;
}
ENDCG
}
// 后备着色器(当当前着色器不支持时使用)
FallBack "Diffuse"
}
查看效果
从上述示例中可以看到,表面着色器无需编写任何显式的光照逻辑即可接受光照、生成阴影和投射阴影。
这是因为表面着色器对顶点/片元着色器进行了封装,统一处理了光照、阴影、反射、折射等计算逻辑,避免了重复编写。
Unity 会自动将其编译为顶点/片元着色器,并可通过“显示生成的代码”查看 Unity 自动生成的顶点/片元着色器代码。
9.2 知识点代码
Lesson09_Test.shader
Shader "Custom/Lesson09_Test"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Lesson09_Shader.shader
Shader "Custom/Lesson09_Shader"
{
// 材质属性面板参数
Properties
{
_Color ("Color", Color) = (1,1,1,1) // 基础颜色参数
_MainTex ("Albedo (RGB)", 2D) = "white" {} // 主纹理(漫反射颜色)
_BumpMap("BumpMap", 2D) = "white"{} // 法线贴图纹理
_BumpScale("BumpScale", Range(0,1)) = 1 // 法线强度调节参数(0-1范围)
}
SubShader
{
// 渲染标签设置
Tags
{
"RenderType"="Opaque" // 标记为不透明物体
}
CGPROGRAM
// 表面着色器编译指令:
// surf - 表面处理函数名称
// Lambert - 使用兰伯特光照模型
#pragma surface surf Lambert
#pragma target 3.0 // 使用 Shader Model 3.0 特性
// 声明属性对应的变量
sampler2D _MainTex; // 主纹理采样器
sampler2D _BumpMap; // 法线贴图采样器
float _BumpScale; // 法线强度系数
fixed4 _Color; // 颜色参数
// 输入结构体(自动填充的顶点数据)
struct Input
{
// 自动匹配的 UV 坐标(uv_ + 纹理变量名)
float2 uv_MainTex; // 主纹理 UV 坐标
float2 uv_BumpMap; // 法线贴图 UV 坐标
};
// 表面着色器处理函数
void surf(Input IN, inout SurfaceOutput o)
{
// 主纹理采样(获取漫反射颜色和透明度)
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb * _Color; // 组合纹理颜色和参数颜色
o.Alpha = tex.a * _Color.a; // 计算最终透明度
/* 法线贴图处理流程 */
// 1. 从法线贴图获取切线空间法线(UnpackNormal 负责解码压缩格式)
float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
// 2. 调整法线强度:通过缩放 XY 分量实现
tangentNormal.xy *= _BumpScale;
// 3. 重新计算 Z 分量,保持法线向量归一化
// 公式推导:根据单位向量性质 x² + y² + z² = 1
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 4. 将处理后的法线传递给输出结构
o.Normal = tangentNormal;
}
ENDCG
}
// 后备着色器(当当前着色器不支持时使用)
FallBack "Diffuse"
}
Lesson09_表面着色器_基本原理和结构.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson09_表面着色器_基本原理和结构 : MonoBehaviour
{
void Start()
{
#region 知识点一 回顾表面着色器基本原理
//我们在之前已经对表面着色器有一定的认识
//我们先来回顾下相关知识
//我们一般会使用以下3种形式来编写Unity Shader
//1.表面着色器(可控性较低)
//2.顶点/片元着色器(重点学习)
//3.固定函数着色器(基本已弃用,了解即可)
//表面着色器(Surface Shader)是Unity自己创造的一种着色器代码类型
//它的本质是对顶点/片元着色器的一层封装
//它需要的代码量很少,很多工作都帮助我们去完成了
//但是缺点是渲染的消耗较大,可控性较低
//它的优点在于,它帮助我们处理了很多光照细节,我们可以直接使用而无需自己计算实现光照细节
//我们可以在创建Shader时,选择创建Standard Surface Shader
//通过观察该Shader文件的内部结构,你会发现
//着色器相关代码被放在SubShader语句块中(并非Pass)的 CGPROGRAM 和 ENDCG 之间
//表面着色器的特点就是
//1.直接在SubShader语句块中书写着色器逻辑
//2.我们不需要关心也不需要使用多个Pass,每个Pass如何渲染,Unity会在内部帮助我们去处理
//3.可以使用CG或HLSL两种Shader语言去编写Shader逻辑
//4.代码量较少,可控性较低,性能消耗较高
//5.适用于处理需要和各种光源打交道的着色器(主机、PC平台时更适用,移动平台需要考虑性能消耗)
//总的来说:
//表面着色器实际上就是在顶点/片元着色器的基础上进行了一次封装
//在处理光照、阴影、反射、折射等等效果时,简化了繁琐的计算流程
//我们不需要自己去处理这些内容,Unity内部会自动帮助我们进行计算
#endregion
#region 知识点二 了解表面着色器的结构
//我们通过事例对比表面着色器和顶点/片元着色器的区别
//最大区别就是
//表面着色器中没有显示的Pass了
//而其中最为关键的几个部分为:
//1.编译指令
//2.结构体
//3.自定义函数
//当我们编写完表面着色器后,Unity会自动将其编译为顶点/片元着色器
//会将光照处理、反射、折射、阴影、雾效等等相关计算代码自动加入
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com