OpenGL进阶 | 绘制一个三角形

一、准备绘图数据

  • VBO(Vertex Buffer Object)

        在opengl中,所有的数据都要放在显存中,通过VBO(Vertex Buffer Object)可将CPU数据传到GPU。

        VBO(Vertex Buffer Object)是OpenGL中的一种机制,用于将顶点数据存储在显存中,以便GPU可以快速访问和处理。VBO是一种缓冲对象,类似于CPU中的数组或列表,可以用来存储顶点的位置、颜色、纹理坐标等属性。使用VBO可以避免每次渲染时都需要从CPU到GPU之间复制顶点数据的过程,从而提高了渲染的效率。

GLuint vbo;
void Init()
{
    float data[] = {
        -0.2f,-0.2f,-0.6f,1.0f,
        0.2f,-0.2f,-0.6f,1.0f,
        -0.2f,0.2f,-0.6f,1.0f
    };
    glGenBuffers(1, &vbo);                                                   //需要1个VBO,把vbo写入到显卡进去,供后续操作
    glBindBuffer(GL_ARRAY_BUFFER, vbo);                                      //把vbo设置到卡槽上
    //glBufferData(GL_ARRAY_BUFFER,sizeof(float)*12, nullptr,GL_STATIC_DRAW);//只在GPU上开辟内存不传数据
    glBufferData(GL_ARRAY_BUFFER,sizeof(float)*12,data,GL_STATIC_DRAW);      //将数据从cpu传到Gpu,此后data数据可删除。
    glBindBuffer(GL_ARRAY_BUFFER, 0);                                        //卡槽重新绑定,防止误操作
}

二、编写Shader

1.顶点着色器

        vertex shader,test.vs文件如下:

attribute vec4 position;
uniform mat4 ModelMatrix;
uniform mat4 ViewMatrix;
uniform mat4 ProjectionMatrix;
void main()
{
	gl_Position=ProjectionMatrix*ViewMatrix*ModelMatrix*position;
}
  •  uniform和attribute

在OpenGL中,uniform和attribute是用于在着色器程序中传递数据的两种不同类型。

  1. Uniform变量: Uniform变量是一种全局变量,用于在不同的着色器阶段之间传递数据。它们在着色器程序中的任何地方都可以访问,不受顶点或片段的限制。Uniform变量通常用于传递全局数据,例如变换矩阵、光照参数、纹理等。在程序中,你可以使用glGetUniformLocation函数获取Uniform变量的位置(或插槽),然后使用glUniform函数将值传递给Uniform变量。

  2. Attribute变量: Attribute变量是一种顶点属性,用于在顶点着色器中接收每个顶点的输入数据。它们通常用于表示顶点的位置、颜色、法线等属性。Attribute变量只能在顶点着色器中使用,并且每个顶点都会有对应的属性值。在程序中,你可以使用glGetAttribLocation函数获取Attribute变量的位置(或插槽),然后使用glVertexAttribPointer函数设置属性数据的格式和位置。

        关于插槽(location)的分配,Uniform变量和Attribute变量都是从0开始分配的。可以使用glGetUniformLocation和glGetAttribLocation函数获取它们在着色器程序中的位置(或插槽)值。对于Uniform变量,可以使用glUniform函数将值传递给指定位置的Uniform变量。对于Attribute变量,可以使用glVertexAttribPointer函数将属性数据绑定到指定位置的Attribute变量。

        需要注意的是,Uniform变量和Attribute变量在不同的着色器阶段之间传递数据的方式和使用方法是不同的。Uniform变量用于在整个着色器程序中传递全局数据,而Attribute变量用于在顶点着色器中接收每个顶点的属性数据。

2.片段着色器

        fragment shader,test.fs文件如下:

#ifdef GL_ES
precision mediump float;
#endif
void main()
{
	gl_FragColor=vec4(1.0,1.0,1.0,1.0);
}

三、编译Shader

1.相关函数

  • glCreateShader

GLuint glCreateShader(GLenum shaderType);

        shaderType指示要创建的着色器的类型,支持两种类型的着色器:

  1.  GL_VERTEX_SHADER类型的着色器是一个用于在可编程顶点处理器上运行的着色器。
  2.  GL_FRAGMENT_SHADER类型的着色器是一个着色器,旨在在可编程片段处理器上运行。

        glCreateShader创建一个空的着色器对象,并返回一个可以引用的非零值(Shader ID)。着色器对象用于维护定义着色器的源代码字符串。

  • glShaderSource

        glShaderSource是OpenGL中用于设置着色器源代码的函数。它的作用是将字符串形式的着色器源代码加载到指定的着色器对象中,以便后续编译和链接。具体来说,glShaderSource函数有四个参数:

void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);
  1. shader:要设置源代码的着色器对象的ID。
  2. count:要设置的源代码字符串数量。
  3. string:源代码字符串数组的指针。
  4. length:源代码字符串数组中每个字符串的长度,如果为NULL,则默认使用字符串的长度。
  • glCompileShader

        glCompileShader是OpenGL中用于编译着色器对象的函数。它的作用是将glShaderSource函数加载的着色器源代码编译成可执行的着色器程序,供OpenGL渲染时使用。

具体来说,glCompileShader函数有一个参数:

void glCompileShader(GLuint shader);
  1. shader:要编译的着色器对象的ID。
  • glGetShaderiv

            glGetShaderiv是OpenGL中获取着色器对象参数的函数之一,它的作用是获取指定着色器对象的特定参数信息,例如编译状态、参数个数、参数类型等等。具体来说,glGetShaderiv函数有三个参数:

void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);
  1. shader:要查询的着色器对象的ID。

  2. pname:要查询的参数名称,可选值包括:

    • GL_SHADER_TYPE:着色器类型,取值为GL_VERTEX_SHADER或GL_FRAGMENT_SHADER。
    • GL_DELETE_STATUS:着色器是否已被标记为删除。
    • GL_COMPILE_STATUS:着色器编译状态,取值为GL_TRUE或GL_FALSE。
    • GL_INFO_LOG_LENGTH:着色器信息日志的长度,以字符数为单位。
    • GL_SHADER_SOURCE_LENGTH:着色器源代码的长度,以字符数为单位。
    • GL_NUM_SHADER_BINARY_FORMATS:支持的着色器二进制格式数量。
    • GL_SHADER_BINARY_FORMATS:支持的着色器二进制格式列表。
    • 其他参数,例如GL_SHADER_IDENTITY_MATRIX等等。
  3. params:指向存储返回参数值的变量的指针。

  • glDeleteShader

        glDeleteShader是OpenGL中删除着色器对象的函数之一。它的作用是删除已经创建的着色器对象,释放显存中的资源。具体来说,glDeleteShader函数有一个参数:

void glDeleteShader(GLuint shader);
  1. shader:要删除的着色器对象的ID。

        需要注意的是,如果着色器对象已经被附加到程序对象中,那么在程序对象链接之前无法删除着色器对象。因此,通常建议在使用完着色器对象后立即删除它,以免占用过多的显存资源。

2.举个例子

GLuint CompileShader(GLenum shaderType, const char* shaderCode)
{
    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &shaderCode, nullptr);
    glCompileShader(shader);
    GLint compileResult = GL_TRUE;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
    if (compileResult == GL_FALSE) {
        char szLog[1024] = { 0 };
        GLsizei logLen = 0;
        glGetShaderInfoLog(shader, 1024, &logLen, szLog);
        printf("Compile Shader fail error log : %s \nshader code :\n%s\n", szLog, shaderCode);
        glDeleteShader(shader);
        shader = 0;
    }
    return shader;
}

四、链接成程序

1.相关函数

  • glCreateProgram

        glCreateProgram是OpenGL中创建着色器程序对象的函数之一。它的作用是创建一个着色器程序对象并返回其ID,用于管理多个着色器对象的链接过程。具体来说,glCreateProgram函数没有参数,只返回一个GLuint类型的着色器程序对象ID:

GLuint glCreateProgram(void);
  • glAttachShader

        glAttachShader是OpenGL中将着色器对象附加到程序对象的函数之一。它的作用是将一个或多个着色器对象附加到一个程序对象上,为后续的链接操作做准备。具体来说,glAttachShader函数有两个参数:

void glAttachShader(GLuint program, GLuint shader);
  1. program:要附加到的程序对象的ID。
  2. shader:要附加的着色器对象的ID。
  • glLinkProgram

        glLinkProgram是OpenGL中链接着色器程序的函数之一,用于将一个程序对象中所有附加的着色器对象链接为一个可执行程序。在链接过程中,OpenGL会将不同着色器之间的变量名和类型进行匹配和连接,最终生成一个可供GPU执行的着色器程序。具体来说,glLinkProgram函数有一个参数:

void glLinkProgram(GLuint program);
  1. program:要链接的程序对象的ID。

        需要注意的是,glLinkProgram函数链接完成后会将链接日志存储在程序对象中,我们可以通过调用glGetProgramiv函数查询链接状态,或者通过调用glGetProgramInfoLog函数获取链接日志。另外,如果链接失败,程序对象的状态将会被标记为链接失败,并且该程序对象无法使用。

  • glDetachShader

        glDetachShader是OpenGL中将着色器对象从程序对象中分离的函数之一。它的作用是将一个着色器对象从一个程序对象中分离,使该着色器对象可以在其他程序对象中使用。具体来说,glDetachShader函数有两个参数:

void glDetachShader(GLuint program, GLuint shader);
  1. program:要分离着色器对象的程序对象的ID。
  2. shader:要分离的着色器对象的ID。
  • glGetProgramiv

        glGetProgramiv是OpenGL中获取着色器程序对象参数的函数之一。它的作用是获取指定着色器程序对象的特定参数信息,例如链接状态、参数个数、参数类型等等。具体来说,glGetProgramiv 函数有三个参数:

void glGetProgramiv(GLuint program, GLenum pname, GLint *params);
  1. program:要查询的着色器程序对象的ID。

  2. pname:要查询的参数名称,可选值包括:

    1. GL_DELETE_STATUS:程序是否已被标记为删除。
    2. GL_LINK_STATUS:程序链接状态,取值为GL_TRUE或GL_FALSE。
    3. GL_VALIDATE_STATUS:程序验证状态,取值为GL_TRUE或GL_FALSE。
    4. GL_INFO_LOG_LENGTH:链接或验证日志的长度,以字符数为单位。
    5. GL_ATTACHED_SHADERS:附加的着色器对象数量。
    6. GL_ACTIVE_ATTRIBUTES:激活的顶点属性数量。
    7. GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:激活的顶点属性名称的最大长度。
    8. GL_ACTIVE_UNIFORMS:激活的Uniform变量数量。
    9. GL_ACTIVE_UNIFORM_MAX_LENGTH:激活的Uniform变量名称的最大长度。
    10. 其他参数,例如GL_PROGRAM_BINARY_RETRIEVABLE_HINT等等。
  3. params:指向存储返回参数值的变量的指针。

  • glDeleteProgram

        glDeleteProgram是OpenGL中删除着色器程序对象的函数之一。它的作用是删除已经创建的着色器程序对象,释放显存中的资源。具体来说,glDeleteProgram函数有一个参数:

void glDeleteProgram(GLuint program);
  1. program:要删除的着色器程序对象的ID。

2.举个例子

GLuint CreateProgram(GLuint vsShader, GLuint fsShader) {
    GLuint program = glCreateProgram();
    glAttachShader(program, vsShader);
    glAttachShader(program, fsShader);
    glLinkProgram(program);
    glDetachShader(program, vsShader);
    glDetachShader(program, fsShader);
    GLint nResult;
    glGetProgramiv(program, GL_LINK_STATUS, &nResult);
    if (nResult == GL_FALSE) {
        char log[1024] = { 0 };
        GLsizei writed = 0;
        glGetProgramInfoLog(program, 1024, &writed, log);
        printf("create gpu program fail,link error : %s\n", log);
        glDeleteProgram(program);
        program = 0;
    }
    return program;
}

五、读取shader源码到GPU程序

GLuint program;
void Init()
{
    //...
    //准备绘图数据
    //...
    
    int fileSize = 0;
    unsigned char * shaderCode = LoadFileContent("Res/test.vs", fileSize);
    GLuint vsShader = CompileShader(GL_VERTEX_SHADER, (char*)shaderCode);
    delete shaderCode;
    shaderCode = LoadFileContent("Res/test.fs", fileSize);
    GLuint fsShader = CompileShader(GL_FRAGMENT_SHADER, (char*)shaderCode);
    delete shaderCode;
    program = CreateProgram(vsShader, fsShader);
    glDeleteShader(vsShader);
    glDeleteShader(fsShader);
}

六、读取shader中的变量

1.相关函数

  • glGetUniformLocation

        glGetUniformLocation是OpenGL中用于获取Uniform变量的位置(或插槽)的函数之一。它用于查询指定着色器程序对象中的Uniform变量的位置,以便后续对其进行赋值。具体来说,glGetUniformLocation函数有两个参数:

GLint glGetUniformLocation(GLuint program, const GLchar *name);
  1. program:要查询Uniform变量的着色器程序对象的ID。
  2. name:Uniform变量的名称。(Uniform变量的名称必须与着色器程序中定义的Uniform变量的名称完全匹配。)
  • glGetAttribLocation

        glGetAttribLocation是OpenGL中用于获取Attribute变量的位置(或插槽)的函数之一。它用于查询指定着色器程序对象中的Attribute变量的位置,以便后续绑定顶点数据。具体来说,glGetAttribLocation函数有两个参数:

GLint glGetAttribLocation(GLuint program, const GLchar *name);
  1. program:要查询Attribute变量的着色器程序对象的ID。
  2. name:Attribute变量的名称。(Attribute变量的名称必须与着色器程序中定义的Attribute变量的名称完全匹配。)

2.举个例子

GLint positionLocation,modelMatrixLocation,viewMatrixLocation,projectionMatrixLocation;
void Init()
{
    //...
    //准备绘图数据
    //...

    //...
    //读取shader源码到GPU程序
    //...

    positionLocation = glGetAttribLocation(program, "position");
    modelMatrixLocation = glGetUniformLocation(program, "ModelMatrix");
    viewMatrixLocation = glGetUniformLocation(program, "ViewMatrix");
    projectionMatrixLocation = glGetUniformLocation(program, "ProjectionMatrix");
}

七、设置MVP矩阵

1.MVP矩阵

        在OpenGL中设置MVP(Model-View-Projection)矩阵是用于进行物体的变换和投影的重要步骤之一。通常,Model矩阵表示物体的模型变换,View矩阵表示摄像机的观察变换,而Projection矩阵表示投影变换。

        对于Model矩阵和View矩阵,默认是单位矩阵,所以不需要显式设置它们,可以通过使用glm库中的glm::mat4的默认构造函数创建单位矩阵。

        对于Projection矩阵,可以使用glm库中的glm::perspective函数来设置。

glm::mat4 glm::perspective(float fov, float aspect, float near, float far);
  1. fov:视野角度(Field of View),以弧度为单位。
  2. aspect:视口的宽高比(宽度除以高度)。
  3. near:近平面距离摄像机的距离。
  4. far:远平面距离摄像机的距离。

        该函数将返回一个透视投影矩阵,所有在近平面和远平面内且处于平截头体内的顶点都会被渲染。可以将其应用于Projection矩阵。

2.设置MVP矩阵

glm::mat4 modelMatrix, viewMatrix, projectionMatrix;
void SetViewPortSize(float width, float height)
{
    projectionMatrix = glm::perspective(glm::radians(45.0f), width / height, 0.1f, 1000.0f);
}

八、设置Uniform变量

1.glUniformMatrix4fv

        glUniformMatrix4fv是OpenGL中用于将4x4矩阵数据传递给Uniform变量的函数之一。它用于将一个或多个4x4矩阵值传递给在着色器程序中定义的Matrix4类型的Uniform变量。

void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
  1. location:要传递数据的Uniform变量的位置(或插槽)。
  2. count:要传递的矩阵数量。如果你只传递一个矩阵,则count为1。
  3. transpose:指定是否应该在传递矩阵之前对其进行转置。通常情况下,你可以将其设置为GL_FALSE。
  4. value:指向包含要传递数据的GLfloat数组的指针。矩阵数据按列主序存储在数组中。

2.代码示例

void Draw()
{
    glUseProgram(program);//启用着色器程序
    glUniformMatrix4fv(modelMatrixLocation, 1, GL_FALSE, glm::value_ptr(modelMatrix));   //将模型矩阵传递给着色器程序中的模型矩阵Uniform变量modelMatrixLocation
    glUniformMatrix4fv(viewMatrixLocation, 1, GL_FALSE, glm::value_ptr(viewMatrix));    //将视图矩阵传递给着色器程序中的视图矩阵Uniform变量viewMatrixLocation
    glUniformMatrix4fv(projectionMatrixLocation, 1, GL_FALSE, glm::value_ptr(projectionMatrix));  //将投影矩阵传递给着色器程序中的投影矩阵Uniform变量projectionMatrixLocation
    glUseProgram(0);      //停用着色器程序
}

九、读取vbo数据,绘制三角形

1.相关函数

  • glVertexAttribPointer

        glVertexAttribPointer函数用于指定顶点属性的指针,并设置相关参数。

void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer);

参数解释如下:

  1. index:顶点属性的索引,对应于着色器程序中的顶点属性变量。
  2. size:顶点属性的分量数量,例如3表示顶点属性由三个分量组成(如位置属性为x、y、z)。
  3. type:顶点属性的数据类型,例如GL_FLOAT表示浮点型数据。
  4. normalized:指定是否对非浮点型数据进行归一化处理,通常设置为GL_FALSE。
  5. stride:顶点属性之间的字节偏移量,用于在顶点数据中定位不同属性的数据。
  6. pointer:顶点属性数据的指针,指向顶点数据缓冲区中的起始位置。
  • glEnableVertexAttribArray

        glEnableVertexAttribArray函数用于启用指定索引的顶点属性数组。

void glEnableVertexAttribArray(GLuint index);

参数解释如下:

  1. index:顶点属性的索引,对应于着色器程序中的顶点属性变量。
  • glDrawArrays

        glDrawArrays函数用于执行基于顶点数组的图元绘制操作。

void glDrawArrays(GLenum mode, GLint first, GLsizei count);

参数解释如下:

  1. mode:指定绘制的图元类型,例如GL_POINTS表示绘制点,GL_TRIANGLES表示绘制三角形等。
  2. first:指定顶点数组中的起始索引,表示从哪个顶点开始绘制。
  3. count:指定要绘制的顶点数量。

2.代码示例

void Draw()
{
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //...
    //设置Uniform变量
    //...

    glBindBuffer(GL_ARRAY_BUFFER, vbo);  //将顶点缓冲对象绑定到OpenGL的顶点缓冲区(GL_ARRAY_BUFFER)
    glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, sizeof(float)*4, 0);   //定义位置属性
    glEnableVertexAttribArray(positionLocation);  //启用位置属性索引为positionLocation的顶点属性数组
    glDrawArrays(GL_TRIANGLES, 0, 3);    //执行绘制操作。该函数指定了绘制的模式、起始顶点索引和顶点数量。
    glBindBuffer(GL_ARRAY_BUFFER, 0);    //解绑顶点缓冲对象
}

十、补充说明

        shader通常称为着色器,作用是把CPU上的点渲染出来,shader在GPU上是并行执行的,比如三个顶点的数据,会在三个core上并行处理。

        比如我们绘制一个三角形,需要三个顶点数据。顶点着色器将会被执行三次,每次使用不同的顶点数据作为输入。(不需要为每个顶点创建一个单独的vertex shader文件,我们可以使用相同的vertex shader文件来处理所有三个顶点,只需要为每个顶点提供不同的位置属性作为输入),并使用统一变量mvpMatrix进行模型-视图-投影变换。(即:position不一样,m、v、p矩阵相同

  • 3*4个数据的顶点矩阵,三个顶点:
    float data[] = {
        -0.2f,-0.2f,-0.6f,1.0f,
        0.2f,-0.2f,-0.6f,1.0f,
        0.0f,0.2f,-0.6f,1.0f
    };
  • vertex shader文件: 
attribute vec4 position;
uniform mat4 ModelMatrix;
uniform mat4 ViewMatrix;
uniform mat4 ProjectionMatrix;

你可能感兴趣的:(OpenGL,opengl,C++)