Eol
Thanks for reading :)
Eol's Blog

Computer Graphics (Autumn 2019) Assignment 1

Computer Graphics (Autumn 2019)  Assignment 1

1 What is a graphics pipeline?

答:

      Graphics pipeline图形渲染流水线

      一些资料将其翻译为“图形渲染管线”,个人不是太喜欢这样的翻译——把它理解为“流水线”之后便豁然开朗。Graphics pipeline 将图形渲染任务划分为若干个小任务,后一个任务的实施往往依赖于前一个任务的完成,各个任务彼此连接形成了一条渲染流水线。

      另外通过查阅一些资料,发现 Graphics pipeline 并非有严格的划分,不同的资料会有不同的划分方法。一般来说将 Graphics pipeline 划分为以下四个阶段:应用程序阶段(Application)、几何处理阶段(Geometry Processing)、光栅化(Rasterization)和像素处理阶段(Pixel Processing)应用程序阶段通常是在CPU端进行处理,包括碰撞检测、动画物理模拟以及视椎体剔除等任务,这个阶段会将数据送到渲染管线中;几何处理阶段主要执行顶点着色器、投影变换、裁剪和屏幕映射的功能;光栅化阶段将图元离散化片段,变成一个个含有信息的点;像素处理阶段包括像素着色和混合的功能。对比各类资料可以发现,虽然管线的划分粒度不一样,但是每个阶段的具体功能其实是差不多的,原理也是一样的,并没有太大的差异。

      下图则是另一种划分方式,蓝色部分代表的是可以自定义的着色器的部分。

https://www.eolstudy.com/wp-content/uploads/2019/09/v2-1e286dd517c717e3f1c48792275f7e87_hd.jpg
(蓝色部分代表的是可以自定义的着色器的部分)

2 What are vertex, primitive and fragment?

答:

      顶点(Vertex)是坐标的数据的集合,它可以包含任何有用的的数据,包括位置信息、颜色信息等。

      图元(Primitive) 则告诉 OpenGL 这些顶点数据所表示的渲染类型——比如一个点、一个三角形、或是仅仅是一条线。

      片段(Fragment)在各类资料中都与片段着色器(Fragment Shader)一起出现。它是供片段着色器使用的像素阵列,个人将其理解为一个没有渲染好的“帧”。它是这样生成的:几何着色器(Geometry Shader)-> 光栅化阶段(Rasterization Stage)–得到片段–> 片段着色器(Fragment Shader)->……

3 What are geometry and topology?

答:

      几何(geometry)是指各个顶点所组成的几何形体,往往在几何处理阶段(Geometry Processor)起作用。在这个阶段,图元的完整数据(比如:所有的顶点数据)和相邻顶点的数据全部都提供给着色器(shader),使其必须眷顾更多的信息,包括除了顶点本身的其他的一些额外的更全面完整的信息。几何处理器还可以将输出的图形拓扑结构转换成在 draw call 中选择的另一种结构。

      由上文可知,拓扑(topology)为各个顶点的组成方式——它们是以什么顺序连接在一起的、它们组成了一个怎样的图形等等。

4 What happens when the viewport is enlarged? Why?

答:

      图像会等比例在变大的窗口里变大。视野不会变化。

      因为改变 viewport 的大小,实际上只改变了视口中物体向屏幕像素之间的映射关系,并没有改变视口观察范围的大小。

5 What happens when the clipping window is enlarged? Why?

答:

      视野里可被观察的物体会变多,视野范围变大,但屏幕窗口中的像素不会变化。

      因为裁剪范围变大,可以理解为有更多范围内的物体能被虚拟摄像机拍摄下来。

https://www.eolstudy.com/wp-content/uploads/2019/09/perspective_frustum.png
(clipping 示意图)

6 How to represent a Solid object in computer graphics?

答:

      将物体(object)的各个数据发送给 GPU,并告诉 GPU 如何解读这些数据,通过这些数据和解读方式就可以描绘(represent)这个物体。

      在 OpenGL 中,使用 VBO、VAO、EBO 来实现。

      顶点缓冲对象(Vertex Buffer Objects, VBO)在 GPU 内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象,可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从 CPU 把数据发送到显卡相对较慢,所以只要可能要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

      顶点数组对象(Vertex Array Object, VAO)记录着对应 VBO 的属性信息,比如如何解读 VBO 中的数据。它可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个 VAO 中。这样的好处就是,当配置顶点属性指针时,只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的 VAO 就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的 VAO 就行了。刚刚设置的所有状态都将存储在 VAO 中。

      索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO),是一个缓冲,它专门储存索引,OpenGL 调用这些顶点的索引来决定该绘制哪个顶点。为此可以避免在 VBO 中存入重复的顶点。

      在后面绘制正方体的作业中,我使用了 EBO 来优化存储空间。      

      VBO、VAO、EBO 三者关系如下:

https://www.eolstudy.com/wp-content/uploads/2019/09/vertex_array_objects_ebo.png
( VBO、VAO、EBO 三者 关系)

Programming

绘制一个方体

      首先,展示绘制效果如下(做了一点小实验)(可能需要一些加载时间):

https://www.eolstudy.com/wp-content/uploads/2019/09/正常222.gif
正常绘制
https://www.eolstudy.com/wp-content/uploads/2019/09/没有深度测试222.gif
没有深度测试
https://www.eolstudy.com/wp-content/uploads/2019/09/没有清空颜色缓冲222.gif
没有清空颜色缓冲

代码

      我自己写了一个 ShaderCompiler 类,它能够完成编译链接 shader 程序的功能,只需在声明时指定 shader 文件路径或者使用 CompileShaderFile() 接口指定路径,然后通过 GetProgramId() 接口得到编译好的程序 ID。

      ShaderCompiler 代码如下:

#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

class ShaderCompiler
{
private:
    std::string VertexShaderSource = "";
    std::string FragmentShaderSource = "";
    unsigned int program_id = 0;
    bool whether_compiled = false;
private:
    /*
    编译shader,并连接到program_id;
    若此对象以前已经编译过,此函数会删除以前的program。
    */
    unsigned int CompileShader(unsigned int shaderType, const std::string& sourceCode)
    {
        if (whether_compiled)
        {
            glDeleteProgram(program_id);
        }
        whether_compiled = true;

        unsigned int id = glCreateShader(shaderType);
        const char* code = sourceCode.c_str();
        glShaderSource(id, 1, &code, nullptr);
        glCompileShader(id);

        int result;
        glGetShaderiv(id, GL_COMPILE_STATUS, &result);
        if (result == GL_FALSE)
        {
            int length;
            glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
            char* message = new(std::nothrow)char[length + 1];
            glGetShaderInfoLog(id, length, &length, message);
            std::cout << "shader fail to compile:" << std::endl;
            std::cout << message << std::endl;

            glDeleteShader(id);
            delete[] message;

            return 0;
        }

        return id;
    }
    /*
    从指定文件取到源码并存入VertexShaderSource、FragmentShaderSource
    */
    void ParseShader(const std::string& filePath)
    {
        std::ifstream stream(filePath);
        if (stream.good() == 0)
        {
            std::cerr << "fail to open shader files" << std::endl;
            VertexShaderSource = "";
            FragmentShaderSource = "";
            return;
        }

        enum class ShaderType
        {
            NONE = -1, VERTEX = 0, FRAGMENT = 1
        };
        std::string line;
        std::stringstream ss[2];
        ShaderType type = ShaderType::NONE;
        while (getline(stream, line))
        {
            if (line.find("#shader") != std::string::npos)
            {
                if (line.find("vertex") != std::string::npos)
                {
                    type = ShaderType::VERTEX;
                }
                else if (line.find("fragment") != std::string::npos)
                {
                    type = ShaderType::FRAGMENT;
                }
            }
            else
            {
                if (type == ShaderType::NONE)
                {
                    continue;
                }
                ss[int(type)] << line << std::endl;
            }
        }
        VertexShaderSource = ss[int(ShaderType::VERTEX)].str();
        FragmentShaderSource = ss[int(ShaderType::FRAGMENT)].str();
        return;
    }
    /*
    调用CompileShader()生成程序。
    需事先知道源码。
    */
    unsigned int CreateShader(const std::string& vertexShaderCode, const std::string& fragmentShaderCode)
    {
        unsigned int program = glCreateProgram();
        unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShaderCode);
        unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShaderCode);

        glAttachShader(program, vs);
        glAttachShader(program, fs);
        glLinkProgram(program);
        glValidateProgram(program);

        glDeleteShader(vs);
        glDeleteShader(fs);

        return program;
    }
public:
    ShaderCompiler(const std::string filepath)
    {
        ParseShader(filepath);
        program_id = CreateShader(VertexShaderSource, FragmentShaderSource);
    }
    ShaderCompiler()
    {
        ;
    }
    /*
    提供shader文件路径,直接返回编译好的program_id
    */
    unsigned int CompileShaderFile(const std::string filepath)
    {
        ParseShader(filepath);
        program_id = CreateShader(VertexShaderSource, FragmentShaderSource);
        return program_id;
    }
    /*
    不编译连接,直接返回内部的program_id
    */
    unsigned int GetProgramId()
    {
        return program_id;
    }
};

      绘制正方体所用的 shader 代码如下:

#shader vertex
#version 330 core

layout (location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
out vec4 oColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
   gl_Position = projection * view * model * vec4(aPos.x, aPos.y, aPos.z, 1.0f);
   oColor = vec4(aColor, 1.0f);
}

/////////////////////////////////

#shader fragment
#version 330 core

out vec4 fragColor;
in vec4 oColor;

void main()
{
   fragColor = oColor;
}

      main() 程序代码如下:

#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
//glm
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>  
#include <glm/gtc/type_ptr.hpp>

#include "ShaderCompiler.hpp"

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 1100;
const unsigned int SCR_HEIGHT = 1100;

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "HELLO WORLD", nullptr, nullptr);
    if (window == nullptr)
    {
        std::cout << "fail to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);//将窗口上下文 设置为 当前线程的主上下文

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "fail to initialize GLAD" << std::endl;
        return -1;
    }

    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);//glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//注册函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数
    
    glEnable(GL_DEPTH_TEST);

    const int vertex_array_num = 48;
    float vertex_position_color[vertex_array_num] =
    {
        //x,y,z,r,g,b
        0.5f, 0.5f, 0.5f, 0.9f, 0.3f, 0.3f,//0
        0.5f, 0.5f, -0.5f, 0.1f, 0.9f, 0.1f,//1
        -0.5f, 0.5f, -0.5f, 0.1f, 0.1f, 0.9f,//2
        -0.5f, 0.5f, 0.5f, 0.9f, 0.9f, 0.9f,//3
        0.5f, -0.5f, 0.5f, 0.3f, 0.3f, 0.9f,//4
        0.5f, -0.5f, -0.5f, 0.9f, 0.9f, 0.9f,//5
        -0.5f, -0.5f, -0.5f, 0.9f, 0.3f, 0.3f,//6
        -0.5f, -0.5f, 0.5f, 0.3f, 0.9f, 0.3f//7
    };

    const int ebo_num = 36;
    int ebo_idx[ebo_num] =
    {
        1, 5, 6,
        1, 6, 2,
        1, 0, 2, 
        0, 3, 2,
        0, 3, 4,
        3, 4, 7,
        4, 5, 6,
        4, 6, 7,
        0, 4, 5,
        0, 1, 5,
        2, 3, 6,
        3, 6, 7
    };

    unsigned int vertex_buffer_id, VAO_id, EBO_id;
    glGenVertexArrays(1, &VAO_id);
    glGenBuffers(1, &vertex_buffer_id);
    glGenBuffers(1, &EBO_id);

    glBindVertexArray(VAO_id);
    
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id);
    glBufferData(GL_ARRAY_BUFFER, vertex_array_num * sizeof(float), vertex_position_color, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_id);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ebo_idx), ebo_idx, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)0);
    glEnableVertexAttribArray(0);
    
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)(sizeof(float) * 3));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    ShaderCompiler sc("./shader.shader");
    unsigned int program_id = sc.GetProgramId();

    while (!glfwWindowShouldClose(window))
    {
        processInput(window);
        glClearColor(0.3f, 0.7f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(program_id);
        glBindVertexArray(VAO_id); 
        
        glm::mat4 model = glm::mat4(1.0f);
        glm::mat4 view = glm::mat4(1.0f);
        glm::mat4 projection = glm::mat4(1.0f);
        model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(1.0f, 1.0f, 1.0f));
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -5.0f));
        projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / SCR_HEIGHT, 0.1f, 100.0f);

        unsigned int modelLoc = glGetUniformLocation(program_id, "model");
        unsigned int viewLoc = glGetUniformLocation(program_id, "view");
        unsigned int projectionLoc = glGetUniformLocation(program_id, "projection");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

        glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
        glBindVertexArray(0);

        glfwSwapBuffers(window);

        glfwPollEvents();
    }
    glDeleteProgram(program_id);
    glfwTerminate();

    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}


void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, true);
    }
}

以上。

完成本次作业。

Eol 提供 Web 技术支持

特别鸣谢:Eol

No tag
Homepage      学习记录      OpenGL      Computer Graphics (Autumn 2019) Assignment 1

爬爬修炼中

Author

Leave a Reply to Clare Cancel

textsms
account_circle
email

Eol's Blog

Computer Graphics (Autumn 2019) Assignment 1
1 What is a graphics pipeline? 答:       Graphics pipeline 是图形渲染流水线。       一些资料将其翻译为“图形渲染…
Scan QR code to continue reading
2019-09-28