您的位置:首页 > 运维架构

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

2015-03-08 04:44 274 查看
过年前忍不住买了本新版的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深夜~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: