OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

半紙淵發表於2017-12-14

前言:如果你沒有 OpenGL ES 2 的基礎知識,請先移步 《OpenGL ES 2.0 (iOS) 筆記大綱》 學習一下基礎的知識。

目錄

一、軟體執行效果演示
(一)、最終效果
(二)、資訊提取
二、紋理處理的流程【核心】
(一)、Texture 是什麼?
(二)、Texture
(三)、引入了 Texture 的 Shader 檔案
(四)、Texture 正確的 “書寫” 順序

三、知識擴充:圖片載入
使用 Quartz Core 的知識載入圖片資料


一、軟體執行效果演示

(一)、最終效果

工程地址:Github

Texture-Base.gif

(二)、資訊提取

1. 不同的模型【2D & 3D】,不同維度下,Texture 的處理區別
2. 單一畫素資訊【pixelBuffer】 與 複雜畫素資訊【圖片】的顯示區別
3. 正方圖【單張或多張圖片】 與 長方圖,畫素的顯示控制區別

二、紋理處理的流程【核心】

(一)、Texture 是什麼?

Texture 紋理,就是一堆被精心排列過的畫素;
  1. 因為 OpenGL 就是影象處理庫,所以 Texture 在 OpenGL 裡面有多重要,可想而知;
  2. 其中間接地鑑明了一點,圖片本身可以有多大變化,OpenGL 就可以有多少種變化。

學好 Texture 非常重要

(二)、Texture

Texture 在 OpenGL 裡面有很多種類,但在 ES 版本中就兩種——Texture_2D + Texture_CubeMap;

Texture_2D: 就是 {x, y} 二維空間下的畫素呈現,也就是說,由效果圖上演示可知,很難做到使正方體的六個面出現不同的畫素組合;圖片處理一般都使用這個模式;[x 、y 屬於 [0, 1] 這個範圍]

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

Texture_CubeMap: 就是 { x, y, z } 三維空間下的畫素呈現,也就如效果圖中演示的正方體的六個面可以出現不同的畫素組合;它一般是用於做環境貼圖——就是製作一個環境,讓 3D 模型如同置身於真實環境中【卡通環境中也行】。[x、y、z 屬於 [-1, 1] 這個範圍,就是與 Vertex Position 的值範圍一致]

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

注:上面提到的所有座標範圍是指有效渲染範圍,也就是說你如果提供的紋理座標超出了這個範圍也沒有問題,只不過超出的部分就不渲染了;

感受一下怎麼具體表達:

// VYVertex
typedef struct {
    GLfloat position[3];
    GLfloat texCoord[2];
    GLfloat normalCoord[3];
}VYVertex;
複製程式碼

Texture_2D:

// Square
static const VYVertex tex2DSquareDatas[] = {
    {{-1.0, -1.0, 0.0}, {0.0, 0.0}},
    {{ 1.0, -1.0, 0.0}, {1.0, 0.0}},
    {{ 1.0,  1.0, 0.0}, {1.0, 1.0}},
    {{-1.0,  1.0, 0.0}, {0.0, 1.0}},
};
複製程式碼
// Cube
static const VYVertex tex2DCubeDatas[] = {

    // Front [Front 的 z 是正的]
    {{-1.0, -1.0,  1.0}, {0.0, 0.0}}, // 0
    {{ 1.0, -1.0,  1.0}, {1.0, 0.0}}, // 1
    {{ 1.0,  1.0,  1.0}, {1.0, 1.0}}, // 2
    {{-1.0,  1.0,  1.0}, {0.0, 1.0}}, // 3
    // Back [Back 的 z 是負的]
    {{-1.0,  1.0, -1.0}, {0.0, 0.0}}, //4[3: -Z]
    {{ 1.0,  1.0, -1.0}, {1.0, 0.0}}, //5[2: -Z]
    {{ 1.0, -1.0, -1.0}, {1.0, 1.0}}, //6[1: -Z]
    {{-1.0, -1.0, -1.0}, {0.0, 1.0}}, //7[0: -Z]
    // Left [Left 的 x 是負的]
    {{-1.0, -1.0,  1.0}, {0.0, 0.0}}, //8[0]
    {{-1.0,  1.0,  1.0}, {1.0, 0.0}}, //9[3]
    {{-1.0,  1.0, -1.0}, {1.0, 1.0}}, //10[4]
    {{-1.0, -1.0, -1.0}, {0.0, 1.0}}, //11[7]
    // Right [Right 的 x 是正的]
    {{ 1.0, -1.0,  1.0}, {0.0, 0.0}}, //12[1]
    {{ 1.0, -1.0, -1.0}, {1.0, 0.0}}, //13[6]
    {{ 1.0,  1.0, -1.0}, {1.0, 1.0}}, //14[5]
    {{ 1.0,  1.0,  1.0}, {0.0, 1.0}}, //15[2]
    // Top [Top 的 y 是正的]
    {{-1.0,  1.0,  1.0}, {0.0, 0.0}}, //16[3]
    {{ 1.0,  1.0,  1.0}, {1.0, 0.0}}, //17[2]
    {{ 1.0,  1.0, -1.0}, {1.0, 1.0}}, //18[5]
    {{-1.0,  1.0, -1.0}, {0.0, 1.0}}, //19[4]
    // Bottom [Bottom 的 y 是負的]
    {{-1.0, -1.0,  1.0}, {0.0, 0.0}}, //20[0]
    {{-1.0, -1.0, -1.0}, {1.0, 0.0}}, //21[7]
    {{ 1.0, -1.0, -1.0}, {1.0, 1.0}}, //22[6]
    {{ 1.0, -1.0,  1.0}, {0.0, 1.0}}, //23[1]
    
};
複製程式碼

Texture_CubeMap:

// Cube Map
static const VYVertex texCubemapCubeDatas[] = {
    
    // Front [Front 的 z 是正的]
    {{-1.0, -1.0,  1.0}, {}, {-1.0, -1.0,  1.0}}, // 0
    {{ 1.0, -1.0,  1.0}, {}, { 1.0, -1.0,  1.0}}, // 1
    {{ 1.0,  1.0,  1.0}, {}, { 1.0,  1.0,  1.0}}, // 2
    {{-1.0,  1.0,  1.0}, {}, {-1.0,  1.0,  1.0}}, // 3
    // Back [Back 的 z 是負的]
    {{-1.0,  1.0, -1.0}, {}, {-1.0,  1.0, -1.0}}, //4[3: -Z]
    {{ 1.0,  1.0, -1.0}, {}, { 1.0,  1.0, -1.0}}, //5[2: -Z]
    {{ 1.0, -1.0, -1.0}, {}, { 1.0, -1.0, -1.0}}, //6[1: -Z]
    {{-1.0, -1.0, -1.0}, {}, {-1.0, -1.0, -1.0}}, //7[0: -Z]
    // Left [Left 的 x 是負的]
    {{-1.0, -1.0,  1.0}, {}, {-1.0, -1.0,  1.0}}, //8[0]
    {{-1.0,  1.0,  1.0}, {}, {-1.0,  1.0,  1.0}}, //9[3]
    {{-1.0,  1.0, -1.0}, {}, {-1.0,  1.0, -1.0}}, //10[4]
    {{-1.0, -1.0, -1.0}, {}, {-1.0, -1.0, -1.0}}, //11[7]
    // Right [Right 的 x 是正的]
    {{ 1.0, -1.0,  1.0}, {}, { 1.0, -1.0,  1.0}}, //12[1]
    {{ 1.0, -1.0, -1.0}, {}, { 1.0, -1.0, -1.0}}, //13[6]
    {{ 1.0,  1.0, -1.0}, {}, { 1.0,  1.0, -1.0}}, //14[5]
    {{ 1.0,  1.0,  1.0}, {}, { 1.0,  1.0,  1.0}}, //15[2]
    // Top [Top 的 y 是正的]
    {{-1.0,  1.0,  1.0}, {}, {-1.0,  1.0,  1.0}}, //16[3]
    {{ 1.0,  1.0,  1.0}, {}, { 1.0,  1.0,  1.0}}, //17[2]
    {{ 1.0,  1.0, -1.0}, {}, { 1.0,  1.0, -1.0}}, //18[5]
    {{-1.0,  1.0, -1.0}, {}, {-1.0,  1.0, -1.0}}, //19[4]
    // Bottom [Bottom 的 y 是負的]
    {{-1.0, -1.0,  1.0}, {}, {-1.0, -1.0,  1.0}}, //20[0]
    {{-1.0, -1.0, -1.0}, {}, { 1.0, -1.0,  1.0}}, //21[7]
    {{ 1.0, -1.0, -1.0}, {}, { 1.0, -1.0, -1.0}}, //22[6]
    {{ 1.0, -1.0,  1.0}, {}, {-1.0, -1.0, -1.0}}, //23[1]
    
};
複製程式碼

這種座標,是剛好貼合【完全覆蓋】的狀態;

資料特點:一個頂點資料繫結一個紋理資料;

【有沒有注意到,CubeMap 裡面就是直接拷貝頂點資料到紋理座標上,就行了。(CubeMap 中間那個空的 {} 是結構體中的 2D 紋理資料(就是空的))】

其它的資料形態【對於不是正方的圖片】, 【希望大一點,或小一點,即只顯示某一部分】:

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

都是類似圖中的分割一樣,劃分成多個小圖片【小的 TexCoord】,最終的資料形態是:

static const VYVertex tex2DElongatedDDCubeDatas[] = {
    
    // Front [Front 的 z 是正的]
    {{-1.0, -1.0,  1.0}, {0.000, 0.000}}, // 0
    {{ 1.0, -1.0,  1.0}, {0.250, 0.000}}, // 1
    {{ 1.0,  1.0,  1.0}, {0.250, 0.500}}, // 2
    {{-1.0,  1.0,  1.0}, {0.000, 0.500}}, // 3
    // Back [Back 的 z 是負的]
    {{-1.0,  1.0, -1.0}, {0.000, 0.500}}, //4[3: -Z]
    {{ 1.0,  1.0, -1.0}, {0.250, 0.500}}, //5[2: -Z]
    {{ 1.0, -1.0, -1.0}, {0.250, 1.000}}, //6[1: -Z]
    {{-1.0, -1.0, -1.0}, {0.000, 1.000}}, //7[0: -Z]
    // Left [Left 的 x 是負的]
    {{-1.0, -1.0,  1.0}, {0.250, 0.000}}, //8[0]
    {{-1.0,  1.0,  1.0}, {0.500, 0.000}}, //9[3]
    {{-1.0,  1.0, -1.0}, {0.500, 0.500}}, //10[4]
    {{-1.0, -1.0, -1.0}, {0.250, 0.500}}, //11[7]
    // Right [Right 的 x 是正的]
    {{ 1.0, -1.0,  1.0}, {0.250, 0.500}}, //12[1]
    {{ 1.0, -1.0, -1.0}, {0.500, 0.500}}, //13[6]
    {{ 1.0,  1.0, -1.0}, {0.500, 1.000}}, //14[5]
    {{ 1.0,  1.0,  1.0}, {0.250, 1.000}}, //15[2]
    // Top [Top 的 y 是正的]
    {{-1.0,  1.0,  1.0}, {0.500, 0.000}}, //16[3]
    {{ 1.0,  1.0,  1.0}, {0.750, 0.000}}, //17[2]
    {{ 1.0,  1.0, -1.0}, {0.750, 0.500}}, //18[5]
    {{-1.0,  1.0, -1.0}, {0.500, 0.500}}, //19[4]
    // Bottom [Bottom 的 y 是負的]
    {{-1.0, -1.0,  1.0}, {0.750, 0.000}}, //20[0]
    {{-1.0, -1.0, -1.0}, {1.000, 0.000}}, //21[7]
    {{ 1.0, -1.0, -1.0}, {1.000, 0.500}}, //22[6]
    {{ 1.0, -1.0,  1.0}, {0.750, 0.500}}, //23[1]
    
};
複製程式碼

也可以是沒有填充完整的圖片,只取其中的一部分,資料形態也是上面的:

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

擴充套件:
CubeMap 用於做環境貼圖,還需要 Light + Shadow 【光 + 陰影】的知識,為什麼?環境,有物體 + 自然光 + 人造光 + 光與物體產生的陰影 + 光與物體作用後的顏色;【顏色和陰影是因為有光才產生的,OpenGL 本身預設有一個全域性光,不然你沒有寫光的程式碼,為什麼可以看到你渲染的模型體】 即只有在具備了 光 + 影 的知識,去學習 環境貼圖才好理解;【貼圖:HDR 圖片 (效果中的那張藍色森林就是 HDR 圖,沒有做 CubeMap) + CubeMap 格式】

CubeMap 圖片格式,就是把下圖中的 HDR 圖片直接轉換成,六個黃色框框的影象,框框之間的邊緣是連線的哦:

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

連線

MipMapping:
根據不同的情形載入不同大小的圖片進行渲染;【不同情形,指不同遠近,不同光影環境下對圖片“看清”“看不清”的程度,OpenGL 自動選擇合適的圖片大小】【不同大小的圖片,程式設計師要事先載入一張圖片的不同大小 ( 2^n , 2^m ) 的畫素資料(0 ~ n level),又因為 ES 是基於移動端的,所以記憶體容易告急,即能不用則不用】

Fliter + 特效 :
我們天天看到的最多的東西,就是給圖片畫素加入各種“想法”變成你想要的效果【加霧、馬賽克、調色、映象、模糊、素描、液化、疊加、藝術化 ......】,它的核心知識在 Fragment Shader【重點】 + OpenGL ES 提供的基礎混合模式【濾波 + Blend】,放在下一篇文章專門講;

粒子系統:Texture + Point Sprites,製作雨水、下雪、飛舞的花瓣...... 只要渲染效果要求有多個相似點在那動來動去的,都可以用它們來實現;【數學中的分形理論好像也可以用上】【粒子,會用專門的一篇文章講】

所有的 “花樣” 特效,不管被稱之為什麼,都與 數學知識【演算法】 和 顏色構成知識【光構成、色彩構成】 密不可分;

所以我就要怕了嗎? 錯,你應該興奮;因為~~ 反正我也沒有什麼可以失去的了,上來不就是幹了嗎? ^ _ ^ + ~_~ + $-$

(三)、引入了 Texture 的 Shader 檔案

Texture_2D:

2D Vertex:

#version 100

uniform mat4 u_modelViewMat4;
uniform mat4 u_projectionMat4;

attribute vec4 a_position;
attribute vec2 a_texCoord;

varying highp vec2 v_texCoord;

void main(void) {
    gl_Position = u_projectionMat4 * u_modelViewMat4 * a_position;
    v_texCoord  = a_texCoord;
}
複製程式碼

紋理輸入輸出:

...
attribute vec2 a_texCoord;
varying highp vec2 v_texCoord;

void main(void) {
    ...
    v_texCoord  = a_texCoord;
}
複製程式碼

輸入:
vec2 a_texCoord,上面提到過它是 {x, y} 的座標,所以使用的也是 vec2 ;

輸出:
同樣是 vec2 ,但是一定要記住加 highp 精度限定符,不然編譯會報錯哦;

不知道,你是否還記得渲染管線中的 Texture Memory ,看下圖:

渲染管線

紅色框框住的虛線,就是指代 Vertex Shader 中的紋理座標資訊;

直接給的,為什麼是虛線? 看清楚 Shader 程式碼,這裡是直接就賦值【輸入 = 輸出,經過其它變換也行】了,也就是 Vertex Shader 內部不需要使用到它,它只是為了傳到 Fragment 裡面使用的【varying 的作用】,所以就使用虛線來表示;

2D Fragment:

#version 100

uniform sampler2D us2d_texture;

varying highp vec2 v_texCoord;

void main(void) {
//    gl_FragColor = vec4(1, 1, 0.5, 1);
    gl_FragColor = texture2D(us2d_texture, v_texCoord);
}

複製程式碼

上面的渲染管線圖中,黃色框框住的實線,就是指代 Fragment Shader 中的畫素資料【sampler2D】來源;

這裡是核心,輸入輸出:

uniform sampler2D us2d_texture;
...

void main(void) {
    gl_FragColor = texture2D(us2d_texture, ...);
}
複製程式碼

輸入:
sampler2D 就是一堆靜態資料的意思,畫素資訊就是一堆固定【不管是寫死,還是程式自動生成,都一樣】的顏色資訊,所以要使用這種常量塊的型別限定符;

輸出:
這裡要使用 texture2D 內建函式來處理畫素資訊生成 vec4 的顏色資訊,原型 vec4 texture2D(sampler2D s, vec2 texCoord);

所以剩下的問題就是如何得到 sampler2D 資料,並如何將畫素資料寫入到 Shader 中

Texture_CubeMap:


#version 100

uniform mat4 u_modelViewMat4;
uniform mat4 u_projectionMat4;

attribute vec4 a_position;
attribute vec3 a_normalCoord;
varying highp vec3 v_normalCoord;

void main(void) {
    gl_Position = u_projectionMat4 * u_modelViewMat4 * a_position;
    v_normalCoord  = a_normalCoord;
}
複製程式碼
#version 100

uniform samplerCube us2d_texture;
varying highp vec3 v_normalCoord;

void main(void) {
    gl_FragColor = textureCube(us2d_texture, v_normalCoord);
}
複製程式碼

CubeMap 與 2D 的 Fragment 區別並不大,原理一樣的; CubeMap Vertex ,只要把 vec2 --> vec3 即可; CubeMap Fragment , 只要把 sampler2D --> samplerCube , texture2D 函式改成 textureCube 即可;

(四)、Texture 正確的 “書寫” 順序

前提,假設基本的渲染管線已經配置完成了,這裡只重點講紋理相關的;

1、 繫結 Texture Coord 紋理座標:

GLuint texCoordAttributeComCount = 2;
    
glEnableVertexAttribArray(texCoordAttributeIndex);
if ( texture2D ) {
    glVertexAttribPointer(texCoordAttributeIndex,
                          texCoordAttributeComCount,
                          GL_FLOAT, GL_FALSE,
                          sizeof(VYVertex),
                          (const GLvoid *) offsetof(VYVertex, texCoord));
 } else {
    texCoordAttributeComCount = 3;
    glVertexAttribPointer(texCoordAttributeIndex,
                          texCoordAttributeComCount,
                          GL_FLOAT, GL_FALSE,
                          sizeof(VYVertex),
                          (const GLvoid *) offsetof(VYVertex, normalCoord));
 }
複製程式碼

【如果看不懂,請回去看看第一篇文章,裡面有詳細講】

2、 請求 Texture 記憶體:

    GLuint texture = 0;
    glGenTextures(1, &texture);
    
    GLenum texMode = texture2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP;
    glBindTexture(texMode, texture);
複製程式碼

glGenTextures(GLsizei n, GLuint* textures); 和 glGenBuffers 等的使用是一樣的;它的意思就是,向 GPU 請求一塊 Texture 記憶體; glBindTexture (GLenum target, GLuint texture); 和其它的 glBind... 方法一樣;它的意思是,告訴 GPU 請求一塊 target 【只有 2D 和 CubeMap 兩種】 型別的記憶體,只有當這個方法完成請求後,這塊 Texture 記憶體才會生成【如果當前記憶體識別符號指向的記憶體已經存在,則不會再建立,只會指向此處】;

3、 載入畫素資料:

    glUseProgram(programObject);
    
    [self setTextureWithProgram:programObject 
                        texture:texture
                        texMode:texMode];
複製程式碼

(1)一定要在 glUseProgram 函式後進行這個步驟,為什麼? 因為 Fragment 使用的是 uniform samplerXXX 的資料,uniform 常量資料要在 glUseProgram 後再載入才有效,而且它的記憶體識別符號【記憶體】要在 link Program 之後 OpenGL 才會分配;

(2)進入 setTextureWithProgram: texture: texMode:方法 先準備畫素資料【pixelsDatas 或 ImageDatas】: 這裡的是,Pixels 的資料,就是寫死的資料

// 2 * 2 For Texture_2D
static const GLfloat tex2DPixelDatas[3*4] = {
    1.000, 1.000, 0.108,//[UIColor colorWithRed:1.000 green:1.000 blue:0.108 alpha:1.000]
    0.458, 1.000, 0.404,//[UIColor colorWithRed:0.458 green:1.000 blue:0.404 alpha:1.000]
    0.458, 1.000, 0.770,//[UIColor colorWithRed:0.458 green:1.000 blue:0.770 alpha:1.000]
    0.729, 0.350, 0.770,//[UIColor colorWithRed:0.729 green:0.350 blue:0.770 alpha:1.000]
};

// (2 * 2 * 6) For Texture_CubeMap
static const GLfloat texCubemapPixelDatas[6][3*4] = {
    1.000, 1.000, 0.108,//[UIColor colorWithRed:1.000 green:1.000 blue:0.108 alpha:1.000]
    0.458, 1.000, 0.404,//[UIColor colorWithRed:0.458 green:1.000 blue:0.404 alpha:1.000]
    0.458, 1.000, 0.770,//[UIColor colorWithRed:0.458 green:1.000 blue:0.770 alpha:1.000]
    0.729, 0.350, 0.770,//[UIColor colorWithRed:0.729 green:0.350 blue:0.770 alpha:1.000]
    
    0.145, 0.319, 0.308,//[UIColor colorWithRed:0.145 green:0.319 blue:0.308 alpha:1.000]
    0.732, 0.319, 0.308,//[UIColor colorWithRed:0.732 green:0.319 blue:0.308 alpha:1.000]
    0.732, 0.727, 0.308,//[UIColor colorWithRed:0.732 green:0.727 blue:0.308 alpha:1.000]
    0.732, 0.727, 0.889,//[UIColor colorWithRed:0.732 green:0.727 blue:0.889 alpha:1.000]
    
    0.633, 0.820, 0.058,//[UIColor colorWithRed:0.633 green:0.820 blue:0.058 alpha:1.000]
    0.936, 0.820, 0.994,//[UIColor colorWithRed:0.936 green:0.820 blue:0.994 alpha:1.000]
    0.017, 0.029, 0.994,//[UIColor colorWithRed:0.017 green:0.029 blue:0.994 alpha:1.000]
    0.000, 0.000, 0.000,//[UIColor colorWithWhite:0.000 alpha:1.000]
    
    0.593, 0.854, 0.000,//[UIColor colorWithRed:0.593 green:0.854 blue:0.000 alpha:1.000]
    0.593, 0.337, 0.000,//[UIColor colorWithRed:0.593 green:0.337 blue:0.000 alpha:1.000]
    1.000, 0.407, 0.709,//[UIColor colorWithRed:1.000 green:0.407 blue:0.709 alpha:1.000]
    0.337, 0.407, 0.709,//[UIColor colorWithRed:0.337 green:0.407 blue:0.709 alpha:1.000]
    
    0.337, 0.738, 0.709,//[UIColor colorWithRed:0.337 green:0.738 blue:0.709 alpha:1.000]
    0.337, 0.994, 0.709,//[UIColor colorWithRed:0.337 green:0.994 blue:0.709 alpha:1.000]
    0.186, 0.105, 0.290,//[UIColor colorWithRed:0.186 green:0.105 blue:0.290 alpha:1.000]
    0.633, 0.872, 0.500,//[UIColor colorWithRed:0.633 green:0.872 blue:0.500 alpha:1.000]
    
    0.290, 0.924, 0.680,//[UIColor colorWithRed:0.290 green:0.924 blue:0.680 alpha:1.000]
    0.290, 0.924, 0.174,//[UIColor colorWithRed:0.290 green:0.924 blue:0.174 alpha:1.000]
    0.982, 0.163, 0.174,//[UIColor colorWithRed:0.982 green:0.163 blue:0.174 alpha:1.000]
    0.628, 0.970, 0.878,//[UIColor colorWithRed:0.628 green:0.970 blue:0.878 alpha:1.000]
};
複製程式碼

因為 Texture_2D 狀態下,只有 {x, y} 平面的資料需要填充,所以這裡就只有一個面的顏色資料;

而在 Texture_CubeMap 狀態下,是 { x, y, z } 三維座標,即六個面需要填充,所以就是6 * 1(1 = 2 * 2) = 6個面的顏色資料;

注:圖片型別的資料要自己寫轉換方法,生成畫素資料;當然也可以使用 GLKit 提供的 TextureLoder 類來載入圖片畫素資料;

(3)【核心】glTexImage2D得到紋理畫素的方法,就是載入紋理畫素到 GPU 的方法:

glTexImage2D
void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels)
target *指 如果是 2D ,就是 GL_Texture_2D,如果是 CubeMap 就是 GL_TEXTURE_CUBE_MAP_XXX [+-x, +-y, +-z, 六個面] *
level *指 mipmapping level 沒有做 mipmapping 則為 0 ;如果做了,則為 0 ~ levelMax [這個 max 是由你自己圖片資料決定的] *
internalformat 指 畫素資料的格式是什麼 GL_RGB 等等
width 指 一塊畫素的寬 [2D 下只有一塊,cubemap 會有多塊(六個面)]
height 指 一塊畫素的高
border *指 ES 下是 GL_FALSE *
format 指 與 internalformat 格式一致
type 指 畫素資料儲存的型別,如:GL_FLOAT, GL_UNSIGNED_BYTE
pixels 指 一塊畫素的記憶體首地址

a. 畫素模式下的使用:

if (texMode == GL_TEXTURE_2D) {
    glTexImage2D(texMode, 0, GL_RGB, 2, 2, GL_FALSE, GL_RGB, GL_FLOAT, tex2DPixelDatas);
} else {
    
//                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 2, 2, GL_FALSE, GL_RGB, GL_FLOAT,  texCubemapPixelDatas[0]);
//                glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 2, 2, GL_FALSE, GL_RGB, GL_FLOAT,  texCubemapPixelDatas[1]);
//                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 2, 2, GL_FALSE, GL_RGB, GL_FLOAT,  texCubemapPixelDatas[2]);
//                glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 2, 2, GL_FALSE, GL_RGB, GL_FLOAT,  texCubemapPixelDatas[3]);
//                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 2, 2, GL_FALSE, GL_RGB, GL_FLOAT,  texCubemapPixelDatas[4]);
//                glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 2, 2, GL_FALSE, GL_RGB, GL_FLOAT,  texCubemapPixelDatas[5]);

    GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
    for (NSUInteger i = 0; i < 6; i++) {
        glTexImage2D(target, 0, GL_RGB,
                     2, 2, GL_FALSE, GL_RGB, GL_FLOAT,  texCubemapPixelDatas[i]);
        target++;
    }
    
}
複製程式碼

上面在 GL_TEXTURE_2D 狀態下的載入,只要理解了glTexImage2D函式引數的意思,也就會使用且明白了,這裡就不再贅述了;

特別要注意的是在 GL_Texture_Cube_Map 狀態下的使用,一定要六個面都進行畫素資料載入;

#define GL_TEXTURE_CUBE_MAP_POSITIVE_X                   0x8515
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X                   0x8516
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y                   0x8517
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y                   0x8518
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z                   0x8519
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z                   0x851A
複製程式碼

看看GL_TEXTURE_CUBE_MAP_POSITIVE_X它們的定義,因為定義是連續的,所以我們才可以用 for 迴圈來 “偷懶”;

b. 圖片畫素模式下的使用:

if (texMode == GL_TEXTURE_2D) {
    
    UIImage *img = // img;
    
    [self.loadTexture textureDataWithResizedCGImageBytes:img.CGImage completion:^(NSData *imageData, size_t newWidth, size_t newHeight) {
        glTexImage2D(texMode, 0, GL_RGBA,
                     (GLsizei)newWidth, (GLsizei)newHeight,
                     GL_FALSE, GL_RGBA, GL_UNSIGNED_BYTE,
                     imageData.bytes);
    }];
    
} else {
    
    NSArray<UIImage *> *imgs = // imgs;
    
    GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
    [self.loadTexture textureDatasWithResizedUIImages:imgs completion:^(NSArray<NSData *> *imageDatas, size_t newWidth, size_t newHeight) {
        [imageDatas enumerateObjectsUsingBlock:^(NSData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            glTexImage2D((GLenum)(target + idx), 0, GL_RGBA,
                         (GLsizei)newWidth, (GLsizei)newHeight,
                         GL_FALSE, GL_RGBA, GL_UNSIGNED_BYTE,
                         obj.bytes);
        }];
    }];
    
}

複製程式碼

這裡的核心就是,self.loadTexture 的圖片載入方法,這是自己寫的載入方法,使用的技術是 Quartz Core ;具體的在下一節【三、知識擴充:圖片載入】會講到;

兩者的使用並不會有什麼區別,這只是兩種畫素資料提供的方式不同罷了

(4)指定濾波設定【下一篇會重點講】 + 畫素繫結 + 啟用紋理

glTexParameteri(texMode, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(texMode, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
GLuint textureSourceLoc = glGetUniformLocation(programObject, "us2d_texture");
glUniform1i(textureSourceLoc, 0);
    
glEnable(texMode);
glActiveTexture(GL_TEXTURE0);
glBindTexture(texMode, texObj);
複製程式碼

a. 設定濾波模式,函式就是glTexparameteri方法 原型:glTexParameteri (GLenum target, GLenum pname, GLint param);

ES 2 有四個這種濾波函式,下圖的引數已經說得很明白了,我就不一一解釋了:

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

MIN / MAG ?:

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

magnification【MAG】:放大的意思,指顯示在螢幕上的一個畫素是紋理畫素放大後的結果; 【只有 x、y 方向都進行放大,才需要這個引數,也就是說它是可選的】

minification【MIN】: 縮小的意思,指顯示在螢幕上的一個畫素是一個紋理畫素集縮小後的結果; 【一定要做的設定,如上述程式碼中的glTexParameteri(xxx, GL_TEXTURE_MIN_FILTER, xxx);】 【MipMapping 發揮作用的地方就是在縮小的時候,OpenGL 會自動選擇合適大小的畫素資料】

如果紋理畫素在 x、y 方向上是做同一個動作【拉伸或壓縮】,則需要放大或縮小畫素;如果紋理畫素在 x、y 方向上是做不同的動作,則需要放大或者縮小,不確定【由 OpenGL 自己選擇】;

WRAP_S / WRAP_T ? : 就是 x 或 y 方向填充覆蓋的意思;

LINEAR / NEAREST ? :

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

前者是指啟用線性濾波【就是平滑過渡】,後者是禁用線性濾波;

平滑過濾使用的技術——訊號取樣,先看看一維的訊號取樣:

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

意思就是,取樣提供的紋理畫素,在放大、縮小的時候,使相鄰的畫素進行“一定程度的融合”產生新的畫素資訊,使最終顯示在螢幕在的圖片更加平滑;上圖【猴子】中的效果就是利用這項技術來的,對於二維、三維,就相應地做多次取樣【二維,兩次;三維,三次......】;

b. 畫素繫結【就是告訴 GPU Shader 的畫素資料在那】+ 啟用紋理

GLuint textureSourceLoc = glGetUniformLocation(programObject, 
                                               "us2d_texture");
glUniform1i(textureSourceLoc, 0);

glEnable(texMode);
glActiveTexture(GL_TEXTURE0);

glBindTexture(texMode, texObj);
複製程式碼

glUniform1i類函式,可以理解成繫結一塊記憶體【畫素塊記憶體】,也可以理解成繫結一個記憶體空間【一般常量】; 函式原型:void glUniform1i(GLint location, GLint x)

glEnable函式,就是開啟一些什麼東西,這裡是開啟 GL_TEXTURE_XXX ,不寫也行,這裡和其它地方的預設一樣, 0 這個位置的紋理就是開啟的;【為了良好習慣,還是寫吧】

glActiveTexture函式,名字已經告訴是啟用紋理的意思,不用多說了;

重點:glUniform1i 的第二個引數是和 glActiveTexture 的第二個引數是對應的,前者使用的是 0,那麼後者就是對應 GL_TEXTURE0 【0~31,共32個】,依此類推

為什麼還要做glBindTexture(texMode, texObj);重新繫結畫素記憶體,**其實就是防止中途有什麼地方把它給改了【如,bind 了其它的紋理】,所以是為了保險起見,就最好寫上;**但是因為這裡很明顯地,只有 layoutSubviews 函式【此渲染程式碼都是寫在這個函式內執行的】會繫結它,而且都是同一個的,所以也可以不寫;


三、知識擴充:圖片載入

使用 Quartz Core 技術 載入圖片資料,Bitmap Context :

OpenGL ES 2 0 (iOS)[06 1]:基礎紋理

本來它不屬於 OpenGL 的內容,但是它本身也是影象處理的技術,包括 Core Image、 Accelerate等影象處理的框架,如果可以,請儘量去了解或去掌握或去熟練。

核心程式碼:

#define kBitsPerComponent   8

#define kBytesPerPixels     4
#define kBytesPerRow(width)         ((width) * kBytesPerPixels)

- (NSData *)textureDataWithResizedCGImageBytes:(CGImageRef)cgImage
                                      widthPtr:(size_t *)widthPtr
                                     heightPtr:(size_t *)heightPtr {
    
    if (cgImage == nil) {
        NSLog(@"Error: CGImage 不能是 nil ! ");
        return [NSData data];
    }
    
    if (widthPtr == NULL || heightPtr == NULL) {
        NSLog(@"Error: 寬度或高度不能為空。");
        return [NSData data];
    }
    
    size_t originalWidth  = CGImageGetWidth(cgImage);
    size_t originalHeight = CGImageGetHeight(cgImage);
    
    // Calculate the width and height of the new texture buffer
    // The new texture buffer will have power of 2 dimensions.
    size_t width  = [self aspectSizeWithDataDimension:originalWidth];
    size_t height = [self aspectSizeWithDataDimension:originalHeight];
    
    // Allocate sufficient storage for RGBA pixel color data with
    // the power of 2 sizes specified
    NSMutableData *imageData =
    [NSMutableData dataWithLength:height * width * kBytesPerPixels]; // 4 bytes per RGBA pixel
    
    // Create a Core Graphics context that draws into the
    // allocated bytes
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef cgContext = CGBitmapContextCreate([imageData mutableBytes],
                                                   width, height,
                                                   kBitsPerComponent,
                                                   kBytesPerRow(width),
                                                   colorSpace,
                                                   kCGImageAlphaPremultipliedLast); // RGBA
    CGColorSpaceRelease(colorSpace);
    // Flip the Core Graphics Y-axis for future drawing
    CGContextTranslateCTM (cgContext, 0, height);
    CGContextScaleCTM (cgContext, 1.0, -1.0);
    // Draw the loaded image into the Core Graphics context
    // resizing as necessary
    CGContextDrawImage(cgContext, CGRectMake(0, 0, width, height), cgImage);
    CGContextRelease(cgContext);
    
    *widthPtr  = width;
    *heightPtr = height;
    
    return imageData;
}
複製程式碼

主流程:
1、規格化圖片尺寸,讓其符合 (2^n, 2^m)[n,m 均為自然數 ] 為什麼?
(1)因為 CGBitmapContextCreate支援的是 size_t ((long) unsigned int) 的【來個 0.25 個畫素也是醉了】;
(2)而且 OpenGL ES
支援的最大畫素尺寸也是有限制的,當前環境支援的最大值是 (4096, 4096),這個值由以下兩個 xx_MAX_xx 得到【就在 aspectSizeWithDataDimension: 方法內】:

    GLint _2dTextureSize;
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &_2dTextureSize);
    
    GLint cubeMapTextureSize;
    glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &cubeMapTextureSize);
複製程式碼

glGetIntegerv函式是可以獲取當前環境下所有的預設常量的方法;

2、確定圖片畫素最終輸出的顏色空間
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();,這個最容易出錯,它的顏色格式要和你使用glTexImage2D函式指名的顏色格式要一致,不然不可能顯示正常【如,你這裡定義成 CYMK, 指名了 GL_RGB 那麼肯定不對的】

3、確定最終畫素的位深與位數
這裡是明確用多少位來表示一個畫素位【如:R 用 8 位表示】,一個畫素由多少個成員組成【如:RGBA 就是 4 個】

4、建立上下文環境
Bitmap 圖就是畫素圖,包含所有的畫素資訊,沒有什麼 jpg / png 容器什麼的; CGBitmapContextCreate函式的各個引數都很明顯了,所以就不廢話了;

5、變換畫素的座標空間
為什麼? Texture 紋理座標空間的座標原點在,左下角,而蘋果裝置顯示的圖形的座標系的座標原點在左上角,剛好是反的;

6、繪製生成最終的畫素資料


謝謝看完,如果有描述不清或講述錯誤的地方,請評論指出!!!

相關文章