当前位置:网站首页>OpenGL - Draw Triangle

OpenGL - Draw Triangle

2022-06-22 06:32:00 农场主er

Hello Triangle

Basic concept

Primitive
指定坐标和颜色的渲染类型,包括GL_POINTS, GL_TRIANGLES and GL_LINE_STRIP

fragments
构成单个像素所需的数据

OpenGL发生在三维空间,但是我们的屏幕是二维的,坐标系的转换依赖graphics pipeline,各个阶段如下所示:
pipeline
每一步的输出都将作为下一步的输入。

  1. Vertex Shader:对输入的顶点数据进行变换
  2. Primitive Assembly:将变换过的顶点数据装配成Primitive指定的形状
  3. Geometry Shader:可以产生新的顶点数据构造出其它Primitive
  4. Rasterization Stage:将Primitive映射成屏幕上的像素,生成fragments,并且会丢弃掉屏幕外的fragments
  5. Fragment Shader:产生像素最终绘制的颜色,包括光照、阴影等因素
  6. Blending:进行深度测试,并且会考虑透明度

Vertex Buffer Objects(VBO)

输入的顶点坐标要求用Normalized Device Coordinates (NDC),即限制范围为[-1.0, 1.0]:
NDC
viewport transform会利用glViewport提供的数据将NDC坐标转化为screen-space coordinates,最终作为fragment shader的输入。

通过VBO管理GPU内存上的顶点数据,优势在于可以一次性将数据送到显卡,毕竟从CPU传数据是比较耗时的。

float vertices[] = {
    
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
}; 

unsigned int VBO; // buffer id
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定buffer到特定类型,如果buffer为0将重置当前buffer的边界为空状态?
// 绑定数据到buffer区域,usage常用的有三种
// GL_STREAM_DRAW: the data is set only once and used by the GPU at most a few times.
// GL_STATIC_DRAW: the data is set only once and used many times.
// GL_DYNAMIC_DRAW: the data is changed a lot and used many times.
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 
glBindBuffer(GL_ARRAY_BUFFER, 0);

Vertex shader

顶点着色器语法如下:

#version 330 core
// 下面会用到location,被in标记的为输入量,称为vertex attributes
// 至少有16个可用的输入,通过int nrAttributes; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes); 可以获取可用数量
layout (location = 0) in vec3 aPos;

void main()
{
    
    // 预定义的输出坐标
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

编译顶点着色器的方式如下:

const char *vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";
unsigned int vertexShader; // reference id
vertexShader = glCreateShader(GL_VERTEX_SHADER);
// 指定编译源码,第二个参数代表str的数量
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
// 可以通过glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);判断编译结果
glCompileShader(vertexShader);

Fragment shader

片段着色器定义了输出像素的颜色:

#version 330 core
out vec4 FragColor;

void main()
{
    
    // 如果输出颜色失败,最终渲染出来会是黑色或者白色
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

编译的方式与顶点着色器一致:

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

如果想让顶点着色器的输出作为片段着色器的一个输入,变量的类型和名称应该一致。

Shader program

需要将上述编译过的着色器linkShader program对象上:

unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 使用shader program
glUseProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader); 

Linking Vertex Attributes

我们需要指定解释顶点数据的方式,比如步长:

// 1、layout (location = 0) 定义的位置,即vertex attributes,对应顶点着色器的输入参数
// 2、vertex attribute,即 vec3
// 3、数据类型
// 4、是否需要将坐标 NDC 化
// 5、两个顶点数据间的步长
// 6、从 buffer 中开始读取的位置
// 读取的 buffer 就是之前 VBO 绑定的
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
// 使顶点着色器对应的输入可用,与上一步的参数1相对应
glEnableVertexAttribArray(0);  

Vertex Array Object(VAO)

VAO存储了VBOvertex attributes的对应关系,优势在于只需绑定VAO就可以绘制顶点数据,并且方便切换。
在这里插入图片描述
工作流如下:

// ..:: Initialization code (done once (unless your object frequently changes)) :: ..
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// 1. bind Vertex Array Object
// 调用之后,随后的VBO、VEO、 第3步的内容都会绑定到 VAO
glBindVertexArray(VAO);
// 2. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. then set our vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); 
[...]

// ..:: Drawing code (in render loop) :: ..
// 4. draw the object
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
// 1、想要绘制的类型
// 2、开始的顶点数据下标
// 3、绘制的顶点数量
glDrawArrays(GL_TRIANGLES, 0, 3);

Element Buffer Objects(EBO)

如果我们想绘制一个矩形,也就是两个三角形,六个顶点,但是会有两个顶点是重复的。那么可不可以预先定义好不重复的所有顶点,然后通过一个索引数组去指定我们想要绘制的顶点呢?

如此就引入了EBO的概念:

float vertices[] = {
    
     0.5f,  0.5f, 0.0f,  // top right
     0.5f, -0.5f, 0.0f,  // bottom right
    -0.5f, -0.5f, 0.0f,  // bottom left
    -0.5f,  0.5f, 0.0f   // top left 
};
unsigned int indices[] = {
      // note that we start from 0!
    0, 1, 3,   // first triangle
    1, 2, 3    // second triangle
}; 

unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// 可见EBO只是索引数组的buffer
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 

// ..:: Initialization code :: ..
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a vertex buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. copy our index array in a element buffer for OpenGL to use
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. then set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);  

[...]
  
// ..:: Drawing code (in render loop) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
// 2、指定绘制的顶点数目
// 3、索引元素数据类型
// 4、EBO 的偏移
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);

现在VAO存储的信息又增加了EBO
在这里插入图片描述

Shader

除了通过上述的方式像着色器中传输数据,还可以用uniform关键词从CPU传输数据到GPU,这种方法是全局性的,在shader program的任何阶段都可以获取到,一旦设置会一直有效,知道重置或更新。如果声明但未赋值,会在编译阶段移除。

#version 330 core
out vec4 FragColor;
  
uniform vec4 ourColor; // we set this variable in the OpenGL code.

void main()
{
    
    FragColor = ourColor;
} 

可以通过下面的方法进行赋值:

float timeValue = glfwGetTime(); // 返回GLFW初始化以来的秒数
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram); // 赋值前先 use
// glUniform 有以下几种后缀:
// f: the function expects a float as its value.
// i: the function expects an int as its value.
// ui: the function expects an unsigned int as its value.
// 3f: the function expects 3 floats as its value.
// fv: the function expects a float vector/array as its value.
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

一般设置每一帧都需要变化的值可以这样做。

绘制顶点颜色不同的三角形

封装 Shader Class

以后可以单独把shader写到一个文件里,使用的时候也很方便:

// 本地测试相对路径不太好使,可以先用绝对路径代替
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
[...]
while(...)
{
    
    ourShader.use();
    ourShader.setFloat("someUniform", 1.0f);
    DrawStuff();
}
原网站

版权声明
本文为[农场主er]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_42403042/article/details/125299991