OpenGL学习脚印 绘制一个三角形_opengl学习脚印,绘制一个三角形(2)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Step3: 通知OpenGL如何解释这个顶点属性数组
将数据传送到GPU后,我们还需要告知OpenGL如何解释这个数据,也就是告知其数据格式,因为从底层来看数据一个字节块而已。要通知OpenGL如何解释数据,要使用函数glVertexAttribPointer.

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

  1. 参数index 表示顶点属性的索引 这个索引即是在顶点着色器中的属性索引,索引从0开始记起。
  2. 参数size 每个属性数据由几个分量组成。例如上面顶点每个属性为3个float组成的,size即为3。分量的个数必须为1,2,3,4这四个值之一。
  3. 参数type表示属性分量的数据类型,例如上面的顶点数据为float则填写GL_FLOAT.
  4. 参数normalized 表示是否规格化,当存储整型时,如果设置为GL_TRUE,那么当被以浮点数形式访问时,有符号整型转换到[-1,1],无符号转换到[0,1]。否则直接转换为float型,而不进行规格化。
  5. 参数stride表示连续的两个顶点属性之间的间隔,以字节大小计算。当顶点属性紧密排列(tightly packed)时,可以填0,由OpenGL代替我们计算出该值。
  6. 参数pointer表示当前绑定到 GL_ARRAY_BUFFER缓冲对象的缓冲区中,顶点属性的第一个分量距离数据的起点的偏移量,以字节为单位计算。

上面这个函数是很重要的,刚接触时可能对多个参数感到厌烦,慢慢就会习惯。这里以上述包含顶点位置的属性数组为例,做一个图解(来自:learn opengl):
这里写图片描述
这里我们可以看出,调用上述函数时,属性索引为0(稍后着色器中会与之对应), 属性的分量个数为3,分量的数据类型为GL_FLOAT, normalized设为GL_FALSE, 参数stride为3*sizeof(GL_FLOAT)=12,
pointer的偏移量为0,但是要写为(GLvoid*)0(强制转换),具体如下所示:

glVertexAttribPointer(0, 3, GL\_FLOAT, 
 3 \* sizeof(GL\_FLOAT), (GLvoid\*)0);
glEnableVertexAttribArray(0);

关于glVertexAttribPointer函数中stride和offset函数的详细解释,你还可以参考我的另一篇关于buffer object的文章

这样我们创建了VBO,并将数据传送到GPU,并告知了OpenGL如何解析这些数据。在整个过程中,我们调用了很多函数,如果在以后绘制时好需要继续调用这些函数,那将会多么麻烦,因此这时候VAO就起到了关键作用。VAO能记录VBO的相关信息,在以后绘图时,只需要绑定对应的VAO就能找到这些状态,方便OPenGL使用。因此,在创建VBO这一过程中,我们要使用VAO来记录。方法便是,在所有VBO操作之前,先创建和绑定VAO。

绘制三角形时创建VAO和VBO的最终的代码如下:

    // 指定顶点属性数据 顶点位置
    GLfloat vertices[] = {
        -0.5f, 0.0f, 0.0f,
        0.5f, 0.0f, 0.0f,
        0.0f, 0.5f, 0.0f
    };
    // 创建缓存对象
    GLuint VAOId, VBOId;
    // Step1: 创建并绑定VAO对象
    glGenVertexArrays(1, &VAOId);
    glBindVertexArray(VAOId);
    // Step2: 创建并绑定VBO对象
    glGenBuffers(1, &VBOId);
    glBindBuffer(GL_ARRAY_BUFFER, VBOId);
    // Step3: 分配空间 传送数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // Step4: 指定解析方式 并启用顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    // 解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

在代码的最后,我们暂时解除绑定,能够防止后续操作干扰到了当前VAO和VBO。
现在在程序中使用VAO绘制三角形则只需要调用:

    glBindVertexArray(VAOId); // 使用VAO信息
    glUseProgram(shaderProgramId); // 使用着色器
    glDrawArrays(GL\_TRIANGLES, 0, 3);

这里使用着色器,稍后介绍。glDrawArrays函数使用VBO数据绘制物体。其使用方法为:

API void glDrawArrays( GLenum mode,
GLint first,
GLsizei count);
1.mode 参数表示绘制的基本类型,OpenGL预制了 GL_POINTS, GL_LINE_STRIP等基本类型。一个复杂的图形,都是有这些基本类型构成的。
2.first表示启用的顶点属性数组中第一个数据的索引。
3.count表示绘制需要的顶点数目。

上述调用时我们选择GL_TRIANGLES表示绘制三角形,使用3个顶点。

着色器程序

目前我们主要使用顶点着色器和片元着色器。对于着色器,采用的是GLSL语言(OpenGL Shading Language)编写的程序,类似于C语言程序。
要使用着色器需要经历3个步骤:

  1. 创建和编译shader object
  2. 创建shader program,链接多个shader object到program
  3. 在绘制场景时启用shader program

具体流程如下图所示:

Created with Raphaël 2.1.0

着色器源文件(shader file)

读取源码(read source)

编译源码(compile source)

链接着色器对象(link shader objects)

着色器程序对象(program object)

在上面的流程中,一个着色器程序对象可以包含多个着色器对象,例如顶点着色器(vertex shader)、几何着色器(geometry shader,后续介绍)、片元着色器(fragment shader)。我们也可以将着色器源码放在程序代码中,当然这一做法仅作为示例,不值得提倡。

我们这里写一个简单的直通着色器,在顶点着色器中输出传入的顶点位置,在片元着色器中输出指定颜色。实际应用中这两个程序将决定图形最终效果,这里只是做一个简单示例。

顶点着色器代码为:

#version 330 // 指定GLSL版本3.3

layout(location = 0) in vec3 position; // 顶点属性索引

void main()
{
    gl\_Position = vec4(position, 1.0); // 输出顶点
}

其中gl_Position为内置变量,表示顶点输出位置,以gl_前缀开头的一般都表示内置变量。position声明为vec3类型, vec3表示3个float类型的向量。gl_Positon为vec4类型,其中第四个分量为1.0,关于这个分量后面会做介绍。

片元着色器代码为:

#version 330

out vec4 color; // 输出片元颜色

void main()
{
    color = vec4(0.8, 0.8, 0.0, 1.0);
}

通过color指定最终颜色为黄色,vec4类型表示颜色为RGB再加上alpha值构成最终的输出颜色。关于alpha值后面会介绍。

首先创建顶点和片元着色器对象,要注意其中的错误处理。其中顶点着色器代码如下:

   const GLchar* vertexShaderSource = "#version 330\n"
        "layout(location = 0) in vec3 position;\n"
        "void main()\n"
        "{\n gl\_Position = vec4(position, 1.0);\n}";
    // Step2 创建Shader object
    // 顶点着色器
    GLuint vertexShaderId = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShaderId, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShaderId);
    GLint compileStatus = 0;
    glGetShaderiv(vertexShaderId, GL_COMPILE_STATUS, &compileStatus); // 检查编译状态
    if (compileStatus == GL_FALSE) // 获取错误报告
    {
        GLint maxLength = 0;
        glGetShaderiv(vertexShaderId, GL_INFO_LOG_LENGTH, &maxLength);
        std::vector<GLchar> errLog(maxLength);
        glGetShaderInfoLog(vertexShaderId, maxLength, &maxLength, &errLog[0]);
        std::cout << "Error::shader vertex shader compile failed," << &errLog[0] << std::endl;
    }

片元着色器也有类似处理,然后创建并连接,形成shader program对象,代码如下:

    GLuint shaderProgramId = glCreateProgram();// 创建program
    glAttachShader(shaderProgramId, vertexShaderId);
    glAttachShader(shaderProgramId, fragShaderId);
    glLinkProgram(shaderProgramId);
    GLint linkStatus;
    glGetProgramiv(shaderProgramId, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE)
    {
        GLint maxLength = 0;
        glGetProgramiv(shaderProgramId, GL_INFO_LOG_LENGTH, &maxLength);
        std::vector<GLchar> errLog(maxLength);
        glGetProgramInfoLog(shaderProgramId, maxLength, &maxLength, &errLog[0]);
        std::cout << "Error::shader link failed," << &errLog[0] << std::endl;
    }

注意: 在shader object链接到program后,即可断开链接,如果不需要再链接到其他program,比较好的做法就是释放资源:

        // 链接完成后detach
    glDetachShader(shaderProgramId, vertexShaderId);
    glDetachShader(shaderProgramId, fragShaderId);
    // 不需要连接到其他程序时 释放空间
    glDeleteShader(vertexShaderId);
    glDeleteShader(fragShaderId);

绘制三角形

通过上面使用VAO和VBO完成了数据存储和解析部分工作,通过着色器完成了图形的渲染,将这两个部分组成一起,我们便可以绘制我们的三角形了。运行结果如下图所示:

这里写图片描述
程序的完整代码可以从github下载

注意如果着色器程序失败,我们得到的图形如下图所示:

这里写图片描述

失败时请检查着色器代码部分。

重构代码

将上述着色器代码,分装成一个shader类,这个类从文件读取着色器源码,并创建着色器程序,是代码更简洁。使用shader类的项目结构为:
这里写图片描述
使用着色器类创建着色器代码简化为:

Shader shader("triangle.vertex", "triangle.frag");

着色器程序只读取程序源码,与文件名称和类型无关。
使用封装的类实现的三角形绘制版本和shader类的代码可以从github下载

添加顶点的颜色属性

上面绘制的三角形,使用的颜色是在片元着色器中指定的,我们可以通过vertex attribute指定顶点颜色属性,同顶点位置属性一样传送给着色器。修改顶点属性数组数据为:

GLfloat vertices[] = {
        // 顶点坐标 顶点颜色
        -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
        0.5f, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f,
        0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f
    };

我们需要重新指定OpenGL解析数据的方式,需要更新着色器。
Step1: 首先通过glVertexAttribPointer重新解释数据,代码变为:

// Step4: 指定解析方式 并启用顶点属性
// 顶点位置属性
glVertexAttribPointer(0, 3, GL\_FLOAT, GL\_FALSE, 
 6 \* sizeof(GL\_FLOAT), (GLvoid\*)0);
glEnableVertexAttribArray(0);
// 顶点颜色属性
glVertexAttribPointer(1, 3, GL\_FLOAT, GL\_FALSE,
 6 \* sizeof(GL\_FLOAT), (GLvoid\*)(3 * sizeof(GL\_FLOAT)));
glEnableVertexAttribArray(1);

这里注意glVertexAttribPointer的参数设定,stride和pointer参数解释如下图所示(来自www.learnopengl.com):
这里写图片描述
即顶点位置和颜色的stride即都为 6 * sizeof(GL_FLOAT) = 24, 顶点位置数据首地址偏移量为0,而颜色数据首地址偏移量为 3 * sizeof(GL_FLOAT) = 12。

Step2 更新着色器,在顶点着色器中为颜色属性指定索引为1,更新后的顶点着色器为:

#version 330

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;

out vec3 vertColor;

void main()


![img](https://img-blog.csdnimg.cn/img_convert/4d13406bb2ffbd2238fba18d862c29c7.png)
![img](https://img-blog.csdnimg.cn/img_convert/4248683d44aaa509904badc3942f1b78.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

lor;

out vec3 vertColor;

void main()


[外链图片转存中...(img-v55u3Fk2-1715839489300)]
[外链图片转存中...(img-oR4tOGyh-1715839489300)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值