Unity Shader 入门精要学习记录(一)

1.代码基础

1.代码结构

Shader "Custom/Test" {
	Properties{
	
	}
	SubShader{
		Pass{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			ENDCG
		}
	}
	SubShader{
		...
	}

	FallBack "Diffuse"
}

2.语义

Unity Shader 中使用的语义(Semantics)定义了Shader变量如何与渲染管线中的其他部分相联系。以下是Unity Shader中常见的一些语义:

  1. POSITION - 用于顶点着色器的输出,表示顶点的位置信息。

  2. NORMAL - 表示顶点的法线方向。

  3. TEXCOORDn - 用于纹理坐标,n可以是0, 1, 2, … 等数字,表示不同的纹理坐标集。

  4. COLOR - 用于顶点颜色或者材质的颜色。

  5. SV_Position(System-Value Position)- 在HLSL中用于顶点着色器的输出和像素着色器的输入,表示裁剪空间中的顶点位置。

  6. SV_Target - 在像素着色器的输出中,表示最终渲染到屏幕或者渲染目标的颜色。

  7. SV_InstanceID - 在顶点着色器中,用于获取当前实例的ID,常用于实例渲染。

  8. SV_VertexID - 在顶点着色器中,用于获取当前顶点的ID。

  9. TANGENT - 表示顶点的切线方向,通常用于法线贴图。

  10. BITANGENT - 表示副切线方向,同样用于法线贴图。

  11. BLENDWEIGHT - 用于顶点的骨骼权重,常用于蒙皮动画。

  12. BLENDINDICES - 用于顶点的骨骼索引,同样用于蒙皮动画。

  13. SV_Depth - 在像素着色器的输出中,表示像素的深度值。

  14. SV_ClipDistance - 用于指定裁剪平面,可以用来进行裁剪操作。

这些语义帮助Unity的渲染管线理解Shader变量代表的数据类型和用途,从而正确地将这些变量用于渲染流程中。在编写Shader代码时,正确使用语义是至关重要的。

Unity中,输入输出变量并不需要有特殊的含义,变量本身存储了什么,shader流水线并不关心,但是有些语义有着特殊的含义规定,例如a2f中 TEXCOORD0即把模型的第一组纹理存储在该变量中。但在v2f中,其修饰的变量含义可以有我们自己来定。

SV_XXX代表系统数值。

例子:

struct a2v{//a2v代表应用到顶点
    float4 vertex : POSITION;//用模型空间的顶点来填充vertex变量
    float3 normal : NORMAL;//用模型空间的发现来填充normal变量
    float4 texcoord : TEXCOORD0;//用模型的第一套纹理填充texcoord变量
    
}

Unity会根据这些语义来填充结构体,对于顶点着色器来说,Unity支持的语义有:POSITION,TANGENT,NORMAL,TEXCOORDN,COLOR等

struct v2f{
    float4 pos : SV_POSITION;//告诉Unity,pos里包含了顶点在 裁剪空间中的位置信息
    fixed4 color : COLOR0;//可以用于存储颜色信息
}

3.常用函数及定义

2.光照

UnityObjectToClipPos(float4 vertex):模型坐标转化到裁剪坐标下

UNITY_LIGHTMODEL_AMBIENT:环境关照

UnityObjectToWorldNormal(float3 normal):法线从模型空间下转化到世界空间下

=========================================================================

_WorladSpaceLightPos0:“是 Unity 游戏引擎中的一个内置着色器变量,用于在渲染过程中处理光照。这个变量表示世界空间中的光源位置或方向向量。具体来说:

  • 如果 “_WorldSpaceLightPos0.w” 的值为 0,这表示光源是平行光,此时 “_WorldSpaceLightPos0.xyz” 代表的是平行光的方向向量。
  • 如果 “_WorldSpaceLightPos0.w” 的值不为 0,这通常表示光源是点光源或聚光灯,此时 “_WorldSpaceLightPos0.xyz” 代表的是光源在世界空间中的位置。

--------------------------------------------------------------------------------------------------------------------------------

  1. _WorldSpaceLightPos0:

    • 它是一个内置变量,而不是一个函数。
    • 它存储的是光源在世界空间中的位置(对于点光源和聚光灯)或方向(对于平行光)。
    • 当处理平行光时,_WorldSpaceLightPos0.w 被设置为 0,并且 _WorldSpaceLightPos0.xyz 存储的是光线的方向。
    • 对于点光源和聚光灯,_WorldSpaceLightPos0.w 被设置为 1,并且 _WorldSpaceLightPos0.xyz 存储的是光源的位置。
  2. WorldSpaceLightDir:

    • 这是一个函数,通常用于在顶点着色器或片元着色器中计算从顶点到光源的方向。
    • 它需要一个世界空间中的顶点位置作为参数,并返回从该顶点到光源的方向向量。
    • 这个函数通常用于计算逐顶点的光照,特别是在需要考虑顶点到光源的方向时。

=========================================================================

_LightColor0:第一个平行光找的颜色

reflect(float3 lightDir,float3 normal):返回反射光线,注意这里的反射光线是从光源到顶点,如果是从_WorldSpaceLightPos0获得的话要取反

Shader "Unlit/Chapter5_DiffuseLight"
{
    Properties{
        _Diffuse("DiffuseColor",Color)= (1,1,1,1)
        _Specular("SpecularColor",Color)=(1,1,1,1)    
        _Gloss("Gloss",Range(8,256)) = 20
    }
    SubShader{
        Pass{
            Tags{ "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            struct a2v{
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f{
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                //DIFFUSE
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLight));
                //SPECULAR
                fixed3 reflectDir = reflect(-worldLight,worldNormal);
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_WorldToObject,v.vertex).xyz);
                fixed3 specular = _Specular.rgb * _LightColor0.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
                o.color = diffuse + ambient + specular;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target{
                return fixed4(i.color,1);
            }
            ENDCG
        }

    }
    Fallback "Diffuse"
}

3.纹理

1.单一纹理:

TRANSFORM_TEX(v.texcoord, _MainTex):进行纹理变换,等价于v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw

Shader "Unlit/Single_Texture"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(8,256)) = 20
    }
    SubShader
    {
        Pass{
            Tags{ "LightMode" = "ForwardBase" }
            CGPROGRAM

            
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Specular;
            float _Gloss;

            struct a2v{
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 texcoord : TEXCOORD0;
            };
            struct v2f{
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };
            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }
            fixed4 frag(v2f i) : SV_Target{
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

                fixed3 vieDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(worldLightDir + vieDir);
                fixed3 specular = _LightColor0.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
                return fixed4(ambient + diffuse + specular, 1);
            }
            
            ENDCG

        }
    }
    Fallback "Diffuse"
}

2.法线纹理:

模型法线存储优点:直观,实现简单,纹理坐标边界处更加平滑。

切线空间存储优点:自由度高,可以复用在其他模型上面,可以实现UV动画,可压缩(仅需存储xy方向,因为z轴都为正方向,可由xy方向向量叉乘推导得到z方向)。

TANGENT_SPACE_ROTATION:获得从模型到切线空间下的旋转矩阵

tex2D():对图像进行采样

UnpackNormal():Unity Shader中用于处理法线纹理的一个函数。这个函数主要作用是对法线纹理的采样结果进行反压缩操作。

流程:将光照向量和视线方向转化成切线空间坐标下=》法线采样和映射=》控制凹凸度=》添加光照计算(用切线下法线计算)

Shader "Unlit/TangentSpaceMat"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
        _BumpMap ("Normal Map", 2D) = "bump" {}
        _BumpMapScale("Bump Scale", Float) = 1.0
        _Specular("Specular", Color) = (0.5, 0.5, 0.5, 1)
        _Gloss("Gloss", Range(8, 256)) = 20
    
    }
    SubShader
    {
        Tags { "LightMode" = "ForwardBase" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpMapScale;
            fixed4 _Specular;
            float _Gloss;

            struct a2v{
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
                float4 texcoord:TEXCOORD0;
            };

            struct v2f{
                float4 pos:SV_POSITION;
                float4 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;
                float3 viewDir:TEXCOORD2;
            };
            
            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord.xy, _BumpMap);
                
                //float3 normal = normalize(v.normal);
                //float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w;
                //float3x3 rotation = float3x3(v.tangent.xyz, binormal, normal);

                TANGENT_SPACE_ROTATION;

                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

                return o;
            }

            fixed4 frag(v2f i):SV_Target{
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentViewDir = normalize(i.viewDir);
                
                fixed4 packNormal = tex2D(_BumpMap, i.uv.zw);
                fixed3 tangentNormal;

                tangentNormal = UnpackNormal(packNormal);
                tangentNormal.xy *= _BumpMapScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

为了理解为什么将切线空间中的向量 bump 与矩阵相乘可以得到其在世界空间中的坐标,我们需要回顾一下线性代数中的坐标变换原理。

坐标变换的基本概念

当一个向量从一个坐标系变换到另一个坐标系时,我们使用一个变换矩阵来执行这个操作。这个变换矩阵是由源坐标系的基向量在新坐标系中的表示组成的。

在3D图形学中,切线空间到世界空间的变换涉及以下步骤:

  1. 定义切线空间基向量:在切线空间中,我们有三个互相垂直的基向量,分别是切线(T),副切线(B),和法线(N)。

  2. 表示基向量在世界空间中:这些基向量在世界空间中的表示组成了变换矩阵的列。即:

    • i.TtoW0 是切线(T)在世界空间中的表示。
    • i.TtoW1 是副切线(B)在世界空间中的表示。
    • i.TtoW2 是法线(N)在世界空间中的表示。

变换矩阵的构成

变换矩阵是由上述基向量的世界空间表示作为列向量组成的,如下所示:

[ i.TtoW0.x i.TtoW1.x i.TtoW2.x ]
[ i.TtoW0.y i.TtoW1.y i.TtoW2.y ]
[ i.TtoW0.z i.TtoW1.z i.TtoW2.z ]

这个矩阵表示了从切线空间到世界空间的变换。

矩阵乘法的意义

当我们有一个切线空间中的向量 bump,它表示为:

[ bump.x ]
[ bump.y ]
[ bump.z ]

将其与变换矩阵相乘,我们实际上是在计算 bump 在世界空间基向量上的投影。具体来说,矩阵乘法的每一行对应于变换后的向量的一个分量,计算如下:

bump_world_space.x = bump.x * i.TtoW0.x + bump.y * i.TtoW1.x + bump.z * i.TtoW2.x
bump_world_space.y = bump.x * i.TtoW0.y + bump.y * i.TtoW1.y + bump.z * i.TtoW2.y
bump_world_space.z = bump.x * i.TtoW0.z + bump.y * i.TtoW1.z + bump.z * i.TtoW2.z

这个计算过程完成了以下操作:

  • 将 bump 向量投影到世界空间的X轴上,即 i.TtoW0
  • 将 bump 向量投影到世界空间的Y轴上,即 i.TtoW1
  • 将 bump 向量投影到世界空间的Z轴上,即 i.TtoW2

通过这三个投影的和,我们得到了 bump 向量在世界空间中的表示,也就是在三个轴上的投影。

Shader "Unlit/WorldTangentMat"
{
    Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_BumpMap ("Normal Map", 2D) = "bump" {}
		_BumpScale ("Bump Scale", Float) = 1.0
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			float _BumpScale;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float4 uv : TEXCOORD0;
				float4 TtoW0 : TEXCOORD1;  
				float4 TtoW1 : TEXCOORD2;  
				float4 TtoW2 : TEXCOORD3; 
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
				
				float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
				fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
				fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
				
				// Compute the matrix that transform directions from tangent space to world space
				// Put the world position in w component for optimization
				o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
				o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
				o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				// Get the position in world space		
				float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
				// Compute the light and view dir in world space
				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				
				// Get the normal in tangent space
				fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
				bump.xy *= _BumpScale;
				bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
				// Transform the narmal from tangent space to world space
				bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
				
				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));

				fixed3 halfDir = normalize(lightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
				
				return fixed4(ambient + diffuse + specular, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Specular"
}

3.渐变纹理

fixed halfLambert  = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;

将乘积从[-1,1]映射到[0,1],然后纹理颜色由深到浅映射,即法线与光照方向夹角越小越亮。

Shader "Unity Shaders Book/Chapter 7/Ramp Texture" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_RampTex ("Ramp Tex", 2D) = "white" {}
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _RampTex;
			float4 _RampTex_ST;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				// Use the texture to sample the diffuse color
				fixed halfLambert  = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
				fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
				
				fixed3 diffuse = _LightColor0.rgb * diffuseColor;
				
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				
				return fixed4(ambient + diffuse + specular, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Specular"
}

4.遮罩纹理

遮罩纹理(Mask Texture),在计算机图形学和游戏开发中,是一种用于控制渲染效果的纹理。它通常是一个灰度图像,其中每个像素的亮度值(通常在0到1之间)代表了一种特定属性的程度或者是否启用该属性。遮罩纹理可以用于多种目的,以下是一些常见的应用:

  1. 透明度遮罩:控制物体的透明度,其中白色表示完全不透明,黑色表示完全透明,灰度值表示不同程度的半透明。

  2. 细节遮罩:用于在着色器中添加细节,如凹凸贴图(bump mapping)或法线贴图(normal mapping),通过遮罩纹理可以控制细节的强度和范围。

  3. 光滑度遮罩:在PBR(基于物理的渲染)工作流中,遮罩纹理可以用来控制表面的光滑程度,影响光照和反射。

  4. 散射遮罩:在皮肤渲染中,遮罩纹理可以用来控制皮肤散射的效果,比如在脸颊或鼻尖等区域增强散射。

  5. 颜色遮罩:用于调整或混合不同区域的颜色,可以用来做颜色变化或者添加污渍、磨损等效果。

  6. 环境遮罩(Ambient Occlusion):用于模拟物体几何体之间或者物体自身不同部分之间的遮挡效果,增强阴影和深度感。

Shader "Unity Shaders Book/Chapter 7/Mask Texture" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_BumpMap ("Normal Map", 2D) = "bump" {}
		_BumpScale("Bump Scale", Float) = 1.0
		_SpecularMask ("Specular Mask", 2D) = "white" {}
		_SpecularScale ("Specular Scale", Float) = 1.0
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float _BumpScale;
			sampler2D _SpecularMask;
			float _SpecularScale;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
				float3 lightDir: TEXCOORD1;
				float3 viewDir : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

				TANGENT_SPACE_ROTATION;
				o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
				o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
			 	fixed3 tangentLightDir = normalize(i.lightDir);
				fixed3 tangentViewDir = normalize(i.viewDir);

				fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
				tangentNormal.xy *= _BumpScale;
				tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
				
			 	fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
			 	// Get the mask value
			 	fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
			 	// Compute specular term with the specular mask
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
			
				return fixed4(ambient + diffuse + specular, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Specular"
}

该处用遮罩主要为了使某些部分反光效果更好,某些部分反光效果差。

4.透明效果

1.透明度测试:

Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}

IgnoreProjector:在Unity中,IgnoreProjector是一个属性,可以应用于材质或Shader,用于控制该材质或Shader是否应该被投影器(Projector)影响。当一个物体使用了带有IgnoreProjector属性的Shader,并且这个属性被设置为true时,任何投影器对该物体的投影都将被忽略。这意味着即使投影器在场景中对该物体所在的区域进行了投影,该物体的外观也不会因为投影器的灯光或纹理而发生变化。

clip():判断内部函数,如果>0则进行下面的程序,反之则舍弃该片元输出。

Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
	}
	SubShader {
		Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _Cutoff;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);
				
				// Alpha test
				clip (texColor.a - _Cutoff);
				// Equal to 
//				if ((texColor.a - _Cutoff) < 0.0) {
//					discard;
//				}
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				return fixed4(ambient + diffuse, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/Cutout/VertexLit"
}

2.透明度混合

Blend Off: 关闭混合。

Blend SrcFactor DstFactor: 开启混合,设置混合因子。该片元中产生的颜色会乘以SrcFactor,颜色缓冲区中的颜色会乘以DstFactor,然后两者相加。

Blend SrcFactor DstFactor,SrcFactorA SrcFactorB:类上,只是使用不同因子来混合透明通道。

BlendOp BlendOperation:使用BlendOperation进行操作。

Blend SrcAlpha OneMinusSrcAlpha:原颜色(片元颜色)的透明度设为第一个因子,1-srcalpha设为第二个英子

ZWrite Off:关闭深度写入

Shader "Unity Shaders Book/Chapter 8/Alpha Blend" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader {
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }

			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _AlphaScale;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/VertexLit"
}

3.开启深度写入半透明效果

两个pass,第一个开启仅开启深度写入,第二个开启透明度测试,从而解决透明物体重叠的排序问题。

ColorMask RGB | A | 0 等,用于控制哪些通道会被写入,RGB即只有RGB通道被写入

ColorMask 0:意味着该Pass不写入任何颜色通道

Shader "Unity Shaders Book/Chapter 8/Alpha Blending With ZWrite" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader {
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
		
		// Extra pass that renders to depth buffer only
		Pass {
			ZWrite On
			ColorMask 0
		}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _AlphaScale;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/VertexLit"
}

                                                                开启深度写入

                                                                 未开启深度写入

4.ShaderLab混合命令

ShaderLab中的混合因子

  1. Zero:结果颜色不会包含源颜色或目标颜色的任何部分。

  2. One:结果颜色将包含源颜色或目标颜色的全部。

  3. SrcColor:结果颜色是源颜色乘以源颜色的RGB值。

  4. SrcAlpha:结果颜色是源颜色乘以源颜色的alpha值。

  5. DstColor:结果颜色是源颜色乘以目标颜色的RGB值。

  6. DstAlpha:结果颜色是源颜色乘以目标颜色的alpha值。

  7. OneMinusSrcColor:结果颜色是源颜色乘以(1 - 源颜色的RGB值)。

  8. OneMinusSrcAlpha:结果颜色是源颜色乘以(1 - 源颜色的alpha值),这是最常用的混合因子之一,用于实现半透明效果。

  9. OneMinusDstColor:结果颜色是源颜色乘以(1 - 目标颜色的RGB值)。

  10. OneMinusDstAlpha:结果颜色是源颜色乘以(1 - 目标颜色的alpha值)。

ShaderLab中的混合操作

(O:OUTPUT 输出颜色; S :SOURCE 片元颜色; D:DESTINATION深度缓冲中的颜色)

Add:O_RGE = S_RGB+D_RGB   O_ALPHA = S_ALPHA + D_ALPHA

Sub:O_RGE = S_RGB-D_RGB   O_ALPHA = S_ALPHA - D_ALPHA

RevSub:O_RGE = -S_RGB+D_RGB   O_ALPHA = -S_ALPHA + D_ALPHA

Min:O_ RGBA = (MIN(S_R,D_R),MIN(S_G,D_G),MIN(S_B,D_B),MIN(S_A,D_A))

Max:O_ RGBA = (MAX(S_R,D_R),MAX(S_G,D_G),MAX(S_B,D_B),MAX(S_A,D_A))

ShaderLab中常见的混合类型

  • 通 过 混 合 操 作 和 混 合 因 子 命 令 的 组 合, 我 们 可 以 得 到 一 些 类 似 Photoshop 混 合 模 式 中 的 混 合 效 果:

    • // 正 常( Normal), 即 透 明 度 混 合

      • Blend SrcAlpha OneMinusSrcAlpha

    • // 柔 和 相 加( Soft Additive)

      • Blend OneMinusDstColor One

    • // 正 片 叠 底( Multiply), 即 相 乘

      • Blend DstColor Zero

    • // 两 倍 相 乘( 2x Multiply)

      • Blend DstColor SrcColor

    • // 变 暗( Darken)

      • BlendOp Min

      • Blend One One

    • // 变 亮( Lighten)

      • BlendOp Max

      • Blend One One

    • // 滤 色( Screen)

      • Blend OneMinusDstColor One

      • // 等 同 于

        • Blend One OneMinusSrcColor

    • // 线 性 减 淡( Linear Dodge)

      • Blend One One

5.双面渲染透明效果

Cull Back | Front | Off : 剔除背面 | 剔除前面 | 无剔除 渲染。

a.关闭剔除

Shader "Unity Shaders Book/Chapter 8/Alpha Test With Both Side" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
	}
	SubShader {
		Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			// Turn off culling
			Cull Off
			
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _Cutoff;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);

				clip (texColor.a - _Cutoff);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				return fixed4(ambient + diffuse, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/Cutout/VertexLit"
}

                                        无背面剔除

                                        有背面剔除

6.透明度混合的双面渲染

两个Pass.第一个只渲染背面,第二个Pass只渲染正面。

Shader "Unity Shaders Book/Chapter 8/Alpha Blend With Both Side" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader {
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			// First pass renders only back faces 
			Cull Front
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _AlphaScale;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
			}
			
			ENDCG
		}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			// Second pass renders only front faces 
			Cull Back
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _AlphaScale;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/VertexLit"
}

5.复杂光照

1.渲染路径

Unity中的渲染路径定义了Unity如何渲染场景中的每个物体。以下是Unity中主要的渲染路径:

  1. 前向渲染路径(Forward Rendering)

    • 在这个路径中,每个灯光对每个对象的影响都是实时计算的,但是对灯光的数量有限制。对于每个对象,只有有限数量的灯光会被计算(通常是4个),超出这个数量的灯光将以简化方式处理。
    • 适合场景中灯光数量不多的情况。
  2. 延迟渲染路径(Deferred Rendering)

    • 延迟渲染首先将物体的几何信息、材质属性等信息渲染到一个称为G-Buffer的缓冲区中,然后再在一个单独的渲染阶段计算灯光。
    • 支持更多的灯光而不会显著降低性能,但是它需要支持特定图形硬件(需要支持多个渲染目标(MRT))。
    • 对于复杂的光照效果和大量灯光的场景比较合适。
  3. 顶点照明渲染路径(Vertex Lit Rendering)

    • 这是一个较老的渲染路径,它主要使用顶点着色器来计算光照,不适用于像素光照。
    • 由于它不计算像素级别的光照,所以渲染速度快,但是光照效果较为简单。
    • 这个路径在现代的Unity开发中已经很少使用。
  4. 自定义渲染路径

    • Unity允许开发者通过编写Shader和修改渲染设置来创建自定义的渲染路径。
    • 这可以用于实现一些特殊的效果或者优化,但是需要较高的技术能力。
  5. 轻量级渲染路径( Lightweight Render Pipeline, LWRP)

    • 是Unity推出的一个较新的渲染管线,设计用于移动设备和VR,专注于性能和轻量级。
    • 它提供了一种更灵活、更高效的渲染方式。
  6. 高清渲染路径(High Definition Render Pipeline, HDRP)

    • 针对高端平台,如PC和主机,提供高质量的视觉效果。
    • 它支持多种高级渲染功能,如物理照明、反射探针、环境遮挡等。

选择哪种渲染路径取决于项目的需求、目标平台以及性能预算。随着Unity版本的更新,这些渲染路径可能会有所改进或者新增。开发者应根据具体情况进行选择和优化。

2.LightMode标签支持的渲染路径设置选项

Always:该pass总会被渲染,但不计算任何光照

ForwardBase:用于前向渲染。该pass会计算环境光、平行光、逐顶点/SH光源和LightMaps

ForwardAdd:用于前向渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源

Deferred:用于延迟渲染。该pass会渲染G-buffer

ShadowCaster:把物体的深度信息渲染到阴影映射纹理或一张深度纹理中

PrePassBase:用于遗留的延迟渲染。该pass会渲染法线和高光反射的指数部分。

PrePassFinal:用于遗留的延迟渲染。该pass会通过合并纹理、光照和自发光来渲染得到最后的颜色。

Vertex\VertexLMRGBM\VertexLM:用于遗留的顶点照明渲染。

3.前向渲染路径:

原理:对通过深度测试的片元进行光照计算,这算一个渲染流程。并且对于每个逐像素的光源都要进行一次该渲染流程。即一个物体需要执行多个Pass,然后在帧缓冲区合并得到最后的颜色。由于受光源数目影响大,所有通常渲染引擎会限制每个物体的逐像素光照数目。

光照处理方式:逐顶点、逐像素、球谐函数(SH),光源处理方式取决于光源类型(Type)和渲染模式(render mode(比如是否是Important))。

4.顶点照明渲染路径

顶点照明渲染对硬件要求最少、运算新能最高,效果最差的一种类型。不支持例如法线映射、阴影等逐像素才能得到的效果。

顶点渲染路径可以使用的内置变量:

5.延迟渲染路径

原理:共两个Pass。在 第 一 个 Pass 中, 我 们 不 进 行 任 何 光 照 计 算, 而 是 仅 仅 计 算 哪 些 片 元 是 可 见 的, 这 主 要 是 通 过 深 度 缓 冲 技 术 来 实 现, 当 发 现 一 个 片 元 是 可 见 的, 我 们 就 把 它 的 相 关 信 息 存 储 到 G 缓 冲 区 中。然 后, 在 第 二 个 Pass 中, 我 们 利 用 G 缓 冲 区 的 各 个 片 元 信 息, 例 如 表 面 法 线、 视 角 方 向、 漫 反 射 系 数 等, 进 行 真 正 的 光 照 计 算。

6.前向渲染中处理不同光源

前向渲染有两种,一种是Bass Pass,一种是Additional Pass,Bass Pass一般计算环境光、自发光(所有逐像素的平行光和所有逐顶点和SH光源)。Additional Pass则是根据光源类型被多次调用(其他影响盖屋体的逐像素光源)

#pragma multi_compile_fwdbase:    

        是 Unity Shader 中的一个编译指令,用于在 前向渲染路径(Forward Rendering) 中生成多个变体(Variants),以支持不同的光照和渲染配置。它的作用是确保 Shader 能够正确处理前向渲染路径中的不同光照情况。

在前向渲染路径中,Unity 会根据场景中的光源数量和类型,对物体进行多次渲染:

  • Base Pass:渲染主方向光(通常是平行光)和逐顶点光照(Vertex Lit)。

  • Additional Passes:渲染其他光源(如点光源、聚光灯等),每个光源会单独渲染一次。

为了支持这些不同的渲染情况,Shader 需要生成多个变体(Variants),以处理不同的光照配置。

如果没有 #pragma multi_compile_fwdbase,Shader 可能无法正确处理以下情况:

  • 场景中没有主方向光。

  • 物体使用了光照贴图。

  • 物体需要接收阴影。

  • 场景中有多个光源。

通过使用 #pragma multi_compile_fwdbase,Unity 会自动生成这些变体,确保 Shader 能够适应不同的渲染情况。

光源属性:位置、方向、颜色、强度、衰减

        Base Pass中处理的逐像素光源类型一定是平行光,所以:

        _WorldSpaceLightPos0:获得平行光的方向;

        _LightColor0:获得平行光的强度和颜色;

        衰减值为1.0(没有衰减)

shader变体:在shader中会定义多个宏,根据不同宏来编译生成多种组合形式的shader源码,每一种组合就叫做shader变体。

Bass Pass:

Pass {
	// Pass for ambient light & first pixel light (directional light)
	Tags { "LightMode"="ForwardBase" }

	CGPROGRAM
	
	// Apparently need to add this declaration 
	#pragma multi_compile_fwdbase	
	
	#pragma vertex vert
	#pragma fragment frag
	
	#include "Lighting.cginc"
	
	fixed4 _Diffuse;
	fixed4 _Specular;
	float _Gloss;
	
	struct a2v {
		float4 vertex : POSITION;
		float3 normal : NORMAL;
	};
	
	struct v2f {
		float4 pos : SV_POSITION;
		float3 worldNormal : TEXCOORD0;
		float3 worldPos : TEXCOORD1;
	};
	
	v2f vert(a2v v) {
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		
		o.worldNormal = UnityObjectToWorldNormal(v.normal);
		
		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		
		return o;
	}
	
	fixed4 frag(v2f i) : SV_Target {
		fixed3 worldNormal = normalize(i.worldNormal);
		fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
		
		fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
		
	 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

	 	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
	 	fixed3 halfDir = normalize(worldLightDir + viewDir);
	 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

		fixed atten = 1.0;
		
		return fixed4(ambient + (diffuse + specular) * atten, 1.0);
	}
	
	ENDCG
}

为其他逐像素光源定义Additional Pass:

Blend One One:希望每次计算得到的光照结果会叠加在前面的结果上,而不是覆盖掉。

float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));

fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

Pass {
	// Pass for other pixel lights
	Tags { "LightMode"="ForwardAdd" }
	
	Blend One One

	CGPROGRAM
	
	// Apparently need to add this declaration
	#pragma multi_compile_fwdadd
	
	#pragma vertex vert
	#pragma fragment frag
	
	#include "Lighting.cginc"
	#include "AutoLight.cginc"
	
	fixed4 _Diffuse;
	fixed4 _Specular;
	float _Gloss;
	
	struct a2v {
		float4 vertex : POSITION;
		float3 normal : NORMAL;
	};
	
	struct v2f {
		float4 pos : SV_POSITION;
		float3 worldNormal : TEXCOORD0;
		float3 worldPos : TEXCOORD1;
	};
	
	v2f vert(a2v v) {
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		
		o.worldNormal = UnityObjectToWorldNormal(v.normal);
		
		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		
		return o;
	}
	
	fixed4 frag(v2f i) : SV_Target {
		fixed3 worldNormal = normalize(i.worldNormal);
		#ifdef USING_DIRECTIONAL_LIGHT
			fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
		#else
			fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
		#endif
		
		fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
		
		fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
		fixed3 halfDir = normalize(worldLightDir + viewDir);
		fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
		
		#ifdef USING_DIRECTIONAL_LIGHT
			fixed atten = 1.0;
		#else
			#if defined (POINT)
		        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
		        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
		    #elif defined (SPOT)
		        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
		        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
		    #else
		        fixed atten = 1.0;
		    #endif
		#endif

		return fixed4((diffuse + specular) * atten, 1.0);
	}
	
	ENDCG
}

7.Unity阴影

Shadow Map:将摄像机放在光源方向,摄像机看不到的地方就是阴影部分。阴影映射纹理本质上也是一张深度图。在Unity中,调用Light Mode为ShadowCaster这个pass来更新光源的阴影映射纹理。

屏幕空间阴影映射技术:

屏幕空间阴影映射技术(Screen Space Shadow Mapping,简称SSSM)是一种在计算机图形学中用于渲染阴影效果的技术。这种技术主要是在屏幕空间(即最终渲染到屏幕上的像素空间)中计算和渲染阴影,而不是在世界空间或光源空间中。以下是屏幕空间阴影映射技术的基本原理和步骤:

原理:

屏幕空间阴影映射技术通常利用深度纹理(Depth Texture)来在屏幕空间中计算阴影。深度纹理包含了场景中每个像素的深度信息,这些信息可以用来判断一个像素是否在阴影中。

步骤:

  1. 渲染深度纹理

    • 首先,从光源的视角渲染场景,只存储每个像素的深度信息到深度纹理中。
  2. 屏幕空间阴影计算

    • 在像素着色器中,对于屏幕上的每个像素,计算它到光源的向量。
    • 使用这个向量在深度纹理上进行采样,得到从光源视角看到的最近深度值。
    • 将这个深度值与当前像素的实际深度值进行比较。如果当前像素的深度值大于采样得到的深度值,那么这个像素就在阴影中。

FallBack “VerterLit”:将深度信息写入阴影映射纹理中。

#include AutoLight.cgnic:包含用于计算阴影的宏

SHADOW_COORDS(2):声明一个用于对阴影纹理采样的坐标。这个宏的参数需要是下一个可用插值寄存器的索引值。‘

TRANSFER_SHADOW(o):用于在顶点着色器中计算上一步中声明的阴影纹理坐标。

SHADOW_ATTENUATION(i):对相关纹理进行采样获得相关信息。

主要注意第一个Pass

Shader "Unity Shaders Book/Chapter 9/Shadow" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		
		Pass {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Apparently need to add this declaration 
			#pragma multi_compile_fwdbase	
			
			#pragma vertex vert
			#pragma fragment frag
			
			// Need these files to get built-in macros
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				SHADOW_COORDS(2)
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);

			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				fixed atten = 1.0;
				
				fixed shadow = SHADOW_ATTENUATION(i);
				
				return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
			}
			
			ENDCG
		}
	
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
			// Use the line below to add shadows for point and spot lights
//			#pragma multi_compile_fwdadd_fullshadows
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 position : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.position = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
				#endif

			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				#ifdef USING_DIRECTIONAL_LIGHT
					fixed atten = 1.0;
				#else
					float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
					fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				#endif
			 	
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Specular"
}

8.统一管理光照衰减和阴影

UNITY_LIGHT_ATTENUATION:用来计算光照衰减和阴影。

注意两个Pass的片元部分。

Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		
		Pass {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdbase	
			
			#pragma vertex vert
			#pragma fragment frag
			
			// Need these files to get built-in macros
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				SHADOW_COORDS(2)
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
			// Use the line below to add shadows for point and spot lights
//			#pragma multi_compile_fwdadd_fullshadows
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				SHADOW_COORDS(2)
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Specular"
}

9.透明度物体的阴影

FallBack “Transparent/Cutout/VertexLit”

Shader "Unity Shaders Book/Chapter 9/Alpha Test With Shadow" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
	}
	SubShader {
		Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			Cull Off
			
			CGPROGRAM
			
			#pragma multi_compile_fwdbase
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _Cutoff;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
				SHADOW_COORDS(3)
			};
			
			v2f vert(a2v v) {
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

			 	o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);

				clip (texColor.a - _Cutoff);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
							 	
			 	// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4(ambient + diffuse * atten, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/Cutout/VertexLit"
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值