1、三角形變金字塔
有了上面OpenGL繪制2D三角形的經(jīng)驗,大家可以想下,要繪制3D的圖形,需要怎么做呢?
也許大家會想到,添加z坐標就可以實現(xiàn)了。前面畫2D三角形時候,坐標點z的值都是0,是不是只要z的值不為0,那么3D的效果就出來了呢?
下面我們把上面的三角形改造為金字塔,帶大家認識3D繪制。
金字塔模型為:
添加金字塔的頂點坐標:
const GLfloat vPyramid[18*3] = {
1.0, -0.8, 0.0,
0.0, -0.8, -1.0,
0.0, 0.8, 0.0,
0.0, -0.8, 1.0,
1.0, -0.8, 0.0,
0.0f, 0.8f, 0.0f,
-1.0, -0.8, 0.0,
0.0, -0.8, 1.0,
0.0, 0.8, 0.0,
0.0, -0.8, -1.0,
-1.0, -0.8, 0.0,
0.0, 0.8, 0.0,
0.0, -0.8, 1.0,
-1.0, -0.8, 0.0,
0.0, -0.8, -1.0,
1.0, -0.8, 0.0,
0.0, -0.8, 1.0,
0.0, -0.8, -1.0,
};
由三個點組成一個三角形面,一組有三個點,前面四組分別表示金字塔的四個側面,下面兩組剛好組成金字塔底部的矩形。
然后修改:
glDrawArrays(GL_TRIANGLES, 0, 18);
最后一個參數(shù)改為18,表示一個有18個點。這時候運行代碼,發(fā)現(xiàn)沒有生成3D的金字塔??梢妴螁瓮ㄟ^添加z軸坐標是不行的。
3D圖理論:
我們現(xiàn)實中如何觀察3D物體。先找到一個觀察位置,然后由觀察位置看向物體。調整不同的觀察位置,可以看到物體不同的角度。并且與觀察物體的距離發(fā)生變化,觀察到的物體大小也會發(fā)生變化,產(chǎn)生遠小近大的效果。
計算機要繪制3D效果,也模仿了我們現(xiàn)實中部分的觀察原理。首先需要設置觀察位置,然后再把物體移動到觀察視野中,并且通過透視投影,讓物體產(chǎn)生遠小近大的效果。
2、坐標系
觀察點:也叫攝像機
如上圖,我們需要把物體移動到攝像機空間中,這樣才能看到物體。
如何移動物體呢?
世界空間-世界坐標系
攝像機和物體必須在同一個坐標系空間中,才能把物體移到攝像機視野中。現(xiàn)實中,我們可以看到物體,是因為物體和我們都在同一個世界坐標系中。對應手機,也有世界坐標系的概念,我們可以認為手機中的世界坐標為:
OpenGL使用的是右手坐標系。關于左右手坐標系可參考:3D圖形.pdf-2.3.3
模型空間-模型坐標系
模型自身的坐標系,坐標原點在模型的某一點上,一般是幾何中心位置為原點,坐標系隨物體移動而移動
攝像機空間-攝像機坐標系
攝像機坐標系就是以攝像機本身為原點建立的坐標系,攝像機本身并不可見,它表示的是有多少區(qū)域可以被顯示(渲染)
變換過程:模型空間轉換到世界空間,再由世界空間轉換到攝像機空間。不能直接從模型空間轉換到攝像機空間,因為它們不在同一個坐標系。需要以世界空間作為轉換的中介。
關于更多坐標系的知識,可以參考:
其實看完網(wǎng)上資料說的各種坐標系的解析,可能會感覺更懵。
對于OpenGL,關于坐標系可以這樣理解:
對于3D物體,因為我們給的坐標點就是在世界坐標系中的,所以物體已存在世界坐標系中。我們給的坐標點是以世界坐標系原點作為中心點定的,所以物體的原點默認與世界坐標系原點重合。
對于攝像機,也是在世界坐標系中的,并且默認在原點(0,0,0),攝像頭朝向為z軸的負方向。
我們需要做的,就是把物體移動到攝像機視野中,或者把攝像機移動到可以看到物體的位置。因為3D物體和攝像機都在世界坐標系中,所以可以省略掉模型空間轉換到世界空間。直接由模型空間轉換到攝像機空間。
3、模型與視變換
物體與攝像機原點一樣,那么物體肯定不在攝像機視野中,根據(jù)攝像頭朝向為z軸的負方向,只需要把物體往z軸負方向移動一段距離,那么物體就在攝像機視野中了。
GLKMatrix4 modelMat4 = GLKMatrix4Identity;
modelMat4 = GLKMatrix4Translate(modelMat4, 0.0, 0.0, -3);
上面代碼是得到一個讓物體往z軸負方向移動3個單位的矩陣。
這里你對矩陣暫時不需要理解太深,只需要知道物體移動、旋轉和縮放等操作,都記錄在矩陣中。物體只需要乘上這個矩陣,就發(fā)生了矩陣記錄的所有動作。
4、投影變換
OpenGL中有兩種投影方式:
- 透視投影:有遠小近大的效果
- 正投影:跟距離沒關系,遠近的同一物體一樣大
透視投影產(chǎn)生的3D圖形更接近我們眼睛觀察到的物體,所以繪制3D圖形,一般選擇透視投影
透視投影原理:
下面我直接截取3D圖形-9.4.4章節(jié),解析小孔成像原理,感興趣的自行去閱讀:
通過上面分析,除了知道小孔成像原理,產(chǎn)生遠小近大效果。還讓我們知道計算機如何將三維坐標投影到同一個平面,形成二維圖像,這樣我們才能在屏幕上顯示,因為我們屏幕本來就是二維的。
代碼實現(xiàn):
GLfloat projectionScaleFix = width / height;
GLKMatrix4 projectionMat4 = GLKMatrix4MakePerspective(FOV, projectionScaleFix , 1, 180);
- FOV:可以理解為攝像機視野的大小,值越大,那么對應的Near和Far面也越大,投影的物體大小不變,場景變大了,相對物體就變小了。
- projectionScaleFix:Near和Far面的寬高比,只有是比率,才能隨FOV值改變面的大小
經(jīng)過透視函數(shù)GLKMatrix4MakePerspective處理后,得到一個透視投影的矩陣。所以,矩陣在3D繪制中是相當重要的。后面也會講到矩陣的一些重要知識。
5、輸入部分
好了,這里我們得到了兩個矩陣:
- 模型移動矩陣:使物體往z軸負方向移動--modelMat4
- 透視投影矩陣:產(chǎn)生透視投影效果--projectionMat4
這兩個矩陣,我們必須給到OpenGL,才能發(fā)生作用了。如何給OpenGL呢,這里可以參考OpenGL 2D繪制時候的輸入部分:
attribute vec4 Position; // position of vertex
attribute vec4 SourceColor; // color of vertex
uniform highp mat4 u_Projection;
uniform highp mat4 u_ModelView;
varying vec4 DestinationColor;
void main(void) {
DestinationColor = SourceColor;
// gl_Position is built-in pass-out variable. Must config for in vertex shader
gl_Position = u_Projection * u_ModelView * Position;
}
對比2D的頂點著色器,我們增加了:
uniform highp mat4 u_Projection;
uniform highp mat4 u_ModelView;
gl_Position = u_Projection * u_ModelView * Position;
很明顯,增加的兩個變量分別對應透視矩陣和移動矩陣。最后在讓物體的每一個點乘以這兩個矩陣,使物體發(fā)生對應的改變。
6、輸出部分
輸入部分已基本完成。對于輸出部分,我們還需要進行一些調整。增加個深度緩存區(qū):
//3D 深度緩沖區(qū)
glGenRenderbuffers(1, &_depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER,
GL_DEPTH_COMPONENT16,
_renderbufferWidth,
_renderbufferHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
繪制2D圖的時候,有幀緩沖區(qū)和渲染緩沖區(qū),這里得增加個深度緩沖區(qū)來緩存3D圖的深度值,在深度測試和顏色混合階段需要用到。
至此,基本工作已完成。無其他問題的話,應該是可以繪制出3D圖形了。
7、讓金字塔動起來
這部分,我們要讓金字塔動起來。
讓金字塔旋轉起來,代碼如下:
//設置模型矩陣
GLKMatrix4 modelMat4 = GLKMatrix4Identity;
//加入位移
modelMat4 = GLKMatrix4Translate(modelMat4, 0.0, 0.0, -3);
//加入旋轉
modelMat4 = GLKMatrix4Rotate(modelMat4, degree, 1, 0, 0);
modelMat4 = GLKMatrix4Rotate(modelMat4, degree, 0, 1, 0);
modelMat4 = GLKMatrix4Rotate(modelMat4, degree, 0, 0, 1);
GLKMatrix4Translate(modelMat4, 0.0, 0.0, -3):這個是上節(jié)提到的,讓物體往z軸負方向移動3個單位,就是把物體移動到攝像機可視范圍中。
GLKMatrix4Rotate(modelMat4, degree, 1, 0, 0):讓物體繞[1, 0 ,0]軸旋轉degree度,[1, 0 ,0]就是x軸。
同理,剩下兩個分別是繞y軸和z軸旋轉一定角度。這里使用了個旋轉組合。
這里引出一個問題,旋轉一定角度,是往哪個方向旋轉呢?這里就得明確正反方向。
前面提到OpenGL使用的是右手坐標系,所以正反方向如右圖。
GLKit還提供很多操作函數(shù):
GLKMatrix4RotateX() 繞x軸旋轉
GLKMatrix4RotateY()
GLKMatrix4RotateZ()
GLKMatrix4Scale() 縮放的
等等....
一些第三方庫和系統(tǒng)庫已經(jīng)幫我們封裝了各種3D圖形線性變換的函數(shù)。使用很方便,就算不理解矩陣變換原理,也能使用。
至此,你對3D繪圖應該有了大體的了解??梢宰约豪L制3D圖形,并且可以讓3D圖形動起來。下節(jié)是介紹矩陣變換的原理,主要是告訴你為什么一個矩陣可以讓3D圖形旋轉,縮放等。理解原理,可能你后續(xù)開發(fā)中還是使用系統(tǒng)提供的api,但增加了自定制的可能性。有興趣的可以繼續(xù)深入學習。
具體實現(xiàn)可參考Demo代碼:https://github.com/wulang150/MyProject/tree/master/MyProject/Controller/TmpTestController/OpenGL