OpenGLES之纹理(三)

2019/07/03 OpenGLES

在上一篇,我们就绘制了一个三角形,填充了一些颜色。但是现实生活中,几乎很少有这种填充了一些颜色的或者固定的对象。这一篇会介绍一个叫做纹理的图形技术。

纹理

纹理是一个用来保存图像的颜色元素值的OpenGL ES缓存。纹理可以使用任何图像,包括数目、面孔、砖块、云彩,或者机器。

当用一个图像初始化一个纹理缓存之后,在这个图像中的每个像素变成了纹理中的一个纹素(texel)。与像素类似,纹素保存颜色数据。像素和纹素之间的差别很微妙,像素通常表示计算机屏幕上的一个实际 的颜色点,因此,像素通常被用来作为测量单位。说一个图像有256 像素宽,256像素高是正确的。而纹素存在于一个虚拟的没有尺寸的数学坐标系中。

纹理坐标系有一个命名为S 和T 的2D轴。在一个纹理中无论有多少个纹素,纹理的尺寸永远是在S轴上从0.0到1.0,在T轴上从0.0到1.0。

在三维顶点坐标被转换成二维平面坐标后,GPU会设置转换生成的三角形内的每个像素的颜色。转换几何形状数据为帧缓存中的颜色像素的渲染步骤叫做点阵化(rasterizing),每个颜色像素叫做片元(fragment)。当OpenGLES没有使用纹理时,GPU会根据包含该片元的对象的顶点的颜色来计算每个片元的颜色。当设置了使用纹理后,GPU会根据在当前绑定的纹理缓存中的纹素来计算每个片元的颜色。

程序需要制定怎么对齐纹理和顶点,以便让GPU知道每个片元的颜色由哪些纹素决定。这个对齐又叫做映射(mapping),是通过扩展为每个顶点保存的数据来实现的:除了X、Y、Z、坐标,每个顶点还给出了U和V坐标。每个U坐标会映射顶点在饰扣中的最终位置到纹理中的沿着S轴的一个位置。V坐标映射到T轴。

每个顶点的U和V坐标会附加到每个顶点在饰扣坐标的最终位置。然后GPU会根据计算出来的每个片元的U、V位置从绑定的纹理中选择纹素。这个过程叫做取样(Sampling)。取样会把纹理的S和T坐标系与每个渲染的三角形的顶点的U、V坐标匹配起来。

渲染过程中的取样可能会导致纹理被拉升、压缩,甚至翻转。因此,OpenGLES支持多个不同的取样模式:考虑一下当一个三角形产生的片元少于绑定的纹理内的可用纹素的数量时。或者一个拥有大量纹素的纹理被映射到帧缓存内的一个只覆盖几个像素的三角形中,或者一个包含少量纹素的纹理被映射到一个帧缓存中产生很多个片元的三角形中。OpenGLES需要知道怎么处理可用纹素与需要被着色的片元的数量之间的不匹配。

// 设置图像拉伸变形时的处理方法
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

使用值GL_LINEAR 来制定参数GL_TEXTURE_MIN_FILTER 会告诉OpenGLES无论何时出现多个纹素对应一个片元时,从相配的多个纹素中取样颜色,然后使用线性内插值法来混合这些颜色以得到偏远颜色。使用GL_NEAREST来制定GL_TEXTURE_MIN_FILTER 会将片元的U、V、坐标最近的纹素的颜色会被取样,最终取出来的纹素要么是这个或者另一个。

GL_TEXTURE_MAG_FILTER 参数用于纹素少于片元时的取样模式。GL_LINEAR 值会有一个放大纹理的效果,并会让它模糊的出现在渲染的三角形上。GL_NEAREST 值仅仅会拾取与片元U、V最接近的纹素的颜色。并放大纹理,这样看起来有点像素化的出现在渲染的三角形上。

另外,如果U、V的坐标的值小于0 或者大于1时,程序会制定要发生什么。要么尽可能多的重复纹理以填充映射到几何图形的整个U、V区域,要么每当片元的U、V坐标的值超出纹理S、T 坐标系的范围时,取样纹理边缘的纹素。

下面始写代码: 步骤一样,但是这一次我们会修改shader程序

1、编辑着色器程序 Vertex Shader:

attribute vec3 in_position; // 顶点位置
// 输入的色彩
attribute vec2 in_tex_coord;
// 输出的色彩
varying lowp vec2 tex_coord;

void main()
{
    gl_Position = vec4(in_position, 1.0); // 传递顶点位置数据
    tex_coord = in_tex_coord; // 传递色彩数据
}

Fragment Shader

precision mediump float; // 声明精度
// 声明色彩变量
varying vec2 tex_coord;
// 传递图片数据
uniform sampler2D tex1;
uniform sampler2D tex2;

void main()
{
    // 通过纹理坐标数据来获取对应坐标色值并传递
    gl_FragColor = mix(texture2D(tex1,tex_coord),texture2D(tex2,tex_coord), 0.6);
    
}

vec4 texture2D(sampler2D sampler, vec2 coord)

The texture2D function returns a texel, i.e. the (color) value of the texture for the given coordinates. 第一个参数代表图片纹理,第二个参数代表纹理坐标点,通过GLSL的内建函数texture2D来获取对应位置纹理的颜色RGBA值

2、设置program

- (void)setupProgram {
    NSString *VertexFile = [[NSBundle mainBundle] pathForResource:@"VertexFile.glsl" ofType:nil];
    NSString *FragmentFile = [[NSBundle mainBundle] pathForResource:@"FragmentFile.glsl" ofType:nil];
    
    _programHandle0 = [OpenGLUtils programWithVertexFile:VertexFile fragmentFile:FragmentFile];
    glUseProgram(_programHandle0);//激活shader 程序
    
    _positionSlot = glGetAttribLocation(_programHandle0, "in_position");//在ES3.0 中有另一种方式
    _textureCoordsSlot0 = glGetAttribLocation(_programHandle0, "in_tex_coord");
    glEnableVertexAttribArray(_positionSlot);
    glEnableVertexAttribArray(_textureCoordsSlot0);
    
    _imageTextureSlot0 = glGetUniformLocation(_programHandle0, "tex1");
    _imageTextureSlot1 = glGetUniformLocation(_programHandle0, "tex2");
    
}

3、给出顶点坐标和纹理坐标

GLfloat vertexes[] = {
        // 三角形           // 纹理
        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,  // 左下
        -1.0, -1.0, 0.0,0.0, 1.0,  // 左下
        -1.0,  1.0,0.0, 0.0, 0.0,  // 左上
        1.0,  1.0, 0.0,1.0, 0.0,   // 右上
    };
    

1)设置顶点缓存和索引缓存

// 设置VBO(顶点缓存)
    GLuint bufferVBO;
    glGenBuffers(1, &bufferVBO);
    glBindBuffer(GL_ARRAY_BUFFER, bufferVBO);
    glBufferData(GL_ARRAY_BUFFER, // 目标
                 sizeof(vertexes), // 顶点数组数据大小
                 vertexes, // 顶点数组数据
                 GL_STATIC_DRAW); // 传入VBO数据的使用方式,这里一般设在表态

这里用到了索引缓存,以后会讲,而且以后也会经常碰到。

2)填充数据

// 设置图形顶点指针数据(因为使用了VBO所以最后一个参数不用传)
    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
    glEnableVertexAttribArray(_positionSlot);
    
    // 设置纹理顶点数据(因为使用了VBO所以最后一个参数不用传)
    glVertexAttribPointer(_textureCoordsSlot0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
    glEnableVertexAttribArray(_textureCoordsSlot0);

3)绘制

glDrawArrays(GL_TRIANGLES, 0, 6);

在这里,我们采用多重纹理混合的模式。值得说明的是纹理单元GL_TEXTURE0 默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元。

使用多重纹理时,我们需要定义多个uniform类型的采样器变量。每个变量都对应着一个不同的纹理单元。

GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

最后看效果图:

最后,附上我自己在学习过程中的源码:

Tutorial03

参考:

[1]:OpenGL ES 应用开发实践指南iOS卷

[2]:OpenGL ES学习笔记之三(创建立方体)

[3]:https://www.jianshu.com/p/2c41ac4f4031

Search

    Post Directory