'Unity自定義粒子頂點流'

Unity引擎 Velocity GPU 清風寫遊戲 2019-08-05
"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


Unity自定義粒子頂點流

在Main模塊,勾選Prewarm,將Start Lifetime設為4,Start Speed設為0。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


Unity自定義粒子頂點流

在Main模塊,勾選Prewarm,將Start Lifetime設為4,Start Speed設為0。

Unity自定義粒子頂點流


完全禁用Shape模塊。因為我們想要完美的點發射器。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


Unity自定義粒子頂點流

在Main模塊,勾選Prewarm,將Start Lifetime設為4,Start Speed設為0。

Unity自定義粒子頂點流


完全禁用Shape模塊。因為我們想要完美的點發射器。

Unity自定義粒子頂點流


最後啟用Velocity over Lifetime模塊,並按下圖進行設置。Linear Y設為2,Orbital Y設為8,Offset X設為1。這樣一來,粒子會上升,繞著本地Y軸,將Space設為Local旋轉,由於旋轉中心在X軸偏移1個單位,它會實現螺旋圖案。

通過修改以上設置,我們可以控制彈簧的外形。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


Unity自定義粒子頂點流

在Main模塊,勾選Prewarm,將Start Lifetime設為4,Start Speed設為0。

Unity自定義粒子頂點流


完全禁用Shape模塊。因為我們想要完美的點發射器。

Unity自定義粒子頂點流


最後啟用Velocity over Lifetime模塊,並按下圖進行設置。Linear Y設為2,Orbital Y設為8,Offset X設為1。這樣一來,粒子會上升,繞著本地Y軸,將Space設為Local旋轉,由於旋轉中心在X軸偏移1個單位,它會實現螺旋圖案。

通過修改以上設置,我們可以控制彈簧的外形。


Unity自定義粒子頂點流

下圖是得到的效果,彈簧會上下移動。


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


Unity自定義粒子頂點流

在Main模塊,勾選Prewarm,將Start Lifetime設為4,Start Speed設為0。

Unity自定義粒子頂點流


完全禁用Shape模塊。因為我們想要完美的點發射器。

Unity自定義粒子頂點流


最後啟用Velocity over Lifetime模塊,並按下圖進行設置。Linear Y設為2,Orbital Y設為8,Offset X設為1。這樣一來,粒子會上升,繞著本地Y軸,將Space設為Local旋轉,由於旋轉中心在X軸偏移1個單位,它會實現螺旋圖案。

通過修改以上設置,我們可以控制彈簧的外形。


Unity自定義粒子頂點流

下圖是得到的效果,彈簧會上下移動。


Unity自定義粒子頂點流

如果想要多個彈簧粒子,應該怎樣處理呢?因為該著色器使用和噪聲相同的輸入,無論材質如何,每個彈簧都有相同的動畫效果。我們需要一種方法來根據材質指定偏移。

現在返回到著色器代碼,在主紋理下添加偏移時間的新屬性。我們在此創建了一個數值滑塊,調整範圍在0.0和100.0之間,默認值為0.0,即無偏移。

_MainTex("Texture", 2D) = "white" {}

_TimeOffset("Noise Offset", Range(0, 100)) = 0.0


"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


Unity自定義粒子頂點流

在Main模塊,勾選Prewarm,將Start Lifetime設為4,Start Speed設為0。

Unity自定義粒子頂點流


完全禁用Shape模塊。因為我們想要完美的點發射器。

Unity自定義粒子頂點流


最後啟用Velocity over Lifetime模塊,並按下圖進行設置。Linear Y設為2,Orbital Y設為8,Offset X設為1。這樣一來,粒子會上升,繞著本地Y軸,將Space設為Local旋轉,由於旋轉中心在X軸偏移1個單位,它會實現螺旋圖案。

通過修改以上設置,我們可以控制彈簧的外形。


Unity自定義粒子頂點流

下圖是得到的效果,彈簧會上下移動。


Unity自定義粒子頂點流

如果想要多個彈簧粒子,應該怎樣處理呢?因為該著色器使用和噪聲相同的輸入,無論材質如何,每個彈簧都有相同的動畫效果。我們需要一種方法來根據材質指定偏移。

現在返回到著色器代碼,在主紋理下添加偏移時間的新屬性。我們在此創建了一個數值滑塊,調整範圍在0.0和100.0之間,默認值為0.0,即無偏移。

_MainTex("Texture", 2D) = "white" {}

_TimeOffset("Noise Offset", Range(0, 100)) = 0.0


Unity自定義粒子頂點流

然後,添加時間偏移變量。

sampler2D _MainTex;

float4 _MainTex_ST;

uniform float _TimeOffset;

最後將偏移值與_Time.y相加,將得到的結果存為新變量,以使代碼更清楚,接下來將該變量傳到正弦函數中,使它在函數中與頻率相乘。

float time = _Time.y + _TimeOffset;

float sineOffset = sin(time * sineFrequency) * sineAmplitude;

現在,我們只需要複製粒子系統和材質,然後修改剛添加的偏移屬性即可。創建當前粒子系統的副本,然後確保每個副本有獨特的材質和不同的偏移值,並使用相同的著色器。

我們完成的效果如下圖所示。

"

在本教程中,我們將學習如何在Unity粒子系統中使用自定義頂點流(Vertex Streams)。頂點流通過粒子系統的Renderer模塊來設置,它可以將額外的單個粒子數據傳遞到著色器。著色器可以使用該數據,為系統中的每個粒子創建各種獨特的效果,所有粒子都會在GPU上以極快的速度處理。最終的效果場景如下圖所示,雖然本文實現的效果不是非常驚豔,但它為後續教程中學習創作精美特效奠定基礎。

Unity自定義粒子頂點流

Part 1:基礎部分

首先,我們使用Unity模板創建一個簡單的無光粒子著色器。在項目窗口中單擊右鍵,選擇Create -> Shader -> Unlit Shader。

Unity自定義粒子頂點流

我們將該文件命名為Simple Particle Unlit,代碼如下圖所示。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

};

struct v2f

{

float2 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

創建新材質,指定著色器,然後設置紋理屬性為默認粒子紋理。

Unity自定義粒子頂點流

現在我們創建粒子系統,指定該材質到粒子系統Renderer模塊的Materials字段。

Unity自定義粒子頂點流

我們會得到下圖的效果。

Unity自定義粒子頂點流

圖中的效果存在一些透明度問題,我們稍後會解決這些問題。

在相同粒子系統的Renderer模塊中,勾選Custom Vertex Streams啟用自定義頂點流,然後單擊右下方的“+”按鈕,添加Lifetime分類中的AgePercent流。

AgePercent是1D值,表示標準化範圍[0.0, 1.0]內粒子的“生命週期”。0.0表示粒子生成時間,0.5表示粒子生命週期終點,1.0表示粒子消逝時間。

Unity自定義粒子頂點流

我們忽略頂點流與著色器輸入不匹配紅色警告信息,現在將頂點流傳給著色器,我們需要接收並處理頂點流的數據。

在頂點流顯示中,可以注意到數據已被緊湊地打包了。實際2D UV座標位於TEXCOORD0.xy,AgePercent數據位於TEXCOORD0.z。要記住這些信息,以便我們知道在著色器中何處以及如何獲取此數據。

Unity自定義粒子頂點流

每個texcoord都可以是4D向量,即CG/HLSL代碼中,以[x, y, z, w]形式保存的float4變量。如果我們要添加額外的1D流,它將位於TEXCOORD0.w。如果數據比當前texcoord的可用空間大,它會作為餘下部分移動到下一個texcoord,例如texcoord1或texcoord2等。

下圖是相應的設置案例,裡面沒有添加這些頂點流。

Unity自定義粒子頂點流

我們可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy屬於下一個texcoord,即TEXCOORD2,儘管它顯示TEXCOORD1.w|xy。因此Velocity從TEXCOORD2.zw開始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

這可能有點難理解,因為Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它類似對Velocity中xyz的處理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

現在關注AgePercent,回到我們的自定義著色器,開始進行處理。

該著色器只處理TEXCOORD0的x和y以獲得實際紋理的UV座標。AgePercent位於TEXCOORD0.z,因此我們需要在頂點輸入和輸出結構,分別為appdata和v2f將float2改為float3。

查看下面的代碼,瞭解改動內容。

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

接下來,我們需要在著色器的頂點部分初始化UV,使UV在被傳遞到片段部分前包含合適的數值,這些“部分”其實是一個.shader文件中的頂點著色器和片段/像素著色器。在柵格化前,先處理頂點操作。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

最後在片段部分,即給對象上色的像素著色器部分,我們可以利用該數據。下面代碼中,我們根據粒子壽命,使用該數據向紋理(col)的粒子插補紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,將紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

經過修改後,以下是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "RenderType"="Opaque" }

LOD 100

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

我們得到了下圖的結果,或許它並不驚豔,但這僅只是開始。


Unity自定義粒子頂點流

Part 2:透明度,深度測試及渲染隊列

繼續下一步前,我們先解決之前出現的問題,從透明度開始。為了從輸入紋理獲取合適的Alpha值,只需添加混合模式即可。

我們可以選擇常用命令,例如:加法(One One命令),Alpha混合(SrcAlpha OneMinusSrcAlpha)和Alpha混合預乘(One OneMinusSrcAlpha)。對黑色背景上紋理(例如默認粒子紋理)的最好選擇是加法和預乘。

我們選擇加法,因為它最直接,在黑暗場景中效果最好,並且和HDR 和 閾值泛光的結合效果很好,因為像素會通過加法互相疊加。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

返回到Unity編輯器,我們增大了粒子大小,以突出目前存在的一個顯示問題。雖然粒子通過加法混合清楚地渲染了出來,渲染效果就像液滴或熔岩燈,但它們沒有半透明效果,而且公告牌四邊形的輪廓很清楚。

Unity自定義粒子頂點流

為了解決該問題,我們需要禁用深度測試。

Tags { "RenderType"="Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off //關閉深度測試

如下圖所示,處理方法很有效。


Unity自定義粒子頂點流

下圖是沒有修改粒子大小時,應該呈現的效果。

Unity自定義粒子頂點流

雖然可能效果不太明顯,但我們的著色器仍然會在場景中對其它透明對象進行排序,例如精靈。解決這個問題很簡單,只需將材質上的渲染隊列更改為透明層即可。


Unity自定義粒子頂點流

我們可以添加Queue = Transparent標記從著色器自動執行此操作。

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

Part 3:頂點顏色和著色

下面我們來解決頂點流不匹配著色器輸入的警告。


Unity自定義粒子頂點流

最簡單的方法是用過在編輯器選擇Color流,單擊“+”旁邊的“-”按鈕,移除Color流,這樣問題就解決了。

Unity自定義粒子頂點流

但是本文想說明,我們應該瞭解真正解決該錯誤,而不是簡單的刪除Color流。

熟悉Unity粒子系統的基礎知識的開發者,應該知道我們可以定義所有粒子初始化時的起始顏色,生命週期顏色和隨速度變化的顏色。在當前著色器中,這些設置沒有任何效果,因為數據是通過COLOR頂點輸入傳遞的。

這些警告實際在告訴我們,著色器中沒有地方接收該數據,即使粒子系統已設置為發送數據。因此當我們移除它時,警告就消失了。如果在著色器中接收Color流,但並不發送該數據,我們也會得到相同的警告。

現在我們更新著色器,以便從粒子系統接收Color輸入流。

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

然後我們將v2f struct和輸入初始化到頂點部分。

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色。

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化uv.z(它保存了粒子壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

返回到Unity編輯器的粒子系統設置,警告已經消失了。

Unity自定義粒子頂點流

現在將Main模塊的Start Color設為藍色。

Unity自定義粒子頂點流

可能你已經明白了,但是這樣做不會改變什麼。因為我們接收了數據,但還沒在片段部分處理數據。

現在修改設置,使粒子系統組件的顏色對紋理顏色進行著色,然後再插補為紅色。

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

//讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

現在Unity編輯器中,我們可以看見下圖畫面,和預期一樣,粒子首先會被著色為藍色。


Unity自定義粒子頂點流

我們已經成功編寫好了粒子著色器,它可以處理自定義頂點流,下面是完整的著色器代碼。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化當前uv.z(包含粒子的壽命百分比)

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag (v2f i) : SV_Target

{

// 採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

//根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

Part 4:頂點動畫

效果依舊不是很特別,因為現在可以接收並處理粒子系統的輸入顏色,所以我們可以通過將Start Color恢復為純白色,註釋掉將col插補為紅色的代碼,並使用Color over Lifetime模塊在標準化粒子生命週期將紅色過渡為藍色,從而獲得完全相同的結果。

Unity自定義粒子頂點流

如果我們只是為了製作一個自定義著色器,它應該能實現更多功能。現在我們要實現無法通過修改組件設置或在CPU編程來輕鬆實現的效果。

我們已經學習如何使用自定義頂點流處理渲染粒子的像素。接下來處理它的頂點。在頂點部分中,在修改位置的代碼前,即代碼o.vertex = UnityObjectToClipPos(v.vertex),添加這部分代碼。

// 奇怪的頂點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float sineOffset = sin(_Time.y * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

注意:_Time是Unity著色器內置的4D變量,_Time的Y值沒有更改。我們也可以使用_SinTime.w,它是未更改的時間正弦。

我們使用正弦波創建了動畫,其中Y偏移被調整為正弦偏移和粒子壽命的乘積。粒子壽命越大,偏移越大,於是得到了下圖效果。


Unity自定義粒子頂點流

通過將基礎粒子系統改為粒子彈簧,我們可以讓效果沒那麼奇怪。在得到正常運行的頂點動畫後,為粒子系統實現粒子彈簧效果非常簡單。

Part 5:粒子彈簧

首先,新粒子系統的默認Rotation X值為-90,請確保將其重置為0。


Unity自定義粒子頂點流

在Main模塊,勾選Prewarm,將Start Lifetime設為4,Start Speed設為0。

Unity自定義粒子頂點流


完全禁用Shape模塊。因為我們想要完美的點發射器。

Unity自定義粒子頂點流


最後啟用Velocity over Lifetime模塊,並按下圖進行設置。Linear Y設為2,Orbital Y設為8,Offset X設為1。這樣一來,粒子會上升,繞著本地Y軸,將Space設為Local旋轉,由於旋轉中心在X軸偏移1個單位,它會實現螺旋圖案。

通過修改以上設置,我們可以控制彈簧的外形。


Unity自定義粒子頂點流

下圖是得到的效果,彈簧會上下移動。


Unity自定義粒子頂點流

如果想要多個彈簧粒子,應該怎樣處理呢?因為該著色器使用和噪聲相同的輸入,無論材質如何,每個彈簧都有相同的動畫效果。我們需要一種方法來根據材質指定偏移。

現在返回到著色器代碼,在主紋理下添加偏移時間的新屬性。我們在此創建了一個數值滑塊,調整範圍在0.0和100.0之間,默認值為0.0,即無偏移。

_MainTex("Texture", 2D) = "white" {}

_TimeOffset("Noise Offset", Range(0, 100)) = 0.0


Unity自定義粒子頂點流

然後,添加時間偏移變量。

sampler2D _MainTex;

float4 _MainTex_ST;

uniform float _TimeOffset;

最後將偏移值與_Time.y相加,將得到的結果存為新變量,以使代碼更清楚,接下來將該變量傳到正弦函數中,使它在函數中與頻率相乘。

float time = _Time.y + _TimeOffset;

float sineOffset = sin(time * sineFrequency) * sineAmplitude;

現在,我們只需要複製粒子系統和材質,然後修改剛添加的偏移屬性即可。創建當前粒子系統的副本,然後確保每個副本有獨特的材質和不同的偏移值,並使用相同的著色器。

我們完成的效果如下圖所示。

Unity自定義粒子頂點流

著色器代碼

完整的著色器代碼如下。

Shader "Unlit/Simple Particle Unlit"

{

Properties

{

_MainTex("Texture", 2D) = "white" {}

_TimeOffset("Noise Offset", Range(0, 100)) = 0.0

}

SubShader

{

Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }

LOD 100

Blend One One // 加法混合

ZWrite Off // 關閉深度測試

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

// 實現模糊效果

#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata

{

float4 vertex : POSITION;

fixed4 color : COLOR;

float3 uv : TEXCOORD0;

};

struct v2f

{

float3 uv : TEXCOORD0;

UNITY_FOG_COORDS(1)

float4 vertex : SV_POSITION;

fixed4 color : COLOR;

};

sampler2D _MainTex;

float4 _MainTex_ST;

float _TimeOffset;

v2f vert(appdata v)

{

v2f o;

// 奇怪的定點動畫

float sineFrequency = 5.0;

float sineAmplitude = 4.0;

float time = _Time.y + _TimeOffset;

float sineOffset = sin(time * sineFrequency) * sineAmplitude;

float agePercent = v.uv.z;

float3 vertexOffset = float3(0, sineOffset * agePercent, 0);

v.vertex.xyz += vertexOffset;

o.vertex = UnityObjectToClipPos(v.vertex);

// 從保存在顏色頂點輸入的粒子系統接收數據,並將該數據用於初始化顏色

o.color = v.color;

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

// 初始化tex coord變量

o.uv.z = v.uv.z;

UNITY_TRANSFER_FOG(o,o.vertex);

return o;

}

fixed4 frag(v2f i) : SV_Target

{

//採樣紋理

fixed4 col = tex2D(_MainTex, i.uv);

// 讓紋理顏色和粒子系統的頂點顏色輸入相乘

col *= i.color;

float particleAgePercent = i.uv.z;

float4 colourRed = float4(1, 0, 0, 1);

// 根據粒子壽命百分比,從紋理顏色插值為紅色

col = lerp(col, colourRed * col.a, particleAgePercent);

// 應用模糊效果

UNITY_APPLY_FOG(i.fogCoord, col);

return col;

}

ENDCG

}

}

}

"

相關推薦

推薦中...