OpenGL ES 入门之旅 -- GLSL纹理单元和纹理翻转解决策略

从上一篇文章中我们了解到片元着色器是如何编写的:

片元着色器

片元着色器shaderf.fsh

//传递过来的纹理坐标
varying lowp vec2 varyTextCoord;
// 纹理采样器 (获取对应的纹理ID)
uniform sampler2D colorMap;

void main() {
//将纹理颜色添加到对应的像素点上
 gl_FragColor = texture2D(colorMap, varyTextCoord);
//返回值应该是一个vec4 即是RGBA--颜色值。
}
复制代码

gl_FragColor GLSL内建变量 (赋值像素点颜色值)GLSL语言已经提前定义好的变量,有相应的特殊含义。 内建函数 GLSL提前封装好的函数 texture2D(纹理采样器,纹理坐标),获取对应坐标纹素(读取纹素,读取每一个像素点的颜色值)。

我们知道sampler(采样器)是GLSL提供的可供纹理对象使用的内建数据,而且sampler通常实在片元着色器中内定义,被uniform修饰符修饰,表示这个变量是不会被修改的。 通过上面的代码可以看到声明sampler的类型还有一个sampler2D。这个只是代表一个二维的纹理类型。sampler1D,sampler2D,sampler3D 表示不同维度的纹理类型.

在上面的代码中我们简单声明了一个纹理对象. uniform sampler2D,将一个纹理添加片元着色器中.

uniform sampler2D colorMap;
复制代码

同时我们使用GLSL内建的texture函数来采样纹理的颜色值.

 gl_FragColor = texture2D(colorMap, varyTextCoord);
复制代码

纹理单元

这里声明的sampler2D变量是个uniform,我们却没有用glUniform给它赋值,一般来讲我们需要用glUniform1i()函数进行将纹理对象(数据)从CPU中传入显存中的着色器。之所以使用glUniform1i()函数,是因为只需要给纹理采样器传入一个索引值(位置)即可,这样我们就能够在一个片元着色器中设置多个纹理。

那么这个索引值就是我们接下来要介绍的‘纹理单元’: 一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元。纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。

如果我们只传入一个纹理对象,那么倒是不用考虑纹理单元的问题。但是当有多个纹理对象要传入的时候,我们必须指定纹理对象,然后在主函数用glUniform1i()函数将纹理对象一个一个绑定到着色器内部。

通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用管理ActiveTexture激活纹理单元,传入我们需要使用的纹理单元。

//在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
复制代码

激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活.

OpenGL提供有16个纹理单元供我们使用,也就是说我们可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

纹理混合

GLSL内建的mix函数会将两个纹理进行结合并输出最终颜色值。

varying vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
void main()
{
    gl_FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}
复制代码
genType mix (genType x, genType y, float a)
返回线性混合的x和y,如:x⋅(1−a)+y⋅a
复制代码

mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值(线性插值是一种针对一维数据的插值方法,它根据一维数据序列中需要插值的点的左右邻近两个数据点来进行数值的估计。当然了它不是求这两个点数据大小的平均值(当然也有求平均值的情况),而是根据到这两个点的距离来分配它们的比重的)。

例如:如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。输入0.2则会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

设置多个纹理

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);  // 手动设置

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);
复制代码

注意,我们使用glUniform1i设置uniform采样器的位置值,或者说纹理单元。通过glUniform1i的设置,我们保证每个uniform采样器对应着正确的纹理单元

其实,使用glUniform1i()函数作为着色器内部和程序来进行传入值,需要知道两个参数,一个是在着色器内部接受信息的对象为位置。一个是外界的数据对象,严格来讲传入数据本身也不是这个函数做的,这个函数只是告诉着色器那个纹理对象对应哪个采样器对象。

重点介绍一下glTexImage2D函数:

glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels) 参数1:纹理模式(绑定纹理对象的种类),GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 参数2:加载的层次,一般设置为0, 0表示没有进行缩小的原始图片等级。 参数3:纹理的颜色值GL_RGBA, 表示了纹理所采用的内部格式,内部格式是我们的像素数据在显卡中存储的格式,这里的GL_RGB显然就表示纹理中像素的颜色值是以RGB的格式存储的。 参数4:纹理的宽 参数5:纹理的高 参数6:border,边界宽度,通常为0. 参数7:format(描述了像素在内存中的存储格式) 参数8:type(描述了像素在内存中的数据类型) 参数9:纹理数据

纹理翻转

在使用OpenGL函数加载纹理到图形时,经常遇到纹理上下颠倒的问题。原因是因为OpenGL要求纹理坐标原点(0,0)在左下角。

而图片中像素的存储顺序是从左上到右下的,因此我们需要对我们的坐标系进行一次Y轴的“翻转”,保持原点坐标一致。

  1. 利用旋转矩阵翻转图形,不翻转纹理 即让图形的顶点坐标旋转180度,而纹理坐标保持不变。
//rotate等于shaderv.vsh中的uniform属性,rotateMatrix
GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
//获取渲旋转的弧度
float radians = 180 * 3.14159f / 180.0f;
//求得弧度对于的sin\cos值
float s = sin(radians);
float c = cos(radians);
    
GLfloat zRotation[16] = {
        c, -s, 0, 0,
        s, c, 0, 0,
        0, 0, 1.0, 0,
        0.0, 0, 0, 1.0
 };
//设置旋转矩阵
glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
/*
glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
location : 对于shader 中的ID
count : 个数
transpose : 转置
value : 指针
*/
复制代码

2.解压图片时,将图片源文件翻转

//将 UIImage 转换为 CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
//读取图片的大小,宽和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
//获取图片字节数 宽*高*4(RGBA)
GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
//创建上下文
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//在CGContextRef上,将图片绘制出来
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);

CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage); 
//释放上下文
CGContextRelease(spriteContext);
//绑定纹理到默认的纹理ID
glBindTexture(GL_TEXTURE_2D, 0);
复制代码

CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。 CGContextDrawImage 参数1:绘图上下文 参数2:rect坐标 参数3:绘制的图片

3.修改片元着色器的纹理坐标

varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
    //gl_FragColor = texture2D(colorMap, varyTextCoord);
    gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0-varyTextCoord.y));
//因为纹理坐标的范围是0-1,所以翻转的话都统一用1去减 
}
复制代码

4.修改顶点着色器纹理坐标

attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;

void main()
{
    //varyTextCoord = textCoordinate;
    varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
    gl_Position = position;

//因为纹理坐标的范围是0-1,所以翻转的话都统一用1去减 
}
复制代码

同时也可以在顶点着色器中直接翻转顶点坐标:

attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;

void main()
{
    varyTextCoord = textCoordinate;
    //varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
    gl_Position = vec4(position.x,-position.y,position.z,1.0f);
}
//在翻转顶点时,就不是直接对Y值用1去减,因为顶点的取值范围是-1 - 1 ,所以我们直接加上负号做翻转即可
复制代码

5.直接从源纹理坐标数据修改

//原始坐标
GLfloat attrArr[] =
     {
     0.5f, -0.5f, 0.0f,        1.0f, 1.0f, //右下
     -0.5f, 0.5f, 0.0f,        0.0f, 0.0f, // 左上
     -0.5f, -0.5f, 0.0f,       0.0f, 1.0f, // 左下
     0.5f, 0.5f, 0.0f,         1.0f, 0.0f, // 右上
     -0.5f, 0.5f, 0.0f,        0.0f, 0.0f, // 左上
     0.5f, -0.5f, 0.0f,        1.0f, 1.0f, // 右下
     };
//更改后的坐标
GLfloat attrArr[] =
    {
        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
        -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
        
        0.5f, 0.5f, -1.0f,      1.0f, 1.0f,
        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    };

复制代码

翻转前效果图:

翻转后效果图:

文中部分内容参考:CC老师 www.jianshu.com/p/848d982db…

转载于:https://juejin.im/post/5d09f9c5e51d455071250b26

你可能感兴趣的:(OpenGL ES 入门之旅 -- GLSL纹理单元和纹理翻转解决策略)