教程
OpenGLES入門(mén)教程1-Tutorial01-GLKit
OpenGLES入門(mén)教程2-Tutorial02-shader入門(mén)
OpenGLES入門(mén)教程3-Tutorial03-三維變換
OpenGLES入門(mén)教程4-Tutorial04-GLKit進(jìn)階
OpenGLES進(jìn)階教程1-Tutorial05-地球月亮
OpenGLES進(jìn)階教程2-Tutorial06-光線(xiàn)
OpenGLES進(jìn)階教程3-Tutorial07-粒子效果
OpenGLES進(jìn)階教程4-Tutorial08-幀緩存
OpenGLES進(jìn)階教程5-Tutorial09-碰碰車(chē)
OpenGLES進(jìn)階教程6-Tutorial10-平截體優(yōu)化
這一次的內(nèi)容是精心準(zhǔn)備的天空盒特效拒秘,為了節(jié)約大家時(shí)間,這次在教程里面不貼代碼,demo部分的內(nèi)容都是干貨。
寫(xiě)這個(gè)demo的過(guò)程中遇到了一些坎,最后會(huì)提到召川。
特別留意天空盒紋理坐標(biāo)推導(dǎo)和頂點(diǎn)數(shù)據(jù)對(duì)象切換。
概念準(zhǔn)備
天空盒特效:OpenGL ES提供了一個(gè)立方體貼圖(cube mapping)的專(zhuān)門(mén)用于產(chǎn)生天空盒效果的紋理貼圖模式。
舉例:一個(gè)人掖桦,站在立方體的中間,上下左右前后看到的都是立方體的圖片供汛。
效果展示
為節(jié)省流量枪汪,gif比較模糊,清晰效果可以看demo怔昨。
核心思路
天空盒的核心就是通過(guò)方向來(lái)取樣紋理雀久,紋理坐標(biāo)被當(dāng)作方向向量,建立適合的正方體后趁舀,位置坐標(biāo)就是紋理坐標(biāo)赖捌。
具體細(xì)節(jié)
1、尺寸大小
天空盒的尺寸可以隨意矮烹,但是需要足夠大以容納渲染的場(chǎng)景越庇。
同時(shí)天空盒的中心要盡可能貼近視點(diǎn)的眼睛位置罩锐,避免太近產(chǎn)生紋理拉伸。
2卤唉、紋理坐標(biāo)到紋素推導(dǎo)(核心)
紋理坐標(biāo)(s, t, r)被當(dāng)作方向向量看待涩惑,每個(gè)紋理單元都表示從原點(diǎn)所看到的紋理立方體上的圖像。
如果是texture2D的情況桑驱,紋理坐標(biāo)(s, t)會(huì)直接返回相應(yīng)位置的紋素竭恬;
textureCube的情況,首先讀取cube紋理熬的,然后以正方體中心為原點(diǎn)痊硕,(s,t,r)為方向,求出正方體和方向向量的交點(diǎn)位置(a, b)悦析,按照位置(a, b)在紋理上面選擇相對(duì)應(yīng)的紋素寿桨。
舉個(gè)例子:
對(duì)于(s, t, r) , 假設(shè) S=fabs(s) ,同理有T R强戴。
如果S > T 并且 S > R亭螟。 那么可以確定交點(diǎn)在面+x 和 -x
根據(jù)s的±可以確定±x面。
直線(xiàn)過(guò)原點(diǎn)和點(diǎn)(s, t, r) 骑歹,那么也會(huì)過(guò)點(diǎn)(1, t/s, r/s)预烙。
(t/s) 和(r/s)就是對(duì)應(yīng)的紋素位置。
考慮到立方體的面為[-1, 1]道媚,那么可以把 (t/s + 1) / 2扁掸,這樣就得到真正的紋素坐標(biāo)( (t/s + 1) / 2, (r/s + 1) / 2)
3、頂點(diǎn)數(shù)據(jù)對(duì)象切換(核心)
glBindVertexArrayOES
很多應(yīng)用會(huì)在同一個(gè)渲染幀調(diào)用多次glBindBuffer()最域、glEnableVertexAttribArray()和glVertexAttribPointer()函數(shù)(用不同的頂點(diǎn)屬性來(lái)渲染多個(gè)對(duì)象)
新的頂點(diǎn)數(shù)據(jù)對(duì)象(VAO) 擴(kuò)展會(huì)幾率當(dāng)前上下文中的與頂點(diǎn)屬性相關(guān)的狀態(tài)谴分,并存儲(chǔ)這些信息到一個(gè)小的緩存中。之后可以通過(guò)單次調(diào)用glBindVertexArrayOES() 函數(shù)來(lái)恢復(fù)镀脂,不需要在調(diào)用glBindBuffer()牺蹄、glEnableVertexAttribArray()和glVertexAttribPointer()。VAO和VBO
VBO:頂點(diǎn)緩沖區(qū)對(duì)象(buffer-object)薄翅,用于存儲(chǔ)頂點(diǎn)坐標(biāo)沙兰、紋理坐標(biāo)、頂點(diǎn)法線(xiàn)翘魄、頂點(diǎn)顏色等鼎天。
VAO:頂點(diǎn)數(shù)據(jù)對(duì)象,記錄頂點(diǎn)數(shù)據(jù)存儲(chǔ)狀態(tài)信息的狀態(tài)對(duì)象(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.
總結(jié)
demo實(shí)現(xiàn)過(guò)程遇到最大的一個(gè)坑斋射,如下:
暫停的適合,天空盒的效果會(huì)消失!
然后開(kāi)始尋找問(wèn)題所在绩鸣,最后發(fā)現(xiàn)問(wèn)題代碼出現(xiàn)在這里
// 增加角度
if (!self.mPauseSwitch.on) {
self.angle += 0.01;
}
實(shí)在無(wú)法理解為什么角度的改變會(huì)影響天空盒的顯示怀大。
回顧了一下OpenGL ES的繪制過(guò)程,從頂點(diǎn)緩存到變換呀闻、著色到幀緩存,發(fā)現(xiàn)天空盒的繪制都沒(méi)有問(wèn)題潜慎。
接著開(kāi)始思考捡多,會(huì)不會(huì)是飛機(jī)的繪制影響了天空盒的繪制?因?yàn)檫@是兩個(gè)著色器铐炫,存在不同的頂點(diǎn)數(shù)據(jù)和紋理垒手。
于是嘗試在繪制完天空盒后調(diào)用下面,防止天空盒綁定的數(shù)據(jù)緩存被飛機(jī)的影響倒信。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
在繪制完飛機(jī)后調(diào)用下面科贬,防止飛機(jī)的頂點(diǎn)數(shù)據(jù)去影響到天空盒。
glDisableVertexAttribArray(GLKVertexAttribPosition);
glDisableVertexAttribArray(GLKVertexAttribNormal);
glBindTexture(GL_TEXTURE_2D, 0);
然而并沒(méi)有什么用 ==鳖悠!
問(wèn)題擱在心頭好幾天榜掌,每天都會(huì)嘗試,也在google查GLKSkyboxEffect相關(guān)的問(wèn)題乘综,可惜GLKSkyboxEffect是蘋(píng)果官方自己實(shí)現(xiàn)憎账。
本來(lái)天空盒是上周就已經(jīng)實(shí)現(xiàn)好,為了寫(xiě)這篇文章實(shí)現(xiàn)了一個(gè)暫停的功能卡辰,就出現(xiàn)這個(gè)bug胞皱。雖然去掉暫停功能很正常,但是這個(gè)表明demo確實(shí)有缺陷九妈,無(wú)法棄之不顧反砌。
經(jīng)過(guò)很多天嘗試后,已經(jīng)可以確定的是萌朱,是飛機(jī)的繪制影響了天空盒的位置宴树,角度的旋轉(zhuǎn)只是隱藏了bug。
開(kāi)始尋找非OpenGL ES的文章嚷兔,看看OpenGL的天空盒實(shí)現(xiàn)森渐,同時(shí)查看蘋(píng)果官方的文檔。
最后偶然在蘋(píng)果的文檔中看到一個(gè)關(guān)鍵詞OES
冒晰,我似乎明白了什么同衣。
OES是OpenGL ES的一個(gè)非標(biāo)準(zhǔn)擴(kuò)展,天空盒里面有用到壶运,而我并沒(méi)有處理耐齐。
嘗試用OES來(lái)管理飛機(jī)的頂點(diǎn)模型。
// glGenVertexArraysOES(1, &_mPositionBuffer);
// glBindVertexArrayOES(_mPositionBuffer);
問(wèn)題果然迎仍而解:暫停的時(shí)候天空盒是正常的。
接下來(lái)開(kāi)始嘗試不用OES埠况,尋找問(wèn)題的根源耸携。
最后的結(jié)論:天空盒的繪制調(diào)用了glBindVertexArrayOES()
,可是在繪制結(jié)束后沒(méi)有glBindVertexArrayOES(0);
導(dǎo)致飛機(jī)的頂點(diǎn)數(shù)據(jù)影響了天空盒的頂點(diǎn)數(shù)據(jù)辕翰。
解決方案:在繪制完天空盒后調(diào)用glBindVertexArrayOES(0);
夺衍,問(wèn)題完美解決。
Tips
天空盒還有兩部分內(nèi)容:一個(gè)是切圖喜命,這個(gè)比較簡(jiǎn)單沟沙,用CoreGraphics即可;另一個(gè)是用Shader來(lái)實(shí)現(xiàn)天空盒壁榕,而非GLKSkyboxEffect矛紫,這部分加進(jìn)來(lái)篇幅就過(guò)長(zhǎng)了。這兩部分都在github上面放了源碼牌里,都已經(jīng)編譯調(diào)試沒(méi)問(wèn)題颊咬,下載完可以直接運(yùn)行。
附上代碼
思考題
- 為什么視點(diǎn)距離天空盒太近會(huì)產(chǎn)生紋理拉伸牡辽?