ShaderLab學習總結

轉載html

Unity ShaderLab學習總結

Why Bothers?

爲何已經有ShaderForge這種可視化Shader編輯器、爲何Asset Store已經有那麼多炫酷的Shader組件可下載,仍是有必要學些Shader的編寫?
2014-0718-1607-11-33.png編程

  • 由於上面這些Shader工具/組件最終都是以Shader文件的形式而存在。
  • 須要開發人員/技術美術有能力對Shader進行功能分析、效率評估、選擇、優化、甚至是Debug。
  • 對於特殊的需求,可能仍是直接編寫Shader比較實際、高效。

總之,Shader編寫是重要的;但至於緊不緊急,視乎項目需求。windows

參考資源

  • Youtube:https://www.youtube.com/watch?v=hDJQXzajiPg (包括part1-6)。視頻是最佳的學習方式沒有之一,因此牆裂建議就算不看下文的全部內容,都要去看一下part1。
  • 書籍:《Unity 3D ShaderLab開發實戰詳解》
  • Unity各類官方文檔

涉及範圍

本文只討論Unity ShaderLab相關的知識和使用方法。但,數據結構

使用Shader

2014-0720-1007-25-36.png
如上圖,一句話總結:
1. GameObject裏有MeshRenderer,
2. MeshRenderer裏有Material列表,
3. 每一個Material裏有且只有一個Shader;
4. Material在編輯器暴露該Shader的可調屬性。
因此關鍵是怎麼編寫Shader。app

Shader基礎

編輯器

使用MonoDevelop這反人類的IDE來編寫Shader竟然是讓人滿意的。有語法高亮,無語法提示。
若是習慣VisualStudio,能夠以下實現.Shader文件的語法高亮。編輯器

  • 下載做者donaldwu本身添加的關鍵詞文件usertype.dat。其包括了Unity ShaderLab的部分關鍵字,和HLSL的全部關鍵字。關鍵字之後持續添加中。
  • 將下載的usertype.dat放到Microsoft Visual Studio xx.x\CommonX\IDE\文件夾下;
  • 打開VS,工具>選項>文本編輯器>文件擴展名,擴展名裏填「shader」,編輯器選VC++,點擊添加;
  • 重啓VS,Done。

Shader

Shader "ShaderLab Tutorials/TestShader"
{
    // ...
}

2014-0720-1707-17-42.png
Shader的名字會直接決定shader在material裏出現的路徑ide

SubShader

Shader "ShaderLab Tutorials/TestShader" {
    SubShader
    {
        //...
    }
}

一個Shader有多個SubShader。一個SubShader可理解爲一個Shader的一個渲染方案。即SubShader是爲了針對不一樣的顯卡而編寫的。每一個Shader至少1個SubShader、理論能夠無限多個,但每每兩三個就足夠。
SubShader和顯卡的兼容性判斷,和SubShader的標籤、Pass的標籤和顯卡支持的「Unity渲染路徑」有關。這些都會在下面逐一提到。函數

SubShader的Tag

Shader "ShaderLab Tutorials/TestShader" {
    SubShader
    {
        Tags { "Queue"="Geometry+10" "RenderType"="Opaque" }
        //...
    }
}

SubShader內部能夠有標籤(Tags)的定義。Tag指定了這個SubShader的渲染順序(時機),以及其餘的一些設置。工具

  • "Queue"標籤。定義渲染順序。預製的值爲
    • "Background"。值爲1000。好比用於天空盒。
    • "Geometry"。值爲2000。大部分物體在這個隊列。不透明的物體也在這裏。這個隊列內部的物體的渲染順序會有進一步的優化(應該是從近到遠,early-z test能夠剔除不需通過FS處理的片元)。其餘隊列的物體都是按空間位置的從遠到近進行渲染。
    • "AlphaTest"。值爲2450。已進行AlphaTest的物體在這個隊列。
    • "Transparent"。值爲3000。透明物體。
    • "Overlay"。值爲4000。好比鏡頭光暈。
    • 用戶能夠定義任意值,好比"Queue"="Geometry+10"
  • "RenderType"標籤。Unity能夠運行時替換符合特定RenderType的全部Shader。Camera.RenderWithShader或者Camera.SetReplacementShader配合使用。Unity內置的RenderType包括:
    • "Opaque":絕大部分不透明的物體都使用這個;
    • "Transparent":絕大部分透明的物體、包括粒子特效都使用這個;
    • "Background":天空盒都使用這個;
    • "Overlay":GUI、鏡頭光暈都使用這個;
    • 還有其餘可參考Rendering with Replaced Shaders;用戶也能夠定義任意本身的RenderType字符串。
  • "ForceNoShadowCasting",值爲"true"時,表示不接受陰影。
  • "IgnoreProjector",值爲"true"時,表示不接受Projector組件的投影。

Pass

Shader "ShaderLab Tutorials/TestShader" {
    SubShader {
        Pass
        {
            //...
        }
    }
}

一個SubShader(渲染方案)是由一個個Pass塊來執行的。每一個Pass都會消耗對應的一個DrawCall。在知足渲染效果的狀況下儘量地減小Pass的數量。性能

Pass的Tag

Shader "ShaderLab Tutorials/TestShader" {
    SubShader {
        Pass
        {
            Tags{ "LightMode"="ForwardBase" }
            //...
        }
    }
}

和SubShader有本身專屬的Tag相似,Pass也有Pass專屬的Tag。
其中最重要Tag是 "LightMode",指定Pass和Unity的哪種渲染路徑(「Rendering Path」)搭配使用。這裏須要描述的Tag取值可包括:

  • Always,永遠都渲染,但不處理光照
  • ShadowCaster,用於渲染產生陰影的物體
  • ShadowCollector,用於收集物體陰影到屏幕座標Buff裏。

其餘渲染路徑相關的Tag詳見下面章節「Unity渲染路徑種類」。
具體全部Tag取值,可參考ShaderLab syntax: Pass Tags

FallBack

Shader "ShaderLab Tutorials/TestShader"{
    SubShader { Pass {} }

    FallBack "Diffuse" // "Diffuse"即Unity預製的固有Shader
    // FallBack Off //將關閉FallBack
}

當本Shader的全部SubShader都不支持當前顯卡,就會使用FallBack語句指定的另外一個Shader。FallBack最好指定Unity本身預製的Shader實現,因其通常可以在當前全部顯卡運行。

Properties

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {
    _Range ("My Range", Range (0.02,0.15)) = 0.07 // sliders
    _Color ("My Color", Color) = (.34, .85, .92, 1) // color
    _2D ("My Texture 2D", 2D) = "" {} // textures
    _Rect("My Rectangle", Rect) = "name" { }
    _Cube ("My Cubemap", Cube) = "name" { }
    _Float ("My Float", Float) = 1
    _Vector ("My Vector", Vector) = (1,2,3,4)
    }

    // Shader
    SubShader{
        Pass{
          //...
          uniform float4 _Color;
          //...
          float4 frag() : COLOR{ return fixed4(_Color); }
          //...
          }
    }

    //fixed pipeline
    SubShader    {
        Pass{
            Color[_Color]
        }
    }
}
  • Shader在Unity編輯器暴露給美術的參數,經過Properties來實現。
  • 全部可能的參數如上所示。主要也就Float、Vector和Texture這3類。
  • 除了經過編輯器編輯Properties,腳本也能夠經過Material的接口(好比SetFloatSetTexture編輯)
  • 以後在Shader程序經過[name](固定管線)或直接name(可編程Shader)訪問這些屬性。

Shader中的數據類型

有3種基本數值類型:floathalffixed
這3種基本數值類型能夠再組成vector和matrix,好比half3是由3個half組成、float4x4是由16個float組成。

  • float:32位高精度浮點數。
  • half:16位中精度浮點數。範圍是[-6萬, +6萬],能精確到十進制的小數點後3.3位。
  • fixed:11位低精度浮點數。範圍是[-2, 2],精度是1/256。

數據類型影響性能

  • 精度夠用就好。
    • 顏色和單位向量,使用fixed
    • 其餘狀況,儘可能使用half(即範圍在[-6萬, +6萬]內、精確到小數點後3.3位);不然才使用float
  • 不要將低精度fixed類型轉換爲更高的精度,不然會產生性能問題。
  • 低精度fixed不要使用「swizzle」(即形如myFixed4.xyzwmyFixed2.xyxy,中文不知咋譯,「攪和
    訪問」?),不然會產生性能問題。

    做者donaldwu說:swizzle在編寫Shader裏是常常用到的,但到底怎樣纔算swizzle?myFixed4.x算不算?myFixed4.xyzw算不算?myFixed4.xyxy算不算?仍是都算?
    這個目前沒有找到權威的定義,因此爲了避免要影響效率,建議fixed儘可能不要出現上面任意一種形式。

Shader形態

Shader形態之1:固定管線

固定管線是爲了兼容老式顯卡。都是頂點光照。以後固定管線多是被Unity拋棄的功能,因此最好不學它、當它不存在。特徵是裏面出現了形以下面Material塊、沒有CGPROGRAMENDCG塊。

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {
    _Color ("My Color", Color) = (.34, .85, .92, 1) // color
    }

    // Fixed Pipeline
    SubShader
    {
        Pass
        {
            Material{
            Diffuse [_Color]
            Ambient [_Color]
            }

            Lighting On
        }
    }
}

Shader形態之2:可編程Shader

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {}

    SubShader
    {
        Pass
        {
          // ... the usual pass state setup ...

          CGPROGRAM
          // compilation directives for this snippet, e.g.:
          #pragma vertex vert
          #pragma fragment frag

          // the Cg/HLSL code itself
          float4 vert(float4 v:POSITION) : SV_POSITION{
              return mul(UNITY_MATRIX_MVP, v);
          }
          float4 frag() : COLOR{
              return fixed4(1.0, 0.0, 0.0, 1.0);
          }
          ENDCG
          // ... the rest of pass setup ...
          }
    }
}
  • 功能最強大、最自由的形態。
  • 特徵是在Pass裏出現CGPROGRAMENDCG
  • 編譯指令#pragma。詳見官網Cg snippets。其中重要的包括:
編譯指令 示例/含義
#pragma vertex name
#pragma fragment name
替換name,來指定Vertex Shader函數、Fragment Shader函數。
#pragma target name 替換name(爲2.03.0等)。設置編譯目標shader model的版本。
#pragma only_renderers name name ...
#pragma exclude_renderers name name...
#pragma only_renderers gles gles3
#pragma exclude_renderers d3d9 d3d11 opengl
只爲指定渲染平臺(render platform)編譯
  • 引用庫。經過形如#include "UnityCG.cginc"引入指定的庫。經常使用的就是UnityCG.cginc了。其餘庫詳見官網Built-in shader include files
  • ShaderLab內置值。Unity給Shader程序提供了便捷的、經常使用的值,好比下面例子中的UNITY_MATRIX_MVP就表明了這個時刻的MVP矩陣。詳見官網ShaderLab built-in values
  • Shader輸入輸出參數語義(Semantics)。在管線流程中每一個階段之間(好比Vertex Shader階段和FragmentShader階段之間)的輸入輸出參數,經過語義字符串,來指定參數的含義。經常使用的語義包括:COLORSV_PositionTEXCOORD[n]。完整的參數語義可見HLSL Semantic(因爲是HLSL的鏈接,因此可能不徹底在Unity裏可使用)。
  • 特別地,由於Vertex Shader的的輸入每每是管線的最開始,Unity爲此內置了經常使用的數據結構:
數據結構 含義
appdata_base vertex shader input with position, normal, one texture coordinate.
appdata_tan vertex shader input with position, normal, tangent, one texture coordinate.
appdata_full vertex shader input with position, normal, tangent, vertex color and two texture coordinates.
appdata_img vertex shader input with position and one texture coordinate.

Shader形態之3:SurfaceShader

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {   }

    // Surface Shader
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float4 color : COLOR;
      };
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = 1;
      }
      ENDCG
    }
    FallBack "Diffuse"
}
  • SurfaceShader能夠認爲是一個光照Shader的語法糖、一個光照VS/FS的生成器。減小了開發者寫重複代碼的須要。
  • 在手遊,因爲對性能要求比較高,因此不建議使用SurfaceShader。由於SurfaceShader是一個比較「通用」的功能,而通用每每致使性能不高。
  • 特徵是在SubShader裏出現CGPROGRAMENDCG塊。(而不是出如今Pass裏。由於SurfaceShader本身會編譯成多個Pass。)
  • 編譯指令是:
    #pragma surface surfaceFunction lightModel [optionalparams]
    • surfaceFunction:surfaceShader函數,形如void surf (Input IN, inout SurfaceOutput o)
    • lightModel:使用的光照模式。包括Lambert(漫反射)和BlinnPhong(鏡面反射)。
      • 也能夠本身定義光照函數。好比編譯指令爲#pragma surface surf MyCalc
  • 你定義輸入數據結構(好比上面的Input)、編寫本身的Surface函數處理輸入、最終輸出修改事後的SurfaceOutput。SurfaceOutput的定義爲
    struct SurfaceOutput {
      half3 Albedo; // 紋理顏色值(r, g, b)
      half3 Normal; // 法向量(x, y, z)
      half3 Emission; // 自發光顏色值(r, g, b)
      half Specular; // 鏡面反射度
      half Gloss; // 光澤度
      half Alpha; // 不透明度
    };

Unity渲染路徑(Rendering Path)種類

概述

開發者能夠在Unity工程的PlayerSettings設置對渲染路徑進行3選1:

  • Deferred Lighting,延遲光照路徑。3者中最高質量地還原光照陰影。光照性能只與最終像素數目有關,光源數量再多都不會影響性能。
  • Forward Rendering,順序渲染路徑。能發揮出Shader所有特性的渲染路徑,固然也就支持像素級光照。最經常使用、功能最自由,性能與光源數目*受光照物體數目有關,具體性能視乎其具體使用到的Shader的複雜度。
  • Vertex Lit,頂點光照路徑。頂點級光照。性能最高、兼容性最強、支持特性最少、品質最差。

渲染路徑的內部階段和Pass的LightMode標籤

每一個渲染路徑的內部會再分爲幾個階段。
而後,Shader裏的每一個Pass,均可以指定爲不一樣的LightMode。而LightMode實際就是說:「我但願這個Pass在這個XXX渲染路徑的這個YYY子階段被執行」。

Deferred Ligting

渲染路徑內部子階段 對應的LightMode 描述
Base Pass "PrepassBase" 渲染物體信息。即把法向量、高光度到一張ARGB32的物體信息紋理上,把深度信息保存在Z-Buff上。
Lighting Pass 無對應可編程Pass 根據Base Pass得出的物體信息,在屏幕座標系下,使用BlinnPhong光照模式,把光照信息渲染到ARGB32的光照信息紋理上(RGB表示diffuse顏色值、A表示高光度)
Final Pass "PrepassFinal" 根據光照信息紋理,物體再渲染一次,將光照信息、紋理信息和自發光信息最終混合。LightMap也在這個Pass進行。

Forward Rendering

渲染路徑內部子階段 對應的LightMode 描述
Base Pass "ForwardBase" 渲染:最亮一個的方向光光源(像素級)和對應的陰影、全部頂點級光源、LightMap、全部LightProbe的SH光源(Sphere Harmonic,球諧函數,效率超高的低頻光)、環境光、自發光。
Additional Passes "ForwardAdd" 其餘須要像素級渲染的的光源

注意到的是,在Forward Rendering中,光源多是像素級光源、頂點級光源或SH光源。其判斷標準是:

  • 配製成「Not Important」的光源都是頂點級光源和SH光源
  • 最亮的方向光永遠都是像素級光源
  • 配置成「Important」的都是像素級光源
  • 上面2種狀況加起來的像素級光源數目小於「Quality Settings」裏面的「Pixel Light Count」的話,會把第1種狀況的光源補爲額外的像素級光源。

另外,配置成「Auto」的光源有更復雜的判斷標註,截圖以下:

2014-0720-1607-31-40.png
具體可參考Forward Rendering Path Details

Vertex Lit

渲染路徑內部子階段 對應的LightMode 描述
Vertex "Vertex" 渲染無LightMap物體
VertexLMRGBM "VertexLMRGBM" 渲染有RGBM編碼的LightMap物體
VertexLM "VertexLM" 渲染有雙LDR編碼的LightMap物體

不一樣LightMode的Pass的被選擇

一個工程的渲染路徑是惟一的,但一個工程裏的Shader是容許配有不一樣LightMode的Pass的。在Unity,策略是「從工程配置的渲染路徑模式開始,按Deferred、Forward、VertxLit的順序,搜索最匹配的LightMode的一個Pass」。好比,在配置成Deferred路徑時,優先選有Deferred相關LightMode的Pass;找不到纔會選Forward相關的Pass;還找不到,纔會選VertexLit相關的Pass。再好比,在配置成Forward路徑時,優先選Forward相關的Pass;找不到纔會選VertexLit相關的Pass。

相關文章
相關標籤/搜索