OpenGL学习- 10.GLKit & GLSL加载图片

1,006 阅读12分钟

10.GLKit & GLSL加载图片

用来使用着色器的OpenGL ES方法

创建着色器
GLuint glCreateShader(GLenum type)
type:创建的着色器类型 GL_VERTEX_SHADER(顶点着色器)/GL_FRAGMENT_SHADER(片元着色器)
返回值:指向创建的空白着色器对象的句柄
删除着色器
void glDeleteShader(GLuint shader)
shader:要删除的着色器句柄
替换着色器的源代码(给着色器链接GLSL写成的着色器程序)
void glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length)
shader:指向着色器的句柄
count:着色器源字符串的数量,着色器可以由多个源字符串组成,但是每个着色器只有一个main函数
string:指向源字符串数组的指针
length:指向保存源字符串长度的整数数组的指针
编译着色器
void glCompileShader(GLuint shader)
shader:需要编译的着色器句柄
查询着色器信息
void glGetShaderiv(GLuint shader,GLenum pname,GLint *params)
shader:要查询的着色器对象
pname:指定要查询的着色器对象信息的参数
params:指向查询信息结果的指针

pname查询信息枚举params查询结果
GL_SHADER_TYPE(着色器类型)GL_VERTEX_SHADER(顶点着色器)、GL_FRAGMENT_SHADER(片元着色器)
GL_DELETE_STATUS(着色器是否被删除)GL_TRUE(已删除)、GL_FALSE(未删除)
GL_COMPILE_STATUS(编译状态)GL_TRUE(编译成功)、GL_FALSE(编译失败)
GL_INFO_LOG_LENGTH(着色器的信息日志的长度)返回着色器的信息日志的长度,包括空终止字符(即存储信息日志所需的字符缓冲区的大小).如果着色器没有信息日志,则返回值0
GL_SHADER_SOURCE_LENGTH(着色器源码长度)返回着色器源码长度,不存在则返回0

获取着色器日志
void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog)
shader: 要查询的着色器句柄
maxLength:指定用于存储返回信息日志的字符缓冲区的大小
length:返回信息日志的字符串长度(不包括空终止符),如果不需要长度,该参数可以设为NULL
infoLog:返回的信息日志
创建program
GLuint glCreateProgram(void)
program对象是可以附加着色器对象的
返回program的句柄
删除program
void glDeleteProgram(GLuint program)
program:要删除的program的句柄
将着色器对象附加到program对象
void glAttachShader(GLuint program, GLuint shader)
program:program的句柄
shader:着色器的句柄
从program中分离着色器
void glDetachShader(GLuint program, GLuint shader)
program:program的句柄
shader:着色器的句柄
链接program
void glLinkProgram(GLuint program)
program:program的句柄
查询program信息
void glGetProgramiv(GLuint program, GLenum pname, GLint *params)
program:program的句柄
pname:指定要查询的program对象信息的参数
params:指向查询信息结果的指针

pname查询信息枚举params查询结果
GL_DELETE_STATUS(是否删除)GL_TRUE、GL_FALSE
GL_LINK_STATUS(program的最后一个链接操作是否成功)GL_TRUE、GL_FALSE
GL_VALIDATE_STATUS(program的最后一次验证操作是否成功)GL_TRUE、GL_FALSE
| GL_INFO_LOG_LENGTH(日志长度)返回program信息日志中的字符数,包括空终止字符(存储信息日志所需的字符缓冲区的大小).如果程序没有信息日志,则返回值0
GL_ATTACHED_SHADERS(着色器对象的数量)返回附加到program的着色器对象的数量
GL_ACTIVE_ATTRIBUTES返回program的激活状态的属性变量数
GL_ACTIVE_ATTRIBUTE_MAX_LENGTH返回program的最长激活状态的属性名称的长度,包括空终止字符(存储最长属性名称所需的字符缓冲区的大小).如果不存在活动属性,则返回0
GL_ACTIVE_UNIFORMS返回program的激活状态的统一变量的数量
GL_ACTIVE_UNIFORM_MAX_LENGTH返回program的最长激活状态的统一变量名称的长度,包括空终止字符(存储最长统一变量名称所需的字符缓冲区的大小).如果不存在活动的统一变量,则返回0

获取属性变量的位置
GLint glGetAttribLocation(GLuint program,const GLchar *name)
program:program的句柄
name:查询其位置的属性变量的名称
返回属性变量的位置
获取program日志
void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog)
shader: 要查询的program句柄
maxLength:指定用于存储返回信息日志的字符缓冲区的大小
length:返回信息日志的字符串长度(不包括空终止符),如果不需要长度,该参数可以设为NULL
infoLog:返回的信息日志
使用program
void glUseProgram(GLuint program)
program:program的句柄

FrameBuffer & RenderBuffer

RenderBuffer(渲染缓冲区)
renderbuffer对象(RBO)是通过应⽤分配的一个2D图像缓存区。renderbuffer能够用来存储颜色、深度或者模板值。也能够在framebuffer中用作颜色、深度、模板的附件。
renderbuffer类似于屏幕窗口系统提供的可绘制表面。比如pBuffer。
renderbuffer并不能像GL纹理一样直接的使⽤。
FrameBuffer(帧缓冲区)
frameBuffer对象(FBO)是一个颜色、深度和模板缓存区的附着点。描述属性的状态,例如颜色、深度和模板缓存区的大小和格式等,都关联到FBO(Frame Buffer Object)。并且纹理的名字和renderBuffer对象也都是关联于FBO。各种各样的2D图形能够被附着framebuffer对象的颜色附着点。它们包含了renderbuffer对象存储的颜色值、一个2D纹理或⽴方体贴图。或者⼀个mip-level的二维切面在3D纹理。同样,各种各样的2D图形包含了当时的深度值可以附加到一个FBO的深度附着点中去。唯一的二维图像,能够附着在FBO的模板附着点,是一个renderbuffer对象存储模板值。

OpenGL ES 3种变量修饰符(uniform, attribute/in, varying/out)

uniform
向顶点着色器和片元着色器传值,但是在着色器内部不会修改传入的值,对着色器而言可以类似const来理解
attribute 在3.0 中为in
只能向顶点着色器传递值
varying 在3.0 中为out
一般用来从顶点着色器向片元着色器传值,在顶点着色器中定义一个varying修饰的输出值,然后在片元着色器定义一个同样用varying修饰的同类型同名字的输入值,就可以做到把值从顶点着色器传递到片元着色器了。

GLSL着色器初探

GLSL着色器里尽量不要写注释,可能会造成编译错误
顶点着色器

attribute vec4 position;//输入的顶点坐标
attribute vec2 textCoordinate;//输入的纹理坐标
varying lowp vec2 varyTextCoord;//输出的纹理坐标

void main()
{
    varyTextCoord = textCoordinate;//通过varying修饰的varyTextCoord把纹理坐标输出
    gl_Position = position;//gl_Position是内建变量,必须进行赋值,存储顶点数据 
}

片元着色器

varying lowp vec2 varyTextCoord;//输入的纹理坐标(varying修饰,表示是从顶点着色器输出的)
uniform sampler2D colorMap;//纹理采样器,获取对应的纹理ID(uniform修饰,表示从外部代码传入)

void main()
{
    gl_FragColor = texture2D(colorMap, varyTextCoord);
    /*
    gl_FragColor是内建变量,必须进行赋值,存储像素点颜色值
    vec4 texture2D(sampler2D sampler, vec2 coord):内建函数,获取纹理对应坐标的纹素(纹素:像素点颜色值)
    */
}

GLSL精度修饰符的使用

顶点着色器有默认精度,片元着色器没有需要自己设置
highp高精度mediump中精度lowp低精度,精度值越低,执行效率越高。但要自己根据存储数据长度选择合适精度
放在指定变量前修饰当前变量精度

highp vec4 position;
varying lowp vec4 color;
mediump float specularExp;

放在着色器源码开始位置,修饰全局的默认精度

precision precision-qualifier type;
precision:固定的描述默认精度的修饰符
precision-qualifier:精度
type:数据类型
例:precision highp float

GLSL常用数据类型

向量(vec)

类型描述
vec2、vec3、vec42维、3维、4维浮点型向量
ivec2、ivec3、ivec42维、3维、4维整型向量
uvec2、uvec3、uvec42维、3维、4维无符号整型向量
bvec2、bvec3、bvec42维、3维、4维布尔型向量

矩阵数据类型(mat列x行)

类型描述
mat2、mat2x22行2列矩阵
mat3、mat3x33行3列矩阵
mat4、mat4x44行4列矩阵
mat2x33行2列矩阵
mat2x44行2列矩阵
mat3x22行3列矩阵
mat3x44行3列矩阵
mat4x22行4列矩阵
mat4x33行4列矩阵

变量存储限定符

限定符描述
<none>无限定符只是普通的本地变量,外部不可见也不可访问
const编译常量,或函数的只读参数
in、varying从上一个阶段传递过来的变量
in centroid、varying centroid从上一个阶段传递过来的变量。centroid 为质心采样关键字,用于避免伪像,不可用于顶点着色器
out、attribute传递到下一阶段或在函数中指定返回值
out centroid、attribute centroid传递到下一阶段的变量。centroid 为质心采样关键字,用于避免伪像,不可用于顶点着色器插值
uniform从客户端代码传递过来的变量,但在着色器内不能被修改只能读取

精度限定符(浮点数、整数可用)

限定符描述
highp高精度
mediump中等精度
lowp低精度

GLSL常用数据类型示例

变量和数据类型

//布尔型 true,false
bool bValue = false;
//有符号整型
int iValue = 42;
//无符号整型
uint uiValue = 34u;
//浮点型
float fValue = 3.14f;

向量数据类型

//向量声明,声明一个4分量float类型向量
vec4 V1;
//声明向量并对其进行构造
vec4 V2 = vec4(1,2,3,4);
//向量运算
vec4 v;
vec4 vOldPos = vec4(1,2,3,4);
vec4 vOffset = vec4(1,2,3,4);
v = vOldPos;
v = vOldPos + vOffset;
v += vec4(1,1,1,1);
v = vOldPos * vOffset;
v *= 5;
//向量中取元素,可用(x,y,z,w)(r,g,b,a)(s,t,p,q)来取,但这三种表示不能混合使用
v.xy = vec2(2.0f, 3.0f);
v.xyz = vec3(1.0f, 1.0f, 1.0f);
v.r = 3.0f;
v.rgba = vec4(1,2,3,4);
v.st = vec2(1,2);
v.st = v2.xt;//xt混用不合法
v.st = v2.xy;//可以,但尽量避免,因为GLSL没有注释,这样写不利于其他人阅读
// 向量支持调换(swizzls)操作,2个或2个以上向量元素来进行交换
v1.rgba = v2.bgra;
v2.bgra = v1.rgba;
//赋值
v1.r = v2.b;
v1.a = v2.a;
//向量还支持一次性对所有分量操作
v1.x = v2.x + 3.0f;
v1.xyz = v2.xyz + vec3(2,3,4);

矩阵

//创建矩阵
mat4 m1,m2,m3;
// 构造单元矩阵
mat4 m1 = mat4(1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);
// 初始化矩阵
mat m4 = mat4(1.0f);
mat m3 = mat4(1,1,1,1,
              1,1,1,1,
              1,1,1,1,
              1,1,1,1);
// 矩阵乘法
m = m2 * m3;

常量

const float aaa = 0.2f;

结构体

struct forStruct{
vec4 color;
float start;
float end;
}fogVar;
fogVar = fogStruct(vec4(1,0,0.5,0),0.1, 1.3);
vec4 color = fogVar.color;
float start = fogVar.start;

数组

float floatArray[4];
vect4 vectArray[];
float a[4] = float[](1,2,3.0,4.0);
vec2 c[2] = vec2[2](vec2(1.0,1.0), vec2(1.0,3.0));

函数:GLSL不支持递归函数

定义函数时给了3个修饰符
in:(没有指定时,默认限定修饰符),传递进入函数中,函数不能对其进行修改
inout:(传入相应数值,并可以在函数中进行修改)
out:(函数返回时,可以将其修改)
vec4 myFunc(inout float myFloat, out vec4 m1 , mat4 m2)
{
// 函数计算
}
vec4 diffuse(vec3 normal ,vec3 light, vec4 baseColor) 
{
return baseColor * dot(normal,light); 
}

控制语句

控制支持if...else...
循环只支持 while循环/do...while循环/for循环
但是尽量在GLSL中少用逻辑判断,少用循环迭代

自己写着色器来渲染图片

先创建自己的顶点着色器和片元着色器,名称后缀都随便,使用的时候只是读取其内部的字符串
顶点着色器 15837332727144.jpg 片元着色器 15837333499817.jpg 使用自定义的View来渲染,所以把控制器的view替换掉 15837334235678.jpg

要渲染的图片
15837335461713.jpg
具体的渲染实现 15837350951431.jpg 15837351516258.jpg 15837351960517.jpg 15837352285868.jpg 15837353937394.jpg 15837354140923.jpg 15846046872283.jpg 15846047127620.jpg 15846047399804.jpg 15846047557434.jpg 15846050503409.jpg

最后得到一张z轴旋转180度的渲染图,这是因为OpenGL的坐标系跟屏幕显示的不同 15837337268078.jpg
我们想要的应该是这样的:
15844989632929.jpg
那么如何解决这个旋转问题,下面列举几种解决方案:

1.通过片元着色器计算来翻转纹理

个人认为不太合适,毕竟这要把计算放到片元着色器里,每次像素点颜色值赋值都会进行一次计算
我们可以在给gl_FragColor赋值的时候,就取翻转过的纹理坐标的颜色 15844996134142.jpg

2.通过旋转矩阵来翻转纹理

个人认为不太合适,毕竟这要把计算放到顶点着色器里,每次顶点坐标赋值都会进行一次计算
先修改顶点着色器,在给gl_Position赋值的时候,传入顶点坐标进行旋转之后的值 15844985593413.jpg 然后给着色器传入用来进行z轴翻转的旋转矩阵 15844990176650.jpg

3.通过顶点着色器计算来翻转纹理

个人认为不太合适,毕竟这要把计算放到顶点着色器里,每次顶点纹理坐标赋值都会进行一次计算
我们可以在顶点着色器向片元着色器传递纹理坐标的时候,把纹理坐标翻转 15845000035175.jpg

4.通过修改顶点纹理坐标来翻转纹理

个人认为可行但需要注意,不推荐使用。虽然计算量上没增加,但直接修改数据这个容易忘记或者写错,出错几率太大
15845005408414.jpg

5.通过仿射变换在载入纹理时直接翻转图片

个人推荐使用。在纹理载入的时候直接把图片翻转,计算量不会增加,书写也不易出错,一劳永逸
使用仿射变换处理绘制,具体的仿射变换实现有多种方式,只要达到效果就行 15845017905304.jpg

总结一下翻转的问题的思考思路

首先我们知道决定渲染出来的每个像素点的具体颜色的是片元着色器的内建属性gl_FragColor,所以我们翻转图片实际上就是修改gl_FragColor存储的色值。而gl_FragColor是通过vec4 texture2D(sampler2D sampler, vec2 coord)来赋值的。texture2D方法有2个参数:纹理采样器sampler和纹理坐标coord.如果我们修改纹理采样器sampler,那就是要修改纹理了,对应上面方案5.如果修改纹理坐标coord,直接修改就是方案1,因为纹理坐标是顶点着色器传入的,所以可以在顶点着色器中修改,对应方案3.顶点中纹理坐标和顶点坐标的映射关系修改,对应方案2、4.

代码资料见:Github