淺談Unity中的Shader

一、Shader基礎知識

1.1、什麼是Shader

在講什麼是Shader之前我們先看看下面兩段代碼 

這裡寫圖片描述

這兩段代碼實現的功能都是提取 2D 圖像上每個像素點的顏色值,第一段代碼是用c++寫的,在cup上面運行,它需要循環遍歷每個像素點,第二段代碼是CG代碼,在GPU上面運行,它隻需要一行代碼就能實現同樣的功能。GPU是專門用來進行圖形處理的,而Shader,就是GPU執行的一段針對3D對象進行操作的程序。

維基百科上對shader的解釋是這樣

Shader(著色器)應用於計算機圖形學領域,指一組供計算機圖形資源在執行渲染任務時使用的指令,用於計算圖像的顏色或明暗。但近來,它也能用於處理一些特殊效果,或者視頻後處理。通俗地說,著色器告訴電腦如何用特有的一種方法去繪制物體。

程序員將著色器應用於圖形處理器(GPU)的可編程流水線,來實現三維應用程序。這樣的圖形處理器有別於傳統的固定流水線處理器,為GPU編程帶來更高的靈活性和適應性。以前固有的流水線隻能進行一些幾何變換和像素灰度計算。現在可編程流水線還能處理所有像素、頂點、紋理的位置、色調、飽和度、明度、對比度並實時地繪制圖像。著色器還能產生如模糊、高光、有體積光源、失焦、卡通渲染、色調分離、畸變、凹凸貼圖、邊緣檢測、運動檢測等效果。

1.2、OpenGL的渲染流程

知道瞭什麼是shader,我們再來瞭解一下shader的種類,首先我先介紹一下OpenGL的渲染流程

這裡寫圖片描述

上圖便是OpenGL的渲染流程,將這個流程簡化之後是這樣的 

頂點變換 → 圖元裝配和光柵化 → 片元紋理映射和著色 → 寫入幀緩存

在頂點變換和片元著色這兩步時,我們就可以對其編程,進行各種操作,其他的部分我們是沒法進行編程的。我們的shader就是作用於頂點變換和片元著色這兩個部分的。

1.3、shader的種類

知道瞭shader起作用的地點,我們現在可以瞭解一下shader的種類瞭。

shader按管線分類一般分為固定渲染管線與可編程渲染管線。固定渲染管線就是功能固定的管線,比如物體表面光的折射,反射的算法是固定無法修改的,我們隻能對這些功能進行配置,比如開啟或關閉反射效果,霧化效果等。因為這種管線功能固定,無法在程序上對物體細節的表現給予更多更自由的控制,無法達到更多我們想要的畫面效果。所以現在的顯卡都是可編程渲染管線,也就是曾經那些我們固定無法修改的部門現在可以編程去修改,自由度高瞭之後,我們也就能實現更多自己想要的特效瞭。

1.4、shader的開發語言

知道瞭shader的種類,我在來說說shader的開發語言

HLSL: 主要用於Direct3D。平臺:windows。

GLSL: 主要用於OpenGL。 平臺:移動平臺(iOS,安卓),mac(only use when you target Mac OS X or OpenGL ES 2.0)

CG:與DirectX 9.0以上以及OpenGL 完全兼容。運行時或事先編譯成GPU匯編代碼。CG比HLSL、GLSL支持更多的平臺,Unity Shader采用CG/HLSL作為開發語言。

二、Unity中Shader知識介紹

2.1、shader在GPU的渲染流程

這裡寫圖片描述

進入GPU運算首先進行的是Vertex Processor頂點處理器,這個部分就需要我們使用Vertex Shader頂點著色器,頂點著色器運算的結果會交給Pixel Processor像素處理器,也就是片段處理器,在這個部分我需要為像素處理編寫Pixel Shader像素著色器程序,這部分計算完後就輸出瞭最終我們可以用於在屏幕上的顏色信息,我們把它叫做Frame Buffer幀緩沖。幀緩沖存儲的是計算機依次顯示所要的數據。

2.2、Unity中shader的類型

①Fixed function shader :屬於固定渲染管線 Shader, 基本用於高級Shader在老顯卡無法顯示時的回滾。使用的是ShaderLab語言,語法與微軟的FX files 或者NVIDIA的 CgFX類似。

②Vertex and Fragment Shader:最強大的Shader類型,屬於可編程渲染管線. 使用的是CG/HLSL語法。

③Surface Shader:Unity3d推崇的Shader類型,使用Unity預制的光照模型來進行光照運算。使用的也是CG/HLSL語法。

我們先瞭解一下這三種shader的異同點。

相同點: 

①都必須從唯一一個根Shader開始 
②Properties參數部分,作用及語法完全相同 
③具體功能都在SubShader裡(Subshader會自上而下運行第一個硬件能支持的) 
④SubShader都可以打標簽 
⑤都可以回滾 
⑥都可以處理基本的功能,例如光照漫反射(Diffuse)以及鏡面反射(Specular)。但是Vertex and Fragment和Surface都能實現Fixed function實現不瞭的高級功能,例如基於uv計算的效果等等。

不同點 

①Fixed function shader以及Vertex and Fragment Shader在subshader下面還有pass{}結構,但是Surface Shader,已經將具體內容打包在光照模型瞭,不能加pass{} 
②Fixed function shader每句代碼之後沒有分號“;”, 但是V&F shader以及Surface shader每句代碼之後都必須加分號“;” 
③核心結構不同 

Fixed function shader的SunShader中的結構為

Material{} 
……
SetTexture[_MainTex]{
    ……
}

Vertex and Fragment Shader的核心結構為

CGPROGRAM
#pragma vertex vert
#pragma fragment frag   
……       
#include "UnityCG.cginc"
ENDCG

Surface Shader的核心結構是

CGPROGRAM
#pragma surface surf Lambert
……
ENDCG

可以看到這三種shader的Subshader內的編碼實現是不一樣的,這三種shader的結構如下圖,他們的不同點都在SubShader裡面 

這裡寫圖片描述

因為Unity推薦Surface Shader,所以文章直接分析Surface Shader的用法,其他兩種shader就不做過多介紹瞭。

三、Surface Shader語法

在Unity的項目面板中直接創建一個Stander surface shader,默認生成的代碼如下

Shader "Custom/DiffuseShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)       //設置一個默認的顏色值
        _MainTex ("Albedo (RGB)", 2D) = "white" {}  //默認的白色紋理
        _Glossiness ("Smoothness", Range(0,1)) = 0.5  //默認的光澤度
        _Metallic ("Metallic", Range(0,1)) = 0.0    //金屬光澤度
    }
    SubShader {
        Tags { "RenderType"="Opaque"}
        LOD 200
 
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
 
        fixed4 _Color;
        sampler2D _MainTex;
        half _Glossiness;
        half _Metallic;
 
 
        struct Input {
            float2 uv_MainTex;
        };
 
 
 
        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

接下來我來介紹一下這段代碼

Properties {}

Properties{}是定義著色器屬性的,在這裡定義的屬性將被作為輸入提供給所有的子著色器。屬性定義的格式如下

_Name(“Display Name”, type) = defaultValue[{options}]

_Name代表的是屬性名,如Color,MainTex,Glossiness ,Metallic 等

”Display Name”則是在Inspector中顯示的名字

type代表屬性:

Color – 一種顏色,由RGBA(紅綠藍和透明度)四個量來定義;

2D – 一張2的階數大小(256,512之類)的貼圖。這張貼圖將在采樣後被轉為對應基於模型UV的每個像素的顏色,最終被顯示出來;

Rect – 一個非2階數大小的貼圖;

Cube – 即Cube map texture(立方體紋理),簡單說就是6張有聯系的2D貼圖的組合,主要用來做反射效果(比如天空盒和動態反射),也會被轉換為對應點的采樣;

Range(min, max) – 一個介於最小值和最大值之間的浮點數,一般用來當作調整Shader某些特性的參數(比如透明度渲染的截止值可以是從0至1的值等);

Float – 任意一個浮點數;

Vector – 一個四維數;

這段默認Properties在Inspector中的顯示效果如下 

這裡寫圖片描述

SubShader{}

Tags :tags標簽是三種類型的shader都具有的標簽,它決定瞭硬件什麼調用該子著色器

Tags標簽裡面默認的“RenderType”=”Opaque”,是告訴系統應該在渲染非透明物體時調用這個SubShader

“RenderType”=”Transparent”表示在渲染含有透明效果的物體時調用該Sunshader,

Tags裡面還有許多其他的我們可選的標簽

①.”Queue”:定義渲染順序。預制的值有這些 

    ”Background”。值為1000。比如用於天空盒。

    ”Geometry”。值為2000。大部分物體在這個隊列。不透明的物體也在這裡。

    ”AlphaTest”。值為2450。已進行AlphaTest的物體在這個隊列。 

    ”Transparent”。值為3000。透明物體。 

    ”Overlay”。值為4000。比如鏡頭光暈。 

    用戶可以定義任意值,比如”Queue”=”Geometry+10”

②“RenderType”:定義渲染類型。預制的值有這些

    ”Opaque”:絕大部分不透明的物體都使用這個; 

    ”Transparent”:絕大部分透明的物體、包括粒子特效都使用這個; 

    ”Background”:天空盒都使用這個; 

    ”Overlay”:GUI、鏡頭光暈都使用這個; 

③”ForceNoShadowCasting”:定義物體是否有陰影效果

    “true”。表示有陰影

    “false”。表示沒有陰影

LOD:Level of Detail的縮寫,它表示著色器的細節層次效果。在某些硬件比較差的系統上,我們可以設置一個低一點的值,減少細節的顯示。Unity內置shader的LOD值如下

  • VertexLit kind of shaders = 100
  • Decal, Reflective VertexLit = 150
  • Diffuse = 200
  • Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
  • Bumped, Specular = 300
  • Bumped Specular = 400
  • Parallax = 500
  • Parallax Specular = 600

從CGPROGRAM 到ENDCG這一部分就這這個shader的核心內容瞭

#pragma surface surf Standard fullforwardshadows

這段編譯指令聲明瞭我們要寫一個Surface Shader,並指定瞭光照模型。它的寫法是這樣的

#pragma surface surfaceFunction lightModel [optionalparams]

surface – 聲明的是一個表面著色器surfaceFunction – 著色器代碼的方法的名字lightModel – 使用的光照模型。

這段代碼默認的surfaceFunction為surf,我們可以在源碼的底部看到在這兒聲明瞭的surf函數。默認的lightModel為Standard。

下面我先介紹一下lightModel光照模型

  • Lambert:該光照模型能很好的表示粗糙表面的光照,但不能表現出鏡面反射高光
  • Toon:最近在遊戲中常用的風格之一即是Toon shading(又稱 cel shading).這是一種非逼真渲染風格,通過改變瞭光在一個模型上反射實際情況來給人以手繪的感覺
  • BlinnPhong:仿真鏡面反射材料
  • Standard:Unity5中默認的光照模式是Standard, 其引入瞭 物理渲染 (PBR), 但是與其它光照模型沒有什麼不同。相比於朗伯反射, PBR提供瞭一個更加逼真的光線物體作用模型,PBR考慮瞭材料的物理屬性, 比如能量守恒以及光的散射

接下來的這段代碼

fixed4 _Color;
sampler2D _MainTex;
half _Glossiness;
half _Metallic;

我們可以發現 _Color,_MainTex,_Glossiness,_Metallic都shader屬性的聲明,在上面的Properties 中已經聲明過瞭這些屬性,但是在這段CG程序,要想訪問在Properties中所定義的變量的話,必須使用和之前變量相同的名字再次進行聲明,其實就是鏈接在上面properties中聲明的屬性。

我再來介紹一下shader中常用的數據類型

3種基本數值類型:float、half和fixed。 
這3種基本數值類型可以再組成vector和matrix,比如half3是由3個half組成、float4x4是由16個float組成。

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

接下來就是Input結構體

struct Input {
    float2 uv_MainTex;
};

這個結構體和surf函數中的另一個參數inout結構體是相對的,一個代表輸入,一個代表輸出。我們可以這樣理解這兩個結構體,你定義輸入數據結構(Inputs Struct)、編寫自己的Surface函數處理輸入、最終輸出修改過後的SurfaceOutput。Input其實是需要我們去定義的結構,所以我們可以把所需要參與計算的數據都放到這個Input結構中,傳入surf函數使用

默認的Input結構體中有一個uv_MainTex參數,代表瞭紋理的UV值,我們便可以在surf函數中直接使用這個參數瞭。

知道瞭Input的結構體,我們在來看看Output的結構體

struct SurfaceOutput
{
    fixed3 Albedo;  // diffuse color  漫反射的顏色值。
    fixed3 Normal;  // tangent space normal, if written 法線坐標
    fixed3 Emission;  //自發光顏色
    half Specular;  // specular power in 0..1 range 鏡面反射系數
    fixed Gloss;    // specular intensity   光澤系數
    fixed Alpha;    // alpha for transparencies  透明度系數
};

現在我們在來看看surf函數裡面的內容,就已經能夠看懂瞭

我們現在在來看看surf函數裡面的代碼,就能知道裡面是什麼意思瞭

void surf (Input IN, inout SurfaceOutputStandard o) {
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;               //將物體顯示的漫反射顏色設置成在紋理的顏色值
    o.Metallic = _Metallic;         //將物體顯示的金屬光澤設置成在properties中定義的光澤
    o.Smoothness = _Glossiness;     //設置物體顯示的光滑度
    o.Alpha = c.a;                  //設置物體顯示的透明度
}

以上就是淺談Unity中的Shader的詳細內容,更多關於Unity Shader的資料請關註WalkonNet其它相關文章!

推薦閱讀: