上一篇文章我們通過金字塔延伸到了正方體,然后到這篇正方體每一個面貼一張圖。
先看效果圖:Demo

接下來讓我們開始學(xué)習(xí)OpenGL 一個重要??的知識點:紋理
借鑒博客:半紙淵--基礎(chǔ)紋理
前言:之前我們說過紋理可以簡單理解為圖片,但是紋理不簡簡單單??圖片。
-
1. Texture 是什么?
Texture 紋理,就是一堆被精心排列過的像素;
Texture 在 OpenGL 里面有很多種類,但在 ES 版本中就兩種:Texture_2D 、 Texture_CubeMap
-
Texture_2D:
就是 {x, y} 二維空間下的像素呈現(xiàn),也就是說,由效果圖上可知,很難做到使正方體的六個面出現(xiàn)不同的像素組合;圖片處理一般都使用這個模式;[x 、y 屬于 [0, 1] 這個范圍]
2D紋理坐標
-
Texture_CubeMap:
就是 { x, y, z } 三維空間下的像素呈現(xiàn),也就如效果圖中演示的正方體的六個面可以出現(xiàn)不同的像素組合;它一般是用于做環(huán)境貼圖——就是制作一個環(huán)境,讓 3D 模型如同置身于真實環(huán)境中【卡通環(huán)境中也行】。[x、y、z 屬于 [-1, 1] 這個范圍,就是與 Vertex Position 的值范圍一致]
3D紋理坐標
注:上面提到的所有坐標范圍是指有效渲染范圍,也就是說你如果提供的紋理坐標超出了這個范圍也沒有問題,只不過超出的部分就不渲染了;
頂點數(shù)據(jù)表示如下:
- Texture_2D:
//------------- 正方體 -------------
let attrArr: [GLfloat] = [
// 頂點:(x, y, z) 顏色:(r, g, b) 紋理: (s, t)
// 前面
-0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 0.0, // 前左上 0
-0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, // 前左下 1
0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // 前右下 2
0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 0.0, // 前右上 3
...
]
- Texture_CubeMap:
let attrArr: [GLfloat] = [
// 頂點:(x, y, z) 顏色:(r, g, b) 紋理: (s, t, p)
// 前面
-1.0, 1.0, 1.0, 1.0, 0.0, 0.0, -1.0, 1.0, 1.0, // 前左上 0
-1.0, -1.0, 1.0, 0.0, 1.0, 0.0, -1.0, -1.0, 1.0, // 前左下 1
1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0, // 前右下 2
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // 前右上 3
...
]
??ps: CubeMap 里面的紋理坐標和頂點數(shù)據(jù)是一樣的
- 加載CubeMap紋理
CubeMap共有6個面,然后分別設(shè)置
GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
代碼如下:注意??:這里是cubeMap 6張圖片的寬高要一致
//7.1 設(shè)置立方體紋理
func setupCubeTexture() {
//7.綁定紋理到默認的紋理ID(這里只有一張圖片,故而相當于默認于片元著色器里面的us2d_texture)
glBindTexture(GLenum(GL_TEXTURE_CUBE_MAP), 0)
for i in 0..<6 {
let spriteImage: CGImage = UIImage(named: "timg-\(i+1)")!.cgImage!
//2.讀取圖片的大?。簩捄透?注意??:這里是cubeMap 6張圖片的寬高要一致
let width = 512//spriteImage.width
let height = 512//spriteImage.height
//3.獲取圖片字節(jié)數(shù): 寬x高x4(RGBA)
// let spriteData: UnsafeMutablePointer = UnsafeMutablePointer<GLbyte>.allocate(capacity: MemoryLayout<GLbyte>.size * width * height * 4)
let spriteData: UnsafeMutableRawPointer = calloc(width * height * 4, MemoryLayout<GLbyte>.size)
//4.創(chuàng)建上下文
/*
參數(shù)1:data,指向要渲染的繪制圖像的內(nèi)存地址
參數(shù)2:width,bitmap的寬度,單位為像素
參數(shù)3:height,bitmap的高度,單位為像素
參數(shù)4:bitPerComponent,內(nèi)存中像素的每個組件的位數(shù),比如32位RGBA,就設(shè)置為8
參數(shù)5:bytesPerRow,bitmap的每一行的內(nèi)存所占的比特數(shù)
參數(shù)6:colorSpace,bitmap上使用的顏色空間 kCGImageAlphaPremultipliedLast:RGBA
let colorSpace = CGColorSpaceCreateDeviceRGB()
*/
let spriteContext: CGContext = CGContext(data: spriteData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: spriteImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
//5.在CGContextRef上繪圖
let rect = CGRect(x: 0, y: 0, width: width, height: height)
spriteContext.draw(spriteImage, in: rect)
//載入紋理2D數(shù)據(jù) 就是加載紋理像素到 GPU 的方法
/*
參數(shù)1:紋理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
參數(shù)2:加載的層次,一般設(shè)置為0
參數(shù)3:紋理的顏色值GL_RGBA
參數(shù)4:寬
參數(shù)5:高
參數(shù)6:border,邊界寬度
參數(shù)7:format
參數(shù)8:type
參數(shù)9:紋理數(shù)據(jù)
*/
glTexImage2D(GLenum(GL_TEXTURE_CUBE_MAP_POSITIVE_X + Int32(i)), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), spriteData)
//釋放spriteData
free(spriteData)
}
//設(shè)置紋理屬性
/*
參數(shù)1:紋理維度
參數(shù)2:線性過濾、為s,t坐標設(shè)置模式
參數(shù)3:wrapMode,環(huán)繞模式
*/
glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_MAG_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)
//綁定紋理
/*
參數(shù)1:紋理維度
參數(shù)2:紋理ID,因為只有一個紋理,給0就可以了。
*/
glBindTexture(GLenum(GL_TEXTURE_CUBE_MAP), 0)
}
因為我們的設(shè)置的紋理坐標由兩位:[s, t] --> [s, t, p],所以著色器中的紋理坐標要做相應(yīng)的改變,還有渲染那里讀取數(shù)據(jù)的時候也做相應(yīng)改變。
- 頂點著色器代碼:
//紋理坐標vec2 --> vec3
attribute vec4 position;
attribute vec4 positionColor; //頂點顏色
attribute vec3 textCoordinate; //紋理坐標
uniform mat4 projectionMatrix; //投影矩陣
uniform mat4 modelViewMatrix; //模型視圖矩陣
varying lowp vec4 varyColor; //頂點顏色
varying lowp vec3 varyTextCoord; //傳遞給片元著色器紋理坐標
void main()
{
varyColor = positionColor;
varyTextCoord = textCoordinate;
vec4 vPos;
vPos = projectionMatrix * modelViewMatrix * position;
gl_Position = vPos;
}
- 片元著色器代碼:
//紋理坐標vec2 --> vec3
varying lowp vec4 varyColor; //頂點顏色
varying lowp vec3 varyTextCoord; //頂點著色器傳遞過來的紋理坐標
//uniform sampler2D colorMap; //紋理
uniform samplerCube us2d_texture;
void main()
{
gl_FragColor = textureCube(us2d_texture, varyTextCoord) * varyColor;
}
渲染代碼:
步長和紋理坐標做相應(yīng)的調(diào)整即可,就不貼了
到此正方體貼圖工作就完成了。詳細請查看源碼
但是從借鑒的博客半紙淵--基礎(chǔ)紋理,看到他能實現(xiàn)下圖像魔方的效果,既然都做到多面貼圖了,所以也想試試看。

通過查看他的源碼,這種實現(xiàn)方式也是:glTexImage2D,只不過最后一個參數(shù)數(shù)據(jù)是個顏色數(shù)組
- 加載紋理的代碼就變成這樣:
//7.2 設(shè)置立方體像素紋理
func setupCubePixelsTexture() {
//7.綁定紋理到默認的紋理ID(這里只有一張圖片,故而相當于默認于片元著色器里面的us2d_texture)
glBindTexture(GLenum(GL_TEXTURE_CUBE_MAP), 0)
for i in 0..<6 {
glTexImage2D(GLenum(GL_TEXTURE_CUBE_MAP_POSITIVE_X + Int32(i)), 0, GL_RGBA, 2, 2, 0, GLenum(GL_RGBA), GLenum(GL_FLOAT), texCubemapPixelDatas[i])
}
//設(shè)置紋理屬性
/*
參數(shù)1:紋理維度
參數(shù)2:線性過濾、為s,t坐標設(shè)置模式
參數(shù)3:wrapMode,環(huán)繞模式
*/
glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
glTexParameteri(GLenum(GL_TEXTURE_CUBE_MAP), GLenum(GL_TEXTURE_MAG_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)
//綁定紋理
/*
參數(shù)1:紋理維度
參數(shù)2:紋理ID,因為只有一個紋理,給0就可以了。
*/
glBindTexture(GLenum(GL_TEXTURE_CUBE_MAP), 0)
}

這并不是我們想要的效果,而且連方格都沒有顯示出來,雖然中間看似有分割線。但是離效果圖還是天差地別的。怎么回事呢?對比了一下發(fā)現(xiàn)在過濾方式不同:GL_LINEAR 和 GL_NEAREST
摘抄自:mChenys -- 六、OpenGL ES紋理的使用
當紋理大小要被擴大或者縮小的時候,我們需要使用紋理過濾明確說明會發(fā)生什么,當我們在渲染表面上繪制一個紋理時,那個紋理的紋理元素可能無法精確地映射到OpenGL生成的片段上,有2種情況:縮小或者放大。
當我們盡力把幾個紋理元素擠進一個片段時,縮小就會發(fā)生了,當把一個紋理元素擴展到許多片段時,放大就發(fā)生了。
針對每一種情況,我們都可以配置OpenGL使用一個紋理過濾器,我會使用下面的圖像闡述每一種過濾模式:
圖1
- GL_NEAREST(也叫鄰近過濾,Nearest Neighbor Filtering)是OpenGL默認的紋理過濾方式。當設(shè)置為GL_NEAREST的時候,OpenGL會選擇中心點最接近紋理坐標的那個像素。下圖中你可以看到四個像素,加號代表紋理坐標。左上角那個紋理像素的中心距離紋理坐標最近,所以它會被選擇為樣本顏色:
圖2
這種方式為每個片段選擇最近的紋理元素,當放大紋理時它的鋸齒效果看起來相當明顯,每個紋理單元都清楚地顯示為一個小方塊。
圖3
當我們縮小紋理時,因為沒有足夠的片段來繪制所有的紋理單元,許多細節(jié)將會丟失。
圖4- GL_LINEAR(也叫線性過濾,(Bi)linear Filtering)它會基于紋理坐標附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顏色。一個紋理像素的中心距離紋理坐標越近,那么這個紋理像素的顏色對最終的樣本顏色的貢獻越大。下圖中你可以看到返回的顏色是鄰近像素的混合色:
圖5
線性過濾使用雙線插值平滑像素之間的過渡,而不是每個片段使用最近的紋理元素,OpenGL會使用四個鄰接的紋理元素,并在他們之間用一個線性差值算法做差值,這個算法與前面介紹平滑著色的算法一樣,之所以叫它雙線性,是因為它是沿兩個維度差值的,它會比近鄰過濾要平滑很多,但還是會有一些鋸齒顯示出來,因為我們把這個紋理擴展得太多了,但是鋸齒沒有最近鄰過濾那么明顯。
圖6
PS:紋理放大時使用線性過濾(GL_LINEAR),縮小時用鄰近過濾(GL_NEAREST)
然后我們修改過濾方式為:鄰近過濾(GL_NEAREST)
效果如下:

這里看到已經(jīng)差不多了和他的一樣了。但是總覺得怪怪的。就是每一個面都會有一塊是黑色的,而對照的卻不是這樣的。然后再去仔細看了看。后面發(fā)現(xiàn)原來還是在這個方法上出現(xiàn)了問題:
glTexImage2D(GLenum(GL_TEXTURE_CUBE_MAP_POSITIVE_X + Int32(i)), 0, GL_RGBA, 2, 2, 0, GLenum(GL_RGBA), GLenum(GL_FLOAT), texCubemapPixelDatas[i])
//載入紋理2D數(shù)據(jù) 就是加載紋理像素到 GPU 的方法
參數(shù)1:紋理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
參數(shù)2:加載的層次,一般設(shè)置為0
參數(shù)3:紋理的顏色值GL_RGBA
參數(shù)4:寬
參數(shù)5:高
參數(shù)6:border,邊界寬度
參數(shù)7:format
參數(shù)8:type
參數(shù)9:紋理數(shù)據(jù)
這里的(參數(shù)3:紋理的顏色值,參數(shù)7:format)我們傳的是GL_RGBA,而數(shù)組里面只有(r, g, b)并沒有a,所以出現(xiàn)取值有問題吧。改成 GL_RGB
運行結(jié)果:








