• OpenGL入门之入门


    programs on the GPU-------shader

    顶点着色器--》形状(图元)装配--》几何着色器--》光栅化--》片段着色器--》测试与混合

    图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。
    顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。

    图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入
    (如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。

    图元装配阶段的输出会传递给几何着色器(Geometry Shader)。
    几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。

    几何着色器的输出会被传入光栅化阶段(Rasterization Stage),
    这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。
    在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。

    片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。
    通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

    在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。
    这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),
    用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。
    这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。
    所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

    先要配置环境,然后创建窗口。

    #include <iostream>
    #define GLEW_STATIC
    #include <GL/glew.h>
    #include <GLFW/glfw3.h>
    
    float vertices[] = {
            -0.5f,-0.5f,0.0f,
            0.5f,-0.5f,0.0f,
            0.5f,0.5f,0.0f,
            -0.5f,0.5f,0.0f,
    };
    
    unsigned int indices[] = { // 注意索引从0开始! 
        0, 1, 2,  //第一个三角形
        0, 2, 3   //第二个三角形
    };
    
    //顶点着色器
    const char* vertexShaderSource =
    "#version 330 core                                             
    "
    "layout(location = 0) in vec3 aPos;                            
    "// 位置变量的属性位置值为0
    "void main() {                                                   
    "
    "gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);}             
    ";
    
    //片段着色器
    const char* fragmentShaderSource =
    "#version 330 core                                 
            "
    "out vec4 FragColor;                               
            "
    "void main() {                                     
            "
    "FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}        
            ";
    
    //函数在main之前存档
    void processInput(GLFWwindow* window) {
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        {//退出键关闭窗口
            glfwSetWindowShouldClose(window, true);
        }
    }
    int main(int argc, char* argv[]) {
    
        glfwInit();//初始化和创建窗口
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//提示用主版本号为3
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本号为3,即为3.3版本的OpenGL
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    
        //open GLFW window
        GLFWwindow* window = glfwCreateWindow(800, 600, "Test window", NULL, NULL);//800*600的窗口
    
        if (window == NULL) {//如果为空指针
            //std::cout << "open window failed." << std::endl;//打印失败
            printf("open window failes.");
            glfwTerminate();//终止
            return -1;
            //return EXIT_FAILURE;
        }
    
        glfwMakeContextCurrent(window);
    
        //init GLEW
        glewExperimental = true;
    
        if (glewInit() != GLEW_OK)
        {
            printf("Init GLEW failed.");
            //std::cout << "glew init failed." << std::endl;
            glfwTerminate();
            return -1;//-1代表不正常退出
        }
    
        glViewport(0, 0, 800, 600);//前两个参数控制窗口左下角的位置,后两个参数设置可绘制的像素大小
        //逆时针作为三角形的正面
        //glEnable(GL_CULL_FACE);
        //glCullFace(GL_FRONT);//剔除正面,back--剔除背面
    
        //第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉我们用线来绘制
        //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//线框模式
        //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//关掉线框模式
    
        unsigned int VAO;//顶点数组对象
        glGenVertexArrays(1, &VAO);
        glBindVertexArray(VAO);
    
    
        unsigned int VBO;//顶点缓冲对象
        glGenBuffers(1, &VBO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);//顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER,使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
        //调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        /*
        glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。它的第一个参数是目标缓冲的类型:
        顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。第二个参数指定传输数据的大小(以字节为单位);
        用一个简单的sizeof计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。
    
    第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
    
    GL_STATIC_DRAW :数据不会或几乎不会改变。
    GL_DYNAMIC_DRAW:数据会被改变很多。
    GL_STREAM_DRAW :数据每次绘制时都会改变。
        */
    
        unsigned int EBO;//索引缓冲对象,储存索引
        glGenBuffers(1, &EBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
        unsigned int vertexShader;//创建一个着色器对象,注意还是用ID来引用的
        vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建这个着色器
        //把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL。
        glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
        glCompileShader(vertexShader);
    
        unsigned int fragmentShader;//创建一个着色器对象,注意还是用ID来引用的
        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//创建这个着色器
        //把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL。
        glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
        glCompileShader(fragmentShader);
    
        unsigned int shaderProgram;//创建一个着色器程序对象
        shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);//链接(Link)为一个着色器程序对象
    
        //顶点属性,顶点属性的大小(vec3),数据类型,是否数据被标准化,步长,强制类型转换(表示位置数据在缓冲中起始位置的偏移量)
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(0);//启用顶点属性
    
        while (!glfwWindowShouldClose(window))//渲染循环,关闭它之前不断绘制图像并能够接受用户输入
        {
    
            processInput(window);
            glClearColor(0.2f, 0.3f, 0.3f, 1.0);//清屏颜色填充RGB,透明度
            glClear(GL_COLOR_BUFFER_BIT);
    
            glBindVertexArray(VAO);//绑定
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
            glUseProgram(shaderProgram);//激活这个程序对象
            //glDrawArrays(GL_TRIANGLES, 0, 3);//OpenGL图元的类型,顶点数组的起始索引,打算绘制多少个顶点,绘制三角形
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//改为glDrawElements即可。6为索引个数,0表示偏移。
            glfwSwapBuffers(window);
            glfwPollEvents();//检查有没有触发什么事件
        }
    
        glfwTerminate();//释放/删除之前的分配的所有资源
    
        return 0;
    }

    结果如图:

    函数介绍:

    使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)了:

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

    glVertexAttribPointer函数的参数非常多,所以我会逐一介绍它们:

    第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)
    定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。
    因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。

    第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。

    第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。

    下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,
    所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。

    第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。
    由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。
    要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)
    我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。
    一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,
    我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方
    到整个数组0位置之间有多少字节)。

    最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。
    它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。
    我们会在后面详细解释这个参数。

    莫说我穷的叮当响,大袖揽清风。 莫讥我困时无处眠,天地做床被。 莫笑我渴时无美酒,江湖来做壶。
  • 相关阅读:
    poj 2417 Discrete Logging
    洛谷 P2886 [USACO07NOV]牛继电器Cow Relays
    bzoj 3232 圈地游戏——0/1分数规划(或网络流)
    bzoj 4753 [Jsoi2016]最佳团体——0/1分数规划
    bzoj 5281 [Usaco2018 Open]Talent Show——0/1分数规划
    CF 949D Curfew——贪心(思路!!!)
    bzoj 3872 [Poi2014]Ant colony——二分答案
    bzoj 1731 [Usaco2005 dec]Layout 排队布局——差分约束
    洛谷 1344 [USACO4.4]追查坏牛奶Pollutant Control——最大流
    洛谷 1262 间谍网络——缩点+拓扑
  • 原文地址:https://www.cnblogs.com/huang--wei/p/10571804.html
Copyright © 2020-2023  润新知