录十六

持之以恒

会旋转的三角形

旋转三角形

一、从文件读取着色器源码

为了将着色器对象封装的更易于使用,可以创建一个顶点着色器类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大小的矩阵。这个矩阵如何产生,这里不是重点,暂不做过多说明。

三、运行结果

无标题.png

源码下载: RotateTriangle

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Copyright © 1999-2019, lu16.com, All Rights Reserved