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()函数作为着色器内部和程序来进行传入值,需要知道两个参数,一个是在着色器内部接受信息的对象为位置。一个是外界的数据对象,严格来讲传入数据本身也不是这个函数做的,这个函数只是告诉着色器那个纹理对象对应哪个采样器对象。
流程.png

重点介绍一下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)在左下角。

纹理坐标.png
而图片中像素的存储顺序是从左上到右下的,因此我们需要对我们的坐标系进行一次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,
    };

翻转前效果图:
翻转前.png

翻转后效果图:
翻转后.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容