上一篇我们用GLSL对图片进行加载,遗留了一个问题。
由于OpenGL要求纹理坐标y轴0.0坐标是在图片的底部(原点在左下角),但是屏幕的y轴0.0坐标通常在顶部(原点在左上角),所以导致了纹理映射到顶点坐标后会有图片翻转了的问题。
iOS纹理翻转解决策略
第一种:旋转矩阵翻转图形,不翻转纹理
在shader.vsh
文件里,我们传入了两个变换矩阵的变量rotateMatrix;
和scaleMatrix;
,将顶点数据position
进行矩阵变换的结果传递给内建变量gl_Position
.
//传入的顶点坐标
attribute vec4 position;
//传入的纹理坐标
attribute vec2 textCoordinate;
//旋转和缩放矩阵
uniform mat4 rotateMatrix;
uniform mat4 scaleMatrix;
//桥接用的纹理坐标
varying lowp vec2 varyTextCoord;
void main(){
//通过varying 修饰的varyTextCoord,将纹理坐标textCoordinate传递到片元着色器
varyTextCoord = textCoordinate;
//旋转翻转后的顶点数据给内建变量gl_Position赋值
gl_Position = position * rotateMatrix * scaleMatrix;
}
我们在绘图glDrawArrays
之前对图形进行翻转
//注意,想要获取shader里面的变量,这里记得要在glLinkProgram后面,后面,后面!
//1. rotate等于shaderv.vsh中的uniform属性,rotateMatrix
GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
//2.获取渲旋转的弧度
float radians = 180 * 3.14159f / 180.0f;
//3.求得弧度对于的sin\cos值
float s = sin(radians);
float c = cos(radians);
//4.因为在3D课程中用的是横向量,在OpenGL ES用的是列向量
/*
参考Z轴旋转矩阵
*/
GLfloat zRotation[16] = {
c,-s,0,0,
s,c,0,0,
0,0,1,0,
0,0,0,1
};
//缩放,将图片x坐标缩放-1倍,达到左右翻转的效果
GLuint scale = glGetUniformLocation(self.myPrograme, "scaleMatrix");
GLfloat xScale[16] = {
-1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1
};
//5.设置旋转矩阵
/*
glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
location : 对于shader 中的ID
count : 个数
transpose : 转置
value : 指针
*/
glUniformMatrix4fv(rotate, 1, GL_FALSE, zRotation);
glUniformMatrix4fv(scale, 1, GL_FALSE, xScale);
为什么需要增加缩放矩阵变换呢?
由于我们对图形绕z轴旋转180度后,图片显示相对于原始图片是左右翻转了的,所以如果需要还原成原始的样子,则需要再对图片进行缩放处理。左右翻转,我们需要对x坐标乘以-1进行缩放,所以构建了一个单位矩阵的x轴坐标乘以-1标量的矩阵。
第二种:修改纹理坐标源数据,顶点坐标不变
原来的纹理数据
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,
};
修改后的纹理数据
GLfloat attrArr[] =
{
0.5f, -0.5f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -1.0f, 0.0f, 1.0f,
0.5f, 0.5f, -1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -1.0f, 1.0f, 1.0f,
};
第三种:修改片元着色器的纹理坐标
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0-varyTextCoord.y));
}
由于纹理坐标基于[0,1]之间,所以用1.0-varyTextCoord.y
可以翻转纹理y坐标,这一步由于是在片元着色器中处理的,每一个像素点就进行一次y坐标处理,所以并不是很高效。
第四种:修改顶点着色器的纹理坐标
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
gl_Position = position;
}
和第三种一样,也是用1.0-textCoordinate.y
的方式,不同的是,我们在将顶点着色器中的纹理坐标数据桥接给片元着色器之前做得翻转处理,所以只需要处理一次,相对第三种更高效。
第五种:在解压图片时,就将图片源文件翻转
在原来的解压图片成位图的过程中再添加一下的代码
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, 0, -rect.size.height);
CGContextDrawImage(spriteContext, rect, spriteImage);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
将图片的y坐标缩放-1倍,相当于围绕x轴翻转
CGContextTranslateCTM(spriteContext, 0, -rect.size.height);
是将图片进行向上移动图片的高度
推荐采用第五种方式,直接在解压图片时翻转。第一种需要手动计算旋转缩放的矩阵,比较繁琐。第二种需要更改顶点和纹理的映射位置,不是很好。第三、四种需要在顶点、片元着色器文件编写,如果文件里的代码比较复杂,则会增加复杂度。
最后看下翻转后的效果