OpenGL学习日记-2015.3.5——Hello glsl(着色器)

    过年前忍不住买了本新版的OpenGL编程指南,主要的目的还是为了系统的学习着色器编程,另外就是接触新版的OpenGL技术和思想。看了几页,就过年了QAQ.回来后也是各种不在状态,不想上班,不想工作,不想写代码。。。昨天终于强迫自己继续看书,也找回了些状态。

    书本基础知识的全面性和权威性就不用说了,不过这个源代码就。。。。这第一个例子照着代码来抄结果。。。我想应该是原来的代码一个参数错了,折腾了半天,代码分析是详说。主要是分析代码,有什么说什么,并没有全面的说明着色器的基本内容,想着在着色器的基础知识看的差不多或者更熟悉了再做一次总结。
顶点着手器:
#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深夜~~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值