iOS-OpenGLES-入門-紋理

前言

這是一篇OpenGlES 系統(tǒng)學習教程,記錄自己的學習過程队他。
環(huán)境: Xcode10 + OpenGL ES 3.0
目標: 紋理貼圖技術(shù)
這里是demo,你的star和fork是對我最好的支持和動力。

效果展示

texture.png

紋理坐標

紋理坐標在x和y軸上妓盲,范圍為0到1之間(注意我們使用的是2D紋理圖像)。使用紋理坐標獲取紋理顏色叫做采樣(Sampling)专普。紋理坐標起始于(0, 0)悯衬,也就是紋理圖片的左下角,終始于(1, 1)檀夹,即紋理圖片的右上角筋粗。


紋理坐標.jpeg

下面的圖片展示了我們是如何把紋理坐標映射到三角形上的。


三角形貼圖.png

注:如果出現(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,下篇會詳解橘霎。

最后編輯于
?著作權(quán)歸作者所有,轉(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