渲染基礎-渲染管線(Render-pipeline)
一開始學習計算機圖形學的小夥伴們肯定對於渲染管線有一點迷茫,至少當初我就有點迷茫,為了能對後來對計算機圖形學感興趣的萌新小夥伴起到一些幫助,在這裡簡單講解一下渲染管線(Render-pipeline)。
=================================================================
物體空間->世界空間->觀察空間->裁剪空間->螢幕空間
==========================================================================
注意:計算機圖形學的座標系系統中各個系統之間都是相對的!!!(都是一個點,只是不同的描述方式)
1.物體座標和物體模型(object space<物體空間>)
什麼是物體座標呢?講理論不如舉個栗子!!!嘿嘿~~
一個單獨物體的物體座標
如圖:這是3dsMax下建模的一個長度為1的正方體(單位立方體),軸心在物體重心,也就是正中心,左手座標系(圖中的這種座標系專業一點的名字)。
不難想象A,B,C三點的座標為A(-0.5,0.5,0.5)B(0.5,0.5,0.5) C(-0.5,-0.5,-0.5),這些A,B,C點和所有該物體的點的集合就是我們說的物體座標(嚴格上說是物體上的點相對於自身原點的座標)。
兩個至多個物體的物體座標
軸心,軸向不變,兩個一樣的單位正方體,A和A'的物體座標不難想象A(0.5,0.5,0.5) A'(0.5,0.5,0.5),是的 A點和A'點座標是一樣的,也就是說物體座標並不會因為軸心和軸的位置不同而發生變化。雖然兩個完全一樣的物體,物體座標只是相當於其自身的座標原點和軸向都在各自的座標系下,也就是說A點只相對於O點,A'點只相對於O'點。這個概念在遊戲裡經常會有,你打的怪獸是不是很多都長一個鬼樣子?那他們都有各自的物體座標。這樣的座標系統構成了物體空間
2.世界座標(World Space<世界空間>)
好了現在我們有了兩個正方體,我們只知道這兩個正方體的各個對應頂點座標是一樣的(比如前面說到的A和A'點),也就是說畫出來這兩個正方體是重合的,那我想讓其中一個偏離不重合並且有自己的大小,位置和旋轉,那麼好了,歡迎進入
好了,什麼是世界空間?同樣舉個栗子(Unity中)!
讓我們來看看這兩個物體各自的軸心座標:
1號立方體的軸心(原點)位置 (0,0,0)---Cube1
2號立方體的軸心(原點)位置 (0 ,0,2)---Cube2
Cube1的軸心位於(0,0,0),Cube2是我複製Cube1向Z軸平移兩個單位軸心變成(0,0,2),現在我們看看A點和A'點的座標。
A點:Cube1的軸心位於(0,0,0)。Cube1為單位正方體,各軸向見圖右上角,所以A點座標為(-0.5,0.5,-0.5)。
A'點:Cube2的軸心位於(0,0,0)。Cube2為單位正方體,各軸向見圖右上角,所以A'點座標為(-0.5,0.5,2-0.5)=(-0.5,0.5,1.5)。
你會發現A點座標不等於A'點座標,對,你沒看錯就是不相等,因為這兩個物體放到了同一個座標系下世界座標系,在該座標系下的所有點的座標都相對於世界座標系的原點(0,0,0),也就是說Cube1的軸心座標和世界座標系的軸心重合了,但是這兩個物體的自身的物體座標並沒有改變(這是相對於自身軸心,也就是物體空間)。
3.觀察空間(View Space)
總覽圖 俯檢視
如圖是一個攝像機的觀察範圍(四稜錐)類似人的眼睛。觀察空間就是將世界空間中的座標變換到以攝像機為軸心計算各個頂點的位置。這個四稜錐我們叫視錐體(view frustum)
4.裁剪空間(clip space)<CVV(canonical view volume)>
什麼是裁剪?!
首先假如場景是這樣的:
攝像機的視錐體範圍:
遊戲真正能看到的畫面:
你會發現在視錐體外面的東西都被剔除了,這就是裁剪,不渲染看不見的東西。
(1)攝像機有兩種:
1>透視攝像機(Perspective)
推導過程:(供參考)
nearClipPalneHeight(近截平面的高)=2*Near*tan(FOV/2)
farClipPlaneHeight(遠截平面)=2*Far*tan(FOV/2)
Aspect(攝像機的縱橫比)=nearClipPlaneWidth/nearClipPalneHeight=farClipPlaneWidth/farClipPlaneHeight
2>正交攝像機(Orthographic)
推導過程:(供參考)
nearClipPlaneHeight=2*Size
farClipPlaneHeight=nearClipPlaneHeight
Aspect=nearClipPlaneHeight/farClipPlaneWidth=nearClipPlaneHeight/nearClipPalneWidth
5.歸一化裝置座標---NDC(Normalized Device Corrdinate)
在這一步會進行一個叫齊次除法的步驟,說白了就是各個點(x,y,z,w)會除以w的值(注:計算機圖形學中經常使用四元數代表一個點(叫齊次空間,齊次點等 就是一個名字而已),w沒什麼特別之處,就是計算矩陣乘法時方便)。
透視裁剪空間到DNC:
正交裁剪空間到DNC:
重點:這樣在OpenGL中所有能被攝像機看到的點將會被轉換成(-1,1),在DirectX中所有能被攝像機看到的點將會被轉換成(0,1)中。為什麼要這樣做?---為了方便投影到顯示屏上!!!
6.螢幕空間(Screen Space)
pixelWidth:螢幕橫向解析度
pixelHeight:螢幕縱向解析度
OpenGL規範:
DirectX規範:
這個過程就是一個縮放的過程:
screenx={clipx*pixelWidth/(2*clipw)}+pixelWidth/2
screeny={clipy*pixelHeight/(2*clipw)}+pixelHeight/2
上式更加形象的描述:
第一步: -1<clipx/clipw<1--->這是之前的齊次除法的
第二步:0<{(clipx/clipw)+1}/2<1--->對其加1再除以2化成0到1區間
第三步:對於pixelWidth:{{(clipx/clipw)+1}/2}*pixelWidth= screenx
對於pixelHeight:{{(clipx/clipw)+1}/2}*Height=screeny
和之前推導一樣!!!
至此你所想要的東西被繪製在了螢幕上!!!
擴充套件:
(1)頂點著色器(vertex shader)
將物體從物體空間->世界空間->觀察空間->裁剪空間就是頂點著色器的工作。
這之中會轉換各種點的座標,我們在哪運算呢?就在頂點著色器中!!!
頂點著色器:
1>將物體空間的資料(點)作為頂點著色器的輸入
2>將所有在自己範圍中的點全部遍歷一遍,就是每個點都會算進行加工
3>高度可程式設計配置!!!(這點絕了!!!太棒啦!!!),也就是說這東西繪製成啥樣我們可以自定義了!!!
(2)片元著色器(fragment shader)
將裁剪空間中的點從裁剪空間->螢幕空間就是片元著色器的工作。
螢幕對映,就是之前說的第六步不是我們做,是顯示卡固定好了的演算法。片元著色器是計算每個畫素的顏色的。如果看過相關程式碼,你會發現片元著色器會返回一個四元數-(r,g,b,a)->分別為(red<紅>,green<綠>,blue<藍>,alpha<透明度>).
什麼是片元(fragment)?片元是一種狀態,剛開始顯示器上的畫素點是不知道自己的顏色的,每個畫素點就像一個空白的格子等著我們上顏色,類似於還沒有裝蜂蜜的蜂巢。系統中,我們之所以會看到桌面是因為系統已經給顯示卡初始化了畫素顏色。在工作管理員中能找到。
片元著色器:
1>將裁減空間的資料(點)作為片元著色器的輸入
2>將所有在自己範圍中的畫素全部遍歷一遍(三角遍歷<Rasterizer>---Triangle Traversal),就是每個片元(畫素)都會運算進行加工。
3>高度可程式設計配置!!!(太棒啦!!!)
===============================================
讓我們看看一個簡單的栗子!Unity中是怎麼做的:
Shader "Test/Shader"
{
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert//告訴編譯器 頂點著色器叫什麼名字
#pragma fragment frag//告訴編譯器 片元著色器叫什麼名字
#include "UnityCG.cginc"//包含內建檔案,方便寫程式碼
struct appdata
{
float4 vertex : POSITION;//物體座標
};
struct v2f
{
float4 vertex : SV_POSITION;//裁剪空間座標
};
v2f vert (appdata v)//頂點著色器,以物體座標為輸入(appdata下的vertex)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);//將物體座標變換到裁剪空間
return o;//返回裁剪空間的資料
}
fixed4 frag (v2f i) : SV_Target//片元著色器,以裁剪空間資料作為輸入(上面頂點著色器的輸出)
{
fixed4 col = fixed4(1,1,1,1);//定義一個白色
return col;//返回白色
}
ENDCG
}
}
}
注意32行:其呼叫了內建函式:UnityObjectToClipPos()
其定義如下:(在Unity/Editor/Data/CGInclude/UnityShaderUtilities.cginc)
inline float4 UnityObjectToClipPos(in float3 pos)
{
// More efficient than computing M*VP matrix product
return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
}
inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{
return UnityObjectToClipPos(pos.xyz);
}
return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)))中mul(unity_ObjectToWorld, float4(pos, 1.0))將從物體空間變換到世界空間,unity_ObjectToWorld是物體空間到世界空間的轉換矩陣,mul()是矩陣乘法內建函式。之後再乘以UNITY_MATRIX_VP(觀察空間和裁剪空間合一起了),乘完後將從世界空間變換到裁剪空間。
完全手動自定義計算的話,這麼寫:
float4 UnityObjectToClipPos(in float3 pos)
{
float4 objectSpaceData = float4(pos, 1.0f);
float4 worldSpaceData = mul(unity_ObjectToWorld, objectSpaceData);
float4 viewSpaceData = mul(UNITY_MATRIX_V, worldSpaceData);
float4 clipSpaceData = mul(UNITY_MATRIX_P,viewSpaceData );
return clipSpaceData;
}
參考文獻:1.《Unity Shader 入門精要》馮樂樂---人民郵電出版社---2017年6月第一版
2.《Real-Time Rendering third edition》Tomas Akenine-Moller,Eric Haines,Naty HoffMan