Shader Graph 入門

以下將使用 Shader Graph 建立一個簡單的 Shader , 這個 Shader 能夠正常接收 Sprite Renderer 的 Sprite,並允許透過 Sprite Renderer 的 Color 來調整顏色。

  1. 建立 Shader Graph : 在 Unity,前往 Assets 資料夾,右鍵點擊 → Create → Shader Graph → Unlit Shader Graph。命名 為 Color Shader Graph。並雙擊打開 Shader Graph 編輯器。
  2. 在 Shader Graph 中,我們需要一個 Texture2D 屬性
    • 在左側的面板上,找到 + , 點擊 + 並選擇 Texture2D,將其命名(Name)為 MainTex。
    • 命名為 MainTex , 會發現 Reference 變為 _MainTex。這樣當這個 Shader 被應用到 Sprite Renderer 的 Material 上時,Sprite Renderer 會自動把它的 Sprite 貼圖傳遞給 _MainTex。這樣 Shader 就能正確使用 Sprite 的貼圖,而不需要手動指定 Texture。
  3. 在 Shader Graph 中,我們需要三個 Node
    • Sample Texture 2D : 把建立好的 Texture2D 屬性 (_MainTex) 輸入到這個 Sample Texture 2D 節點的 Texture 2D 。
    • Vertex Color : 這個節點會讀取 Sprite Renderer 設定的 Color 。
    • Multiply : 建立一個 後。
      • 將 Sample Texture 2D 的輸出(RGBA) 連接到 Multiply(A 輸入)
      • 將 Vertex Color 的輸出(RGBA) 連接到 Multiply(B 輸入)
  4. 最後將 Multiply 的輸出 連接到 Fragment Output 的 Base Color。
  5. 建立一個 Material , 將他的 Shader 設為在上面的 Shader Graph 。
  6. 建立一個 Sprite Renderer , 指定一個 Sprite , 並賦予剛剛建立的 Material
  7. 調整 Sprite Renderer 中的 Color 看看是否有變更顏色。
  8. 可以發現當 Sprite Renderer Color 為白色的時候,呈現的是材質本身的顏色。這就是我們為何使用相乘 (Multiply) 的原因。

為什麼需要建立一個 Vertex Color ?

  • 這是因為 Sprite Renderer 的 Color 其實是「頂點顏色(Vertex Color)」,Unity 會把它當作一個 Color 傳進 Shader。
  • 如果你的 Shader 沒有讀取 Vertex Color,那麼 Sprite Renderer 改變 Color 也不會影響最終顯示的顏色。

為什麼要用相乘 (Multiply)?

  • 因為 Sprite Renderer 的 Color 本質上是「顏色的縮放因子」,影響材質的最終顏色。
  • 所以,Shader 必須把 Texture 的顏色與 Vertex Color 相乘,這樣才能讓 Color 正確影響結果!
  • 假設:
    • Texture Color 是 (1, 1, 1, 1)(白色)
    • Vertex Color 是 (1, 0, 0, 1)(紅色)
    • 那麼,最終顏色 = 𝑇𝑒𝑥𝑡𝑢𝑟𝑒𝐶𝑜𝑙𝑜𝑟 × 𝑉𝑒𝑟𝑡𝑒𝑥𝐶𝑜𝑙𝑜𝑟 = (1,1,1,1)×(1,0,0,1)=(1,0,0,1) → 結果變成紅色!
  • 同理:
    • (0.5, 0.5, 0.5, 1) × (1, 0, 0, 1) = (0.5, 0, 0, 1) → 半透明紅色!
    • (1, 1, 1, 1) × (1, 1, 1, 1) = (1, 1, 1, 1) → 不變!
    • (1, 1, 1, 1) × (0.5, 0.5, 0.5, 1) = (0.5, 0.5, 0.5, 1) → 降低亮度!

Shader Graph Properties

在 Shader Graph 中,Properties(屬性)是用來設定和控制著色器的外部變量,這些變量可以從 Unity 的材質面板中進行調整,並影響著色器的輸出。這些屬性讓你在不修改 Shader Graph 設定的情況下,動態改變材質的外觀。它們通常用來創建可調整的材質效果,並且可以進行繫結到 Shader Graph 中的不同節點,以控制最終渲染結果。

在 Unity 中,屬性有 NameReference 兩種名稱,其中

  • Name 這是人類可讀的屬性名稱,會在 Unity Editor 的 Inspector 視窗中顯示,供設計師或開發者查看和修改。
  • Reference 這是 C# 腳本 中引用該屬性的名稱。在編寫腳本時,會使用這個名稱來取得和設置該屬性的值。
  • 命名規範一般為
    • Name : Main Tex
    • Reference : _MainTex

Shader Graph Properties 的種類

  • Float (浮點數) : 這是一個單一的數值屬性,用來控制各種數值參數,如金屬度、光滑度、透明度等。
  • Vector2、Vector3 和 Vector4 : 這是一個具有多個分量的數值屬性,常見的有 Vector2、Vector3 和 Vector4。
  • Color (顏色) : 顏色屬性通常表示為 Vector4,其中四個分量分別對應紅色 (R)、綠色 (G)、藍色 (B) 和透明度(Alpha)。
  • Boolean (布林): 用來表示一個開(True)/關(False)狀態,通常是用來控制某些效果的開啟或關閉。
  • Gradient (漸變): 用來控制顏色漸變,在 Gradient Window 中添加或移除控制點(handles),來設定漸變中某個位置的顏色(底部標記設定顏色)或透明度(上部標誌設定透明度)。
  • Texture 2D (2D 紋理) : 此屬性用來將圖像或紋理資料傳遞給著色器,並可以用來改變物體的外觀。
  • Texture 2D Array (2D 紋理陣列) : Texture 2D Array 是一組具有相同大小和格式的 2D 紋理,這些紋理被打包在一起,讓 GPU 可以將它們當作一個單一的紋理來讀取,從而提高效能。
  • Texture 3D (3D 紋理) : Texture 3D 類似於 Texture 2D,但它多了一個維度,因此它是一個 3D 顏色數據的區塊。簡單來說,Texture 3D 是一個包含多層紋理的三維紋理,每一層就像是 2D 紋理,但它們在第三個維度中有不同的深度。
  • Cubemap : Cubemap 是一種特殊的紋理類型,概念上就像是立方體的網格(網格展開圖)。可以將它理解為六個紋理被拼接在一起,形成一個立方體的外表面。這使得 Cubemap 在處理一些三維效果時非常有用,特別是對於天空盒和反射映射。
  • Virtual Texture : 虛擬紋理(Virtual Textures) 可用於減少高解析度紋理的記憶體佔用,這對於使用多個高解析度紋理的情況特別有用。不過,虛擬紋理目前僅在 HDRP(高畫質渲染管線)中受支持。
  • Matrix (矩陣) : 這些屬性用來描述坐標轉換,通常是用來處理模型、視圖和投影矩陣。
    • Matrix 2 : 2x2 浮點數矩陣,當你創建一個這種類型的屬性時,預設值會是 2x2 單位矩陣(identity matrix),該矩陣的對角線上是 1,其餘位置是 0。
    • Matrix 3 : 3x3 浮點數矩陣。
    • Matrix 4 : 4x4 浮點數矩陣。
    • 矩陣類型(Matrix 2、Matrix 3 和 Matrix 4)都無法暴露到 Inspector 面板中。
  • Sampler State : Sampler State 用來控制如何取樣一個紋理(Texture)。這些屬性可以幫助我們定義紋理在渲染過程中的取樣行為,包括濾鏡(Filter)和包裹模式(Wrap Mode)等。這些設置決定了當紋理的 UV 坐標超過紋理邊界時的行為,以及紋理如何進行平滑處理。
    • Sampler State 無法暴露到 Inspector 面板中
    • Filter(濾鏡):控制紋理的平滑方式,通常用於當紋理縮放時進行處理。
      • Point:不進行平滑處理,使用最接近的像素顏色,通常稱為最近鄰取樣,適合小尺寸紋理。
      • Linear:對相鄰的像素進行平滑過渡,這是一種常見的紋理過渡方式,適用於中等解析度的紋理。
      • Trilinear:除了在像素間進行平滑處理,還會在不同的 mipmap 層級之間進行平滑過渡,這對於大範圍的紋理變換來說更為平滑。
    • Wrap Mode(包裹模式):控制當紋理的 UV 坐標超出範圍時的處理方式。
      • Repeat:紋理會在超出範圍的區域重複顯示,無縫平鋪。
      • Clamp:UV 坐標會被限制在紋理邊界內,超出範圍的部分會使用紋理的邊緣顏色。
      • Mirror:類似於 Repeat,但每次穿越邊界時會鏡像反射紋理,會創建一種鏡像效果。
      • MirrorOnce:與 Mirror 類似,但超出範圍的 UV 坐標會被限制在第一次反射後的區域。

Keyword

使用 Keyword 創建不同的 Shader Graph 變體 (variants),這些特性可以根據需求開啟或關閉,並可以針對不同的平台或條件進行調整。這些變體可以增加 Shader 的複雜度,也能根據設定的條件進行縮放。

  • 在 Keyword 面板中,會有一個 Scope 屬性,選擇 Local 的話,表示是只在該 Shader Graph 私有(private) , 選擇 Global 則表示在整個專案中所有 Shaders 皆可使用此屬性。
  • Boolean (Keyword) : 值為 true 或 false,使用它會導致生成兩個不同的著色器變體(shader variants)。根據定義的不同,這些變體的行為會有所不同。這類關鍵字常用於啟用或禁用某些功能,或根據條件選擇不同的著色器邏輯。
  • Enum (Keyword) : Enum 關鍵字類型 允許我們添加一組字符串,這些字符串是該枚舉可以取的值,並可以設置其中一個為預設值。這使得我們能夠根據這個枚舉的值來改變 Shader Graph 的行為。
  • Material Quality (Keyword) : 是一個內建的枚舉關鍵字,根據您的項目中的品質設置,自動由 Unity 或特定的渲染管線添加。這個關鍵字基於遊戲的圖形質量設定來調整 Shader Graph 的行為。例如,您可能會選擇在較低的材質品質設置下,使用較低的 LOD(細節層級)來減少渲染負擔,從而提高遊戲的運行效能。

參考

Shader Graph Block Node 筆記

Block Node

  • 是一種特殊類型的節點,用於 Master Stack 中。每個 Block 代表了 Shader Graph 在最終著色器輸出中使用的單一表面(surface)(或頂點(vertex))描述資料。
  • 內建的 Block 節點 始終可用,
  • 但特定渲染管線的節點僅在該管線中可用。例如,
    • Universal Block 節點 只在 Universal Render Pipeline(URP) 中可用,
    • 而 High Definition Block 節點 只在 High Definition Render Pipeline(HDRP) 中可用。
  • 某些 Block 只有在特定的 Graph Settings 下才會兼容,並且可能會根據你選擇的圖表設置變為啟用或禁用。
  • 你無法對 Block 進行剪切、複製或粘貼操作。
  • 而某些節點只能在 頂點階段(Vertex Stage) 或 片段階段(Fragment Stage) 使用。

Vertex Stage Blocks

在 頂點階段(Vertex Stage),著色器(shader)會對網格 (mesh) 上的每個頂點(vertex)進行處理,並將它們移動到螢幕(screen)上的正確位置。我們可以對頂點進行操作,例如移動它們,或改變光照與它們的互動方式。

Position (Block): Position(位置)區塊 定義了 網格(Mesh)上每個頂點的位置。

  • 如果不進行修改,頂點的位置將與 建模軟體中的原始位置 相同。
  • 我們可以調整這個 Vector3 來改變頂點的位置,使網格實際發生形變,例如海浪效果。
  • 但要注意,我們無法修改單獨的像素(Pixels)或片段(Fragments)的位置,只能操作頂點(Vertices)。

Normal (Block): Normal(法線)區塊 定義了 頂點法線(vertex normal points)的方向(direction)。

  • 這個方向對許多 光照計算 來說非常重要,因此修改它可能會影響 物件與光線的互動方式。
  • 與 Position Block 不同,我們可以在 片段階段(Fragment Stage) 透過其他區塊節點來 逐像素(Per-Pixel)修改法線,而不只是修改頂點的法線。
  • Normal 也是一個 Vector3,用來表示法線的方向。

Tangent (Block) : Tangent(切線)區塊 可以用來 修改切線向量。

  • 切線向量(Tangent vector) 與 頂點法線(Vertex Normal)垂直(perpendicular),在平坦的表面上,它通常會沿著物件的表面方向。
  • 如果你更改了頂點法線(Vertex Normal),建議你也調整切線(Tangent),以確保它仍然與法線保持垂直關係。
  • Tangent 也是一個 Vector3,表示方向。

Fragment Stage Blocks

  • 當 頂點階段(Vertex Stage)完成頂點的變換並確定它們的新位置後,畫面會進行 光柵化(Rasterization),將 3D 資訊轉換成一個片段(Fragment)陣列。
  • 通常,每個片段(Fragment)對應到一個像素(Pixel),但在某些情況下,片段的大小可能會小於像素(例如抗鋸齒技術)。
  • 為了簡化討論,這裡我們將「片段」與「像素」視為相同的概念。
    片段階段(Fragment Stage) 中的區塊(Block Nodes)會對每個像素進行操作,例如決定顏色、透明度、光照等效果。

Base Color (Block)

  • 在某些版本的 Shader Graph 中,這個屬性被稱為 Albedo(反照率)。
    Base Color(基礎顏色) 是物件的純顏色,如果忽略所有光照、透明度與其他效果,物件將會顯示這個顏色。

Normal (Tangent/Object/World) (Block)

  • 在頂點階段(Vertex Stage) 有自己的 Normal(法線)區塊。在這個片段階段(Fragment Stage),我們可以存取這個法線資訊,進一步修改每個像素的法線向量,並將修改後的法線傳回 Unity 的內建光照計算系統。
  • Shader Graph 中有 三種 Normal(法線)區塊,它們的差別在於所使用的座標空間:
    • Tangent Space(切線空間)
    • Object Space(物件空間)
    • World Space(世界空間)
  • 在 Shader 設定中,同一時間只能啟用一種法線空間,你可以在 Graph Settings(圖形設定)中透過 Fragment Normal Space(片段法線空間) 選擇要使用的空間類型。

Emission (Block)

  • 自發光(Emissive)光源 是一種非常適合用來 創建物件周圍的輝光(Bloom)效果 的技術。可以想像像是 霓虹燈、發光火焰 或 魔法咒語 等效果。
  • Emission(發光)區塊 接受 HDR 顏色(高動態範圍顏色),這讓我們能夠將光源的強度 提高到遠超過一般顏色的範圍,產生更亮、更強烈的光效。

Metallic (Block)

  • Metallic(金屬性)區塊 接受一個 浮點數(float) 值。當數值為 0 時,物體的光照效果會像是 完全非金屬(non-metallic),而當數值為 1 時,物體會呈現 完全金屬(totally metallic)。
  • 這個屬性只有在使用 金屬性工作流(Metallic workflow) 時才會生效。你可以在 Graph Settings(圖形設定) 中,使用 Workflow 選項來選擇使用金屬性工作流還是 鏡面反射工作流(Specular workflow),且只有在材質設定為 Lit(有光照) 時,這個選項才會出現。

Specular (Block)

  • 與 Metallic(金屬性) 區塊不同,Specular(鏡面反射) 區塊接收的是 顏色 作為輸入,因為鏡面反射高光(Specular Highlights)可以有不同的顏色。顏色越亮、越接近白色,鏡面反射的高光就越強。

Smoothness (Block)

  • 物體表面越光滑,光照高光的顯示就越明顯。當 Smoothness(光滑度) 為 0 時,物體表面的光照會顯得粗糙且無光澤;而當 Smoothness 為 1 時,物體表面會像 鏡面一樣光滑,反射出明亮的高光。

Ambient Occlusion (Block)

環境遮蔽(Ambient Occlusion,簡稱 AO) 是一種測量像素受到其他場景物體(如牆壁)遮擋,從而減少光源照射的程度。這是一個 浮點數(float) 值,當值為 0 時,該像素應該完全根據照射到它的光源來顯示照明效果;當值為 1 時,光照會被人為減少到最小值。

Alpha (Block)

Alpha 是衡量像素透明度的數值,它的範圍從 0 到 1,其中 0 代表完全透明(totally transparent),而 1 代表完全不透明(fully opaque)。渲染透明物體比渲染不透明物體更消耗計算資源,因此在 Unity 中,我們需要在 Graph Settings 中選擇 Transparent Surface 選項,讓 Unity 正確處理這個著色器。

Alpha Clip Threshold (Block)

Alpha 剪裁(Alpha Clipping),當像素的 alpha 值低於特定閾值時,這些像素會被剔除(culling)。我們可以通過在 Graph Settings 中勾選 Alpha Clip 選項來啟用 Alpha Clip Threshold 區塊。這項技術無論是物體表面設置為透明(Transparent)還是完全不透明(Opaque)都可以使用,因此 Alpha 區塊在不透明材質上也不一定是完全無用的!這在模擬透明效果時非常有用,通常會使用不透明渲染,但會根據一定模式剔除像素,從而創造出透明的假象。


參考

Shader Graph 的空間

Object Space

  • 模型的所有頂點位置都是 相對於該物件的中心點(Pivot Point) 來計算的,而不是世界原點 (0,0,0)

World Space

  • 世界空間(World Space) 是所有物件共享的一個全域座標系統,其中所有物件的位置都是相對於 世界原點 (0,0,0) 來計算的。在 Unity Editor 中,當你修改某個 Object Transform 的位置,就是在修改那個 Object 的 World Space。
  • 假設有一個物件 A 在 (3,2,1) 的世界座標,然後它的子物件 B 在 localPosition = (1,1,1):
    • A.position = (3,2,1)(世界空間)
    • B.localPosition = (1,1,1)(本地空間,相對於 A)
    • B.position = (4,3,2)(世界空間,因為 A 的位置 + B 的本地位置 = 世界位置)
    • 所以,如果你在 Unity 編輯器中修改 B.position,你就是在改變它的 世界空間座標,而 B.localPosition 會自動調整,以維持相對於 A 的關係。

Absolute World Space 與 World Space

  • URP:Universal Render Pipeline(通用渲染管線),較強調效能,適合跨平台。
  • HDRP:High Definition Render Pipeline(高解析度渲染管線),較強調畫質,適用於 高端 PC、主機遊戲、電影級畫質
  • 在 URP 中,world space 與 absolute world space 是一樣的。
  • 但是在 HDRP 中,他使用 camera relative rendering , 因此 HDRP 的 world space 是相對於相機的 (camera-relative) ,而他的 absolute world space 則不管相機。

Tangent Space

  • 在 切線空間(Tangent Space) 中,位置和方向是相對於每個頂點及其法線來定義的。

View/Eye Space

  • 在 視圖空間(View/Eye Space) 中,物件的位置是相對於相機及其朝前的方向( forward-facing direction )來描述的。這與 相機相對渲染(Camera-Relative Rendering) 不同,因為視圖空間會考慮相機的旋轉。

Clip space

  • 在 裁剪空間(Clip Space) 中,物件的位置是相對於螢幕(Screen)的。這個空間是在視圖空間經過投影變換(projected)後產生的,其結果取決於相機的視野範圍(Field of View, FOV)和裁剪平面(Clipping Planes)。通常,超出裁剪空間範圍的物體會被裁剪(Clipped),也稱為剔除(Culled),簡單來說就是被刪除,因此這個空間被稱為「裁剪空間」。

Shader Graph Node 筆記


Add Node : 將輸入 A 與 B 相加後輸出。可用在

  • 顏色混合:兩個顏色相加會讓結果更亮,例如 紅色(1,0,0) + 綠色(0,1,0) = 黃色(1,1,0)。
  • 偏移效果:可以為 UV 座標 添加一個數值,實現紋理偏移或滾動效果。
  • 漸變控制:用於疊加額外的數值,如增加光照強度或提高某些參數的基礎值。

Multiply Node : 將輸入 A 與 B 相乘後輸出。可用在

  • 顏色調整:用 Multiply 乘以一個顏色值可以調整其亮度,例如 (1,1,1) * 0.5 讓顏色變暗。
  • 遮罩(Mask):如果將一個紋理乘以一個黑白影像(如漸層遮罩),黑色部分(0)會被隱藏,白色部分(1)保持不變。
  • 縮放(Scaling):可以用 Multiply 來控制值的縮放,例如控制波動強度、光照強度等。

Split Node: 會將輸入向量 In 拆分為四個 Float 輸出 R、G、B 和 A。

這些輸出對應於輸入 In 向量的各個通道:

  • R (紅色通道)
  • G (綠色通道)
  • B (藍色通道)
  • A (Alpha 透明度通道)
  • 如果輸入向量 In 的維度小於 4(即不是 Vector4),則缺少的輸出通道將預設為 0。

生成的程式碼

1
2
3
4
float _Split_R = In[0];
float _Split_G = In[1];
float _Split_B = 0;
float _Split_A = 0;

參考: https://docs.unity3d.com/Packages/com.unity.shadergraph@17.0/manual/Split-Node.html


Transform Node: 用來將輸入從一個座標空間轉換到另一個座標空間。可以用來處理位置 (Position)、方向 (Direction) 或法線 (Normal) 等資料的變換。

  • 位置 (Position) 會受 平移、旋轉、縮放 影響。
  • 方向 (Direction) 只受 旋轉縮放 影響,不會受 平移 影響。

當轉換的不是 Position(位置)時,Unity 建議使用 World 選項,而不要使用 Absolute World,否則可能會產生非預期行為。
例如,當轉換方向(Direction)或法線(Normal)時,Camera Relative(相機相對世界) 可能會影響計算結果,因此 World 會更適合。

參考: https://docs.unity3d.com/Packages/com.unity.shadergraph@17.0/manual/Transform-Node.html


對於計算機圖形學中的應用中,噪聲應該是偽隨機的,也就是說兩次調用應得到同樣的結果。

Gradient Noise (梯度噪聲): 梯度噪聲產生的紋理具有連續性,因此常用於模擬自然現象(如雲層、山脈、火焰等)。

  • Perlin 噪聲(Perlin noise,又稱為柏林噪聲)是梯度噪聲的一種。由Ken Perlin開發的自然噪聲生成算法,具有在函數上的連續性,可在多次調用時給出一致的數值。該噪聲可以用來模擬人體的隨機運動,螞蟻行進的線路等。另外,還可以通過計算分形和模擬雲朵,火焰等非常複雜的紋理。

Gradient Noise Node :梯度噪聲節點,根據輸入 UV 生成梯度噪聲(Gradient Noise)或 Perlin 噪聲(Perlin Noise)。

  • 可調整 Scale 來控制噪聲的大小與細節。

輸入

  • UV : 為 Vector2 , UV 位置作為噪聲的「種子」,可透過偏移、縮放來改變噪聲分佈
    • UV + 偏移(Offset) → 平移噪聲圖案
    • UV * 縮放(Scale) → 改變噪聲細節
    • UV 變形(Distortion) → 產生扭曲效果
  • Scale : 為 Float , 可改變噪聲的細節程度
    • 較大值 → 細節更密集,較小值 → 細節較稀疏
      輸出
  • Output : 為 Float , 輸出範圍為 0.0 到 1.0

Simple Noise Node : 根據輸入的 UV 可以產生 簡單 (Simple) 或是 Value 雜訊。

參考:


UV Node : UV 節點 用來獲取頂點或片段的 UV 坐標。Unity 允許你在網格的數據中處理多個紋理座標集,因此可以透過 Channel 下拉選單來選擇從四組 UV 坐標中擷取資料。大多數網格通常只會使用 UV0,但你也可以利用其他通道來隱藏更多數據。
需要注意的是,Shader Graph 有一個限制,就是它只能存取 UV0 到 UV3,而 UV4 到 UV7 則只能在著色器程式碼中訪問。這意味著如果需要更多的 UV 通道,Shader Graph 本身無法直接處理,但你仍然可以在手寫的著色器代碼中處理更高的 UV 通道。


Position Node: 根據此節點所在的 Shader 階段 (Shader Stage)來決定,此節點是存取 網格頂點 (Vertex) 或片段 (Fragment) 的位置 (Position) 。
可以使用 Space (空間) 下拉選單來選擇輸出值的座標空間。

Ports

名稱 描述 類型 綁定 描述
Out 輸出 Vector3 網格頂點 (Vertex) 或片段 (Fragment) 的位置 (Position)

Controls

名稱 類型 選項 描述
Space 下拉選單 Object, View, World, Tangent, Absolute World 為這個 Position 節點的輸出選一個座標空間(coordinate space)

World 與 Absolute World
Position 節點 提供 World (世界) 和 Absolute World (絕對世界) 兩種座標空間選項:

  • Absolute World (絕對世界座標): 在所有 Scriptable Render Pipeline (SRP) 中,始終返回物件在場景中的絕對世界位置。
  • World (世界座標): 返回所選 SRP 預設的世界座標。

不同渲染管線的 World 預設行為:

  • High Definition Render Pipeline (HDRP) 預設使用 Camera Relative (相機相對) 世界座標。
  • Universal Render Pipeline (URP) 預設使用 Absolute World (絕對世界座標)。

舊版本

  • Shader Graph 6.7.0 或更早版本,如果 Position 節點 使用 World 空間,則會自動升級為 Absolute World,以確保計算結果不變。
  • HDRP 中,如果之前使用 World 空間 手動計算 Camera Relative 世界座標,現在可以直接選擇 World,系統會自動使用相機相對的世界座標。

參考: https://docs.unity3d.com/Packages/com.unity.shadergraph@7.1/manual/Position-Node.html


Tiling And Offset Node(平鋪與偏移節點): Tiling And Offset Node 用於調整紋理(Texture)或 UV 坐標的平鋪(Tiling)和偏移(Offset),允許你在 Shader Graph 中動態改變紋理的大小與位置。

  • Tiling 輸入是 Vector 2,它控制紋理在物件上複製的次數(即平鋪的數量)。 UV * Tilling
  • Offset 輸入是 Vector 2,它用來在任何方向上移動(滾動)紋理。 UV + Offset
  • UVs 輸入是原始的 UV 座標,平鋪和偏移會應用到這些座標上。

UV 乘以一個數值就是縮放 (Tiling)
UV 加減一個值,就是移動 (Offset)

產生的程式碼

1
2
3
4
void Unity_TilingAndOffset_float(float2 UV, float2 Tiling, float2 Offset, out float2 Out)
{
Out = UV * Tiling + Offset;
}

Absolute Node : 將 輸入 取絕對值後 輸出。


Power Node : Power Node 就是次方的意思,當輸入 A 、 B, 輸出為 A的B次方, A^B

1
2
3
4
void Unity_Power_float4(float4 A, float4 B, out float4 Out)
{
Out = pow(A, B);
}

Sample Texture 2D :

  • 有三個輸入:
    • Texture:要取樣的紋理(Texture 2D)。
    • UV coordinate:紋理的取樣座標,這決定了紋理中哪個位置的顏色將被取樣。
    • Sampler State:決定如何取樣紋理的採樣器狀態。
  • 這個節點還有兩個額外選項:
    • Type:這個選項決定節點的取樣行為:
      • 當選擇 Default 時,該節點會取樣紋理的顏色(顏色值)。
      • 當選擇 Normal 時,該節點會取樣法線圖(Normal Map),這樣可以獲得法線的資訊來用於光照計算等效果。
    • Space:這個選項只有在 Normal 模式下有效,決定輸出的法線信息應該處於哪個空間:
      • Object:???。
      • Tabgent:???。

在 SpriteRenderer 有時候會有 Material does not have a _MainTex texture property. It is required for SpriteRenderer 的警告,這表示 你的 Shader 沒有 _MainTex 屬性。可以這麼理解:在 Shader Graph 中,如果你設定了一個 Texture2D 屬性,並將它的 Reference 設為 _MainTex,那麼,當這個 Shader 被應用到 Sprite Renderer 的 Material 上時,Sprite Renderer 會自動把它的 Sprite 貼圖傳遞給 _MainTex。這樣 Shader 就能正確使用 Sprite 的貼圖,而不需要手動指定 Texture。


Time Node (時間節點) : 用來提供 時間資訊

  • Time: 返回自場景(scene)開始以來的時間(以秒為單位)。這個值隨著場景運行而增加。
  • Sine Time: sin(Time),產生 -1 到 1 之間的循環值,這個輸出經常用於創建周期性或擺動的效果。
  • Cosine Time: cos(Time),效果類似於 Sine Time,但它的波形會有不同的相位(即開始的點不同),通常用於不同的波動模式。
  • Delta Time: 返回自上一幀以來經過的時間(以秒為單位)。這個值用於幀率無關的動畫或運動計算,確保無論運行速度如何,行為都保持一致。
  • Smooth Delta: 類似於 Delta Time,但它會對多幀的時間差進行平滑處理,通過平均多個幀的時間差來減少突變,使過渡更加平滑。

當希望物件可以持續的動,或者 Shader 需要依賴時間來運作時,就可以使用 Time 節點,可以嘗試使用 Time 節點 來做 動畫漸變(Gradient)顏色(Color)紋理(Texture),以創造獨特的 Shader 效果。

下圖是一個簡單的使用方式,如果你想要產生 0 到 1 之間循環的數值,可以這樣做。

參考:


在 Unity 中使用 Unit test

本文記錄如何在 Unity 中使用內建的 Unity Test Runner 進行 Unit Test。 。

首先選擇 Window -> General -> Test Runner 開啟 Test Runner

Test Runner 中找到 Create a Test new Assembly Folder in the active path. 按鈕,按下。

按下後會建立一個 Tests 資料夾,裡面會幫你建立一個 Tests.asmdef 檔案,這個檔案是 Unity 的 Assembly Definition File (程序集定義文件),讓你可以管理測試腳本的組件。

之後,為了讓測試腳本知道你寫的腳本,你需要在你放腳本的地方(如 Asserts/Scripts) 建立一個 Assembly Definition File (Create -> Scripting -> Assembly Definition), 並取為自己想要的名稱,這邊命名為 MyScriptAssembly。

注意,如果你有使用 TextMeshPro 或是 DOTween 的話,你還需要在 MyScriptAssembly 中的 Assembly Definition References 加入 Unity.TextMeshPro 和 DOTween.Modules (預設不會有 DOTween.Modules,請參考 建立 DoTween 的 asmdef 檔案)。

接著回到 Tests.asmdef 檔案,將 MyScriptAssembly 加入到 Tests.asmdef 的 Assembly Definition References 中。

最後你就可以在 Unity 中開始使用 Unit Test 了,到 Tests 資料夾中,選擇 Create -> Testing -> C# Test Script 建立測試腳本。此外,你也可以在 Tests 資料夾中建立其他資料夾規劃你的測試。

建立測試腳本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NewTestScript
{
// A Test behaves as an ordinary method
[Test]
public void NewTestScriptSimplePasses()
{
// Use the Assert class to test conditions
}

// A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
// `yield return null;` to skip a frame.
[UnityTest]
public IEnumerator NewTestScriptWithEnumeratorPasses()
{
// Use the Assert class to test conditions.
// Use yield to skip a frame.
yield return null;
}
}

Test Runner 就可以看到測試了,可以一個一個選擇執行,或是執行全部。


一些注意事項:

  1. 如果要測試拋出例外之類的測試的話,需要使用 LogAssert.Expect 而不是 Assert.***

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 測試錯誤日誌
    [Test]
    public void TestLogError()
    {
    testComponent.LogError("Test error message");
    LogAssert.Expect(LogType.Error, "<color=red>❌</color>[<color=lightblue>TestObject</color>]: Test error message\n");
    }

    // 測試例外日誌
    [Test]
    public void TestLogException()
    {
    try {
    int a = 0;
    float b = 1 / a;
    }
    catch (System.Exception e)
    {
    testComponent.LogException(e, "My Message");
    }
    LogAssert.Expect(LogType.Exception, "DivideByZeroException: Attempted to divide by zero.");
    }
  2. SetUp 與 TearDown

    • SetUp: 每個測試案例開始前,會執行此方法。通常用來還原測試案例初始化狀態,確保測試案例不互相干擾。
    • TearDown: 每個測試案例完成後,會執行此方法。通常用來清除測試案例的狀態,確保測試案例不互相干擾。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      private class TestObject : MonoBehaviour { }

      private GameObject testGameObject;
      private TestObject testComponent;
      private LogCapture logCapture;

      [SetUp]
      public void Setup()
      {
      testGameObject = new GameObject("TestObject");
      testComponent = testGameObject.AddComponent<TestObject>();
      logCapture = new LogCapture();
      Application.logMessageReceived += logCapture.HandleLog;
      }

      [TearDown]
      public void Teardown()
      {
      Application.logMessageReceived -= logCapture.HandleLog;
      Object.DestroyImmediate(testGameObject);
      }

建立 DoTween 的 asmdef 檔案

一般預設是不會有 DoTween 的 asmdef 檔案,必須到 Tools -> Demigiant -> DOTween Utility Panel 中,開啟 DOTween Utility Panel
並找到 Create ASMDEF 按鈕 ,按下建立 asmdef 檔案。


使用粒子系統(Particle System) 製作 2D 煙霧效果

首先需要一張煙霧圖片,這邊使用 Photopea 來製作做煙霧圖片,
進入 Photopea 後,選擇 檔案 -> 新增 , 會彈出新增專案畫面。

  • 將寬高設定為 512 像素 。
  • DPI 設為 100 像素/Inch 。
  • 背景選擇白色。

選擇 檢視 -> 顯示 -> 格線 。啟用格線,方便我們之後對齊

選擇 編輯 -> 選項 打開選項畫面,調整格線大小,由於 寬高是 512 ,我們想要切為四等分,因此設定網格間隙為 512 / 4 = 128

接著選擇 濾鏡 -> 渲染 -> 雲彩 。 建立一個雲霧的圖片

再建立一個新專案,寬高一樣是 512 , 但是這次背景選 透明

回到有雲彩圖片的專案,選擇套索工具,羽化設為 20 px , 使用 套索工具 切出煙霧圖案

按下 ctrl + c 將切出的圖案複製 , 切換到 背景透明的專案,按下 ctrl + v 貼上。可以使用移動工具,移動煙霧圖片,對齊中心。 如下圖:

最後,輸出煙霧圖片為 PNG

至此,我們的煙霧圖片就準備完畢了。

接著到 Unity 編輯器中。將煙霧圖片複製到裡面

建立一個 Material,命名為 SmokeMaterial

將 SmokeMaterial 的 Shader 更改為 Particles/Unlit

  • Surface Type 設為 Transparent 。設為透明。
  • Surface InputsBase Map 設為剛剛的煙霧圖片。

建立一個 GameObject ,命名為 Smoke 。接著在這個物件下建立一個 Particle System , 命名為 Smoke Particle System

以下開始設定 Smoke Particle System ,
Transform 組件

  • 預設噴射方向是朝 Z 軸方向噴射,由於是 2D 遊戲,我們將其 Rotation 的 X 設為 -90 , 讓粒子系統朝 Y 軸方向噴射,在畫面上會呈現朝上噴射。

修改以下模組
主模組

  • Start Life Time : 設為 Random Between Two Constants, 25
  • Start Speed :設為 Random Between Two Constants, 34
  • Start Rotation :設為 Random Between Two Constants, -180180
  • Scaling ModeHierarchy 。 讓粒子系統可以直接隨父級物件縮放。

Emission 模組

  • Rate over Time40 。 每秒生成的40個粒子。

Shape 模組

  • ShapeCone

Color over Lifetime 模組

  • 開始時顏色為白色且透明,逐漸變得不透明,然後變為黑色並再次變透明,模擬煙霧散去的效果。

Texture Sheet Animation 模組

  • ModeGrid 。確保煙霧圖片已經被切割為 2×2 的動畫格,這樣才能正確使用 Grid 模式來播放動畫。
  • Tile : 圖片是 4 個,因此 X, Y 設為 2
    • X2
    • Y2
  • AnimationWhole Sheet 。 整張圖片
  • Time ModeLifeTime
  • Frame over Time : 選擇 Random Between Constants , 數字填 03

Renderer 模組

  • Material : 更改為上面建立的 SmokeMaterial

最後效果如圖

參考: https://www.bilibili.com/video/BV1UY4y1n76t?spm_id_from=333.788.videopod.sections&vd_source=f103d4eb21cc24456defbcf356882852

在 2D世界中使用粒子系統 (Particle system) 碰撞物體

使用粒子系統 (Particle system) 產生粒子,並讓粒子與其他物件碰撞。可以用來製作粒子推動其他物體的效果,

首先建立一個 GameObject , 依自己需要取名(這邊取名 WindObject )

在這個 GameObject 之下建立一個 Particle system (Effects -> Particle system) ,依自己需要取名(這邊取名 WindEffect )

修改以下模組參數:
主要模組中的
Duration1 , 設定粒子系統的持續時間。設為 1 意味著粒子系統的 一次發射周期持續 1 秒。

  • 如果粒子系統是循環播放的(Looping 開啟),它會每 1 秒重置並重新發射粒子。
  • 如果 Looping 關閉,粒子系統只會運行 1 秒後停止。

Start Lifetime0.5 , 每個粒子的生命週期(以秒為單位)。 設為 0.5 表示每個粒子在生成後僅存活 0.5 秒。當生命週期結束時,粒子將被銷毀。如果 Start Lifetime 設置為隨機範圍,則每個粒子會有不同的存活時間。
Start Speed2 , 粒子生成時的初始速度(以單位/秒為單位)。 設為 2 表示粒子從生成位置以每秒 2 單位的速度移動。
Gravity Source2D Physics 。 設為 2D Physics 表示粒子受到 Unity 2D 重力場的影響,會根據 Unity 2D 的 Physics2D.gravity 設定進行模擬。
Simulation SpaceWorld 。 設為 World 表示粒子生成後會與世界空間對齊,並且粒子位置不會受到粒子系統的移動影響。
Scaling ModeHierarchy 。 Hierarchy 模式指的是粒子系統會根據其所在的父級物件及其整個層級結構的縮放來影響粒子的大小、速度、壽命等屬性。讓粒子系統可以直接隨父級物件縮放,無需手動調整。
Max Particles10 。 粒子系統同一時間允許存在的最大粒子數。設為 10 表示在任何時刻,粒子系統內的粒子數不會超過 10 個。

Emission 模組
Rate over Time10 。 每秒生成的粒子數量。設置為 10 意味著粒子系統會在每秒內均勻生成 10 個粒子。

  • 會受到 Max Particles 與 Start Lifetime 限制,
    • 如果 Max Particles 設置為 10,且粒子的存活時間(Start Lifetime)足夠長,那麼粒子系統最多同時顯示 10 個粒子,即便 Rate over Time 想生成更多粒子,也會受到限制。
    • 當 Start Lifetime 為 0.5 時,粒子會在 0.5 秒內消失。因此,即使 Rate over Time 為 10,由於粒子存活時間短,每秒可能只有一半的粒子還存活。

Shape 模組
ShapeEdge 。 粒子會沿著一條 邊線(Edge) 發射,而不是從一個平面或三維體積中生成。
Radius0.1 。 當 Shape 設為 Edge 時,Radius 表示該邊線的長度(在視覺上呈現為一小段線段)。

Force over Lifetime
X-20 。 表示粒子在 X 軸方向 持續受到 -20 的力,造成向左的運動。
Y-5 。 粒子會向 負 Y 方向 持續加速,模擬下墜或重力效果。
Z0 。 表示粒子在 Z 軸方向 不受任何外力影響。
SpaceWorld 。 表示所有的力都基於 世界空間,而非粒子系統的局部空間。

Color over Lifetime 模組
Color : 將後段設為透明,讓粒子發射後逐漸變透明。

Size over Lifetime 模組
Size : 指定粒子的大小如何隨著其生命週期從生成到消亡逐漸改變,將它設為一開始小後面變大的曲線,

Collision 模組
TypeWorld 。 讓粒子會與世界空間中的物理對象(如場景中的碰撞器)發生碰撞,而不是局限於粒子系統本身。
Mode2D

Render 模組
Material : 用於指定粒子的材質,從而決定粒子的顯示樣式。

  • 需要建立一個 Material ,然後選擇 Sprite-Lit-Default

在 Unity 中使用 Google AdMob

以下說明如何在 Unity 中使用 Google AdMob

  1. 在 Unity 編輯器中, Edit -> Project Settings -> Package Manager ,輸入以下資料並儲存
    1
    2
    3
    Name: OpenUPM
    URL: https://package.openupm.com
    Scopes: com.google
  2. 在 Unity 編輯器中, Window -> Package Manager 開啟 Package Manager ,找到 Google Mobile Ads for Unity 並按下 Install 安裝。
  3. 在 Unity 編輯器中, Project Settings -> Player -> Android -> Publishing Settings -> Build 確認 Custom Gradle Properties TemplateCustom Gradle Settings Template 有被勾選。
  4. 在 Unity 編輯器中,選取 Assets -> External Dependency Manager -> Android Resolver -> Resolve ,讓 Unity External Dependency Manager 程式庫將已宣告的依附元件複製到 Unity 應用程式的 Assets/Plugins/Android 目錄中。
  5. 在 Unity 編輯器中,選取選單中的 Assets -> Google Mobile Ads -> Settings ,設定 AdMob 應用程式 ID
  6. 在載入廣告之前,需要呼叫 MobileAds.Initialize() 讓應用程式初始化 Google Mobile Ads SDK。初始化只需執行一次,最好是在應用程式啟動時執行。
    1
    2
    3
    4
    5
    6
    7
    public class LoadGoogleMobileAdsScript : MonoBehaviour
    {
    public void Start()
    {
    MobileAds.Initialize(initStatus => { });
    }
    }
  7. 可以使用以下 id 使用測試廣告進行測試
    1
    2
    3
    4
    5
    6
    7
    #if UNITY_ANDROID
    private string _adUnitId = "ca-app-pub-3940256099942544/6300978111";
    #elif UNITY_IPHONE
    private string _adUnitId = "ca-app-pub-3940256099942544/2934735716";
    #else
    private string _adUnitId = "unused";
    #endif
  8. 使用橫幅廣告 (Banner Ad)
    1. 使用 BannerView 類建立橫幅廣告
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // _adUnitId :BannerView 應從中載入廣告的廣告單元 ID。
      // AdSize :您要使用的廣告尺寸。詳情請參閱「橫幅廣告尺寸」。
      // AdPosition :橫幅廣告應放置的位置。AdPosition 列舉了有效的廣告排序值。
      BannerView bannerView = new BannerView(_adUnitId, AdSize.Banner, AdPosition.Top);
      ```
      2. 使用 request 載入廣告
      ```csharp
      var adRequest = new AdRequest();
      bannerView.LoadAd(adRequest);
    2. 使用後記得用銷毀廣告
      1
      2
      3
      4
      5
      if (bannerView != null)
      {
      bannerView.Destroy();
      bannerView = null;
      }
    3. 完整程式碼
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      public class BannerAd : MonoBehaviour
      {
      #if UNITY_ANDROID
      private string _adUnitId = "ca-app-pub-3940256099942544/6300978111";
      #elif UNITY_IPHONE
      private string _adUnitId = "ca-app-pub-3940256099942544/2934735716";
      #else
      private string _adUnitId = "unused";
      #endif

      private BannerView _bannerView;

      void Start()
      {
      LoadAd();
      }

      private void OnEnable()
      {
      LoadAd();
      }

      private void OnDisable()
      {
      DestroyAd();
      }

      public void CreateBannerView()
      {
      if (_bannerView != null)
      {
      DestroyAd();
      }
      _bannerView = new BannerView(_adUnitId, AdSize.Banner, AdPosition.Top);
      }

      public void LoadAd()
      {
      if (_bannerView == null)
      {
      CreateBannerView();
      }
      var adRequest = new AdRequest();
      _bannerView.LoadAd(adRequest);
      }

      public void DestroyAd()
      {
      if (_bannerView != null)
      {
      _bannerView.Destroy();
      _bannerView = null;
      }
      }
      }

Reference:

使用 AuidoMixer 管理聲音音量

Unity 的 AuidoMixer 可以讓開發者將遊戲音樂,音效分組,方便管理。
以下說明如何設定。

  1. 在 Unity 編輯器中, 找到 Create -> Audio -> AudioMixer , 建立一個 AudioMixer
  2. 建立 AudioMixer 之後,可以更改為自己需要的名稱,例如 MainAudioMixer
  3. 在剛剛建立的 AudioMixer ,使用滑鼠左鍵點擊 Groups 區域旁邊的 + , 建立兩個分組並命名為 MusicSoundEffect
  4. 找到有 AudioSource 的物件,將Group的欄位設定為對應的 Group ,將 AudioSource 分配到對應的分組,例如下圖將 Sound 的 Group 設為 SoundEffect 而 BackgroundMusic 的 Group 設為 Music
  5. 接著到 MainAudioMixer 物件,把它展開,會看到建立的 Group ,選擇 Music ,在 Inspector 上面,找到 Volume ,對著 Volume 按下滑鼠右鍵,選 Expose Volume (of Music) to script 。 SoundEffect 也是同樣的操作
  6. 在回到 MainAudioMixer , 找到右上角的 Exposed Parameters 把它展開,並把它們命名為自己想要的名稱,注意這邊的名稱會在腳本中使用,腳本想要透過 audioMixer.SetFloat 變更數值的話,傳入名稱要一致才行。
  7. 使用範例程式碼。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    using UnityEngine;
    using UnityEngine.UI;

    public class SettingMenu : MonoBehaviour
    {
    public AudioMixer audioMixer;

    public void SetSoundVolume(float value)
    {
    float dB = value > 0 ? Mathf.Log10(value) * 20 : -80f;
    audioMixer.SetFloat("sound", dB);
    }

    public void SetMusicVolume()
    {
    float dB = value > 0 ? Mathf.Log10(value) * 20 : -80f;
    audioMixer.SetFloat("music", dB);
    }
    }
    注意,audioMixer.SetFloat 接受的是分貝值範圍(20f 為最大,-80f 為最小),如果希望使用範圍為 0 ~ 1 的線性值,可透過 Mathf.Log10 進行轉換。
    • float dB = value > 0 ? Mathf.Log10(value) * 20 : -80f;
      • 當 value 小於 0 或等於 0 時,直接返回 -80f 表示靜音
      • 當 value 大於 0 時,計算 Mathf.Log10(value) * 20 ,
      • 當 value 等於 1 時, Log10(1) 會返回 0 , 剛好是 audioMixer 的原來音量

Reference: