教程
OpenGLES入門教程1-Tutorial01-GLKit
OpenGLES入門教程2-Tutorial02-shader入門
OpenGLES入門教程3-Tutorial03-三維變換
OpenGLES入門教程4-Tutorial04-GLKit進階
OpenGLES進階教程1-Tutorial05-地球月亮
OpenGLES進階教程2-Tutorial06-光線
OpenGLES進階教程3-Tutorial07-粒子效果
OpenGLES進階教程4-Tutorial08-幀緩存
OpenGLES進階教程5-Tutorial09-碰碰車
OpenGLES進階教程6-Tutorial10-平截體優化
這一次的內容是精心準備的天空盒特效,為了節約大家時間,這次在教程里面不貼代碼,demo部分的內容都是干貨。
寫這個demo的過程中遇到了一些坎,最后會提到。
特別留意天空盒紋理坐標推導和頂點數據對象切換。
概念準備
天空盒特效:OpenGL ES提供了一個立方體貼圖(cube mapping)的專門用于產生天空盒效果的紋理貼圖模式。
舉例:一個人,站在立方體的中間,上下左右前后看到的都是立方體的圖片。
效果展示
為節省流量,gif比較模糊,清晰效果可以看demo。
核心思路
天空盒的核心就是通過方向來取樣紋理,紋理坐標被當作方向向量,建立適合的正方體后,位置坐標就是紋理坐標。
具體細節
1、尺寸大小
天空盒的尺寸可以隨意,但是需要足夠大以容納渲染的場景。
同時天空盒的中心要盡可能貼近視點的眼睛位置,避免太近產生紋理拉伸。
2、紋理坐標到紋素推導(核心)
紋理坐標(s, t, r)被當作方向向量看待,每個紋理單元都表示從原點所看到的紋理立方體上的圖像。
如果是texture2D的情況,紋理坐標(s, t)會直接返回相應位置的紋素;
textureCube的情況,首先讀取cube紋理,然后以正方體中心為原點,(s,t,r)為方向,求出正方體和方向向量的交點位置(a, b),按照位置(a, b)在紋理上面選擇相對應的紋素。
舉個例子:
對于(s, t, r) , 假設 S=fabs(s) ,同理有T R。
如果S > T 并且 S > R。 那么可以確定交點在面+x 和 -x
根據s的±可以確定±x面。
直線過原點和點(s, t, r) ,那么也會過點(1, t/s, r/s)。
(t/s) 和(r/s)就是對應的紋素位置。
考慮到立方體的面為[-1, 1],那么可以把 (t/s + 1) / 2,這樣就得到真正的紋素坐標( (t/s + 1) / 2, (r/s + 1) / 2)
3、頂點數據對象切換(核心)
glBindVertexArrayOES
很多應用會在同一個渲染幀調用多次glBindBuffer()、glEnableVertexAttribArray()和glVertexAttribPointer()函數(用不同的頂點屬性來渲染多個對象)
新的頂點數據對象(VAO) 擴展會幾率當前上下文中的與頂點屬性相關的狀態,并存儲這些信息到一個小的緩存中。之后可以通過單次調用glBindVertexArrayOES() 函數來恢復,不需要在調用glBindBuffer()、glEnableVertexAttribArray()和glVertexAttribPointer()。VAO和VBO
VBO:頂點緩沖區對象(buffer-object),用于存儲頂點坐標、紋理坐標、頂點法線、頂點顏色等。
VAO:頂點數據對象,記錄頂點數據存儲狀態信息的狀態對象(status-object)。
This extension introduces vertex array objects which encapsulate vertex array states on the server side (vertex buffer objects).
These objects aim to keep pointers to vertex data and to provide
names for different sets of vertex data. Therefore applications are
allowed to rapidly switch between different sets of vertex array
state, and to easily return to the default vertex array state.
Q:Should vertex array objects be sharable across multiple OpenGL ES
contexts?
A: No.
總結
demo實現過程遇到最大的一個坑,如下:
暫停的適合,天空盒的效果會消失!
然后開始尋找問題所在,最后發現問題代碼出現在這里
// 增加角度
if (!self.mPauseSwitch.on) {
self.angle += 0.01;
}
實在無法理解為什么角度的改變會影響天空盒的顯示。
回顧了一下OpenGL ES的繪制過程,從頂點緩存到變換、著色到幀緩存,發現天空盒的繪制都沒有問題。
接著開始思考,會不會是飛機的繪制影響了天空盒的繪制?因為這是兩個著色器,存在不同的頂點數據和紋理。
于是嘗試在繪制完天空盒后調用下面,防止天空盒綁定的數據緩存被飛機的影響。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
在繪制完飛機后調用下面,防止飛機的頂點數據去影響到天空盒。
glDisableVertexAttribArray(GLKVertexAttribPosition);
glDisableVertexAttribArray(GLKVertexAttribNormal);
glBindTexture(GL_TEXTURE_2D, 0);
然而并沒有什么用 ==!
問題擱在心頭好幾天,每天都會嘗試,也在google查GLKSkyboxEffect相關的問題,可惜GLKSkyboxEffect是蘋果官方自己實現。
本來天空盒是上周就已經實現好,為了寫這篇文章實現了一個暫停的功能,就出現這個bug。雖然去掉暫停功能很正常,但是這個表明demo確實有缺陷,無法棄之不顧。
經過很多天嘗試后,已經可以確定的是,是飛機的繪制影響了天空盒的位置,角度的旋轉只是隱藏了bug。
開始尋找非OpenGL ES的文章,看看OpenGL的天空盒實現,同時查看蘋果官方的文檔。
最后偶然在蘋果的文檔中看到一個關鍵詞OES
,我似乎明白了什么。
OES是OpenGL ES的一個非標準擴展,天空盒里面有用到,而我并沒有處理。
嘗試用OES來管理飛機的頂點模型。
// glGenVertexArraysOES(1, &_mPositionBuffer);
// glBindVertexArrayOES(_mPositionBuffer);
問題果然迎仍而解:暫停的時候天空盒是正常的。
接下來開始嘗試不用OES,尋找問題的根源。
最后的結論:天空盒的繪制調用了glBindVertexArrayOES()
,可是在繪制結束后沒有glBindVertexArrayOES(0);
導致飛機的頂點數據影響了天空盒的頂點數據。
解決方案:在繪制完天空盒后調用glBindVertexArrayOES(0);
,問題完美解決。
Tips
天空盒還有兩部分內容:一個是切圖,這個比較簡單,用CoreGraphics即可;另一個是用Shader來實現天空盒,而非GLKSkyboxEffect,這部分加進來篇幅就過長了。這兩部分都在github上面放了源碼,都已經編譯調試沒問題,下載完可以直接運行。
附上代碼
思考題
- 為什么視點距離天空盒太近會產生紋理拉伸?