OpenGL ES 2.0 (iOS)[06-1]:基础纹理
前言:如果你没有 OpenGL ES 2 的基础知识,请先移步 《OpenGL ES 2.0 (iOS) 笔记大纲》 学习一下基础的知识。
目录
一、软件运行效果演示
(一)、最终效果
(二)、信息提取
二、纹理处理的流程【核心】
(一)、Texture 是什么?
(二)、Texture
(三)、引入了 Texture 的 Shader 文件
(四)、Texture 正确的 “书写” 顺序
三、知识扩充:图片加载
使用 Quartz Core 的知识加载图片数据
一、软件运行效果演示
(一)、最终效果
工程地址:Github
Texture-Base.gif(二)、信息提取
-
不同的模型【2D & 3D】,不同维度下,Texture 的处理区别;
-
单一像素信息【pixelBuffer】 与 复杂像素信息【图片】的显示区别;
-
正方图【单张或多张图片】 与 长方图,像素的显示控制区别;
二、纹理处理的流程【核心】
(一)、Texture 是什么?
Texture 纹理,就是一堆被精心排列过的像素;
-
因为 OpenGL 就是图像处理库,所以 Texture 在 OpenGL 里面有多重要,可想而知;
-
其中间接地鉴明了一点,图片本身可以有多大变化,OpenGL 就可以有多少种变化;
学好 Texture 非常重要
(二)、Texture
Texture 在 OpenGL 里面有很多种类,但在 ES 版本中就两种——Texture_2D + Texture_CubeMap;
Texture_2D: 就是 {x, y} 二维空间下的像素呈现,也就是说,由效果图上演示可知,很难做到使正方体的六个面出现不同的像素组合;图片处理一般都使用这个模式;[x 、y 属于 [0, 1] 这个范围]
Texture_CubeMap: 就是 { x, y, z } 三维空间下的像素呈现,也就如效果图中演示的正方体的六个面可以出现不同的像素组合;它一般是用于做环境贴图——就是制作一个环境,让 3D 模型如同置身于真实环境中【卡通环境中也行】。[x、y、z 属于 [-1, 1] 这个范围,就是与 Vertex Position 的值范围一致]
注:上面提到的所有坐标范围是指有效渲染范围,也就是说你如果提供的纹理坐标超出了这个范围也没有问题,只不过超出的部分就不渲染了;
感受一下怎么具体表达:
// 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 纹理数据(就是空的))】
其它的数据形态【对于不是正方的图片】,
【希望大一点,或小一点,即只显示某一部分】:
都是类似图中的分割一样,划分成多个小图片【小的 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]
};
也可以是没有填充完整的图片,只取其中的一部分,数据形态也是上面的:
扩展:
CubeMap 用于做环境贴图,还需要 Light + Shadow 【光 + 阴影】的知识,为什么?环境,有物体 + 自然光 + 人造光 + 光与物体产生的阴影 + 光与物体作用后的颜色;【颜色和阴影是因为有光才产生的,OpenGL 本身默认有一个全局光,不然你没有写光的代码,为什么可以看到你渲染的模型体】
即只有在具备了 光 + 影 的知识,去学习 环境贴图才好理解;【贴图:HDR 图片 (效果中的那张蓝色森林就是 HDR 图,没有做 CubeMap) + CubeMap 格式】
CubeMap 图片格式,就是把下图中的 HDR 图片直接转换成,六个黄色框框的图像,框框之间的边缘是连接的哦:
连接
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 有四个这种滤波函数,下图的参数已经说得很明白了,我就不一一解释了:
MIN / MAG ?:
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 ? :
前者是指启用线性滤波【就是平滑过渡】,后者是禁用线性滤波;
平滑过滤使用的技术——信号采样,先看看一维的信号采样:
意思就是,采样提供的纹理像素,在放大、缩小的时候,使相邻的像素进行“一定程度的融合”产生新的像素信息,使最终显示在屏幕在的图片更加平滑;上图【猴子】中的效果就是利用这项技术来的,对于二维、三维,就相应地做多次采样【二维,两次;三维,三次......】;
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 的内容,但是它本身也是图像处理的技术,包括 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、绘制生成最终的像素数据
谢谢看完,如果有描述不清或讲述错误的地方,请评论指出!!!