Unity,SurfaceShader

介绍

  1. Surface Shader和CG其他部分一样,代码也是要写在CGPROGRAM和ENDCG之间。但区别是,它必须写在SubShader内部,而不能写在Pass内部。Surface Shader自己会自动生成所需的各个Pass。由上面的编译格式可以看出,surfaceFunction和lightModel是必须指定的。
  2. surfaceFunction通常就是名为surf的函数(函数名可以任意),它的函数格式是固定的:
  3. void surf (Input IN, inout SurfaceOutput o)
  4. void surf (Input IN, inout SurfaceOutputStandard o)
  5. void surf (Input IN, inout SurfaceOutputStandardSpecular o)
  6. lightModel也是必须指定的。由于Unity内置了一些光照函数——Lambert(diffuse)和Blinn-Phong(specular),因此这里在默认情况下会使用内置的Lambert模型。当然我们也可以自定义,给出的Blinn-Phong同时也计算了Lambert
  7. optionalparams包含了很多可用的指令类型,包括开启、关闭一些状态,设置生成的Pass类型,指定可选函数等。除了上述的surfaceFuntion和lightModel,我们还可以自定义两种函数:vertex:VertexFunction和finalcolor:ColorFunction。也就是说,Surface Shader允许我们自定义四种函数。

最重要的定义

1
2
3
4
#pragma surface surfaceFunction lightModel [optionalparams]
// Surface 关键函数 光照模型 操作参数,当前的意义是打开前向渲染的所有阴影
pragma surface surf Standard fullforwardshadows
//光照模型一般以Light开头

流程

image-20241108110459784

SurfaceShader编译成为基本Shader形式后的重要内容解释

1
2
#include "UnityShaderVariables.cginc"
#include "UnityShaderUtilities.cginc"

上面的代码用来引入

image-20241108111716064

建议将该内容创建在和Shader同级的目录

功能是当Shader第一次渲染后保存编译结果,之后就直接调用该文件中的内容,节省开销

输入结构体

默认使用的是在UnityCG.cging中的 appdata_full,结构体

计算函数

  1. 最重要的

    void surf (Input IN, inout SurfaceOutput o)

    void surf (Input IN, inout SurfaceOutputStandard o)

    void surf (Input IN, inout SurfaceOutputStandardSpecular o)

  2. 顶点修改

    void vert (inout appdata_full v)

    void vert(inout appdata_full v, out Input o)

  3. unity老版本(新版本兼容)不包含GI的

    half4 Lighting (SurfaceOutput s, half3 lightDir, half atten)

    half4 Lighting (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)

  4. unity新版本(包含GI,要自定义GI函数)

    half4 Lighting (SurfaceOutput s, UnityGI gi)

    half4 Lighting (SurfaceOutput s, half3 viewDir, UnityGI gi)

    新老版本中的函数的第一个参数需要和Surf函数中的第二个参数对应

  5. /延迟渲染

    half4 Lighting_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal)

  6. 遗留的延迟渲染

    half4 Lighting_PrePass (SurfaceOutput s, half4 light)

  7. 自定义GI

    half4 Lighting_GI (SurfaceOutput s, UnityGIInput data, inout UnityGI gi);

  8. //最终颜色修改

    void final(Input IN, SurfaceOutput o, inout fixed4 color)

    void final(Input IN, SurfaceOutputStandard o, inout fixed4 color)

    void final(Input IN, SurfaceOutputStandardSpecular o, inout fixed4 color)

插入函数

光照模型

image-20241108215808520

顶点和最终颜色修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma surface surf Mao fullforwardshadows vertex:vert finalcolor:final //重要定义中的最后两个就是设定两个函数


。。。。。。

void vert(inout appdata_full v)
{

}

void final(Input IN, SurfaceOutputStandard o, inout fixed4 color)
{

}

插入位置

以新建的SurfaceShader为标准,顶点修改和最终颜色插入到它下面,自定义光照模型在它上面

1
2
3
4
5
6
7
8
9

//大致上要将顶点修改和最终颜色修改加入到这个位置

// 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)

Surface结构体限制

两个结构体就是指struct Input和SurfaceOutput。其中Input结构体是允许我们自定义的。如下表。这些变量只有在真正使用的时候才会被计算生成。在一个贴图变量之前加上uv两个字母,就代表提取它的uv值,例如uv_MainTex 。

Input

变量 描述
float3 viewDir 视角方向 (view direction)。可用于计算边缘光照等。
float4 with COLOR semantic 每个顶点插值后的颜色
float4 screenPos 屏幕空间中的位置。 为了反射效果,需要包含屏幕空间中的位置信息。
float3 worldPos 世界空间中的位置。
float3 worldRefl 世界空间中的反射向量。 如果surface shader没有赋值o.Normal,将会包含世界反射向量。
float3 worldNormal 世界空间中的法线向量。如果surface shader没有赋值o.Normal,将会包含世界法向量
float3 worldRefl; INTERNAL_DATA 世界空间中的反射向量。如果surface shader没有赋值o.Normal,将会包含这个参数。为了获得逐像素法线贴图的反射向量,请使用WorldReflectionVector (IN, o.Normal)。
float3 worldNormal; INTERNAL_DATA 世界空间中的法线向量。如果surface shader没有赋值o.Normal,将会包含世界法向量。为了获得逐像素法线贴图的法向量,请使用WorldNormalVector (IN, o.Normal),获得法线贴图的世界坐标向量

SuffaceSahder

另一个结构体是(SurfaceOutput、SurfaceOutputStandard和SurfaceOutputStandardSpecular)。我们也可以自定义这个结构体内的变量,自定义最少需要有4个成员变量:Albedo、Normal、Emission和Alpha,缺少一个都会报错。关于它最难理解的也就是每个变量的具体含义以及工作机制(对像素颜色的影响)。

SurfaceOutput的示例

1
2
3
4
5
6
7
8
struct SurfaceOutput {
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
half Specular;
fixed Gloss;
fixed Alpha;
};
  • Albedo:我们通常理解的对光源的反射率。它是通过在Fragment Shader中计算颜色叠加时,和一些变量(如vertex lights)相乘后,叠加到最后的颜色上的。(漫反射颜色)

  • Normal:即其对应的法线方向。只要是受法线影响的计算都会受到影响。

  • Emission:自发光。会在Fragment 最后输出前(调用final函数前,如果定义了的话),使用下面的语句进行简单的颜色叠加:

    c.rgb += o.Emission;

  • Specular:高光反射中的指数部分的系数。影响一些高光反射的计算。按目前的理解,也就是在光照模型里会使用到(如果你没有在光照函数等函数——包括Unity内置的光照函数,中使用它,这个变量就算设置了也没用)。有时候,你只在surf函数里设置了它,但也会影响最后的结果。这是因为,你可能使用了Unity内置的光照模型,如BlinnPhong,它会使用如下语句计算高光反射的强度(在Lighting.cginc里):

    float spec = pow (nh, s.Specular*128.0) * s.Gloss;

  • Gloss:高光反射中的强度系数。和上面的Specular类似,一般在光照模型里使用。

  • Alpha:通常理解的透明通道。在Fragment Shader中会直接使用下列方式赋值(如果开启了透明通道的话):c.a = o.Alpha;

操作符

透明度混合与透明度测试

alpha or alpha:auto 为简单光照选择褪色透明度(等同于alpha:fade) ,以及基于物理照明的预乘透明度(等同于alpha:premul)

alpha:blend 开启透明度混合

alpha:fade 开启传统渐变透明

alpha:premul 开启预乘a透明度

alphatest:VariableName 根据VariableName的变量来控制透明度混合和透明度测试,VariableName是一个float型的变量,剔除不满足条件的片元,此时往往需要用到addshadow来生成正确阴影投射的Pass

keepalpha 默认不透明表面着色器将1写入A通道,不管alpha输出值以及光照函数的返回值

decal:add 对其他表面上的物体使用additive blending

decal:blend 对其他表面上的物体使用alpha blending

自定义修改函数

vertex:VertexFunction 顶点修改函数,用于修改计算顶点位置、信息等

finalcolor:ColorFunction 最终颜色修改函数

finalgbuffer:ColorFunction 自定义延迟路径,用于更改gbuffer

finalprepass:ColorFunction 自定义预处理路径

阴影

addshadow 生成一个阴影投射的Pass,为一些使用了顶点动画、透明度测试的物体产生正确的阴影

fullforwardshadows 支持前向渲染路径中所有光源类型的阴影,shader默认只支持最重要平行光的阴影,添加该参数可以支持点光源或聚光灯的阴影效果

tessellate:TessFunction 使用DX11 GPU曲面细分

控制代码生成(表面着色器默认处理所有坑能的光照、阴影、光照烘培,可手动调整跳过一些不必要的加载提升性能)

exclude_path:deferred, exclude_path:forward, exclude_path:prepass 不为某个渲染路径生成代码

noshadow 禁用阴影

noambient 不应用任何环境光以及光照探针

novertexlights 在前向渲染路径中不应用任何逐顶点光照及光照探针

nolightmap 不应用任何光照烘培

nodynlightmap 不应用实时GI

nodirlightmap 不应用directional lightmaps

nofog 不应用任何雾效

nometa 生成meta这个Pass(that’s used by lightmapping & dynamic global illumination to extract surface information)

noforwardadd 不应用前向渲染中所有的additive pass,使得shader只支持一个重要平行光,其他光用逐顶点/SH光源计算光照影响,使shader更精简

nolppv 不应用光照探针代理Light Probe Proxy Volume(LPPV)

noshadowmask 不应用Shadowmask

其他

softvegetation 只有当Soft Vegetation(软植被)开启时该shader才被渲染

interpolateview 在顶点而不是片元着色器中计算 view direction并插值,需多使用一张纹理插值器,提升渲染速度

halfasview Pass half-direction vector into the lighting function instead of view-direction. Half-direction will be computed and normalized per vertex. This is faster, but not entirely correct.

dualforward 在前向渲染中使用dual lightmaps

dithercrossfade 使表面着色器支持 dithering effects

实现

BlinnPhong

流程上使用了官方实现的简单光照模型,同时在Properties中的属性要对应,就比如高光反射的颜色就需要命名为 _SpecColor,同时还不需要自己在CGPROGRAM中定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Shader "Custom/Surface01"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}

//surfaceShader中官方给出的BlinnPhong光照中的高光颜色名
_SpecColor ("SpecColor", Color) = (1,1,1,1)
_Gloss ("Gloss", Range(0,1)) = 0.5
_Specular ("Specular", 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 BlinnPhong fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0

//surfaceShader中不需要声明贴图的ST
sampler2D _MainTex;

struct Input
{
float2 uv_MainTex;
};

half _Gloss;
half _Specular;
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 SurfaceOutput 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.Specular = _Specular;
o.Gloss = _Gloss;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

法线贴图采样

唯一的代码只需要在Surf函数中输入以下代码就行

Normal在使用法线贴图的时候就代表贴图法线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

//法线贴图
float3 normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap));
normal.xy *= _BumpScale;
o.Normal = normal;
o.Alpha = c.a;
}

最终颜色控制

Properties控制

1
_FinalColor("FinalColor",Color) = (1,1,1,1)

关键 pragma设置

1
#pragma surface surf Standard fullforwardshadows finalcolor:final

函数

1
2
3
4
void final(Input IN, SurfaceOutputStandard o, inout fixed4 color)
{
color *= _FinalColor;
}

image-20241108230530783

边缘光

Properties中设置

Range中的最小值取一个0.0001,一是为了作为被除数 的时候算式有效,二是让最小值尽可能大

1
_RimPower("RimPower",Range(0.0001,8)) = 1 

Input结构体

注意拼写

1
2
3
4
5
6
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap; //输入结构体uv贴图要注意的格式
float3 viewDir; //因为需要用到视角坐标,这里需要添加,但是拼写必须和这个一样
};

属性定义

给_RimPower一个float,配合Properties中的 0.0001

1
2
fixed4 _RimColor;
float _RimPower;

Surf方法

1
2
half rim = 1.0 - saturate(dot(normalize(IN.viewDir),o.Normal));
o.Emission = _RimColor * pow(rim,1/_RimPower); //使用的自发光通道

卡通效果

Properties定义

1
2
_Step("Steps",Range(1,30)) = 5 
_ToonEffect("ToonEffect",Range(0,1)) =0.5

重要的pragrma

1
#pragma surface surf Toon fullforwardshadows nolightmap finalcolor:final

自定义光照函数

1
2
3
4
5
6
7
8
9
half4 LightingToon (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
float diffuseLight = dot(lightDir,s.Normal) * 0.5 + 0.5;
diffuseLight = smoothstep(0,1,diffuseLight);
float toon = floor(diffuseLight * _Step) / _Step;
diffuseLight = lerp(diffuseLight,toon,_ToonEffect);
fixed3 diffuse = _LightColor0 * s.Albedo * diffuseLight;
return half4(diffuse,1);
}

描边

Properties设置

1
2
_OutLine("OutLine",Range(0,1)) = 0.5
_OutLineColor("OutLineColor",Color) = (1,1,1,1)

Pass通道

一整个直接在SurfaceSahder 的CGPROGRAM上面直接新开一个Pass通道就行

这里使用的是裁剪空间下的法线外拓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200

Pass
{
Name "OutLine"
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

fixed4 _OutLineColor;
float _OutLine;

struct v2f
{
float4 pos : SV_POSITION;
};

v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));
float2 viewNormal = TransformViewToProjection(normal.xy);
o.pos.xy += viewNormal * _OutLine;
return o;
}

float4 frag(v2f i):SV_Target
{
return _OutLineColor;
}

ENDCG
}

CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Toon fullforwardshadows nolightmap finalcolor:final

XRay

在Properties中的设置很常规,是边缘光的翻版

在SurfaceShader中直接加入的Pass通道如下

注意深度测试和blend的参数设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Pass
{
Name "XRay"
Blend SrcAlpha One
ZWrite Off
ZTest Greater
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

fixed4 _XRayColor;
float _XRayPower;

struct v2f
{
float4 pos : SV_POSITION;
float3 viewDir : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
};

v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.viewDir = UnityWorldSpaceViewDir(v.vertex);
return o;
}

float4 frag(v2f i) : SV_Target
{
float3 viewDir = normalize(i.viewDir);
float3 worldNormal = normalize(i.worldNormal);
float rim = 1.0 - saturate(dot(viewDir,worldNormal));
return _XRayColor * pow(rim,1/_XRayPower);
}
ENDCG
}

熔岩流动

基于uv实现完整的熔岩流动效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Shader "Custom/1607"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Smoothness ("Smoothness", Range(0,1)) = 0.5
_Normal("Normal",2D) = "bump"{}
_Mask("Mask",2D) = "white"{}
_Specular("Specular",2D) = "white"{}
_Fire("Fire",2D) = "white"{}
_FirePower("FirePower",Range(0,200)) = 1
_FireSpeed("FireSpeed",Vector) = (0,0,0,0)
_FireLightChangeSpeed("FireChangeSpeed",Range(0,5)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue" = "Geometry"}
LOD 200

CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf StandardSpecular fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0

sampler2D _MainTex;
sampler2D _Normal;
sampler2D _Mask;
sampler2D _Specular;
sampler2D _Fire;

struct Input
{
float2 uv_MainTex;
};

half _Smoothness;
fixed4 _Color;
half2 _FireSpeed;
half _FirePower;
float _FireLightChangeSpeed;

// 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 SurfaceOutputStandardSpecular 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.Smoothness = _Smoothness;
o.Normal = UnpackNormal(tex2D(_Normal,IN.uv_MainTex));
//现在使用的x的速度是最慢的,不过自己加上一个系数进行控制就没什么影响
float2 uvFlow = IN.uv_MainTex * _Time.x * _FireSpeed;

//遮罩颜色 和 火焰贴图颜色 和 变化强度
o.Emission = (tex2D(_Mask,IN.uv_MainTex) * tex2D(_Fire,uvFlow) * (_FirePower *(_SinTime.w + _FireLightChangeSpeed))).rgb;
o.Specular = tex2D(_Specular,IN.uv_MainTex).rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
image-20241109170052173

补充

遮罩可以控制区域的显示和不显示

遮罩的制作

1.将mask贴图的rgb通道都进行一个采样,然后和现有的贴图进行相乘,颜色一样就会变得更亮,黑色相乘就是0,最后不

2.将mask贴图的某一个通道进行单独的采样

基于法线贴图扭曲UV

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Shader "Custom/1608"
{
Properties
{
[HDR]_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_DistortTex("DistortTex",2D)= "bump"{}
_Speed("Speed",Float) = 1
_UVDisPower("UVDisPower",Range(0,1)) = 0
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue" = "Geometry"}
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;
sampler2D _DistortTex;

struct Input
{
float2 uv_MainTex;
};


half _Glossiness;
half _Metallic;
fixed4 _Color;
half _Speed;
half _UVDisPower;

// 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)
{
float2 uv1 = IN.uv_MainTex + _Time.x * _Speed * float2(-1,-1);
float2 uv2 = IN.uv_MainTex + _Time.x * _Speed * float2(1,1);
//withScale结尾的函数可以对法线强度进行干预
float3 distort = UnpackNormalWithScale(tex2D(_DistortTex,IN.uv_MainTex),_UVDisPower);

//对主贴图进行两个采样
float4 mainTex1 = tex2D(_MainTex,(float3(uv1,0) + distort).xy);
float4 mainTex2 = tex2D(_MainTex,(float3(uv2,0) + distort).xy);
float4 color = _Color * mainTex1 * mainTex2;

o.Albedo = color;
o.Emission = color;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = 1;
}
ENDCG
}
FallBack "Diffuse"
}
image-20241109173459564

glow效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
Shader "Custom/1609"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_MainMix("MainMix",Range(0,1)) = 0.3
_Normal("Normal",2D) = "bump"{}
//此贴图是常规贴图,a通道另有它用
_BurnNormal("BurnNormal",2D) = "white"{}
_NormalTill("NormalTill",Range(0,5)) = 1
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Mask("Mask",2D) = "white"{}
_BurnTill("BurnTill",Float) = 1
_BurnOffset("BurnOffset",Float) = 1
_BurnRange("BurnRange",Range(0,1)) = 0.5
_BurnColor("BurnColor",Color) = (1,1,1,1)


_GlowColor("GlowColor",Color) = (1,1,1,1)
_GlowPower("GlowPower",Range(0,2)) = 0.5
_GlowFrequency("GlowFrequency",Range(0,5)) = 0.4
_GlowOver("GlowOver",Range(0,2)) = 0.2
_Glow("Glow",Range(0,2)) = 0.2
}
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;

sampler2D _Normal;
sampler2D _BurnNormal;
sampler2D _Mask;
half _NormalTill;
half _BurnTill;
half _BurnOffset;
half _BurnRange;
fixed4 _BurnColor;
half _MainMix;

//glow相关
fixed4 _GlowColor;
half _GlowPower;
half _GlowFrequency;
half _GlowOver;
half _Glow;

// 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)
{
//这新添加的代码实际上就是将一个法线贴图通过mask和另一张法线贴图限制在一起
float3 normal1 = UnpackNormal(tex2D(_Normal,IN.uv_MainTex));

//直接对贴图进行法线采样,需要贴图本身的设置就是Normal map
//没有设置的情况下是取用贴图的rg通道进行操作
//
fixed4 burnTex = tex2D(_BurnNormal,IN.uv_MainTex * _NormalTill);
fixed4 burnNormal = fixed4(1,burnTex.g,0,burnTex.r);
float3 normal2 = UnpackNormal(burnNormal);
float2 maskUV = IN.uv_MainTex * _BurnTill + _BurnOffset * float2(1,1);
fixed3 maskColor = tex2D(_Mask,maskUV);
float maskR = _BurnRange + maskColor.r;
o.Normal= lerp(normal1,normal2,maskR);

// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
fixed4 diffuse = lerp(c * _MainMix,_BurnColor,maskR);

//在时间扰动中添加burnTex的a值作为变量,图片各个点d不同的情况下,发光点的观感随机
fixed4 glow = _GlowColor * _GlowPower * (0.5 * sin(_Time.y * _GlowFrequency) + _GlowOver * burnTex.a);
//burnTex.a和maskColor.r是两个遮罩
//burn是控制燃烧的部分中需要发光的部分
//maskColor是控制那些区域是燃烧的
o.Emission = glow * burnTex.a * maskColor.r * _Glow;
o.Albedo = diffuse.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

image-20241111125009922

法线外扩变换

在SurfaceShader中加入对顶点的调整

唯一要特别注意的是在DX环境下对vert的初始化处理

最后的公式记忆就行

1
2
3
4
5
6
7
void vertFun (inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);//在DX环境下需要注意初始化
float3 normal = v.normal.xyz;
float3 vertexPos = v.vertex.xyz;
v.vertex.xyz += normal * max(sin((vertexPos.y + _Time.x) * _ExtrusionFrency) / _ExtrusionSwing ,0);
}

简单模糊

均值模糊,高斯模糊

Properties额外定义

1
2
3
      //加上该控制标签,并且将Range的范围限定为0,1,最终的结果就是一个bool
[Toggle]_ToggleBulr("ToggleBulr", Range(0,1)) = 0
_BlurSize("Bulr Size", Range(0,0.1)) = 0

surf函数

1
2
3
4
5
6
7
8
9
10
         //多次采样并按照给定的比例进行相加,进行一个模糊处理
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
fixed4 offset1 = tex2D (_MainTex, IN.uv_MainTex + float2(0, _BlurSize));
fixed4 offset2 = tex2D (_MainTex, IN.uv_MainTex + float2(_BlurSize, 0));
fixed4 offset3 = tex2D (_MainTex, IN.uv_MainTex + float2(_BlurSize, _BlurSize));
fixed4 offsetColor = c*0.4 + offset1*0.2 + offset2*0.2 + offset3*0.2;

//step函数的作用,第一个参数是0.5,第二个值大于0.5 Return 1,小于0,5,Return 0
fixed4 color = lerp(c,offsetColor,step(0.5,_ToggleBulr));
o.Albedo = color.rgb;
image-20241111151441855

溶解效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Shader "Custom/1612"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Normal("Normal", 2D) = "bump" {}
_NormalScale("NormalScale", Range(0,5)) = 1
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_DisolveTex("DisolveTex",2D) = "white"{}
_Threshold("Threshold",Range(0,1)) = 0
_EdgeLength("EdgeLength", Range(0.0001,0.2)) = 0.1
_BurnTex("BurnTex", 2D) = "white"{}
_BurnInstensity("BurnInstensity", Range(0,5)) = 1
}
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;
sampler2D _Normal;
half _NormalScale;
sampler2D _DisolveTex;
half _Threshold;
sampler2D _BurnTex;
half _EdgeLength;
half _BurnInstensity;

struct Input
{
float2 uv_MainTex;
float2 uv_Normal;
float2 uv_DisolveTex;
};

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.Normal = UnpackScaleNormal(tex2D(_Normal, IN.uv_Normal),_NormalScale);
o.Albedo = c.rgb;

//标准不合适的进行裁剪
float cutout = tex2D(_DisolveTex, IN.uv_DisolveTex).r;
clip(cutout - _Threshold);

//因为裁剪值小于0的已经能被舍弃,而给出的噪声图中的r值是连续变化的
//所以temp的应该是从边缘开始到未裁剪中心逐渐变大
//使用 除法 / 宽度是为了在Unity操作窗口中比较只直观的看到宽度变化
//saturate在这里的作用是为了将大于1 的值给限制成1
float temp = saturate((cutout - _Threshold) / _EdgeLength);

//使用向未裁剪中心逐步变大的temp作为燃烧图的uv
//实际效果是从左下角到右上角进行颜色采样,表现出渐变的效果
fixed4 edgeColor = tex2D(_BurnTex,float2(temp,temp));

//使用lerp对燃烧颜色渐变处理,超过1就a通道设置为0
fixed4 finalColor = _BurnInstensity * lerp(edgeColor, fixed4(0,0,0,0), temp);
o.Emission = finalColor;

// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
image-20241111162614375

区域过度

Properties设置

1
2
3
4
_Dis("Dis",Range(0.1,10)) = 1
_StartPoint("StratPoint",Range(-10,10)) = 1
_Tex2("Tex2",2D) = "white" {}
_UnderInfluence("UnderInfluence",Range(0,1)) = 0

Surface函数

唯一需要注意是startPoint的计算是从世界坐标原点开始的

clamp(a,b,c),将a限制在b和c之间

lerp就是渐变

/Dis,还是为了在Unity操作窗中直观的看到渐变效果

在lerp中,dis设置的很小就会让temp的值很接近1,clampTemp=1的话就会是C的颜色,因此dis越小,渐变越短

1
2
3
4
5
6
7
8
9
10
11
12
13
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
float temp = saturate((IN.worldPos.y + _StartPoint)/_Dis);
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
float clampTemp = clamp(temp, _UnderInfluence,1);
fixed4 color = lerp(tex2D(_Tex2,IN.uv_Tex2),c,clampTemp);
o.Albedo = color.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
image-20241111172122698

AlphaTEST

Propertie中添加内容

常规的透明度测试中用到的透明度

1
_Cutoff("Cutoff",Range(0,1)) = 0.5

subshader中内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "AlphaTest"}

LOD 200
Cull off //关闭一面渲染


CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
//启动alphatest并禁止接受阴影的,但是在alpha的前提下投射阴影
//noshadow 禁止接受投影
//addShadow 在透明度测试的前提下投射阴影
#pragma surface surf Standard alphatest:_Cutoff noshadow addshadow
image-20241111180116963

AlphaBlend

Tags中的type和队列改变成为对应的就不用提了

背面也能看见就需要Cull Off

添加宏

最后还要将FallBack就取消掉,默认是Diffuse,会产生阴影

一般花花草草用的是透明度测试,粒子特效才用AlphaBlend

1
2
3
4
5
6
7
8
9
10
11
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
LOD 200
Cull Off


CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
//透明度混合宏,干掉阴影,加上混合,关闭接受阴影
#pragma surface surf Standard alpha:blend noshadow
image-20241111181327631

雪效果

两种实现,使用模型的世界法线,使用法线贴图的世界法线

image-20241112102147702

在这两个方法出出现的最重要方法是 WorldNormalVector,实际上该方法进行了下面的操作

求得法线的旋转矩阵,具体操作见Unity —— Shader入门,法线贴图 | mao的博客 (mao1mao2mao3mao4.github.io)的4.3小节

1
2
3
o.tSpace0 = float3(worldTangent.x, worldBinormal.x, worldNormal.x);
o.tSpace1 = float3(worldTangent.y, worldBinormal.y, worldNormal.y);
o.tSpace2 = float3(worldTangent.z, worldBinormal.z, worldNormal.z);

在surfshader中,虽然 Input结构体中可以自定义worldNormal,但是在实际使用中,只有在o.Normal没有被赋值的情况下才能正常使用,否则都需要在Input结构体的worldNormal下面添加以下定义

1
2
3
4
5
6
7
8
9
struct Input
{
float2 uv_MainTex;
float2 uv_NormalTex;
float2 uv_SonwTex;
float2 uv_SnowNormal;
float3 worldNormal;
INTERNAL_DATA //新加定义
};

这里简单介绍两种用法

  1. 传递法线贴图中提取的法线并传入,返回该法线贴图的法线在世界空间下的法线
  2. 传递float3(0,0,1),因为法线渲染矩阵的组成,b通道就代表模型在世界坐标下的法线,因此返回模型在世界坐标下的法线

模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
Shader "Custom/1617"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
_MainTex("Albedo (RGB)", 2D) = "white" {}
_SonwTex("SnowTex",2D) = "white"{}
_NormalTex("Noraml", 2D) = "bump"{}
_SnowNormal("SnowNoraml",2D) = "bump"{}
_SnowDir("SnowDir",Vector) = (0,1,0)
_SnowAmount("_SnowAmount", Range(0,2)) = 1
}

SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 200

CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf StandardSpecular fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0

sampler2D _MainTex;
sampler2D _NormalTex;
sampler2D _SonwTex;
sampler2D _SnowNormal;

struct Input
{
float2 uv_MainTex;
float2 uv_NormalTex;
float2 uv_SonwTex;
float2 uv_SnowNormal;
float3 worldNormal;
INTERNAL_DATA
};

half _Glossiness;
half _Metallic;
fixed4 _Color;
float4 _SnowDir;
half _SnowAmount;

// 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 SurfaceOutputStandardSpecular o)
{
float3 normalTex = UnpackNormal(tex2D(_NormalTex, IN.uv_NormalTex));
float3 snowNorTex = UnpackNormal(tex2D(_SnowNormal, IN.uv_SnowNormal));
fixed3 wNormal = WorldNormalVector(IN, float3(0,0,1));
fixed3 finalNormal = lerp(normalTex, snowNorTex, saturate(dot(wNormal, _SnowDir.xyz)) * _SnowAmount);
o.Normal = finalNormal;

fixed3 fWNormal = WorldNormalVector(IN, finalNormal);
float lerpVal = saturate(dot(fWNormal, _SnowDir.xyz));
// Albedo comes from a texture tinted by color
fixed4 c = lerp(tex2D(_MainTex, IN.uv_MainTex), tex2D(_SonwTex, IN.uv_SonwTex), lerpVal * _SnowAmount);
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

法线贴图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Shader "Custom/1616"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_SnowTex("SnowTex",2D) = "white"{}
_NormalTex("Normal",2D)= "bump"{}
_SnowDir("SnowDir",Vector) = (1,1,1)
_SnowAmount("SnowAmount",Range(0,2)) = 1
_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 StandardSpecular fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0

sampler2D _MainTex;
sampler2D _NormalTex;
sampler2D _SnowTex;

struct Input
{
float2 uv_MainTex;
float2 uv_NormalTex;
float2 uv_SnowTex;
float3 worldNormal;
INTERNAL_DATA
};

half _Glossiness;
half _Metallic;
fixed4 _Color;
float4 _SnowDir;
half _SnowAmount;

// 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 SurfaceOutputStandardSpecular o)
{
//将法线贴图中的向量提取出来并转换为世界坐标下
float3 normalTex = UnpackNormal(tex2D(_NormalTex,IN.uv_NormalTex));
o.Normal = normalTex;
//真正的世界法线
fixed3 wNormal = WorldNormalVector(IN,normalTex);

//法线贴图的法线和雪方向的点乘
float lerpVal = saturate(dot(wNormal,_SnowDir.xyz));

// Albedo comes from a texture tinted by color
fixed4 c = lerp( tex2D(_MainTex, IN.uv_MainTex),tex2D(_SnowTex,IN.uv_SnowTex),lerpVal * _SnowAmount) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Specular = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

补充

Surface的可操作性并不是很强

虽然给出了很多操作符号,还是建议用Pass通道搭配Surface使用,两个Surface使用效率不高