旋转三角形
一、从文件读取着色器源码
为了将着色器对象封装的更易于使用,可以创建一个顶点着色器类MCVertexShader和一个片段着色器类MCFragmentShader,它们分别从MCShader类继承。创建不同类型的着色器对象时,只有调用glCreateShader函数传递的着色器类型参数不一样。因此,可以在MCShader类抽象出一个创建着色器句柄的纯虚函数。
protected: //定义为纯虚函数。由子类实现,根据不同的着色器类型创建着色器句柄。 virtual unsigned int CreateShader() = 0;
定义了MCVertexShader类和MCFragmentShader类之后。在OpenGL窗口中类,修改着色器类对象的声明:
private: MCVertexShader m_vsShader; //顶点shader MCFragmentShader m_psShader; //片段shader
然后,创建和编译着色器对象也简单了许多,不再需要传入着色器类型:
//编译顶点shader if(!m_vsShader.LoadShaderSource(vsShaderStr)) { printf("the vertex shader compiled failed.\n"); } //编译片段shader if(!m_psShader.LoadShaderSource(psShaderStr)) { printf("the fragment shader compiled failed.\n"); }
将着色器源码定义在字符串数组中有诸多不便。为了便于修改着色器源码,可以将着色器源码写在文本文件中,然后以读文件的方式来加载。因此,需要在MCShader类中,增加一个从文件创建着色器的函数:
bool MCShader::LoadShaderFile(const char* fileName) { FILE *pfile = fopen( fileName, "rb"); if (!pfile) { return false; } //获取文件长度 fseek(pfile, 0, SEEK_END); int length = ftell(pfile); fseek(pfile, 0, SEEK_SET); char *shaderSource = new char[length + 1]; //读取文件 size_t nReadByte = fread(shaderSource, sizeof(char), length, pfile); if( nReadByte != length) { delete[] shaderSource; fclose(pfile); return false; } else { fclose(pfile); } // 根据子类的实现,创建不同的着色器对象句柄。 unsigned int shader = CreateShader(); if (shader == 0) { return false; } // 设置shader源码 GL30::glShaderSource(shader, 1, &shaderSource, &length); // 后续其它代码略 … }
二、在着色器中使用统一变量
为了旋转三角形,可以使用OpenGL函数向顶点着色程序传递一个变换矩阵,然后,使用这个矩阵对所有的顶点坐标进行旋转变换。下面附上完整的顶点着色器代码:
//变换矩阵,统一变量 uniform mat4 matModel; //顶点坐标,属性变量 attribute vec4 vPosition; void main() { gl_Position = matModel * vPosition; }
第2行:声明了一个4x4的变换矩阵,前面的uniform为统一变量修饰符,在顶点shader和片段shader中都可以使用,表示我们声明了一个统一变量。统一变量是一个全局变量的只读变量,在整个图元处理的过程中一直保持不变。仅可以通过OpenGL API进行初始化。
第7行:顶点坐标和变换矩阵相乘,进行顶点变换。注意,这里是右乘矩阵,虽然在代码中定义的矩阵为行主序,但是OpenGL内部存储矩阵时,会变成列主序,所以这里是右乘,而不是左乘。
二、向shader传递变换矩阵
为了将变换矩阵传递给顶点着色器,必选先调用glGetUniformLocation函数,获取统一变量matModel对应的索引:
GLint glGetUniformLocation(GLuint program, const GLchar* name)
-
program — 着色程序的句柄
-
name — 统一变量的名字
统一变量的索引,在着色程序创建成功后就生成了,而且不再会发生改变。因此可以在OpenGL窗口初始化函数中,获取统一变量的索引:
//获取matModel统一变量的索引 m_modelLoc = GL30::glGetUniformLocation(m_program.GetHandle(), "matModel");
获得统一变量的索引之后,就可以使用下面的函数向着色程序中的统一变量传递数据了:
void glUniform1f(GLint location, GLfloat x); void glUniform1fv(GLint location, GLsizei count, const GLfloat* value); void glUniform1i(GLint location, GLint x); void glUniform1iv(GLint location, GLsizei count, const GLint* value); void glUniform2f(GLint location, GLfloat x, GLfloat y); void glUniform2fv(GLint location, GLsizei count, const GLfloat* value); void glUniform2i(GLint location, GLint x, GLint y); void glUniform2iv(GLint location, GLsizei count, const GLint* value); void glUniform3f(GLint location, GLfloat x, GLfloat y, GLfloat z); void glUniform3fv(GLint location, GLsizei count, const GLfloat* value); void glUniform3i(GLint location, GLint x, GLint y, GLint z); void glUniform3iv(GLint location, GLsizei count, const GLint* value); void glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w); void glUniform4fv(GLint location, GLsizei count, const GLfloat* value); void glUniform4i(GLint location, GLint x, GLint y, GLint z, GLint w); void glUniform4iv(GLint location, GLsizei count, const GLint* value); void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
-
location — 统一变量的索引
-
count — 数组中向量或者矩阵的数量
-
transpose — 对数据中的矩阵是否进行转置
-
x, y, z, w — 需要更新的统一变量的值
-
value — 数组的指针
在顶点着色中,我们定义了一个mat4类型的统一变量,所以需要使用glUniformMatrix4fv函数,向着色程序的matModel变量传递一个4x4的矩阵:
// 角度转换成弧度,生成旋转矩阵 Matrix4x4f matModel = Matrix4x4f::MakeRotationZ(Mathf::Radians((float)nAngle)); // 设置旋转矩阵 GL30::glUniformMatrix4fv(m_modelLoc, //统一变量的索引 1, //只有一个矩阵 GL_FALSE, //矩阵无需转置 &matModel.mat[0]); //矩阵数组的指针
上面的代码,根据角度生成了一个绕Z轴旋转的,4x4大小的矩阵。这个矩阵如何产生,这里不是重点,暂不做过多说明。
三、运行结果
源码下载: RotateTriangle