前言
本文是关于OpenGL ES的系统性学习过程,记录了自己在学习OpenGL ES时的收获。
这篇文章的目标是学习OpenGL ES着色器语言。
环境是Xcode8.1+OpenGL ES 2.0
目前代码已经放到github上面,OpenGL ES入门02-OpenGL ES着色器
欢迎关注我的 OpenGL ES入门专题
概述
着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
效果
着色器语言
着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。
着色器分类
顶点着色器 是一个可编程的处理单元,执行顶点变换、纹理坐标变换、光照、材质等顶点的相关操作,每顶点执行一次。替代了传统渲染管线中顶点变换、光照以及纹理坐标的处理。
attribute vec3 position;
attribute vec3 color;
varying vec3 outColor;
void main()
{
gl_Position = vec4(position, 1.0);
outColor = color;
}
片元着色器 是一个处理片元值及其相关联数据的可编程单元,片元着色器可执行纹理的访问、颜色的汇总、雾化等操作,每片元执行一次。
precision mediump float;
varying vec3 outColor;
void main()
{
gl_FragColor = vec4(outColor, 1.0);
}
数据类型
类型 | 说明 |
---|---|
float | 浮点型 |
bool | 布尔型 |
int | 整形 |
vec2 | 包含了2个浮点数的向量 |
vec3 | 包含了3个浮点数的向量 |
vec4 | 包含了4个浮点数的向量 |
ivec2 | 包含了2个整数的向量 |
ivec3 | 包含了3个整数的向量 |
ivec4 | 包含了4个整数的向量 |
bvec2 | 包含了2个布尔数的向量 |
bvec3 | 包含了3个布尔数的向量 |
bvec4 | 包含了4个布尔数的向量 |
mat2 | 2*2维矩阵 |
mat3 | 3*3维矩阵 |
mat4 | 4*4维矩阵 |
sampler1D | 1D纹理采样器 |
sampler2D | 2D纹理采样器 |
sampler3D | 3D纹理采样器 |
samplerCube | Cube纹理采样器 |
常量
const 可以用来修饰任何基本数据类型。通常const变量在声明的同时要进行初始化,结构体字段不能使用const修饰吗,但是变量可以,并通过构造器进行初始化。包含数组的数组和结构体不能声明为常量,因为它们不能被初始化。
onst vec4 color = vec4 (1.0, 1.0, 1.0, 1.0);
存储修饰符
attribute 变量(属性变量)只能用于顶点着色器中,不能用于片元着色器。 一般用该变量来表示一些顶点数据,如:顶点坐标、纹理坐标、颜色等。
uniforms 是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
samplers 一种特殊的 uniform,用于呈现纹理。sampler 可用于顶点着色器和片元着色器。
varying 变量(易变变量)是从顶点着色器传递到片元着色器的数据变量。顶点着色器可以使用易变变量来传递需要插值的颜色、法向量、纹理坐标等任意值。 在顶点与片元shader程序间传递数据是很容易的,一般在顶点shader中修改varying变量值,然后片元shader中使用该值,当然,该变量在顶点及片元这两段shader程序中声明必须是一致的 。例如:上面代码中应用程序中由顶点着色器传入片元着色器中的vColor变量。
精度修饰符
precision 可以用来确定默认精度修饰符。类型可以是int或float或采样器类型,precision-qualifier可以是lowp, mediump, 或者highp。任何其他类型和修饰符都会引起错误。如果type是float类型,那么该精度(precision-qualifier)将适用于所有无精度修饰符的浮点数声明(标量,向量,矩阵)。如果type是int类型,那么该精度(precision-qualifier)将适用于所有无精度修饰符的整型数声明(标量,向量)。包括全局变量声明,函数返回值声明,函数参数声明,和本地变量声明等。没有声明精度修饰符的变量将使用和它最近的precision语句中的精度。
precision highp float;
precision highp int;
precision lowp sampler2D;
precision lowp samplerCube;
内建变量
gl_Position 顶点着色器内建变量,表示变换后点的空间位置。 顶点着色器从应用程序中获得原始的顶点位置数据,这些原始的顶点数据在顶点着色器中经过平移、旋转、缩放等数学变换后,生成新的顶点位置。新的顶点位置通过在顶点着色器中写入gl_Position传递到渲染管线的后继阶段继续处理。
*** gl_PointSize*** 顶点着色器内置变量,设置栅格化点的直径,也就是点的大小,通常用于点精灵,粒子等绘制。
gl_FragColor 片元着色器内置变量,用来保存片元着色器计算完成的片元颜色值,此颜色值将送入渲染管线的后继阶段进行处理。
加载着色器
static GLuint createGLShader(const char *shaderText, GLenum shaderType)
{
GLuint shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &shaderText, NULL);
glCompileShader(shader);
int compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLen = 0;
glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
char *infoLog = (char *)malloc(sizeof(char) * infoLen);
if (infoLog) {
glGetShaderInfoLog (shader, infoLen, NULL, infoLog);
GLlog("Error compiling shader: %s\n", infoLog);
free(infoLog);
}
}
glDeleteShader(shader);
return 0;
}
return shader;
}
1、创建着色器,通过glCreateShader创建着色器,type为着色器的类型GL_VERTEX_SHADER 和 GL_FRAGMENT_SHADER
glCreateShader (GLenum type) //type: GL_VERTEX_SHADER 或者 GL_FRAGMENT_SHADER
2、添加着色器源程序,将着色器源码关联到一个着色器对象shader上。string是一个有count行GLchar类型的字符串组成的数组,用来表示着色器的源代码数据。string可以以NULL结尾,也可以不是。如果length为NULL则string给出的每行都是以NULL结尾,否则length中必须有count个表示string长度的元素。(也就是说字符串以NULL结尾我们不用指定长度,否则必须制定每行的长度)
glShaderSource (GLuint shader, GLsizei count, const GLchar* const *string, const GLint* length)
3、编译着色器源程序
glCompileShader (GLuint shader)
4、删除着色器
glDeleteShader (GLuint shader)
5、容错处理,通过glGetShaderiv获取编译状态,通过glGetShaderInfoLog获取错误信息。
#define GL_COMPILE_STATUS 0x8B81
#define GL_INFO_LOG_LENGTH 0x8B84
glGetShaderiv (GLuint shader, GLenum pname, GLint* params)
glGetShaderInfoLog (GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* infolog)
创建着色器程序
GLuint createGLProgram(const char *vertext, const char *frag)
{
GLuint program = glCreateProgram();
GLuint vertShader = createGLShader(vertext, GL_VERTEX_SHADER);
GLuint fragShader = createGLShader(frag, GL_FRAGMENT_SHADER);
if (vertShader == 0 || fragShader == 0) {
return 0;
}
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
GLint infoLen;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
GLchar *infoText = (GLchar *)malloc(sizeof(GLchar)*infoLen + 1);
if (infoText) {
memset(infoText, 0x00, sizeof(GLchar)*infoLen + 1);
glGetProgramInfoLog(program, infoLen, NULL, infoText);
GLlog("%s", infoText);
free(infoText);
}
}
glDeleteShader(vertShader);
glDeleteShader(fragShader);
glDeleteProgram(program);
return 0;
}
glDetachShader(program, vertShader);
glDetachShader(program, fragShader);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
return program;
}
1、创建着色器程序
glCreateProgram (void)
2、装配着色器
glAttachShader (GLuint program, GLuint shader)
3、链接着色器程序
glLinkProgram (GLuint program)
4、卸载着色器程序
glDetachShader (GLuint program, GLuint shader)
5、使用着色器程序
glUseProgram (GLuint program)
6、容错处理
#define GL_LINK_STATUS 0x8B82
#define GL_INFO_LOG_LENGTH 0x8B84
glGetProgramiv (GLuint program, GLenum pname, GLint* params)
glGetProgramInfoLog (GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog)
三角形绘制
- (void)setupGLProgram
{
NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"vert.glsl" ofType:nil];
NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"frag.glsl" ofType:nil];
_program = createGLProgramFromFile(vertFile.UTF8String, fragFile.UTF8String);
glUseProgram(_program);
}
- (void)setupVertexData
{
// 需要加static关键字,否则数据传输存在问题
static GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
GLint posSlot = glGetAttribLocation(_program, "position");
glVertexAttribPointer(posSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(posSlot);
//颜色数据
static GLfloat colors[] = {
0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f
};
GLint colorSlot = glGetAttribLocation(_program, "color");
glVertexAttribPointer(colorSlot, 3, GL_FLOAT, GL_FALSE, 0, colors);
glEnableVertexAttribArray(colorSlot);
}
- (void)render
{
glClearColor(1.0, 1.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
// 在这里初始化数据,可以加static关键字,也可以不加
// GLfloat vertices[] = {
// 0.0f, 0.5f, 0.0f,
// -0.5f, -0.5f, 0.0f,
// 0.5f, -0.5f, 0.0f
// };
// GLint posSlot = glGetAttribLocation(_program, "position");
// glVertexAttribPointer(posSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
// glEnableVertexAttribArray(posSlot);
//
// GLfloat colors[] = {
// 0.0f, 1.0f, 0.0f,
// 0.0f, 1.0f, 0.0f,
// 0.0f, 1.0f, 0.0f
// };
// GLint colorSlot = glGetAttribLocation(_program, "color");
// glVertexAttribPointer(colorSlot, 3, GL_FLOAT, GL_FALSE, 0, colors);
// glEnableVertexAttribArray(colorSlot);
[self setupVertexData];
glDrawArrays(GL_TRIANGLES, 0, 3);
//将指定 renderbuffer 呈现在屏幕上,在这里我们指定的是前面已经绑定为当前 renderbuffer 的那个,在 renderbuffer 可以被呈现之前,必须调用renderbufferStorage:fromDrawable: 为之分配存储空间。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
indx 指定要修改的顶点着色器中顶点变量id;
size 指定每个顶点属性的组件数量。必须为1、2、3或者4。
type 指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT;
normalized 指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE);
stride 指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0;
ptr 顶点数据指针。
参考链接
https://www.khronos.org/opengles/sdk/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf
http://www.cnblogs.com/kesalin/archive/2012/11/25/opengl_es_tutorial_02.html