這是一篇OpenGlES 系統(tǒng)學習教程崭参,記錄自己的學習過程扔字。
環(huán)境: Xcode10.2.1 + OpenGL ES 3.0
目標: 天空盒
代碼已上傳github
,Tutorial-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))
參考