碼字不易,不求打賞,不求打臉,只求轉載注明出處:
https://blog.csdn.net/newchenxf/article/details/119803489
GPU 渲染管線與著色器 大白話總結 ---- 一篇就夠
- 1 前言
- 2 什么是渲染管線
- 3 CPU處理 ---- 應用階段
- 3.1 把資料加載到顯存
- 3.1.1 加載模型資料例子
- 3.1.2 加載紋理的例子
- 3.2 設定渲染狀態
- 3.3 呼叫Draw Call
- 3.4 小結
- 4 GPU處理 ----幾何階段
- 4.1 頂點資料是什么
- 4.2 頂點著色器
- 4.3 坐標空間
- 4.3.1 模型空間
- 4.3.2 世界空間
- 4.3.3 觀察空間
- 4.3.4 裁減空間
- 4.3.5 螢屏空間
- 4.4 頂點坐標變換總結
- 5 GPU處理 ---- 光柵化階段
- 5.1 三角形設定與遍歷
- 5.2 片元著色器
- 6 附錄知識
- 6.1 什么是齊次坐標
- 6.2 平移矩陣
- 6.3 縮放矩陣
- 6.4 旋轉矩陣
- 6.5 組合變換
- 參考
1 前言
做圖形影像,就要懂GPU;
要懂GPU,最重要是懂渲染管線(或者叫流水線);
而渲染管線,最重要的環節就是shader,即著色器,
所以本文嘗試總結GPU的渲染流程和shader,可作為影像開發的入門教程,
2 什么是渲染管線
管線,又稱流水線,
什么是流水線呢?
流水線是指在重復執行一項任務時,把它細分成很多小任務,讓這些小任務重疊執行,來提高整體的運行效率,
舉個栗子:
卸貨搬運工,3個人從車上搬到店里,3個人分別是A, B, C,
A給B,B給C,C到店里,
A不需要等C放到店里,再開始下一次的搬運,而是C在搬的程序中,就可以開始搬第二個貨物了,
CPU的處理,其實也是用流水線技術,它把一條指令的執行,拆分成五個部分:取指令、解碼、取資料,運算和寫結果,然后流程跟上面的搬運工差不多,
而渲染管線,就是根據一個三維場景中的頂點、紋理等資訊,轉換成一張二維影像,這個作業由CPU + GPU 共同完成,
通常把一個渲染流程分3個階段:
(1) 應用階段
(2) 幾何階段
(3) 光柵化階段
應用階段由CPU完成,幾何階段 和 光柵化階段 由GPU完成,
當然了,既然是流水線,意味著3個階段的執行是異步的,也就是,CPU執行完了,不需要GPU執行完才開始下一次呼叫,
另外,GPU執行的幾何階段和光柵化階段,也細分了子任務,也是使用流水線的技術,
下面根據這3個階段,分別討論一下,
3 CPU處理 ---- 應用階段
主要作業:
(1) 把資料加載到顯存(GPU的記憶體)
(2) 設定渲染狀態
(3) 呼叫Draw Call
3.1 把資料加載到顯存
GPU一般不能直接訪問記憶體,所以自己就搞了個記憶體,叫顯存(顯卡記憶體),CPU把一些渲染所需資料從硬碟或網路加載到記憶體,然后送到顯存,
資料最主要就兩個,模型資料(頂點坐標+紋理坐標)和紋理影像,其他的資料量都很小,如法線方向,加載到顯存后,在CPU的記憶體中的資料,可以洗掉,例如用bitmap生成一個紋理,加載到GPU后就可以回收了,
3.1.1 加載模型資料例子
下面用一個android視頻播放渲染的例子,來說明如何加載模型資料,
視頻是在一個二維的矩形框內顯示,所以只需要4個頂點坐標,以及對應的4個紋理坐標,紋理用的是視頻解碼后的影像,
//頂點資料
private float[] vertexData = {
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
};
//紋理坐標資料
private float[] fragmentData = {
0f, 0f,
1f, 0,
0f, 1f,
1f, 1f
};
public void onCreate() {
//頂點坐標資料在JVM的記憶體,復制到系統記憶體
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
vertexBuffer.position(0);
//紋理坐標資料在JVM的記憶體,復制到系統記憶體
fragmentBuffer = ByteBuffer.allocateDirect(fragmentData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(fragmentData);
//VBO全名頂點緩沖物件(Vertex Buffer Object),他主要的作用就是可以一次性的發送一大批頂點資料到顯卡上
int [] vbos = new int[1];
GLES20.glGenBuffers(1, vbos, 0);
vboId = vbos[0];
//下面四句代碼,把記憶體的資料復制到顯存中
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboId);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20. GL_STATIC_DRAW);
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);
}
代碼中已經加了注釋,不過這里還是可以再說明一下,
vertexData 畢竟是java定義的變數,使用的JAVA虛擬機的記憶體,而不是系統記憶體,沒辦法直接給GPU的,所以,先用ByteBuffer,把vertexData拷貝到系統記憶體,
接著,創建一個頂點緩沖物件(Vertex Buffer Object),他主要的作用就是可以一次性的發送一大批頂點資料到顯卡上,然后,呼叫glBufferData,把資料拷貝到顯卡記憶體上!這樣后面GPU開始作業時,就可以快速訪問了,
3.1.2 加載紋理的例子
這里再貼一個android app加載紋理影像的例子:
public static int createTexture(Bitmap bitmap){
int[] texture=new int[1];
//生成紋理
GLES20.glGenTextures(1,texture,0);
//生成紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture[0]);
//設定縮小過濾為使用紋理中坐標最接近的一個像素的顏色作為需要繪制的像素顏色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
//設定放大過濾為使用紋理中坐標最接近的若干個顏色,通過加權平均演算法得到需要繪制的像素顏色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
//設定環繞方向S,截取紋理坐標到[1/2n,1-1/2n],將導致永遠不會與border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
//設定環繞方向T,截取紋理坐標到[1/2n,1-1/2n],將導致永遠不會與border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
if(bitmap!=null&&!bitmap.isRecycled()){
//根據以上指定的引數,生成一個2D紋理,上傳到GPU
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
}
return texture[0];
}
先用glGenTextures生成一個紋理id,這時候,還沒有創建實際資料,
接著,設定一下紋理的屬性,然后,呼叫texImage2D,這會先創建一個紋理buffer,然后把bitmap拷貝到buffer上,然后完事,上面的函式結束后,bitmap的內容就拷貝到GPU了,可以根據需要回收,
需要強調的是,不管是加載頂點還是紋理,都只需要在初始化時,一次性完成,不需要每次onDraw都加載,否則就沒有意義了,
3.2 設定渲染狀態
即宣告場景的網格(模型)如何被渲染,比如用哪個vertex shader/fragment shader,光源屬性,材質(紋理)等,如果繪制不同的網格時沒有切換渲染狀態,那么前后的網格,將使用同一種渲染狀態,
3.3 呼叫Draw Call
其實就是一個命令,發起方是CPU,接收方是GPU,
當一個Draw Call呼叫時,GPU就會根據渲染狀態(材質,紋理,著色器)和所有的頂點資料進行計算,GPU流水線開始運轉,最終生成螢屏顯示的像素,
3.4 小結
第一步只需要1次加載就夠了,不用每次繪制都加載, 第二和第三,需要每次onDraw時設定,
這里也用繪制視頻或圖片的onDraw函式,來舉個栗子:
public void onDraw(int textureId)
{
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(1f,0f, 0f, 1f);
//設定渲染狀態: program根據具體的shader編譯,這里就是指定用哪個shader
GLES20.glUseProgram(program);
//設定渲染狀態:指定紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
//設定渲染狀態:系結VBO
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboId);
//設定渲染狀態:指定頂點坐標在VBO的從0開始,8個資料
GLES20.glEnableVertexAttribArray(vPosition);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8,
0);
//設定渲染狀態:指定頂點坐標在VBO的從8*4(float是4個位元組)開始,8個資料
GLES20.glEnableVertexAttribArray(fPosition);
GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8,
vertexData.length * 4);
//呼叫Draw Call, GPU 管線開始作業
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//繪制完成,恢復現場
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
}
4 GPU處理 ----幾何階段
可以把幾何階段和光柵化階段放在一起,畫一張圖,

當然了,上圖可能不全,中間可能還有些可選的步驟,我只是把最重要的列出來,
其中,藍色部分的頂點著色器,和片元著色器,是開發者完全可自定義編程的,也是大部分影像開發同學需要關心的2個步驟,
4.1 頂點資料是什么
先來個基本說明:GPU認識的資料,只有點,線,三角形,
這對應1個頂點,2個頂點,3個頂點,這三個又稱為圖元,點和線一般在2D場景使用,在3D場景,基本是由N多個三角形,來拼接成一個物體模型,
下圖是一個人物模型的例子:

放大一些細節,發現都是三角形組合而成,而三角形的頂點,就是我們說的頂點坐標,

所有的三角形貼上二維圖片紋理后,就是一個完整的影像:

所以說,頂點資料,一般會包含頂點坐標 + 紋理坐標,
一個模型檔案,一般會包含頂點坐標,紋理坐標,紋理本身(紋理可以有多張)等,舉個栗子,打開3D max的obj后綴的模型檔案,就可以看到如下文本內容:

4.2 頂點著色器
頂點著色器是GPU內部流水線的第一步,
它的處理單位是頂點,也就是每個頂點,都會呼叫一次頂點著色器,
它的主要作業:坐標轉換和逐頂點光照,以及準備后續階段(如片元著色器)的資料,
坐標轉換,即把頂點坐標從模型坐標空間轉換到齊次裁切坐標空間,
這里給一個Unity的shader代碼,Unity把頂點著色器和片元著色器的代碼,放到了一個檔案中,不過Unity引擎會決議檔案,轉換2個著色器代碼段,傳遞給GPU,
Shader "Unlit/SimpleUnlitTexturedShader"
{
Properties
{
// 我們已洗掉對紋理平鋪/偏移的支持,
// 因此請讓它們不要顯示在材質檢視面板中
[NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
// 使用 "vert" 函式作為頂點著色器
#pragma vertex vert
// 使用 "frag" 函式作為像素(片元)著色器
#pragma fragment frag
// 頂點著色器輸入
struct appdata
{
float4 vertex : POSITION; // 頂點位置
float2 uv : TEXCOORD0; // 紋理坐標
};
// 頂點著色器輸出("頂點到片元")
struct v2f
{
float2 uv : TEXCOORD0; // 紋理坐標
float4 vertex : SV_POSITION; // 裁剪空間位置
};
// 頂點著色器
v2f vert (appdata v)
{
v2f o;
// 將位置轉換為裁剪空間
//(乘以模型*視圖*投影矩陣)
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
// 僅傳遞紋理坐標
o.uv = v.uv;
return o;
}
// 我們將進行采樣的紋理
sampler2D _MainTex;
// 像素著色器;回傳低精度("fixed4" 型別)
// 顏色("SV_Target" 語意)
fixed4 frag (v2f i) : SV_Target
{
// 對紋理進行采樣并將其回傳
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
代碼已經加了清晰的注釋,
appdata就是頂點著色器的輸入,包括頂點坐標和紋理坐標,
通常,頂點著色器只處理頂點坐標,進行空間轉換, 紋理坐標,一般透傳給片元著色器,
空間轉換代碼竟然及其簡單,就是坐標乘于一個MVP矩陣!
接下來,我們就基于MVP矩陣,來展開說一下空間轉換的概念,
4.3 坐標空間
什么是坐標空間呢?其實他在我們生活中處處存在,例如,你跟朋友約定在博物館大門右轉100米處見面,這時候,你說的位置,是一個以博物館的大門為原點的空間,所以坐標空間,是一個相對的概念,
在游戲世界中也一樣,
根據不同的參照物,可以分為模型空間-世界空間-觀察空間-裁剪空間-螢屏空間,
在3D世界中,頂點坐標有3個資料,是(x, y, z),但為了方便做平移/旋轉/縮放等轉換,需要補充一個w分量,即(x, y, z, w),把這個4維的坐標,稱為齊次坐標,這些知識,見第五節的附錄知識,
接下來,我們來挨個討論這些空間,
4.3.1 模型空間
你在3D max制作了一個人物模型,該模型可以放到各種軟體中適配, 對模型內部來說,有一個坐標原點,比如就在心臟處,那身體各個部位相對于心臟,就有一個坐標值;
每個模型物件,都有自己獨立的坐標空間,所以也可以稱為物件空間或區域空間,
4.3.2 世界空間
可以把游戲中的一個地圖,作為一個小小的世界,地圖的中心,可以作為坐標原點,人物模型作為一個整體,放到地圖中,就有了相對地圖中心的一個坐標值,這就是世界空間,
把模型空間的坐標,轉換到世界空間的坐標,可以通過矩陣運算得到,這個矩陣叫Model Matrix,
事實上,一個模型放到地圖中,可能會先縮放,然后旋轉或者平移,這3者都有對應的矩陣,具體見附錄,
換言之,這個矩陣是由物體在地圖中的縮放,旋轉,平移引數決定的,
換算的目的,就是得到物體的某個頂點,在地圖中是個什么具體位置,
4.3.3 觀察空間
也可以稱為攝像機空間,觀察空間指的是,游戲地圖那么大,我們只能看到一部分內容,于是在游戲地圖中,定義一個Camera,它也處于世界空間中,camera就相當于我們的眼睛,camera照到哪里,哪里就是我們看到的畫面,
在3D游戲中,經常就把Camera和用戶角色綁在一起,角色物件走到哪,Camera物件就移動到哪,效果便是,走到哪,就看到哪里的風景!
所以地圖中的任何物體,如果把Camera作為坐標原點,也有一個相對于Camera的坐標值,以Camera為原點的空間,就是觀察空間了,
把世界空間的坐標,換算到觀察空間,也可以通過矩陣運算得到,這個矩陣叫View Matrix,
這個矩陣依賴什么引數呢?
首先,攝像機本身也位于世界坐標系中,如果不考慮攝像機往哪里看,那其實就是很簡單的平移矩陣,
即,攝像機A坐標如果是(-3, 0, 0), 那攝像機就是從世界原點O平移(-3, 0, 0)的結果,如果把攝像機的坐標作為新的原點,那原來的世界原點O,相當于從攝像機平移(3, 0, 0)的結果,所以兩個坐標系的轉換矩陣,近乎是一個平移矩陣,
當然了,實際攝像機還有朝向,以及眼睛是正面看,還是倒立看,不知道啥是倒立看?看下圖

所以這個矩陣總用有三個因素影響,
來,一個函式解決煩惱,忘記復雜的計算程序,
glm::mat4 CameraMatrix = glm::LookAt(
cameraPosition, // the position of your camera, in world space
cameraTarget, // where you want to look at, in world space
upVector // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
);
4.3.4 裁減空間
裁減空間就是攝像機能看到的區域,這個區域成為視椎體,
它由6個平面組成,這些平面也成為裁減平面,視椎體有兩種型別,涉及兩種投影方式,一個是透視投影,一個是正交投影,
完全位于這塊空間的圖元,會被保留;
完全位于空間之外的圖元,完全丟棄;
和空間邊界相交的圖元,會被裁減,
下圖是一個透視投影的示意圖,人物完全在視椎體內,所以右下角的小圖,是最終用戶看到的2D畫面樣子,

當然了,圖中的Near 和Far,我是為了顯示方便,設定了比較小的值,實際游戲中,Near和Far差別很大,比如Near = 0.1, Far = 1000,這跟人眼可看的范圍是差不多的,近到貼眼睛的東西看不見,非常遠的東西,也看不見,
那么,如何判斷一個東西就在視椎體內呢?
答案是通過一個投影矩陣,把頂點轉換到一個裁減空間,
這個矩陣,基本上由攝像機的引數決定,
引數示意圖如下:

FOV代表視角的度數(Field of View);
Near和Far是攝像機原點,距離裁切面的距離;
AspectRatio是裁切面的寬高比;
從觀察空間到裁減空間的變化矩陣,咱成為Projection Matrix,
矩陣可以用一個函式來生成:
// Generates a really hard-to-read matrix, but a normal, standard 4x4 matrix nonetheless
glm::mat4 projectionMatrix = glm::perspective(
glm::radians(FoV), // The vertical Field of View, in radians: the amount of "zoom". Think "camera lens". Usually between 90° (extra wide) and 30° (quite zoomed in)
AspectRatio, // Aspect Ratio. Depends on the size of your window. such as 4/3 == 800/600 == 1280/960, sounds familiar
Near, // Near clipping plane. Keep as big as possible, or you'll get precision issues.
Far // Far clipping plane. Keep as little as possible.
);
具體公式生成原理,這里就不展開了,詳情自行google或者看這里:https://zhuanlan.zhihu.com/p/104768669,
投影矩陣雖然有投影兩個字,但是沒有做真正的投影,而是在做投影的準備作業, 投影要到下一步,到螢屏空間的轉換,才使用,
進行投影矩陣運算后,或者說,到了裁剪空間后,w值就不一定是0和1了,有特殊的含義,具體而言,如果一個頂點在視椎體內,那么它變換后的坐標,必須滿足:
-w <= x <= w
-w <= y <= w
-w <= z <= w
不符合要求的,要么剔除,要么裁減,
舉個例子:
一個人物模型的手的一個頂點坐標,到觀察空間的坐標是
【9, 8.81, -27.31, 1】
經過換算到裁減空間,值是
【11.691, 15.311, 23.69, 27.3】
那么,該頂點是在裁減空間內,可以被顯示,
前面都是以透視投影來說明的,那么,正交投影跟透視投影啥區別呢?
透視投影:越遠的東西,呈現在螢屏的越小; 正交投影,遠和近的同樣大小東西,呈現在螢屏上一樣大,
來一張親手繪制的圖,你就知道我的意思了!

從上面可知,透視投影更有3D效果,正交透明則沒有,比較適合2D游戲,
4.3.5 螢屏空間
螢屏空間是一個二維空間了,也是我們將要看到的畫面,也是最后一個需要知道的空間了(是不是長吁一口氣,哈哈)
要把頂點從裁減空間轉換到螢屏空間,三維變二維了,所以所叫投影,
這個投影主要做兩件事:
第一,需要進行齊次除法,不要覺得復雜,其實就是用w分量去處于x, y, z分量,在Open GL中,把這一步得到的坐標,叫做歸一化的設備坐標(Normalized Device Coordinates, NDC),
NDC的x, y, z的有效值范圍,都是負1到1,在這個值之外的,就是被剔除的點,
第二,需要映射到螢屏,得到螢屏坐標,在openGL中,螢屏的左下角的像素坐標是(0, 0),右上角是(pixelWidth, pixelHeight),
比如歸一化坐標是(x, y),螢屏寬高是(w,h),那么,螢屏坐標是
Sx = x*w + w/2
Sy = y*h + h/2
你是不是想說,NDC的z分量不要了?
不會不會, z分量也有很大的價值,它作為深度緩沖,比如,如果之后有2個頂點,螢屏坐標都一樣,且都不透明,那就看深度緩沖了,誰離攝像頭近,誰就顯示,
需要強調的是,從裁減空間到螢屏空間的轉換,是由底層幫我們完成的,不需要代碼實作,
頂點著色器,只需要把頂點,從模型空間轉換到裁減空間即可,
在片元著色器,可以得到該片元在螢屏空間的位置,
4.4 頂點坐標變換總結
回到4.2節,我們說了一個MVP矩陣,現在終于知道了,它代表了一個可以把頂點從模型空間轉換到裁減空間的矩陣!
這個矩陣,又是由 模型矩陣 * 觀察矩陣 * 投影矩陣相乘得到的!
用一張圖來說明一下:

這和4.2節的這段代碼,終于對上了:
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
這個宏UNITY_MATRIX_MVP,就是圖中的MVP矩陣,
當你確定了模型位置/旋轉/縮放,就有了Model Matrix,當確定了攝像機位置,朝向,就有了View Matrix,當確定了攝像機的FOV視角,近遠裁切面的距離等,就有了Projection Matrix,
三者都確定了,相乘,MVP Matrix也就確定了,
所以頂點著色器非常的簡單,乘于一個固定MVP矩陣就OK了!
咱們再往回看3.1節,那是一個視頻播放的例子,頂點坐標用了:
private float[] vertexData = {
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
};
為什么這么簡單?因為它僅用于視頻播放,畫面是矩形,只需要4個頂點,頂點坐標就是二維的,
所以,這里的頂點,直接寫成了裁減空間下的值(z沒寫默認為0),
這種情況下,MVP矩陣是一個單位矩陣,
來看一下3.1節對應的頂點著色器代碼,也是簡單到令人發指:
attribute vec4 v_Position;
attribute vec4 f_Position;
varying vec2 textureCoordinate;
void main()
{
gl_Position = v_Position;
textureCoordinate = f_Position.xy;
}
注意,這個和4.2節不太一樣,這一段已經是可以直接給openGL編譯的shader,4.2節是unity封裝的代碼形式,最后Unity的引擎,也會翻譯成類似上面的代碼,有main函式,
v_Position是外部輸入的頂點坐標,gl_Position是全域內建變數,代表最后在裁減空間下的坐標,
如果上面的shader非要寫MVP,也是可以的,只不過,這個MVP是一個單位矩陣,
即
gl_Position = mvpMatrix * v_Position;
5 GPU處理 ---- 光柵化階段
光柵化的主要任務:計算每個圖元都覆寫了哪些像素,然后給這些像素著色,
5.1 三角形設定與遍歷
三角形設定:
這是光柵化流水線的第一個階段,
這個階段會計算光柵化一個三角網格所需的資訊,
具體而言,上一個階段輸出的,都是在螢屏上的二維的三角網格的頂點,即我們得到的是三角網格每條邊的兩個端點,但如果要得到整個三角網格對像素的覆寫情況,我們就必須計算每條邊上的像素坐標,為了能夠計算邊界像素的坐標資訊,我們就需要得到三角形邊界的表示方式,這樣一個計算三角網格表示資料的程序就叫做三角形設定,它的輸出是為了給下一個階段做準備,
三角形遍歷:
三角形遍階段將會檢查每個像素是否被一個三角網格所覆寫,如果被覆寫的話,就會生成一個片元,而這樣一個找到哪些像素被三角網格覆寫的程序就是三角形遍歷,三角形遍歷階段會根據上一個階段的計算結果來判斷一個三角網格覆寫了哪些像素,并使用三角網格3個頂點的頂點資訊對整個覆寫區域的像素進行插值,下圖展示了三角形遍歷階段的簡化計算程序,

根據幾何階段輸出的頂點資訊,得到三角網格覆寫的像素位置,對應的像素會生成一個片元,片元中的狀態,由三角形的頂點資訊,進行差值計算得到,
像上圖的三角網格,共產生了8個片元,
5.2 片元著色器
6 附錄知識
6.1 什么是齊次坐標
齊次坐標就是用N+1維來代表N維坐標,
例如笛卡爾坐標系的坐標(x, y, z),用(x, y, z, w)來表示,坐標可以是點,或向量,
目的:
區分向量或者點,輔助 平移T、旋轉R、縮放S這3個最常見的仿射變換
引經據典:
齊次坐標表示是計算機圖形學的重要手段之一,它既能夠用來明確區分向量和點,同時也更易用于進行仿射幾何變換,
—— F.S. Hill, JR 《計算機圖形學 (OpenGL 版)》作者
關于區分向量和點,指的是:
(1) 從普通坐標轉換成齊次坐標時
如果(x,y,z)是個點,則變為(x,y,z,1);
如果(x,y,z)是個向量,則變為(x,y,z,0)
(2) 從齊次坐標轉換成普通坐標時
如果是(x,y,z,1),則知道它是個點,變成(x,y,z);
如果是(x,y,z,0),則知道它是個向量,仍然變成(x,y,z)
關于平移,旋轉,縮放,且慢慢道來,
6.2 平移矩陣
平移矩陣是最簡單的變換矩陣,平移矩陣是這樣的:

其中,X、Y、Z是點的位移增量,
例如,若想把向量(10, 10, 10, 1)沿X軸方向平移10個單位,可得:

看到沒,如果平移要用矩陣來運算,矩陣必須是4x4的,那點(10, 10, 10)只能增加一維w,才能被計算(矩陣乘法的要求,mn的矩陣,只能乘于nt 的矩陣),
6.3 縮放矩陣
縮放也很簡單

例如把一個向量(點或方向皆可)沿各方向放大2倍:

6.4 旋轉矩陣
這個稍微復雜一些,沿著X, Y, Z軸的旋轉不一樣,不過最后還是一個矩陣,這里不再單獨展開,有興趣可以參考:
6.5 組合變換
上面介紹了旋轉、平移和縮放的運算方法,把這些矩陣相乘就能將它們組合起來,例如:
TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;
注意,是先執行縮放,接著旋轉,最后才是平移,這就是矩陣乘法的作業方式,也是我們對3D模型放到地圖中的常用操作方式,
3個矩陣相乘,還是一個矩陣,這就是組合變換的矩陣,所以最后也可以寫成:
TransformedVector = TRSMatrix * OriginalVector;
參考
Android OpenGL ES 1.基礎概念
計算機組成原理–GPU
計算機那些事(8)——圖形影像渲染原理
opengl-tutorial
Unity檔案
光柵化階段設定
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/310675.html
標籤:其他
