標籤:
前言
在前2篇文章中,我們都說到著色器,且在第二篇中正式說到,這著色器只能用在OpenGL ES2.x等可程式化管道裡面,而在OpenGL ES1.x是不能用的。但我們一直沒有說這是為什麼,兩者有什麼區別。那這篇我們就一起來學習下OpenGL ES中的渲染管道。
本文
管道,英文名叫Pipeline,相信用過FaceBook圖片載入庫的同學對這個管道並不陌生,因為SimpleImageDrawee裡面也是用的管道來對圖片進行的一個處理。由於其底層也是C,因此我可以大膽的猜想,FaceBook圖片載入庫的設計思路可能有參考OpenGL(這當然純屬臆想^_^)。
管道用正確的電腦語言來描述就是:
顯卡執行的、從幾何體到最終渲染映像的、資料轉送處理計算的過程。
即是管道,那就得有先後順序。整體是從上遊流到下遊。
在OpenGL ES1.x中,它是固定管道,整體是封閉的,中間的各道工藝按固定的流程順序走。看:
從可以看出,這些工藝順序是固定的。整個過程又可以分成三部分:處理頂點、處理片元、驗證片元資訊並存入記憶體。
Rasterizer:光柵化處理,當頂點處理完後,會交給rasterizer來進行光柵化處理,結果會把頂點的座標資訊轉換成能在螢幕顯示的像素資訊即片元(fragments)。產生片元後,接下來就是對fragments片元的各種驗證,即過濾掉無用的片元,裁剪掉不在視野內的片元,最終把有效片中繼存放區入記憶體中。
這裡對於Rasterizer光柵化,讓我們一起來瞭解學習下:
Rasterizer/Rasterization:光柵化處理
這個詞兒Adobe官方翻譯成柵格化或者像素化。沒錯,就是把向量圖形轉化成像素點兒的過程。我們螢幕上顯示的畫面都是由像素組成,而三維物體都是點線面構成的。要讓點線面,變成能在螢幕上顯示的像素,就需要Rasterize這個過程。就是從向量的點線面的描述,變成像素的描述。(或:所頂點從全局座標系轉換為螢幕座標系的片元)
如,這是一個放大了1200%的螢幕,前面是告訴電腦我有一個圓形,後面就是電腦把圓形轉換成可以顯示的像素點。這個過程就是Rasterize。
現在是一個多元化的社會,是一個講個人化的社會,什麼都想著個人化,OpenGL ES也不例外,它為個人化的需求提供了介面。一中的藍色方塊部分,就是可以高度定製化的地方,因此也就形成了OpenGL ES2.x等的可程式化管道,在OpenGL ES裡面有兩個專用的詞VertexShader(頂點著色器)、FragmentShader(片元著色器),分別對應圖一中的Coordinate藍色塊和Texture等藍色塊。
下面就看下OpenGL ES2.0 可渲染管道圖:
VertexShader:頂點著色器
頂點著色器,記得在前2篇中,我們有貼出2個著色器的指令碼語句,再次貼出如下:
/** * 頂點著色器的語句 */ private final String mVertexShader = "uniform mat4 uMVPMatrix;\n" + "attribute vec4 aPosition;\n" + "attribute vec2 aTextureCoord;\n" + "varying vec2 vTextureCoord;\n" + "void main() {\n" + " gl_Position = uMVPMatrix * aPosition;\n" + " vTextureCoord = aTextureCoord;\n" + "}\n"; /** * 片元著色器的語句 */ private final String mFragmentShader = "precision mediump float;\n" + "varying vec2 vTextureCoord;\n" + "uniform sampler2D sTexture;\n" + "void main() {\n" + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + "}\n";
下面看下vertexShader語句中的關鍵字:
attribute :使用頂點數組封裝每個頂點的資料,一般用於每個頂點都各不相同的變數,如頂點位置、顏色等
uniform : 頂點著色器使用的常量資料,不能被著色器修改,一般用於對同一組頂點組成的單個3D物體中所有頂點都相同的變數,如當前光源的位置。
sampler:這個是可選的,一種特殊的uniform,表示頂點著色器使用的紋理。
mat4: 表示4×4浮點數矩陣,該變數儲存了組合模型視圖和投影矩陣
vec4:表示包含了4個浮點數的向量
varying是用於從頂點著色器傳遞到片元著色器或FragmentShader傳遞到下一步的輸出變數
uMVPMatrix * aPosition:通過4*4的變換矩陣變換位置後,輸出給gl_Position。gl_Position是頂點著色器內建的輸出變數。gl_FragColor:是片元著色器內建的輸出變數。
PrimitiveAssembly:圖元裝配
圖元即圖形,在OpenGL中有幾個基本圖元:點,線,三角形,其它的複雜圖元都是基於這些基本圖元來繪成的。
在圖元裝配不為階段,那些經過頂點著色器(VertexShader)處理過的頂點數組或緩衝區的資料(VertexArrays/Buff Objects),被組裝到一個個獨立的幾何圖形中(eg:點,線,三角形等)。
對裝配好的每一個圖元,都必須確保它在全局座標系(即能顯示在螢幕的可見地區)中,而對於不在全局座標系中的圖元,就必須進行裁剪,使其處在全局座標系中才能流到下一道工序(光柵化處理)。
在這裡注意下還有一個剔除操作(Cull),前提是這個功能的開關是開啟的:GLES20.glEnable(GLES20.GL_CULL_FACE);,剔除的是圖元的背影,陰影,背面等。
FragmentShader:片元著色器
片元著色器主要是對光柵化處理後產生的片元逐個進行處理。接收頂點著色器輸出的值,需要傳入的資料,以及它經過變換矩陣後輸出值儲存在哪裡可以通過 一目瞭然:
gl_FragColor:是片元著色器內建的輸出變數。
因為Rasterization光柵化處理後,圖元只是在螢幕有了象素,卻還沒有進行顏色處理,還是看不到東西。
因此FragmentShader可以理解為:告訴電腦如何上色的——如何處理光、陰影、遮擋、環境等等。
Per-Fragment Operations:逐個片元操作階段
在片元著色器對片元進行綜合的處理,並最終為片元產生一個顏色值並儲存在gl_FragColor變數後,接下來就是逐個對片元進行一系列的測試。在上面我們說到,在光柵化處理時,它由於是把頂點從全局座標系轉換到螢幕座標系,因此在光柵處理後,每個片元在螢幕上都有個座標(Xw,Yw).且儲存在了框架緩衝區(FrameBuffer),包括片元著色器也是對(Xw,Yw)這個座標的片元進行處理。
展示了Per-Fragment Operations的過程:
Pixel ownership test:像素所有權測試,它決定FrameBuffer中某一個(Xw, Yw)位置的像素是否屬於當前 Opengl ES的context,比如:如果一個Opengl ES幀緩衝視窗被其他視窗遮住了,視窗系統將決定被遮住的像素不屬於當前Opengl ES的context,因此也就不會被顯示。
Scissor test:裁剪測試決定,判斷某一個位置為(Xw, Yw)的片元是否位於裁剪矩形內,如果不在,則被丟棄。
Stencil Test / Depth tests:模板和深度測試,傳入片元的模板和深度值,決定是否丟棄片元。
Blending:將FragmentShader新產生的片元顏色值和FrameBuffer中某一個位置為(Xw, Yw)的片中繼存放區的顏色值進行混合。
Dithering:對於可用顏色較少的系統,可以以犧牲解析度為代價,通過顏色值的抖動來增加可用顏色數量。抖動操作是和硬體相關的,OpenGL允許程式員所做的操作就只有開啟或關閉抖動操作。實際上,若機器的解析度已經相當高,啟用抖動操作根本就沒有任何意義。要啟用或取消抖動,可以用glEnable(GL_DITHER)和glDisable(GL_DITHER)函數。預設情況下,抖動是啟用的。
參考資料
Android OpenGL ES 開發教程(3):OpenGL ES管道(Pipeline)
如何理解OpenGL中著色器,渲染管線,光柵化等概念?
OpenGL ES 2.0渲染管線
Android OpenGL ES零基礎系列(三):OpenGL ES的渲染管道及VertexShader與FragmentShader