iOS-OpenGLES-進階-天空盒

這是一篇OpenGlES 系統(tǒng)學習教程崭参,記錄自己的學習過程扔字。
環(huán)境: Xcode10.2.1 + OpenGL ES 3.0
目標: 天空盒
代碼已上傳githubTutorial-06-天空盒嘉裤,你的star和fork是對我最好的支持和動力逆屡。

概述

所謂天空盒是紋理的一種應用方式咖气,它可以將整個場景高效的封裝到一個立方體的大盒子里睬隶,同時確保觀察者位于盒子的正中央愉烙。在場景渲染的時候满力,場景內(nèi)任何沒有被遮擋的物體都會出現(xiàn)在盒子的內(nèi)部焕参。通過選擇合適的紋理內(nèi)容,我們就可以讓整個立方體從觀察者的角度看起來就是環(huán)境本身油额。例如:吃雞中的場景叠纷,會隨著玩家的視點轉(zhuǎn)動而轉(zhuǎn)動。制作天空盒之前我們需要準備一下天空盒的資源潦嘶,可以在這里獲取自己喜歡的場景涩嚣。

效果展示

創(chuàng)建cubemap

天空盒的專業(yè)術語叫立體貼圖。OpenGlES支持GL_TEXTURE_CUBE_MAP的Texture掂僵。GL_TEXTURE_CUBE_MAP是由六個2D紋理綁定到GL_TEXTURE_CUBE_MAP目標而創(chuàng)建的紋理航厚。

綁定目標 紋理方向
GL_TEXTURE_CUBE_MAP_POSITIVE_X 右邊
GL_TEXTURE_CUBE_MAP_NEGATIVE_X 左邊
GL_TEXTURE_CUBE_MAP_POSITIVE_Y 頂部
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 底部
GL_TEXTURE_CUBE_MAP_POSITIVE_Z 背面
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 前面

如下圖所示,形成一個立方體紋理



在構(gòu)建cubemaps锰蓬,一般利用枚舉常量遞增的特性幔睬,一次綁定到上述6個目標。例如在OpenGLES中枚舉常量定義為(雖然在swift中并不是枚舉芹扭,但值是一樣的):

public var GL_TEXTURE_CUBE_MAP_POSITIVE_X: Int32 { get }    //  34069
public var GL_TEXTURE_CUBE_MAP_NEGATIVE_X: Int32 { get }    //  34070
public var GL_TEXTURE_CUBE_MAP_POSITIVE_Y: Int32 { get }    //  34071
public var GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: Int32 { get }    //  34072
public var GL_TEXTURE_CUBE_MAP_POSITIVE_Z: Int32 { get }    //  34073
public var GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: Int32 { get }    //  34074

可以看到值是依次遞增的麻顶,我們可以使用循環(huán)來創(chuàng)建這個立方體紋理赦抖,如下:

    fileprivate func loadCubeMapTexture(fileNames:[String]) -> GLuint {
        
        var textId: GLuint = 0
        glGenTextures(1, &textId)
        glBindTexture(GLenum(GL_TEXTURE_CUBE_MAP), textId)
        
        
        
        for (index,name) in fileNames.enumerated() {
            guard let spriteImage = UIImage(named: name)?.cgImage else {
                print("Failed to load image \(name)")
                return textId
            }
            
            let width = spriteImage.width
            let height = spriteImage.height
            
            let spriteData = calloc(width * height * 4, MemoryLayout<GLubyte>.size)
            
            let spriteContext = CGContext(data: spriteData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width*4, space: spriteImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
            
            spriteContext?.draw(spriteImage, in: CGRect(x: 0, y: 0, width: width, height: height))
            
            // 使用GL_TEXTURE_CUBE_MAP_POSITIVE_X + i的方式來一次創(chuàng)建了6個2D紋理
            glTexImage2D(GLenum(GL_TEXTURE_CUBE_MAP_POSITIVE_X + Int32(index)), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), spriteData)
            free(spriteData)
        }
        
        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)
        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE)
        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE)
        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_WRAP_R), GL_CLAMP_TO_EDGE)
        
        glBindTexture(GLenum(GL_TEXTURE_CUBE_MAP), 0)
        
        return textId
    }

使用cubemaps

cubemaps采樣不同于2D紋理使用的紋理坐標(s,t),這邊需要使用三維紋理坐標(s,t,r)辅肾,如下圖所示:


首先根據(jù)(s,t,r)中模最大的分量決定在哪個面采樣队萤,然后使用剩下的2個坐標在對應的面上做2D紋理采樣。具體的計算過程可以參考cubemaps

當紋理坐標超出[0,1]范圍時的紋理采樣方式矫钓。上述代碼中要尔,我們使用

        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE)
        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE)
        glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_WRAP_R), GL_CLAMP_TO_EDGE)

其中參數(shù)GL_CLAMP_TO_EDGE主要用于指定,當(s,t,r)坐標沒有落在哪個面新娜,而是落在兩個面之間時的紋理采樣赵辕,使用GL_CLAMP_TO_EDGE參數(shù)表明,當在兩個面之間采樣時使用邊緣的紋理值概龄。同時OpenGlES默認是打開了無縫立方體映射濾波的匆帚。
頂點著色器:

attribute vec3 position;

uniform highp mat4 u_mvpMatrix;

varying lowp vec3 TextCoord;

void main()
{
    gl_Position = u_mvpMatrix * vec4(position, 1.0);
    TextCoord = position;
}

需要注意的是:我們使用位于模型局部坐標系下的位置坐標作為 3D 紋理坐標,這樣做可行的原因是在對 cubemap 采樣的過程中也是通過從圓心發(fā)出一條射線到立方體或者球體表面上旁钧,所以這里天空盒的位置坐標就可以直接作為我們的紋理坐標吸重。之后我們將這些數(shù)據(jù)都傳遞到片元著色器中。

片元著色器:

varying lowp vec3 TextCoord;
uniform samplerCube skybox;

void main()
{
    gl_FragColor = textureCube(skybox, TextCoord);
}

要渲染一個天空盒需要這些組件——一個著色器對象歪今、一個cubemap 紋理對象和一個立方體盒子(模型)嚎幸,以及模型位置轉(zhuǎn)換過程(從這篇文章開始矩陣轉(zhuǎn)換會使用GLKEffectPropertyTransform),我們將這些組件都封裝在同一個類中寄猩。這里有使用VAO(頂點數(shù)組對象)嫉晶,相關細節(jié)可以查看learnopengl
為了讓天空盒效果看起來比較逼真田篇,我們需要把天空盒中心移到觀察者中心替废,同時以一定比例縮放天空盒,如下:

var modelView = GLKMatrix4Translate(transform.modelviewMatrix, center.x, center.y, center.z)  // 移到觀察者中心位置
modelView = GLKMatrix4Scale(modelView, xSize, ySize, zSize)  // 縮放

let modelViewProjection = GLKMatrix4Multiply(transform.projectionMatrix, modelView)

glUniformMatrix4fv(glGetUniformLocation(skyBoxProgram, "u_mvpMatrix"), 1, GLboolean(GL_FALSE), modelViewProjection.array)

設置錯的話會出現(xiàn)一些視覺bug泊柬,其實天空盒效果相當來說比較簡單椎镣,重點在于理解坐標系和物體之間的坐標聯(lián)動。不太明白的可以先看看坐標系和攝像機系統(tǒng)的一些知識傳送門還是老地方兽赁。

渲染

由于相機是放在天空盒內(nèi)部的状答,所以我們要禁止背面剔除。其次我們需要關閉深度測試刀崖。

glClearColor(0.18, 0.04, 0.14, 1.0)
glClear(UInt32(GL_COLOR_BUFFER_BIT) | UInt32(GL_DEPTH_BUFFER_BIT))
glViewport(0, 0, GLsizei(frame.size.width), GLsizei(frame.size.height))

let width = frame.size.width
let height = frame.size.height

baseEffect.transform.projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(85.0), Float(width/height), 0.1, 20.0)

baseEffect.transform.modelviewMatrix = GLKMatrix4MakeLookAt(eyePosition.x,
                                                            eyePosition.y,
                                                            eyePosition.z,
                                                            targetPosition.x,
                                                            targetPosition.y, targetPosition.z,
                                                            upVector.x,
                                                            upVector.y,
                                                            upVector.z)
skyboxEffect?.center = eyePosition
skyboxEffect?.transform.projectionMatrix = baseEffect.transform.projectionMatrix
skyboxEffect?.transform.modelviewMatrix = baseEffect.transform.modelviewMatrix

// 繪制天空盒
skyboxEffect?.prepareToDraw()
glDepthMask(GLboolean(GL_FALSE))    // 關閉深度緩存
glDisable(GLenum(GL_CULL_FACE))     // 關閉背面剔除
skyboxEffect?.draw()
glDepthMask(GLboolean(GL_TRUE))     // 開啟深度緩存
glEnable(GLenum(GL_CULL_FACE)) 

// 繪制物體
glUseProgram(sceneProgram)

let modelViewProjection = GLKMatrix4Multiply(baseEffect.transform.projectionMatrix, baseEffect.transform.modelviewMatrix)

glUniformMatrix4fv(glGetUniformLocation(sceneProgram, "u_mvpMatrix"), 1, GLboolean(GL_FALSE), modelViewProjection.array)

// 綁定頂點數(shù)組對象
glBindVertexArray(cubeVAOId)
glActiveTexture(GLenum(GL_TEXTURE0))
glBindTexture(GLenum(GL_TEXTURE_2D), cubeTextId)
glUniform1i(glGetUniformLocation(sceneProgram, "colorMap"), 0)
glDrawArrays(GLenum(GL_TRIANGLES), 0, 36)

myContext?.presentRenderbuffer(Int(GL_RENDERBUFFER))

參考

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惊科,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亮钦,更是在濱河造成了極大的恐慌馆截,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜂莉,死亡現(xiàn)場離奇詭異蜡娶,居然都是意外死亡堪唐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門翎蹈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人男公,你說我怎么就攤上這事荤堪。” “怎么了枢赔?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵澄阳,是天一觀的道長。 經(jīng)常有香客問我踏拜,道長碎赢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任速梗,我火速辦了婚禮肮塞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姻锁。我一直安慰自己枕赵,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布位隶。 她就那樣靜靜地躺著拷窜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涧黄。 梳的紋絲不亂的頭發(fā)上篮昧,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音笋妥,去河邊找鬼懊昨。 笑死,一個胖子當著我的面吹牛春宣,可吹牛的內(nèi)容都是我干的疚颊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼信认,長吁一口氣:“原來是場噩夢啊……” “哼材义!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嫁赏,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤其掂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后潦蝇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體款熬,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡深寥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贤牛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惋鹅。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖殉簸,靈堂內(nèi)的尸體忽然破棺而出闰集,到底是詐尸還是另有隱情,我是刑警寧澤般卑,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布武鲁,位于F島的核電站,受9級特大地震影響蝠检,放射性物質(zhì)發(fā)生泄漏沐鼠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一叹谁、第九天 我趴在偏房一處隱蔽的房頂上張望饲梭。 院中可真熱鬧,春花似錦焰檩、人聲如沸排拷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽监氢。三九已至,卻和暖如春藤违,著一層夾襖步出監(jiān)牢的瞬間浪腐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工顿乒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留议街,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓璧榄,卻偏偏與公主長得像特漩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子骨杂,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345