OpenGL学习日记-2015.3.5——Hello glsl(着色器)
时间:2015-03-08 06:45:57
收藏:0
阅读:6914
过年前忍不住买了本新版的OpenGL编程指南,主要的目的还是为了系统的学习着色器编程,另外就是接触新版的OpenGL技术和思想。看了几页,就过年了QAQ.回来后也是各种不在状态,不想上班,不想工作,不想写代码。。。昨天终于强迫自己继续看书,也找回了些状态。
![技术分享](http://img.blog.csdn.net/20150308044114674?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY29kZXJsaW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
书本基础知识的全面性和权威性就不用说了,不过这个源代码就。。。。这第一个例子照着代码来抄结果。。。我想应该是原来的代码一个参数错了,折腾了半天,代码分析是详说。主要是分析代码,有什么说什么,并没有全面的说明着色器的基本内容,想着在着色器的基础知识看的差不多或者更熟悉了再做一次总结。
顶点着手器:
#version 330 uniform mat4 model_matrix; uniform mat4 projection_matrix; layout (location = 0) in vec4 position; layout (location = 1) in vec4 color; out vec4 vs_fs_color; void main(void) { vs_fs_color = color; gl_Position = projection_matrix * (model_matrix * position); }
第一行是着色器版本说明,以上是对应于OpenGL 3.3版本,在编写着色器程序是虽然使用最新的版本可以使得程序充分利用的Opengl的新特性,当时也同时带来兼容性的问题。所以根据自己的程序需要做一个权衡。
第二三行定义了4x4的矩阵,从字面意义上看,他们分别是透视投影矩阵和,模型视图矩阵,在main与顶点坐标做透视投影变换和模型视图变换。 关于uniform关键字:是个存储限制符,uniform修饰符可以指定一个在应用程序中定义好的变量,他不会再图元处理过程中变化(着色器流水中),它在所有的着色器阶段都是共享的所以它必须是全局的变量(相对于着色器)。任何类型的变量都可以为uniform修饰。!!!重要:着色器无法修改,写入uniform修饰的变量,无法改变它的值。
第四五行定义了两个4维向量,比较生疏的修饰符。glsl声明的解读和c++一样,从右往左读。position是个变量名,vec4说明了这个变量是4维向量,in修饰符代表着变量是一个输入变量(无修饰时,默认值),然后是layout(location = 0),她叫做布局限定符,目的是为了方便给变量提供数据,layout()的还有其他的选项,在这里location相当于设定了变量在着色器程序中的访问位置。
第六行同样定义了一个4维向量,不同的是out修饰符,同理易知代表着这个变量是用于输出数据,这里是给片段着色器提供颜色的输入数据,在下面的片元着色器中将再次看到该变量。
最后是main()函数,每一个着色器都必须有一个main()函数,与c、c++不同的是这个函数没有返回值,没有参数。函数里第一行对输出变量赋予应用程序中传递过来的颜色值,以便传递给片元着色器使用、第二行对gl_Position赋值,这是个OpenGL的内置变量,所有OpenGL的内置变量都是以gl_为前缀。gl_Position表示顶点坐标,最终OpenGL将根据该值绘制每一个顶点。
片元着色器:
#version 330 in vec4 vs_fs_color; layout (location = 0) out vec4 color; void main(void) { color = vs_fs_color; }
从第二行说起吧,这个变量在顶点着色器中出现过,不同的是修饰符从out变成了in。对,这个变量的值就是从顶点着色器中传递过来的。
最后main函数中输出了颜色color。
两个必要的着色器编写完成,接下来需要进行着色器装配。着色器的装配过程有点像c++程序的编译生成过程,经过了几个阶段,编译,连接,执行。程序使用了书中源码的LoadShaders接口。
typedef struct { GLenum type; const char* filename; GLuint shader; } ShaderInfo; GLuint LoadShaders( ShaderInfo* );
繁琐的细节就略过,关注于着色器的装配。一下是LoaderShaders.cpp的内容,将在代码注释中详细描述每一步
///////////////////////////////////////////////////////////////////////////// // // --- LoadShaders.cxx --- // ////////////////////////////////////////////////////////////////////////////// #include <cstdlib> #include <iostream> / #define GLEW_STATIC #include <GL/glew.h> #include "LoadShaders.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus //---------------------------------------------------------------------------- static const GLchar* ReadShader( const char* filename ) { #ifdef WIN32 FILE* infile; fopen_s( &infile, filename, "rb" ); #else FILE* infile = fopen( filename, "rb" ); #endif // WIN32 if ( !infile ) { #ifdef _DEBUG std::cerr << "Unable to open file '" << filename << "'" << std::endl; #endif /* DEBUG */ return NULL; } fseek( infile, 0, SEEK_END ); int len = ftell( infile ); fseek( infile, 0, SEEK_SET ); GLchar* source = new GLchar[len+1]; fread( source, 1, len, infile ); fclose( infile ); source[len] = 0; return const_cast<const GLchar*>(source); } //---------------------------------------------------------------------------- GLuint LoadShaders( ShaderInfo* shaders ) { if ( shaders == NULL ) { return 0; } GLuint program = glCreateProgram(); //1.first,创建着色器程序实例。返回整形,类似于指针一样的东西。 ShaderInfo* entry = shaders; while ( entry->type != GL_NONE ) { GLuint shader = glCreateShader( entry->type ); //根据信息(枚举值),创建对应的着色器。 entry->shader = shader; const GLchar* source = ReadShader( entry->filename ); //读取着色器字符串 if ( source == NULL ) { for ( entry = shaders; entry->type != GL_NONE; ++entry ) { glDeleteShader( entry->shader ); //读取文件出错,删除着色器对象。 entry->shader = 0; } return 0; } glShaderSource( shader, 1, &source, NULL ); //将shader,着色器对象与相应的着色器字符串关联。 delete [] source; //删除着色器字符串,glShaderSource应该会做一个拷贝的操作,保存了字符串信息。 glCompileShader( shader ); //编译着色器 GLint compiled; glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled ); //检查编译是否成功,输出编译信息。 if ( !compiled ) { #ifdef _DEBUG GLsizei len; glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &len ); //获取编译日志的长度 GLchar* log = new GLchar[len+1]; glGetShaderInfoLog( shader, len, &len, log ); //取得日志 std::cerr << "Shader compilation failed: " << log << std::endl; //打印 delete [] log; #endif /* DEBUG */ return 0; } glAttachShader( program, shader ); //装配,将着色器对象关联都着色器程序中 //???一个着色器可以关联多个同一类型的着色器么?如多个顶点着色器你,不过好像没有什么意义 ++entry; } #ifdef GL_VERSION_4_1 if ( GLEW_VERSION_4_1 ) { // glProgramParameteri( program, GL_PROGRAM_SEPARABLE, GL_TRUE ); //需要多个着色器程序时使用4.1以上的OpenGL } #endif /* GL_VERSION_4_1 */ glLinkProgram( program ); //连接各个模块成完整的着色器程序。 GLint linked; glGetProgramiv( program, GL_LINK_STATUS, &linked );//检查连接过程是否成功,获取失败是的日志爱 if ( !linked ) { #ifdef _DEBUG GLsizei len; glGetProgramiv( program, GL_INFO_LOG_LENGTH, &len ); GLchar* log = new GLchar[len+1]; glGetProgramInfoLog( program, len, &len, log ); std::cerr << "Shader linking failed: " << log << std::endl; delete [] log; #endif /* DEBUG */ for ( entry = shaders; entry->type != GL_NONE; ++entry ) { glDeleteShader( entry->shader );//连接出现错误,删除相应着色器 entry->shader = 0; } return 0; } return program; } //---------------------------------------------------------------------------- #ifdef __cplusplus } #endif // __cplusplus
总结来说: 1、glCreateProgram()创建一个着色器程序。
2、glCreateShader() 根据着色器不同阶段创建需要的着色器。
3、glShaderSource()把着色器字符串提交到OpenGL等待编译。
4、glCompileShader()编译着色器。
5、glAttachShader()把编译好的着色器与着色器程序关联,装配的过程。
6、glLinkProgram()连接装配好的着色器成为一个可使用的着色器程序。
7、glUseProgram()激活对应的着色器,使他对应用程序生效。
接下来是程序主体,最终效果绘制4个排列的彩色三角形
首先是初始化函数:整个cpp文件全贴出来了,详细注释。
#include"shaderBase.h" #include "include\vmath.h" #include "include\LoadShaders.h" float shade_aspect = 800/600; //固定的长宽比,由于之前的窗口初始化代码走的的固定流水线过程,这里并没有重构。 GLuint render_prog; GLuint shade_vao[1]; GLuint shade_vbo[1]; GLuint shade_ebo[1]; GLint render_model_matrix_loc; GLint render_projection_matrix_loc; void shadeBaseInit() { static ShaderInfo shader_info[] = { { GL_VERTEX_SHADER, "../8edlib/shaders/primitive_restart.vs.glsl"}, { GL_FRAGMENT_SHADER, "../8edlib/shaders/primitive_restart.fs.glsl"}, { GL_NONE, NULL }, };// render_prog = LoadShaders(shader_info);//着色器程序生成 glUseProgram( render_prog );//使用该着色器程序 GLenum error = glGetError();//之前因为源代码,还有书本的错误,各种glGetError找错误。。。。 const GLubyte* errorStr = gluErrorString(error); render_model_matrix_loc = glGetUniformLocation( render_prog, "model_matrix");//获取Uniform变量在着色器程序中的位置。 error = glGetError(); errorStr = gluErrorString(error); render_projection_matrix_loc = glGetUniformLocation( render_prog, "projection_matrix"); error = glGetError(); errorStr = gluErrorString(error); ////三角形数据,顶点,颜色,索引 // A single triangle static const GLfloat vertex_positions[] = { -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, }; // Color for each vertex static const GLfloat vertex_colors[] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f }; // Indices for the triangle strips static const GLuint vertex_indices[] = { 0, 1, 2 }; //set up the element array buffer glGenBuffers(1, shade_ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shade_ebo[0]); glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(vertex_indices), vertex_indices, GL_STATIC_DRAW ); //set up then vertex attributes glGenVertexArrays(1, shade_vao); //顶点数组对象标识申请,管理着顶点属性的集合 glBindVertexArray(shade_vao[0]);//在这里将做3个事情:1、如果参数是非0,并且是glGenvertexArrays()返回的新值,未经glBindVertexArray的 //那么他将创建一个新的顶点数组对象(这里才真正创建),并且与其名称关联起来。 //2、如果绑定包已经创建过的顶点数组对象,那么该顶点数组对象将被激活。这便于帧间切换绘制数据 //3、如果输入参数是0那么OpenGL将不再使用程序所分配的任何顶点数组对象,并且将渲染状态重设为默认值。 glGenBuffers(1, shade_vbo);//申请顶点缓存对象标识。 glBindBuffer(GL_ARRAY_BUFFER, shade_vbo[0]);//指定顶点缓存对象的用途,GL_ARRAY_BUFFER表示顶点数据。之力glBindBuffer同样完成了3项工作: //1、如果是第一次绑定对象(第二个参数),他是一个非0的无符号整型,那么将创建一个与名称对应的(第一个参数)新的缓存对象 //2、如果绑定到一个已经创建的缓存对象,那么她将被激活为当前使用对象。 //3、如果第二个参数是0,那么OpenGL不再为当前名称(第一个参数)应用任何缓存对象。 glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_positions) + sizeof(vertex_colors), NULL, GL_STATIC_DRAW);//所有前置工作准备好后,就到了向 //缓存对象输入数据的环节:glBufferData( GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage ), glBufferData是真正为缓存对象分配存储空间。 //1、target目标:顶点数据(GL_ARRAY_BUFFER)、索引数据(GL_ELEMENT_ARRAY_BUFFER)、OpenGL的像素数据(GL_PIXEL_UNPACK_BUFFER)、 //从OpenGL中获取的像素数据(GL_PIXEL_PACK_BUFFER)、缓存之间复制数据(GL_COPY_READ_BUFFER/GL_COPY_WRITE_BUFFER)、 //纹理缓存中存储的纹理数据(GL_TEXTURE_BUFFER)、一致性变量(GL_UNIFORM_BUFFER) //2、size:表示缓存数据的总量,字节数。 //3、data:是客户端应用程序的内存指针,数据的来源。要么是NULL,否则如果合法则将会有size大小的数据从客户端拷贝到服务端(显卡内存),如果data数据未初始化 //将保留size大小的内存备用。 //4、usage:用于设置分配数据之后的读取和写入方式,这关系都OpenGL对于缓存对象存储数据中的最优分配方案的管理。说白了,这个参数试图向OpenGL提供 //这堆数据的用途,是否只读,是否静态,用于绘制?拷贝?通过内置标识符的方式告诉OpenGL,OpenGL根据信息来优化内存分配,管理。 glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertex_positions), vertex_positions );//用数据部分替换目标缓存的内容,注意在glBufferData中我们的data为NULL,所以在这里才真正初始化。 //这个接口使得我们的操作更为灵活,对数据的组织更为紧凑,因为我们只为顶点坐标数据和颜色数据分配了一块连续的空间。 //第二个参数是偏移地址,第三个参数是替换数据的大小,第四个参数是客户端内存指针,也就是数据源。需要注意的是不可超越glBufferData保留的内存。 //到这里关于缓存对象的操作只是最简单的部分,还有很多OpenGL接口供我们去灵活控制,优化。 glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertex_positions), sizeof(vertex_colors), vertex_colors ); glVertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, 0, NULL );//最后需要在顶点着色器和缓村数据之间建立联系,以便着色器使用。 //glVertexAttribPointer(GLuint index, Glint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer ) //index:着色器中属性的位置,还记得layout(location = 0)的定义么,就是这个location. //size:表示每个顶点该属性需要更新的分量数目,这里顶点坐标为4个float,所以就是4.值的范围可以是1,2,3,4或GL_BGRA //type:数据类型 //normalized:使用顶点数据之前是否要进行归一化。 //stride:每组数据之间是否要进行偏移,如果是0则,数据是紧密的。 //pointer:表示缓存对象中,从开始位置开始计算数组数据的偏移值 glVertexAttribPointer( 1, 4, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)sizeof(vertex_positions) ); glEnableVertexAttribArray(0);//参数对应于glVertexAttribPointer的index.也就是location值,与这个值相关联的定点数组将被启用。 glEnableVertexAttribArray(1);//启用颜色属性数组 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } void shadeBaseDisplay() { float t = float(GetTickCount() & 0x1FFF) / float(0x1FFF); static float q = 0.0f; //static const vmath::vec3 X(1.0f, 0.0f, 0.0f ); //static const vmath::vec3 Y(0.0f, 1.0f, 0.0f ); //static const vmath::vec3 Z(0.0f, 0.0f, 1.0f ); vmath::mat4 model_matrix; //setup glEnable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //active simple shading program glUseProgram( render_prog );//确定使用了对应的着色器程序 //set up the model and projection matrix vmath::mat4 projection_matrix(vmath::frustum(-1.0f, 1.0f, -shade_aspect, shade_aspect, 1.0f, 500.0f));//生成投影矩阵 glUniformMatrix4fv(render_projection_matrix_loc, 1, GL_FALSE, projection_matrix);//glUniform*()是一系列的函数,他们的作用是 //设置uniform变量的值,第一个参数是uniform的位置,通过glGetUniformLocation()获取,第二个参数是,有多少个对应 //数据集(前面函数名称后缀,这里是Matrix4fv是矩阵,第三个参数说明了数据的读取顺序,GL_FALSE以列为主序,否则以行为主序) //第四个参数是数据源。 GLenum error = glGetError(); const GLubyte* errorStr = gluErrorString(error); //set up for a glDrawElements call glBindVertexArray(shade_vao[0]);//已绑定过的顶点缓存对象,确保激活为使用对象。 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shade_ebo[0]);//同理索引缓存激活 //draw arrays... model_matrix = vmath::translate( -3.0f, 0.0f, -5.0f ); glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix); error = glGetError(); errorStr = gluErrorString(error); glDrawArrays(GL_TRIANGLES, 0, 3 );//绘制三角形,非索引绘制 error = glGetError(); errorStr = gluErrorString(error); //DrawElements model_matrix = vmath::translate( -1.0f, 0.0f, -5.0f ); glUniformMatrix4fv( render_model_matrix_loc, 1, GL_FALSE, model_matrix ); error = glGetError(); glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, NULL);//根据顶点索引进行绘制。 error = glGetError(); //DrawElementBaseVertex model_matrix = vmath::translate(1.0f, 0.0f, -5.0f ); glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix ); glDrawElementsBaseVertex(GL_TRIANGLES, 3, GL_UNSIGNED_INT, NULL, 1);//也是根据顶点索引绘制,本质上和glDrawElements没区别。 //根据偏移量灵活选择绘制数据,使得动画数据每一帧都可以索引相同的数据集。 error = glGetError(); //DrawArraysInstanced model_matrix = vmath::translate(3.0f, 0.0f, -5.0f); glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix ); glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 1 ); //glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count GLsizei primcount),对glDrawArray() primcount次调用。 //first, count 指定了传递给glDrawArrays的范围。 error = glGetError(); //对了差点忘了那个书本和源代码的失误:glUniformMatrix4fv(render_model_matrix_loc, 4, GL_FALSE, model_matrix ); //第二个参数都写成4了,第二个参数的意义应该是多少个函数对应类型的数据集,这里是4*4的矩阵,无论是模型视图矩阵,还是投影 //变换矩阵都是1才对。不然OpenGL将产生无效操作,的错误信息。 } void shadeBaseUpdate(float dt) { }
到此,全部核心代码已经详细注释,特此记录,2015.3.8深夜~~
评论(0)