10.表面着色器-编译指令
10.1 知识点
知识回顾:表面着色器中的编译指令
表面着色器中最为关键的部分包括编译指令、结构体和自定义函数。
编译指令是表面着色器中用来与 Unity 沟通的重要方式,通过它我们可以告诉 Unity 需要执行哪些操作、忽略哪些部分。
由于表面着色器只需要实现少量代码,大部分功能(如光照、阴影、反射、折射等)均由 Unity 自动生成。
表面着色器中编译指令的基本构成
编译指令的基本格式为:
#pragma surface 表面函数名 光照模型 可选额外参数
它由以下四个部分构成:
- 固定部分:
#pragma surface
- 表面函数名
- 光照模型
- 可选额外参数
例如,一个表面着色器的编译指令示例如下:
surf
—— 表面处理函数名称Lambert
—— 使用兰伯特光照模型
#pragma surface surf Lambert
第一部分:固定写法
在编译指令中,#pragma surface
是固定写法,用于指明该编译指令适用于表面着色器。
在它后面必须填写表面函数和光照模型,并可附加一些可选参数以控制表面着色器的行为。
第二部分:表面函数名
表面函数名可以随意命名,但后续代码中必须存在同名的函数。
表面函数的参数有三种固定格式:
void 表面函数名(Input IN, inout SurfaceOutput o)
void 表面函数名(Input IN, inout SurfaceOutputStandard o)
void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)
其中,Input
为输入结构体,可获取各种表面属性;SurfaceOutput
、SurfaceOutputStandard
和 SurfaceOutputStandardSpecular
是 Unity 内置的输出结构体,分别用于不同的工作流,可以配合不同的光照模型使用。
在函数中利用 Input
结构体中的数据进行计算,然后将结果赋值给输出结构体 o
的各个成员,后续这些数据会自动传递给光照函数进行计算(如果不自定义,Unity 会自动生成计算代码)。
第三部分:光照模型
光照模型用于计算物体表面的光照效果。
Unity 内置了基于物理的光照模型函数,例如 Standard
和 StandardSpecular
,以及非基于物理的简单光照模型如 Lambert
和 BlinnPhong
。
我们可以直接填写这些预定义的光照模型,也可以自定义光照模型计算,只需按照以下格式书写函数即可:
- 不依赖视角的光照模型(如漫反射):
half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half atten)
- 依赖视角的光照模型(如高光反射):
half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
只需在编译指令中的光照模型位置填写自定义的函数名称,Unity 将自动调用该函数中的逻辑来处理光照计算。
下面是一段示例 Shader 代码:
Shader "Custom/Lesson10_Shader"
{
// 材质属性面板参数
Properties
{
//...
}
SubShader
{
//...
CGPROGRAM
//...
// 自定义光照模型编译指令
#pragma surface surf LightingMyLight
//...
// 自定义光照模型
half4 LightingMyLight(SurfaceOutput s, half3 lightDir, half atten)
{
// 自定义光照计算逻辑
}
ENDCG
}
// 后备着色器(当当前着色器不支持时使用)
FallBack "Diffuse"
}
可选额外参数
可选额外参数包含了许多非常有用的指令,用于控制顶点修改、最终颜色修改、阴影、透明性以及代码生成等。
以下列举几个常用的可选参数:
自定义顶点修改函数
使用格式:vertex:顶点函数名
对应函数格式:void 顶点函数名(inout appdata_full v)
最终颜色修改函数
使用格式:finalcolor:自定义最终颜色修改函数名
对应函数格式:void 自定义最终颜色修改函数名(Input IN, SurfaceOutput o, inout fixed4 color)
阴影相关
addshadow
:用于对进行了顶点动画、透明度测试的物体进行阴影投射,避免 FallBack 无法准确处理fullforwardshadows
:在前向渲染路径中支持所有光源类型和阴影noshadow
:禁用阴影
透明相关
alphatest:变量名
:使用指定名字的变量剔除不满足条件的片元
光照相关
noambient
:不使用任何环境光和光照探针novertexlights
:不使用逐顶点光照noforwardadd
:去掉所有前向渲染中的额外 pass,只支持一个逐像素平行光,其余光源按逐顶点计算nofog
:关闭雾效处理nolightmap
:关闭光照烘焙处理
控制代码生成
默认情况下,表面着色器自动生成的代码包含前向渲染和延迟渲染路径的 Pass,可能使 Shader 文件较大。
如果确定只在某些渲染路径中使用,可以使用以下指令排除不需要的路径,来明确告诉Unity,不需要为某些渲染路径生成代码:exclude_path:deferred
(排除延迟渲染路径)exclude_path:forward
(排除前向渲染路径)exclude_path:prepass
(排除预通道渲染路径)
总结
表面着色器中的编译指令用于定义着色器的基本行为、配置光照模型并优化渲染流程。
#pragma surface
:固定写法,表明这是用于表面着色器的编译指令。- 表面函数名:指定一个用于计算物体表面颜色、法线、反射等特性的函数,Unity 会自动生成对应的顶点着色器和片段着色器代码,并结合指定的光照模型进行渲染。
- 光照模型:指定表面着色器使用的光照计算模型,既可以选择 Unity 提供的预定义模型,也可以自定义。
- 可选额外参数:允许传递额外的编译选项,用于自定义顶点计算、最终颜色计算、阴影、透明测试、混合以及控制代码生成等。
通过这些指令,我们能够精准地控制表面着色器的行为,优化渲染流程,并实现更复杂的光照和视觉效果。
10.2 知识点代码
Lesson10_Shader.shader
Shader "Custom/Lesson10_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 surface surf LightingMyLight
#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;
}
// 自定义光照模型
half4 LightingMyLight(SurfaceOutput s, half3 lightDir, half atten)
{
}
ENDCG
}
// 后备着色器(当当前着色器不支持时使用)
FallBack "Diffuse"
}
Lesson10_表面着色器_编译指令.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson10_表面着色器_编译指令 : MonoBehaviour
{
void Start()
{
#region 知识回顾 表面着色器中的 编译指令
//表面着色器中
//最为关键的几个部分为:
//1.编译指令
//2.结构体
//3.自定义函数
//编译指令是表面着色器中用来和Unity沟通的重要方式
//通过编译指令,我们可以告诉Unity需要让他做什么和不做什么
//因为通过上节课我们知道表面着色器只需要实现少量代码
//大部分代码(比如光照、阴影、反射、折射等)都交给Unity自动生成
#endregion
#region 知识点一 表面着色器中 编译指令 的基本构成
//#pragma surface 表面函数名 光照模型 可选额外参数
//它是又4个部分
//1.固定部分 #pragma surface
//2.表面函数名
//3.光照模型
//4.可选额外参数
//构成的
//例如
// 表面着色器编译指令:
// surf - 表面处理函数名称
// Lambert - 使用兰伯特光照模型
// #pragma surface surf Lambert
#endregion
#region 知识点二 第一部分 固定写法
//#pragma surface 表面函数名 光照模型 可选额外参数
//中
//#pragma surface 是固定写法
//是用于指明该编译指令是用于表面着色器的
//在他后面我们必须填写表面函数和光照模型
//还可以填写一些可选参数来控制表面着色器的行为
#endregion
#region 知识点三 第二部分 表面函数名
//#pragma surface 表面函数名 光照模型 可选额外参数
//中
//表面函数名可以随意取名,但是需要在后面的代码中有对应名字的函数
//函数的参数有三种固定格式
//1. void 表面函数名(Input IN, inout SurfaceOutput o)
//2. void 表面函数名(Input IN, inout SurfaceOutputStandard o)
//3. void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)
//其中 Input 为输入结构体,其中可以得到各种表面属性
//SurfaceOutput、SurfaceOutputStandard 和 SurfaceOutputStandardSpecular
//是Unity内置的写好的用于输出的结构体,他们分别用于不同的工作流,可以配合不同的光照模型使用
//我们之后就可以利用Input结构体中的数据进行计算,计算得到的结构赋值给输出结构体o中的成员
//之后会自动传递给光照函数进行下一步计算(如果我们不自定义,Unity会自动生成计算代码)
#endregion
#region 知识点四 第三部分 光照模型
//#pragma surface 表面函数名 光照模型 可选额外参数
//中
//光照模型是用来计算物体表面的光照效果的
//Unity内置了基于物理的光照模型函数 Standard 和 StandardSpecular
//还有简单的非基于物理的光照模型函数 Lambert 和 BlinnPhong
//我们可以直接填写他们来应用对应的光照计算
//同样我们也可以自定义光照模型计算相关
//只需要在代码中按照格式书写以下函数
//不依赖视角的光照模型,比如漫反射
//half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half atten)
//依赖视角的光照模型,比如高光反射
//half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
//然后只需要在光照模型处填写 自定义名字 即可
//就会自动调用函数中的逻辑来处理光照相关逻辑了
#endregion
#region 知识点五 第四部分 可选额外参数
//#pragma surface 表面函数名 光照模型 可选额外参数
//中
//可选额外参数包含了很多非常有用的指令类型
//我们着重提及几个可选参数:
//1.自定义顶点修改函数 vertex:顶点函数名
// void 顶点函数名(inout appdata_full v)
//2.最终颜色修改函数 finalcolor:自定义最终颜色修改函数名
// void 自定义最终颜色修改函数名(Input IN, SurfaceOutput o, inout fixed4 color)
//3.阴影相关
// addshadow 为一些进行了顶点动画、透明度测试的物体显示的进行阴影投射,避免通过FallBack无法准确处理
// fullforwardshadows 在前向渲染路径中支持所有光源类型和阴影
// noshadow 禁用阴影
//4.透明相关
// alphatest:变量名 利用它可以使用指定名字的变量来剔除不满足条件的片元
//5.光照
// noambient 不使用任何环境光和光照探针
// novertexlights 不使用任何逐顶点光照
// noforwardadd 去掉所有前向渲染中的额外pass,只会支持一个逐像素平行光,其他光源按照逐顶点huoSH来计算
// nofog 关闭雾效处理
// nolightmap 关闭光照烘焙处理
//6.控制代码生成
// 默认情况下,表面着色器自动生成的代码包含前向渲染路径、延迟渲染路径使用的Pass
// 这会让Shader文件较大,如果我们明确表面着色器只会在某些渲染路径中使用
// 可以使用
// exclude_path:deferred(排除延迟渲染路径)
// exclude_path:forward(排除前向渲染路径)
// exclude_path:prepass(排除预通道渲染路径)
// 来明确告诉Unity,不需要为某些渲染路径生成代码
#endregion
#region 总结
//表面着色器中的编译指令
//是用于定义着色器的基本行为、配置光照模型,优化渲染流程的
//#pragma surface 表面函数名 光照模型 可选额外参数
//#pragma surface:
//固定写法,表明是表面着色器的编译指令
//表面函数名:
//指定一个表面着色器函数的名称,这个函数负责计算物体表面的颜色、法线、反射等特性。
//Unity会自动生成对应的顶点着色器和片段着色器代码,然后结合指定的光照模型来处理表面渲染。
//光照模型:
//指定表面着色器使用的光照计算模型,Unity提供了几种预定义的光照模型,也可以自定义
//可选额外参数:
//允许你传递一些额外的编译选项
//这些选项可以自定义顶点计算、自定义最终颜色计算、控制是否计算阴影、控制透明测试和混合等等
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com