Shader入門

<轉(zhuǎn)>我也忘了轉(zhuǎn)自哪里,抱歉,感謝原作者

什么是Shader

Shader(著色器)是一段能夠針對3D對象進(jìn)行操作、并被GPU所執(zhí)行的程序。Shader并不是一個統(tǒng)一的標(biāo)準(zhǔn),不同的圖形接口的Shader并不相同。OpenGL的著色語言是GLSL, NVidia開發(fā)了Cg,而微軟的Direct3D使用高級著色器語言(HLSL)。而Unity的Shader 是將傳統(tǒng)的圖形接口的Shader(由 Cg / HLSL編寫)嵌入到獨有的描述性結(jié)構(gòu)中而形成的一種代碼生成框架,最終會自動生成各硬件平臺自己的Shader,從而實現(xiàn)跨平臺。

Unity Shader 其實并不難,初學(xué)者往往很迷惑是因為它有太多固定的命令和結(jié)構(gòu),而這些命令又需要我們對3D渲染有一定的了解才能知道它們是做什么的。


Shader種類

OpenGL和Direct3D都提供了三類著色器:

頂點著色器:處理每個頂點,將頂點的空間位置投影在屏幕上,即計算頂點的二維坐標(biāo)。同時,它也負(fù)責(zé)頂點的深度緩沖(Z-Buffer)的計算。頂點著色器可以掌控頂點的位置、顏色和紋理坐標(biāo)等屬性,但無法生成新的頂點。頂點著色器的輸出傳遞到流水線的下一步。如果有之后定義了幾何著色器,則幾何著色器會處理頂點著色器的輸出數(shù)據(jù),否則,光柵化器繼續(xù)流水線任務(wù)。

像素著色器(Direct3D),常常又稱為片斷著色器(OpenGL):處理來自光柵化器的數(shù)據(jù)。光柵化器已經(jīng)將多邊形填滿并通過流水線傳送至像素著色器,后者逐像素計算顏色。像素著色器常用來處理場景光照和與之相關(guān)的效果,如凸凹紋理映射和調(diào)色。名稱片斷著色器似乎更為準(zhǔn)確,因為對于著色器的調(diào)用和屏幕上像素的顯示并非一一對應(yīng)。舉個例子,對于一個像素,片斷著色器可能會被調(diào)用若干次來決定它最終的顏色,那些被遮擋的物體也會被計算,直到最后的深度緩沖才將各物體前后排序。

幾何著色器:可以從多邊形網(wǎng)格中增刪頂點。它能夠執(zhí)行對CPU來說過于繁重的生成幾何結(jié)構(gòu)和增加模型細(xì)節(jié)的工作。Direct3D版本10增加了支持幾何著色器的API, 成為Shader Model 4.0的組成部分。OpenGL只可通過它的一個插件來使用幾何著色器。

Unity Shader 分為 表面著色器(Surface Shader)和 頂點片段著色器(Vertex And Fragment Shader)。

表面著色器(Surface Shader)是Unity提出的一個概念。編寫著色器與光照的交互是復(fù)雜的,光源有很多類型,不同的陰影選項,不同的渲染路徑(正向和延時渲染),表面著色器將這一部分簡化。Unity建議使用表面著色器來編寫和光照有關(guān)的Shader。

頂點片段著色器(Vertex And Fragment Shader)和OpenGL,Direct3D中的頂點著色器和片段著色器沒有什么區(qū)別。頂點片段著色器比表面著色器使用更自由也更強(qiáng)大,當(dāng)然光照需要自行處理。Unity也允許在里面編寫幾何著色器,一般用得不多。


Shader程序結(jié)構(gòu)

//Shader語法:
Shader "name" { [Properties] Subshaders [Fallback] [CustomEditor] }

//Properties 語法
Properties { Property [Property ...] }

// Subshader 語法
Subshader { [Tags] [CommonState] Passdef [Passdef ...] }

// Pass 語法
Pass { [Name and Tags] [RenderSetup] }

// Fallback 語法
Fallback "name"

屬性定義(Property Definition):定義Shader的輸入,這些輸入可以在材質(zhì)編輯的時候指定

子著色器(SubShader):一個Shader可以有多個子著色器。這些子著色器互不相干且只有一個會在最終的平臺運行。編寫多個的目的是解決兼容性問題。Unity會自己選擇兼容終端平臺的Shader運行。

回滾(Fallback):如果子著色器在終端平臺上都無法運行,那么使用Fallback指定的備用Shader,俗稱備胎。

Pass:一個Pass就是一次繪制。對于表面著色器,只能有一個Pass,所以不存在Pass節(jié)。頂點片段著色器可以有多個Pass。多次Pass可以實現(xiàn)很多特殊效果,例如當(dāng)人物被環(huán)境遮擋時還可以看到人物輪廓就可以用多Pass來實現(xiàn)。

Cg代碼:每個Pass中都可以包含自定義的Cg代碼,從CGPROGRAM開始到ENDCG結(jié)束。

表面著色器及頂點著色器:

Shader "Custom/NewShader" {
??????? Properties {
??????????? _MainTex ("Base (RGB)", 2D) = "white" {}
??????? }
??????? SubShader {
??????????? Tags { "RenderType" = "Opaque" }
??????????? LOD 200

??????????? CGPROGRAM
??????????? #pragma surface surf Lambert

??????????? sampler2D _MainTex;

??????????? struct Input {
??????????????? float2 uv_MainTex;
??????????? };

??????????? void surf (Input IN, inout SurfaceOutput o) {
??????????????? half4 c = tex2D (_MainTex, IN.uv_MainTex);
??????????????? o.Albedo = c.rgb;
??????????????? o.Alpha = c.a;
??????????? }
??????????? ENDCG
??????? }
??????? FallBack "Diffuse"
??? }

Shader "VertexInputSimple" {
? SubShader {
??? Pass {
????? CGPROGRAM
????? #pragma vertex vert
????? #pragma fragment frag
????? #include "UnityCG.cginc"

????? struct v2f {
????????? float4 pos : SV_POSITION;
????????? fixed4 color : COLOR;
????? };

????? v2f vert (appdata_base v)
????? {
????????? v2f o;
????????? o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
????????? o.color.xyz = v.normal * 0.5 + 0.5;
????????? o.color.w = 1.0;
????????? return o;
????? }

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



Shader 輸入

Shader的輸入有兩個來源,一是通過屬性定義,一是通過Shader.SetGlobalXXX方法全局設(shè)置。

屬性定義變量:屬性定義中的變量是Shader參數(shù)的主要設(shè)置方式。 它是隨材質(zhì)變化的,每個使用該Shader的材質(zhì)都可以在Inspector或者腳本中設(shè)置這些參數(shù)。這些參數(shù)除了在Shader的Properties段中定義外,還需要在Cg中聲明方可使用。例如上面表面著色器的例子中我們定義了_MainTex這個類型為2D的屬性,還需要在Cg中聲明 sampler2D _MainTex。

全局變量:Shader有一組SetGlobalXXX方法,可以對Shader的在Cg中定義而沒有在屬性中定義的uniform變量進(jìn)行設(shè)置。這個設(shè)置是全局的,所有定義了該uniform的Shader都會受到影響。例如我們希望場景隨著時間變化而改變顏色,就可以給場景所使用到的Shader設(shè)置統(tǒng)一的全局顏色變量,然后在腳本中通過設(shè)置該顏色來改變場景的顏色。在角色釋放技能時場景變黑也可以使用這個方法。

Unity shader 中允許定義的屬性類型有:

Unity shader 中允許定義的屬性類型有:

關(guān)鍵字類型對應(yīng)Cg類型例

Float浮點數(shù)float_MyFloat (“My float”, Float) = 0.5Range浮點數(shù) (在指定范圍內(nèi))float_MyRange (“My Range”, Range(0.01, 0.5)) = 0.1Color浮點四元組float4_MyColor (“Some Color”, Color) = (1,1,1,1)Vector浮點四元組float4_MyVector(“Some Vector”,Vector) = (1,1,1,1)2D2的階數(shù)大小的貼圖sampler2D_MyTexture (“Texture”, 2D) = “white” {}Rect非2的階數(shù)大小的貼圖sampler2D_MyRect(“My Rect”, Rect) = “white” {}CUBECubeMapsamplerCUBE_MyCubemap (“Cubemap”, CUBE) = “” {}

注:CubeMap 是6張有聯(lián)系的2D貼圖的組合主要用來做反射效果(比如天空盒和動態(tài)反射)


SubShader

SubShader中除了Pass,有兩個標(biāo)簽值得關(guān)注:LOD和Tags

LOD

LOD是 Level of Detail的簡寫,確切地說是Shader Level of Detail的簡寫,因為Unity中還有一個模型的LOD概念,這是兩個不同的東西。我們這里只介紹Shader中LOD,模型的LOD請參考這里

Shader LOD 就是讓我們設(shè)置一個數(shù)值,這個數(shù)值決定了我們能用什么樣的Shader。可以通過Shader.maximumLOD或者Shader.globalMaximumLOD 設(shè)定允許的最大LOD,當(dāng)設(shè)定的LOD小于SubShader所指定的LOD時,這個SubShader將不可用。通過LOD,我們就可以為某個材質(zhì)寫一組SubShader,指定不同的LOD,LOD越大則渲染效果越好,當(dāng)然對硬件的要求也可能越高,然后根據(jù)不同的終端硬件配置來設(shè)置 globalMaximumLOD來達(dá)到兼顧性能的最佳顯示效果。

Unity內(nèi)建Shader定義了一組LOD的數(shù)值,我們在實現(xiàn)自己的Shader的時候可以將其作為參考來設(shè)定自己的LOD數(shù)值

VertexLit及其系列 = 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

Tag

SubShader可以被若干的標(biāo)簽(tags)所修飾,而硬件將通過判定這些標(biāo)簽來決定什么時候調(diào)用該著色器。?
比較常見的標(biāo)簽有:

Queue?


這個標(biāo)簽很重要,它定義了一個整數(shù),決定了Shader的渲染的次序,數(shù)字越小就越早被渲染,早渲染就意味著可能被后面渲染的東西覆蓋掉看不見。?


預(yù)定義的Queue有:

名字值描述

Background1000最早被調(diào)用的渲染,用來渲染天空盒或者背景Geometry2000這是默認(rèn)值,用來渲染非透明物體(普通情況下,場景中的絕大多數(shù)物體應(yīng)該是非透明的)AlphaTest2450用來渲染經(jīng)過Alpha Test的像素,單獨為AlphaTest設(shè)定一個Queue是出于對效率的考慮Transparent3000以從后往前的順序渲染透明物體Overlay4000用來渲染疊加的效果,是渲染的最后階段(比如鏡頭光暈等特效)

RenderType?


“Opaque”或”Transparent”是兩個常用的RenderType。如果輸出中都是非透明物體,那寫在Opaque里;如果想渲染透明或者半透明的像素,那應(yīng)該寫在Transparent中。這個Tag主要用

ShaderReplacement

,一般情況下這Tag好像也沒什么作用。

CommonState

SubShader中可以定義一組Render State,基本上就是一些渲染的開關(guān)選項,他們對該SubShader的所有的Pass都有效,所以稱Common。這些Render State也可以在每個Pass中分別定義,將在Pass中詳細(xì)介紹。


Pass

Render State

Render State主要就是控制渲染過程的一些開關(guān)選項,例如是否開啟alpha blending ,是否開啟depth testing。?
常用的Render State有:

Cull?
用法:Cull Back | Front | Off?
多邊形表面剔除開關(guān)。Back表示背面剔除,F(xiàn)ront表示正面剔除,Off表示關(guān)閉表面剔除即雙面渲染。有時候如裙擺,飄帶之類很薄的東西在建模時會做成一個面片,這就需要設(shè)置Cull Off來雙面渲染,否則背面會是黑色。

ZWrite?
用法:ZWrite On | Off?
控制當(dāng)前對象的像素是否寫入深度緩沖區(qū)(depth buffer),默認(rèn)是開啟的。一般來說繪制不透明物體的話ZWrite開啟,繪制透明或半透明物體則ZWrite關(guān)閉。?
深度緩沖區(qū):當(dāng)圖形處理卡渲染物體的時候,每一個所生成的像素的深度(即 z 坐標(biāo))就保存在一個緩沖區(qū)中。這個緩沖區(qū)叫作 z 緩沖區(qū)或者深度緩沖區(qū),這個緩沖區(qū)通常組織成一個保存每個屏幕像素深度的 x-y 二維數(shù)組。如果場景中的另外一個物體也在同一個像素生成渲染結(jié)果,那么圖形處理卡就會比較二者的深度,并且保留距離觀察者較近的物體。然后這個所保留的物體點深度保存到深度緩沖區(qū)中。最后,圖形卡就可以根據(jù)深度緩沖區(qū)正確地生成通常的深度感知效果:較近的物體遮擋較遠(yuǎn)的物體。?
理解了深度緩沖區(qū)也就理解了為什么繪制透明或半透明物體需要關(guān)閉ZWrite, 如果不關(guān)閉,透明物體的depth也會被寫入深度緩沖區(qū),從而會剔除掉它后面的物體,后面的物體就不會被渲染,看不見后面的物體還能叫透明嗎?因此我們使用Alpha blending的時候需要設(shè)置ZWrite Off。

ZTest?
用法:ZTest (Less | Greater | LEqual | GEqual | Equal | NotEqual | Always)?
控制如何進(jìn)行深度測試,也就是上面說的圖形處理卡比較二者的深度的比較方法。默認(rèn)是LEqual。?
值得一提的是使用Aplha blending的時候ZWrite需要關(guān)閉但是ZTest是要開啟的,因為如果透明物體前面還有不透明物體,透明物體還是應(yīng)該被遮擋剔除的。

Blend?
混合??刂屏嗣總€Shader的輸出如何和屏幕上已有的顏色混合。?
用法:?
Blend Off: 關(guān)閉混合?
Blend SrcFactor DstFactor:最終顏色 = Shader產(chǎn)生的顏色 × SrcFactor + 屏幕上原來的顏色 × DstFactor
Blend SrcFactor DstFactor, SrcFactorA DstFactor:和上面一樣,只是Alpha通道使用后面兩個參數(shù)計算?
常用的Blend模式有:?
Blend SrcAlpha OneMinusSrcAlpha // Alpha blending?
Blend One One // Additive?
Blend OneMinusDstColor One // Soft Additive?
Blend DstColor Zero // Multiplicative?
Blend DstColor SrcColor // 2x Multiplicative?
具體參考這里

Unity5開始下列固定功能的Shader命令被標(biāo)記為過時了,這些命令的功能現(xiàn)在建議在Shader(Cg)中通過代碼來實現(xiàn),這里列出是為了方便閱讀以前寫的Shader:

Lighting On | Off

Material { Material Block }

SeparateSpecular On | Off

Color Color-value

ColorMaterial AmbientAndDiffuse | Emission

Fog { Fog Block }

AlphaTest (Less | Greater | LEqual | GEqual | Equal | NotEqual | Always) CutoffValue

SetTexture textureProperty { combine options }


Surface Shader

Surface Shader 隱藏了很多光照處理的細(xì)節(jié),它的設(shè)計初衷是為了讓用戶僅僅使用一些指令(#pragma)就可以完成很多事情,并且封裝了很多常用的光照模型和函數(shù)。相比底層的Vertex And Fragment Shader,Suface Shader的限制比較多,它只能有一次Pass。如果做一些常規(guī)的功能又需要光照,可以用Surface Shader寫,比較快速便捷。如果要寫比較高級的Shader還是建議使用Vertex Shader 和 Fragment Shader。?
Surface Shader主要有兩部分組成,一個是#pragma后面的指令,一個是surf函數(shù)。?
pragma的語法是?#pragma surface surfaceFunction lightModel [optionalparams]?
- surfaceFunction 通常就是名為surf的函數(shù), 函數(shù)名可以自己取?
surf函數(shù)原型是:void surf (Input IN, inout SurfaceOutput o)?
- lightModel是Unity內(nèi)置的光照模型,可以是Lambert,Blinn-Phong等。?
- optionalparams: 包含很多指令?詳細(xì)參數(shù)參考這里

surf函數(shù)主要有一個Input結(jié)構(gòu)的輸入和SurfaceOutput結(jié)構(gòu)的輸出。

Input

Input 結(jié)構(gòu)需要在Shader中定義。它可以包含如下字段, 如果你定義了這些字段就可以在surf函數(shù)中使用它們(好神奇的黑科技)

多個貼圖的uv坐標(biāo),名字必須符合格式uv+貼圖名。例如?

float2 uv_MainTex

float3 viewDir - 視圖方向( view direction)值。為了計算視差效果(Parallax effects),邊緣光照(rim lighting)等,需要包含視圖方向( view direction)值。

float4 with COLOR semantic - 每個頂點(per-vertex)顏色的插值。

float4 screenPos - 屏幕空間中的位置。 為了反射效果,需要包含屏幕空間中的位置信息。比如在Dark Unity中所使用的 WetStreet著色器。

float3 worldPos - 世界空間中的位置。

float3 worldRefl - 世界空間中的反射向量。如果表面著色器(surface shader)不寫入法線(o.Normal)參數(shù),將包含這個參數(shù)。 請參考這個例子:Reflect-Diffuse 著色器。

float3 worldNormal - 世界空間中的法線向量(normal vector)。如果表面著色器(surface shader)不寫入法線(o.Normal)參數(shù),將包含這個參數(shù)。

float3 worldRefl; INTERNAL_DATA - 世界空間中的反射向量。如果表面著色器(surface shader)不寫入法線(o.Normal)參數(shù),將包含這個參數(shù)。

float3 worldNormal; INTERNAL_DATA -世界空間中的法線向量(normal vector)。如果表面著色器(surface shader)不寫入法線(o.Normal)參數(shù),將包含這個參數(shù)。

SurfaceOutput

SurfaceOutput 描述了表面的特性(光照的顏色反射率、法線、散射、鏡面等),這個結(jié)構(gòu)是固定的,不需要在Shader中再定義。

struct SurfaceOutput {
??? half3 Albedo;?????? //反射率,一般就是在光照之前的原始顏色
??? half3 Normal;?????? //法線
??? half3 Emission;???? //自發(fā)光,用于增強(qiáng)物體自身的亮度,使之看起來好像可以自己發(fā)光
??? half Specular;????? //鏡面
??? half Gloss;???????? //光澤
??? half Alpha;???????? //透明
};

Unity5 由于引入了基于物理的光照模型,所以新增加了兩個Output

struct SurfaceOutputStandard
{
??? fixed3 Albedo;????? // base (diffuse or specular) color
??? fixed3 Normal;????? // tangent space normal, if written
??? half3 Emission;
??? half Metallic;????? // 0=non-metal, 1=metal
??? half Smoothness;??? // 0=rough, 1=smooth
??? half Occlusion;???? // occlusion (default 1)
??? fixed Alpha;??????? // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{
??? fixed3 Albedo;????? // diffuse color
??? fixed3 Specular;??? // specular color
??? fixed3 Normal;????? // tangent space normal, if written
??? half3 Emission;
??? half Smoothness;??? // 0=rough, 1=smooth
??? half Occlusion;???? // occlusion (default 1)
??? fixed Alpha;??????? // alpha for transparencies
};

Unity提供了一些基本的SurfaceShader的例子,有助于我們理解輸入輸出是如何被使用的。?


Vertex Shader

如果不想使用Surface Shader而直接編寫opengl和Direct3D中常見的頂點著色器和片段著色器,可以通過Cg代碼段嵌入到Pass中:

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

????? ENDCG
????? // ... the rest of pass setup ...
? }

其中vert就是頂點著色器函數(shù),frag就是片段著色器函數(shù)。一般來說,可以在頂點著色器中進(jìn)行的計算就不應(yīng)該放到片段著色器中去算,因為頂點著色器是逐頂點計算的而片段著色器是逐像素計算的,一個模型頂點總比表明像素少很多吧。

編寫頂點和片段著色器一般需要包含Unity預(yù)定義的一個幫助文件UnityCG.cginc,里面預(yù)定義了一些常用的結(jié)構(gòu)和方法。Windows版Unity這個文件位于({unity install path}/Data/CGIncludes/UnityCG.cginc。 Mac版位于/Applications/Unity/Unity.app/Contents/CGIncludes/UnityCG.cginc

在代碼中我們只需要添加?#include "UnityCG.cginc"就可以使用里面的結(jié)構(gòu)和方法。

Input

頂點著色器的原型是 v2f vert (appdata v)?
appdata 是輸入,可以自己定義也可以使用Unity預(yù)定義的。Unity在UnityCG.cginc預(yù)定義了三種常用的輸入結(jié)構(gòu):appdata_base,appdata_tan,appdata_full。

struct appdata_base {
??? float4 vertex : POSITION;
??? float3 normal : NORMAL;
??? float4 texcoord : TEXCOORD0;
};

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

struct appdata_full {
??? float4 vertex : POSITION;
??? float4 tangent : TANGENT;
??? float3 normal : NORMAL;
??? float4 texcoord : TEXCOORD0;
??? float4 texcoord1 : TEXCOORD1;
??? float4 texcoord2 : TEXCOORD2;
??? float4 texcoord3 : TEXCOORD3;
#if defined(SHADER_API_XBOX360)
??? half4 texcoord4 : TEXCOORD4;
??? half4 texcoord5 : TEXCOORD5;
#endif
??? fixed4 color : COLOR;
};

    我們注意到這些結(jié)構(gòu)的字段和表面著色器中的字段不同,后面多了一個冒號和一個標(biāo)簽。這是該字段的語義,用于告訴GPU這個字段的數(shù)據(jù)應(yīng)該去哪里讀寫。GPU畢竟是為了圖形計算而特別設(shè)計的東西,很多東西都是固定的,我們只要記得有這么幾個名字可以用行了。

    類型名字標(biāo)簽備注

    float4vertexPOSITION頂點在模型坐標(biāo)系下的位置float3normalNORMAL頂點的法向量float4tangentTANGENT頂點的切向量float4colorCOLOR頂點色float4texcoordTEXCOORD0頂點的第一個uv坐標(biāo)float4texcoord1TEXCOORD1頂點的第二個uv坐標(biāo),最多可以到5

    Output

    頂點著色器的輸出是也是一個可以自己定義的結(jié)構(gòu),但是結(jié)構(gòu)內(nèi)容也是比較固定的,一般包含了頂點投影后的位置,uv,頂點色等,也可以加一些后面片段著色器需要用到但是需要在頂點著色器中計算的值。這個輸出就是后面片段著色器的輸入。

    ?struct v2f
    {
    ??? float4 pos : SV_POSITION;
    ??? half2 uv?? : TEXCOORD0;
    };

    可以使用的字段有:

    類型標(biāo)簽描述

    float4SV_POSITION頂點在投影空間下的位置,注意和輸入的模型坐標(biāo)系下的位置不同,這個字段必必須設(shè)置,這個坐標(biāo)轉(zhuǎn)換是頂點著色器的重要工作float3NORMAL頂點在視圖坐標(biāo)系下的法向量float4TEXCOORD0第一張貼圖的uv坐標(biāo)float4TEXCOORD1第二張貼圖的uv坐標(biāo)float4TANGENT切向量,主要用來修正法線貼圖Normal Mapsfixed4COLOR第一個定點色fixed4COLOR1第二個定點色AnyAny其他自定義的字段

    坐標(biāo)變換

    頂點著色器有一項重要的工作就是進(jìn)行坐標(biāo)變換。頂點著色器的輸入中的坐標(biāo)是模型坐標(biāo)系(ObjectSpace)下的坐標(biāo),而最終繪制到屏幕上的是投影坐標(biāo)。?
    在我們Shader里面只需要一句話就可以完成坐標(biāo)的轉(zhuǎn)換,這也是最簡單的頂點著色器:

    v2f vert(appdata v) {
    ????????? v2f o;
    ????????? o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    ????????? return o;
    }

    用UNITY_MATRIX_MVP矩陣乘以頂點在模型坐標(biāo)系下的坐標(biāo)就得到投影坐標(biāo)。?
    UNITY_MATRIX_MVP是Unity內(nèi)建的模型->視->投影矩陣, Unity內(nèi)建矩陣如下:

    UNITY_MATRIX_MVP:當(dāng)前模型->視圖->投影矩陣。(注:模型矩陣為 本地->世界)

    UNITY_MATRIX_MV:當(dāng)前模型->視圖矩陣

    UNITY_MATRIX_V:當(dāng)前視圖矩陣

    UNITY_MATRIX_P:當(dāng)前投影矩陣

    UNITY_MATRIX_VP:當(dāng)前視圖->投影矩陣

    UNITY_MATRIX_T_MV:轉(zhuǎn)置模型->視圖矩陣

    UNITY_MATRIX_IT_MV:逆轉(zhuǎn)置模型->視矩陣, 用于將法線從ObjectSpace旋轉(zhuǎn)到WorldSpace。為什么法線變化不能和位置變換一樣用UNITY_MATRIX_MV呢?一是因為法線是3維的向量而- UNITY_MATRIX_MV是一個4x4矩陣,二是因為法線是向量,我們只希望對它旋轉(zhuǎn),但是在進(jìn)行空間變換的時候,如果發(fā)生非等比縮放,方向會發(fā)生偏移。

    UNITY_MATRIX_TEXTURE0 to UNITY_MATRIX_TEXTURE3:紋理變換矩陣

    下面簡單介紹一下里面提到的幾個坐標(biāo)系:?
    模型坐標(biāo)系:也叫物體坐標(biāo)系,3D建模的時候每個模型都是在自己的坐標(biāo)系下建立的,如果一個人物模型腳底是(0,0,0) 點的話它的身上其它點的坐標(biāo)都是相對腳底這個原點的。?
    世界坐標(biāo)系:我們場景是一個世界,有自己的原點,模型放置到場景中后模型上的每個頂點就有了一個新的世界坐標(biāo)。這個坐標(biāo)可以通過模型矩陣×模型上頂點的模型坐標(biāo)得到。?
    視圖坐標(biāo)系:又叫觀察坐標(biāo)系,是以觀察者(相機(jī))為原點的坐標(biāo)系。場景中的物體只有被相機(jī)觀察到才會繪制到屏幕上,相機(jī)可以設(shè)置視口大小和裁剪平面來控制可視范圍,這些都是相對相機(jī)來說的,所以需要把世界坐標(biāo)轉(zhuǎn)換到視圖坐標(biāo)系來方便處理。?
    投影坐標(biāo)系:場景是3D的,但是最終繪制到屏幕上是2D,投影坐標(biāo)系完成這個降維的工作,投影變換后3D的坐標(biāo)就變成2D的坐標(biāo)了。投影有平行投影和透視投影兩種,可以在Unity的相機(jī)上設(shè)置。?
    屏幕坐標(biāo)系 :?最終繪制到屏幕上的坐標(biāo)。屏幕的左下角為原點。

    除了內(nèi)建矩陣,Unity還內(nèi)建了一些輔助函數(shù)也可以在頂點著色器里面使用:

    float3 WorldSpaceViewDir (float4 v):根據(jù)給定的局部空間頂點位置到相機(jī)返回世界空間的方向(非規(guī)范化的)

    float3 ObjSpaceViewDir (float4 v):根據(jù)給定的局部空間頂點位置到相機(jī)返回局部空間的方向(非規(guī)范化的)

    float2 ParallaxOffset (half h, half height, half3 viewDir):為視差法線貼圖計算UV偏移

    fixed Luminance (fixed3 c):將顏色轉(zhuǎn)換為亮度(灰度)

    fixed3 DecodeLightmap (fixed4 color):從Unity光照貼圖解碼顏色(基于平臺為RGBM 或dLDR)

    float4 EncodeFloatRGBA (float v):為儲存低精度的渲染目標(biāo),編碼[0..1)范圍的浮點數(shù)到RGBA顏色。

    float DecodeFloatRGBA (float4 enc):解碼RGBA顏色到float。

    float2 EncodeViewNormalStereo (float3 n):編碼視圖空間法線到在0到1范圍的兩個數(shù)。

    float3 DecodeViewNormalStereo (float4 enc4):從enc4.xy解碼視圖空間法線

    ?

    最后編輯于
    ?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
    【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
    平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

    相關(guān)閱讀更多精彩內(nèi)容

    • 轉(zhuǎn)載注明出處:點擊打開鏈接 Shader(著色器)是一段能夠針對3D對象進(jìn)行操作、并被GPU所執(zhí)行的程序。Shad...
      游戲開發(fā)小Y閱讀 3,694評論 0 4
    • 動機(jī)自己使用Unity3D也有一段時間了,但是很多時候是流于表面,更多地是把這個引擎簡單地用作腳本控制,而對更深入...
      JumboWu閱讀 2,225評論 0 24
    • 關(guān)于本系列這是Unity3D Shader入門指南系列的第二篇,本系列面向的對象是新接觸Shader開發(fā)的Unit...
      JumboWu閱讀 1,660評論 0 2
    • 作者學(xué)習(xí)shader已經(jīng)有很長一段時間了,這篇文章是對shader的整理和總結(jié)。shader的世界繽紛多彩,浩瀚無...
      葉小健閱讀 13,144評論 5 79
    • 我一直不理解不明白什么是愛。小時候,心大就知道玩兒,覺得父母或者爺爺奶奶給點零花錢就是愛。初中,我覺得每周三看見...
      假裝是思想wjy閱讀 465評論 0 0

    友情鏈接更多精彩內(nèi)容