前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenGL ES 2.0 (iOS)[06-1]:基础纹理

OpenGL ES 2.0 (iOS)[06-1]:基础纹理

作者头像
半纸渊
发布2018-08-30 14:57:57
1.9K0
发布2018-08-30 14:57:57
举报
文章被收录于专栏:Code_iOSCode_iOS

一、软件运行效果演示

(一)、最终效果

工程地址: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] 这个范围]

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、绘制生成最终的像素数据


谢谢看完,如果有描述不清或讲述错误的地方,请评论指出!!!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.05.06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、软件运行效果演示
    • (一)、最终效果
      • (二)、信息提取
      • 二、纹理处理的流程【核心】
        • (一)、Texture 是什么?
          • (二)、Texture
            • (三)、引入了 Texture 的 Shader 文件
              • (四)、Texture 正确的 “书写” 顺序
              • 三、知识扩充:图片加载
              相关产品与服务
              图片处理
              图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档