创建Shader
关于在OpenGL中怎么创建Shader这个在很早我博客中就有过详细介绍了。这里全当复习,温故而知新~
在OpenGL中,存在Program和Shader两个概念,Program相当于当前渲染管线所使用的程序,是Shader的容器,可以挂载多个Shader。而每个Shader相当于一个C模块,首先需要对Shader脚本进行编译,然后讲编译好的Shader挂载到Program上,在OpenGL的渲染中使用Program来使Shader生效,整个流程如下图所示:
整个流程其实就是创建Shader和创建Program两个子流程。创建Shader的流程如下:
- 调用glCreateShader()创建一个Shader对象。
- 调用glShaderSource()创建加载Shader脚本源码。
- 调用glCompileShader()编译Shader脚本。
然后创建好的Shader需要被挂载到Program中,创建Program的流程如下:
- 调用glCreateProgram()创建一个Program对象。
- 调用glAttachShader()将创建好的Shader进行挂载。
- 调用glLinkProgram()执行链接操作。
- 最后在需要使用Shader时,调用glUseerProgram()应用当前Shader。
整个流程中的编译、链接其实和我们的c/c++的编译、链接一样,先编译成.o/.dll库,然后把这些库文件链接成一个可执行的程序。
我们可以创建多个Program,但同时只可以激活一个Program。创建完Program之后,只需要在调用OpenGL绘制方法前使用glUseProgram即可应用当前的Shader。如果激活的Program没有挂载片段Shader,那么片段Shader执行的结果就是为定义的。如果激活一个不存在的Shader,那么所有Shader的执行结果都是未定义的。
下面来简单介绍下流程相关的API:
GLuint glCreateShader(GLenum shaderType);
void glShaderSource(GLuint shader, int count, const char **string, int *length);
void glCompileShader(GLuint shader);
----------
GLuint glCreateProgram();
void glAttachShader(GLuint program, GLuint shader);
void glLinkProgram(GLuint program);
void glUseProgram(GLuint program);
最后演示下上面讲的接口的使用事例,useShader()函数接收两个参数,分别是顶点着色器和片段着色器的源码。
void useShader(const char* vs, const char* fs)
{
int v = glCreateShader(GL_VERTEX_SHADER);
int f = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(v, 1, &vs, NULL);
glShaderSource(f, 1, &fs, NULL);
glCompileShader(v);
glCompileShader(f);
int p = glCreateProgram();
glAttachShader(p, v);
glAttachShader(p, f);
glLinkProgram(p);
glUseProgram(p);
}
属性变量
在Shader中,属性变量和统一变量由应用程序设置,Attribute属性变量用于传递顶点信息,而Uniform统一变量则用于传递用户自定义的变量。这两种变量在Shader中会被定义为全局变量,在OpenGL中要设置这两种变量,就需要先获取它们的地址,然后调用OpenGL相关的设置接口为它们赋值。
1.设置属性变量的接口
属性变量包含的是顶点数据,因此在片段着色器中不能直接使用。而且在顶点着色器中它是只读的。要使用它首先得获取地址,这个信息只有在Program链接之后才可以获取。(某些驱动程序,在获取地址之前还必须调用glUseProgram()方法激活Program)
下面是获取属性变量地址的接口:
GLint glGetAttribLocation(GLuint program, char *name);
获取到位置后,然后就可以使用glVertexAttribxx系列方法来为这个属性赋值了。
2.设置属性变量的时机
先假设有这样一个属性变量,获取它的位置如下:
GLint local = glGetAttribLocation(program, "myattribute");
设置属性变量是在渲染时为其赋值的,OpenGL渲染时赋值有两种形式,一种是在glBegin()和glEnd()中间,在使用glVertex系列函数生成顶点前。先调用glVertexAttrib系列函数进行赋值,接下来生成的顶点会绑定前面设置的属性变量。
glBegin(GL_TRIANGLE_STRIP);
glVertexAttrib1f(local, 1.0f);
glVertex2f(0.0f, 0.0f);
glVertexAttrib1f(local, 2.0f);
glVertex2f(0.0f, 1.0f);
glVertexAttrib1f(local, 3.0f);
glVertex2f(1.0f, 1.0f);
glVertexAttrib1f(local, 4.0f);
glVertex2f(1.0f, 0.0f);
glEnd();
第二种情况是使用顶点数组渲染时,这个得先激活属性变量数组的功能,
void glEnableVertexAttribArray(GLint local);
开启这个功能后,需要调用glVertexAttribPointer()方法,将属性变量的值批量传入,属性变量的数组和顶点数组是一一对应的。
void glVertexAttribPointer(GLint local, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
下面是使用顶点数组进行渲染时,为顶点数组中的每一个顶点绑定属性变量的事例:
float vertices[8] = {
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f
};
float myattributes[4] = {1.0f, 2.0f, 3.0f, 4.0f};
GLint local = glGetAttribLocation(p, "myattribute");
glEnableClientState(GL_VERTEX_ARRAY);
glEnableVertexAttribArray(local);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glVertexAttribPointer(local, 1, GL_FLOAT, GL_FALSE, 0, myattributes);
统一变量
属性变量相当于每个顶点的私有只读变量,而Uniform统一变量则相当于整个Program的全局只读变量。统一变量和属性变量一样,都是先获取变量的位置,然后调用相关的接口进行设置的。不过统一变量在绘制时不能修改,所以必须在绘制前设置它的值。
至于设置接口和属性变量一致,只是将方法名中的Attrib或VertexAttrib替换成Uniform。统一变量的设置比属性变量要轻松得多,因为不需要想办法绑定到每个顶点上,只需要在渲染之前进行设置就可以了。当然它数据类型可以是纹理或者矩阵类型,这些使用时自行find~这里就不再累赘呢。
错误处理
Shader在编译或链接的时候一般比较容易出现错误,而编译和链接方法的返回值都是void,那么当它们出现错误时怎么知道呢?这就需要使用glGetShaderiv()和glGetProgramiv()这两个函数,它们接口原型一致,都是传入指定的对象,以及要获取的状态枚举,并传入一个GLint指针来接收状态的值。
void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);
void glGetProgramiv(GLuint program, GLenum pname, GLint *params);
如果发生错误,错误日志会被保存到InfoLog中,可以调用glGetShaderInfoLog()和glGetProgramInfoLog()方法从中查询错误相关信息。这两个方法原型也一致,第一个参数为Shader或Program的句柄,maxLength参数表示infoLog缓冲区的长度,length 参数指针会输出实际复制到infoLog中的字节数,infoLog参数为用于接收日志信息字符串。
void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);
void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog);
那么InfoLog日志长度是多少呢?使用glGetShaderiv()和glGetProgramiv()这两个函数,传入GL_INFO_LOG_LENGTH类型,可以获取日志的长度。
下面我们来实际看个事例,它获取Shader日志并将日志用printf()打印出来。
void printShaderLog(GLuint shader)
{
GLint shaderState;
glGetShaderiv(shader, GL_COMPILE_STATES, &shaderState);
if(shaderState == GL_TRUE)
{
return;
}
GLsizei bufferSize = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &bufferSize);
if(bufferSize > 0)
{
GLchae* buffer = new char[bufferSize];
glGetShaderInfoLog(shader, bufferSize, NULL, buffer);
printf("%s", buffer);
delete[] buffer;
}
}
清理工作
在调用glCreateShader()和glCreateProgram()方法后,不需要使用的时候,需要使用glDeleteShader()和glDeleteProgram()进行释放。
当一个Shader被挂载到Program中时,glDeleteShader()是无法释放这个Shader的,只会将这个Shader标记为以删除,还需要调用glDetachShader()将Shader从Program中卸除。
void glDetachShader(GLuint program, GLuint shader);
void glDeleteShader(GLuint shader);
void glDeleteProgram(GLuint program);
同样当一个Program在被使用时,glDeleteProgram()方法是无法释放这个Program的,只是将这个Program标记为已删除,当Program不再被使用时,Program才会被释放。当Program真正被释放时,所有挂载在它上面的Shader都会被自动卸载。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)