前言
這是一篇OpenGlES 系統(tǒng)學習教程,記錄自己的學習過程队他。
環(huán)境: Xcode10 + OpenGL ES 3.0
目標: 紋理貼圖技術(shù)
這里是demo,你的star和fork是對我最好的支持和動力。
效果展示
紋理坐標
紋理坐標在x和y軸上妓盲,范圍為0到1之間(注意我們使用的是2D紋理圖像)。使用紋理坐標獲取紋理顏色叫做采樣(Sampling)专普。紋理坐標起始于(0, 0)悯衬,也就是紋理圖片的左下角,終始于(1, 1)檀夹,即紋理圖片的右上角筋粗。
下面的圖片展示了我們是如何把紋理坐標映射到三角形上的。
注:如果出現(xiàn)紋理上下顛倒的現(xiàn)象炸渡,這是因為OpenGL要求y軸0.0坐標是在圖片的底部的娜亿,但是圖片的y軸0.0坐標通常在頂部,下面會給出解決方法蚌堵。
紋理的部分理論知識可以參考這里
生產(chǎn)紋理
跟生成OpenGl 對象差不多
-
glGenTextures
:生成紋理ID -
glBindTexture
: 綁定的紋理 -
glTexImage2D
:根據(jù)圖片數(shù)據(jù)生成紋理
glTexImage2D
原型如下:
func glTexImage2D(_ target: GLenum, _ level: GLint, _ internalformat: GLint, _ width: GLsizei, _ height: GLsizei, _ border: GLint, _ format: GLenum, _ type: GLenum, _ pixels: UnsafeRawPointer!)
- 第一個參數(shù)指定了紋理目標(Target)买决。設置為GL_TEXTURE_2D意味著會生成與當前綁定的紋理對象在同一個目標上的紋理(任何綁定到GL_TEXTURE_1D和GL_TEXTURE_3D的紋理不會受到影響)
- 第二個參數(shù)為紋理指定多級漸遠紋理的級別,如果你希望單獨手動設置每個多級漸遠紋理的級別的話吼畏。這里我們填0督赤,也就是基本級別
- 第三個參數(shù)告訴OpenGL我們希望把紋理儲存為何種格式。我們的圖像只有RGB值宫仗,因此我們也把紋理儲存為RGB值够挂。
- 第四個和第五個參數(shù)設置最終的紋理的寬度和高度。我們之前加載圖像的時候儲存了它們藕夫,所以我們使用對應的變量
- 下個參數(shù)應該總是被設為0(歷史遺留的問題)
- 第七第八個參數(shù)定義了源圖的格式和數(shù)據(jù)類型孽糖。我們使用RGB值加載這個圖像枯冈,并把它們儲存為char(byte)數(shù)組,我們將會傳入對應值
- 最后一個參數(shù)是真正的圖像數(shù)據(jù)
大致如下:
fileprivate func setupTexture(fileName:String) {
// 獲取圖片的CGImageRef
guard let spriteImage = UIImage(named: fileName)?.cgImage else {
print("Failed to load image \(fileName)")
return
}
// 讀取圖片大小
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)
// 在CGContextRef上繪圖
spriteContext?.draw(spriteImage, in: CGRect(x: 0, y: 0, width: width, height: height))
// 綁定紋理到默認的紋理ID(這里只有一張圖片办悟,故而相當于默認于片元著色器里面的colorMap尘奏,如果有多張圖不可以這么做)
glBindTexture(GLenum(GL_TEXTURE_2D), 0);
// 為當前綁定的紋理對象設置環(huán)繞、過濾方式
glTexParameteri( GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR );
glTexParameteri( GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR );
glTexParameteri( GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE);
glTexParameteri( GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE);
// 加載并生成紋理
let fw = width
let fh = height;
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(fw), GLsizei(fh), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), spriteData);
// 釋放資源
free(spriteData)
}
紋理單元
一個紋理的位置值通常稱為一個紋理單元(Texture Unit)病蛉。一個紋理的默認紋理單元是0炫加,它是默認的激活紋理單元,本文前面部分我們沒有分配一個位置值铺然。如果使用多個紋理單元需要手動分配俗孝。
紋理單元的主要目的是讓我們在著色器中可以使用多于一個的紋理。通過把紋理單元賦值給采樣器魄健,我們可以一次綁定多個紋理赋铝,只要我們首先激活對應的紋理單元。就像glBindTexture一樣沽瘦,我們可以使用glActiveTexture激活紋理單元革骨,傳入我們需要使用的紋理單元:
glActiveTexture(GL_TEXTURE0); // 在綁定紋理之前先激活紋理單元
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
你可能會奇怪為什么sampler2D變量是個uniform,我們卻不用glUniform給它賦值析恋。使用glUniform1i良哲,我們可以給紋理采樣器分配一個位置值,這樣的話我們能夠在一個片段著色器中設置多個紋理助隧。
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手動設置
應用紋理
紋理上下顛倒解決方法
// 注:紋理上下顛倒,這是因為OpenGL要求y軸0.0坐標是在圖片的底部的筑凫,但是圖片的y軸0.0坐標通常在頂部。
// 解決1:glsl 里面 反轉(zhuǎn) y 軸(gl_Position = vec4(vPosition.x,-vPosition.y,vPosition.z,1.0))
// 解決2:紋理坐標(s,t) -> (s,abs(t - 1))
let vertices: [GLfloat] = [
0.5, 0.5, -1, 1, 1, // 右上 1, 0
0.5, -0.5, -1, 1, 0, // 右下 1, 1
-0.5, -0.5, -1, 0, 0, // 左下 0, 1
-0.5, -0.5, -1, 0, 0, // 左下 0, 1
-0.5, 0.5, -1, 0, 1, // 左上 0, 0
0.5, 0.5, -1, 1, 1 // 右上 1, 0
]
渲染
fileprivate func render() {
glClearColor(1.0, 1.0, 0, 1.0)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
glViewport(0, 0, GLsizei(frame.size.width), GLsizei(frame.size.height))
// 注:紋理上下顛倒,這是因為OpenGL要求y軸0.0坐標是在圖片的底部的喇颁,但是圖片的y軸0.0坐標通常在頂部漏健。
// 解決1:glsl 里面 反轉(zhuǎn) y 軸(gl_Position = vec4(vPosition.x,-vPosition.y,vPosition.z,1.0))
// 解決2:紋理坐標(s,t) -> (s,abs(t - 1))
let vertices: [GLfloat] = [
0.5, 0.5, -1, 1, 1, // 右上 1, 0
0.5, -0.5, -1, 1, 0, // 右下 1, 1
-0.5, -0.5, -1, 0, 0, // 左下 0, 1
-0.5, -0.5, -1, 0, 0, // 左下 0, 1
-0.5, 0.5, -1, 0, 1, // 左上 0, 0
0.5, 0.5, -1, 1, 1 // 右上 1, 0
]
var VAO:GLuint = 0
var VBO:GLuint = 0
glGenVertexArrays(1, &VAO)
glGenBuffers(GLsizei(1), &VBO)
glBindVertexArray(VAO)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), VBO)
let count = vertices.count
let size = MemoryLayout<GLfloat>.size
glBufferData(GLenum(GL_ARRAY_BUFFER), count * size, vertices, GLenum(GL_STATIC_DRAW))
glVertexAttribPointer(
positionSlot,
3,
GLenum(GL_FLOAT),
GLboolean(GL_FALSE),
GLsizei(MemoryLayout<GLfloat>.size * 5), UnsafeRawPointer(bitPattern: 0))
glEnableVertexAttribArray(positionSlot)
glVertexAttribPointer(
GLuint(textCoordSlot),
2,
GLenum(GL_FLOAT),
GLboolean(GL_FALSE),
GLsizei(MemoryLayout<GLfloat>.size * 5), UnsafeRawPointer(bitPattern:3 * MemoryLayout<GLfloat>.size))
glEnableVertexAttribArray(GLuint(textCoordSlot))
setupTexture(fileName: "dungeon_01.jpg")
// 繪制
glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)
myContext?.presentRenderbuffer(Int(GL_RENDERBUFFER))
glDeleteVertexArrays(1, &VAO)
glDeleteBuffers(1, &VBO)
}
注:這里使用到了Vertex Buffer Object,下篇會詳解橘霎。