三、OpenGL 3D繪制

1、三角形變金字塔

有了上面OpenGL繪制2D三角形的經(jīng)驗,大家可以想下,要繪制3D的圖形,需要怎么做呢?

也許大家會想到,添加z坐標就可以實現(xiàn)了。前面畫2D三角形時候,坐標點z的值都是0,是不是只要z的值不為0,那么3D的效果就出來了呢?

下面我們把上面的三角形改造為金字塔,帶大家認識3D繪制。

金字塔模型為:

image

添加金字塔的頂點坐標:

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、坐標系

觀察點:也叫攝像機

image

如上圖,我們需要把物體移動到攝像機空間中,這樣才能看到物體。

如何移動物體呢?


世界空間-世界坐標系

攝像機和物體必須在同一個坐標系空間中,才能把物體移到攝像機視野中。現(xiàn)實中,我們可以看到物體,是因為物體和我們都在同一個世界坐標系中。對應手機,也有世界坐標系的概念,我們可以認為手機中的世界坐標為:

image

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、投影變換

image

OpenGL中有兩種投影方式:

  • 透視投影:有遠小近大的效果
  • 正投影:跟距離沒關系,遠近的同一物體一樣大

透視投影產(chǎn)生的3D圖形更接近我們眼睛觀察到的物體,所以繪制3D圖形,一般選擇透視投影

透視投影原理


下面我直接截取3D圖形-9.4.4章節(jié),解析小孔成像原理,感興趣的自行去閱讀:

image
image
image
image

通過上面分析,除了知道小孔成像原理,產(chǎn)生遠小近大效果。還讓我們知道計算機如何將三維坐標投影到同一個平面,形成二維圖像,這樣我們才能在屏幕上顯示,因為我們屏幕本來就是二維的。


代碼實現(xiàn):

GLfloat projectionScaleFix = width / height;
GLKMatrix4 projectionMat4 = GLKMatrix4MakePerspective(FOV, projectionScaleFix , 1, 180);
image
  • 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軸旋轉一定角度。這里使用了個旋轉組合。

這里引出一個問題,旋轉一定角度,是往哪個方向旋轉呢?這里就得明確正反方向。

image

前面提到OpenGL使用的是右手坐標系,所以正反方向如右圖。

image

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

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容