美文网首页
OpenGL Hello World

OpenGL Hello World

作者: Lucky胡 | 来源:发表于2019-11-26 23:08 被阅读0次

    参考学习资料:
    https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/

    程序代码地址:
    https://github.com/Hujunjob/OpenGLPro

    OpenGL渲染过程

    顶点数据Vertex Data --> 顶点着色器Vertex Shader -> 几何着色器Geometry Shader --> 光栅化 --> 片元着色器Fragment Shader --> 混合和Alpha测试 Blending and Alpha Test

    其中,顶点着色器和片元着色器是必须实现的,几何着色器可以不实现用默认的。

    一、VBO:将顶点数据放入显存

    //定义顶点数组
    float vertices[] = {
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f,
            0.0f, 0.5f, 0.0f
    };
    
    //顶点着色器会在GPU上创建内存,用于存储顶点数据
    //需要配置OpenGL如何解释这些内存,指定如何发送给显卡
    //通过顶点缓冲对象VBO来管理这个内存,在GPU内存(显存)中存储大量顶点
    //这是从CPU发送到GPU,比较慢,所以尽量一次发送尽可能多的数据
    
    //生成一个VBO对象,用一个id来表示
    unsigned int VBO;
    
    //着色器程序
    uint mProgram;
    
    void generateVBO() {
        //生成一个VBO缓冲对象
        glGenBuffers(1, &VBO);
    
        //OpenGL有很多缓冲对象,VBO的缓冲类型是GL_ARRAY_BUFFER
        //同一种类型的缓冲对象只能绑定一个id
        //将VBO绑定到GL_ARRAY_BUFFER类型上
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
    
        //当VBO被绑定到GL_ARRAY_BUFFER上时,所有操作GL_ARRAY_BUFFER的配置都是配置VBO了
    
        //接下来将顶点数据保存到VBO上,也就是保存到GL_ARRAY_BUFFER上
        //将数据保存到显存里,最后一个参数是保存方式,显卡如何管理这些数据
        //有三种管理方法
        //GL_STATIC_DRAW :数据不会或几乎不会改变。
        //GL_DYNAMIC_DRAW:数据会被改变很多。
        //GL_STREAM_DRAW :数据每次绘制时都会改变。
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    }
    

    二、编译自定义着色器

    首先先自定义顶点着色器和片元着色器:
    顶点着色器vtriangle.vert

    #version 320 es
    
    layout (location = 0) in vec3 aPos;
    
    void main() {
        gl_Position = vec4(aPos, 1.0);
    }
    
    

    片元着色器ftriangle.glsl

    #version 320 es
    
    precision lowp float;
    
    out vec4 FragColor;
    
    void main() {
        FragColor = vec4(1.0f,0.5f,0.2f,1.0f);
    }
    
    

    编译着色器

    //动态编译顶点着色器源码
    //创建着色器对象,还是用id存储
    unsigned int generateVertexShader() {
        unsigned int vertexShaderId = 0;
        //创建shader
        vertexShaderId = glCreateShader(GL_VERTEX_SHADER);
    
        const char *vShader = readFromAsset("vtriangle.vert");
        //将着色器代码附着到着色器对象上
        glShaderSource(vertexShaderId, 1, &vShader, nullptr);
    
        //编译着色器代码
        glCompileShader(vertexShaderId);
    
        //检测是否编译成功
        int success;
        char infoLog[512];
        glGetShaderiv(vertexShaderId, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(vertexShaderId, 512, nullptr, infoLog);
            LOGE("opengl", "generateVertexShader 编译顶点着色器错误 %s", infoLog);
        }
        return vertexShaderId;
    }
    
    //动态编译片段着色器
    unsigned int generateFramgentShader() {
        unsigned int fragmentShaderId = 0;
        fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER);
    
    
        const char *fShader = readFromAsset("ftriangle.glsl");
        glShaderSource(fragmentShaderId, 1, &fShader, nullptr);
        glCompileShader(fragmentShaderId);
    
        int success;
        char infoLog[512];
        glGetShaderiv(fragmentShaderId, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(fragmentShaderId, 512, nullptr, infoLog);
            LOGE("opengl", "generateVertexShader 编译片元着色器错误 %s", infoLog);
        }
        return fragmentShaderId;
    }
    

    三、链接着色器形成着色器程序

    //着色器程序是多个着色器合并后并最终链接在一起完成的版本
    //链接多个着色器,会把一个着色器的输出当做另一个着色器的输入,如果输出输入不匹配,则会链接失败
    
    unsigned int generateProgram(uint vShader, uint fShader) {
        //创建 --> 附着 --> 链接
        uint program = glCreateProgram();
    
        glAttachShader(program, vShader);
        glAttachShader(program, fShader);
    
        glLinkProgram(program);
    
        //检测
        int success;
        glGetProgramiv(program, GL_LINK_STATUS, &success);
        if (!success) {
            char info[512];
            glGetProgramInfoLog(program, 512, nullptr, info);
            LOGE("opengl", "链接程序错误 %s", info);
            return 0;
        } else {
            LOGD("opengl", "链接成功");
            return program;
        }
    }
    
    uint linkProgram() {
        auto vShader = generateVertexShader();
        if (vShader == 0) {
            LOGD("opengl", "linkProgram 顶点着色器生成失败");
            return 0;
        }
        auto fShader = generateFramgentShader();
        if (fShader == 0) {
            LOGD("opengl", "linkProgram 片元着色器生成失败");
            return 0;
        }
        auto program = generateProgram(vShader, fShader);
        if (program == 0) {
            LOGD("opengl", "linkProgram 链接失败");
            return 0;
        }
    
        //激活程序对象,这样以后渲染都是调用这个着色器程序了
        glUseProgram(program);
    
        //删除
        glDeleteShader(vShader);
        glDeleteShader(fShader);
    
        return program;
    }
    

    四、让OpenGL解析顶点数据

    //由于顶点着色器允许指定各种形式的输入,所有我们需要让OpenGL知道如何读取这些数据
    //例如我们当前的VBO是顶点数据以XYZ形式连续排列,所以顶点连续排列
    void handleVBO() {
        //告诉OpenGL如何读取VBO数据
        //各个参数说明
    //
    //    第一个参数指定从索引0开始取数据,与顶点着色器中layout(location=0)对应。
    //
    //    第二个参数指定顶点属性大小,顶点属性是vec3,则由3个值xyz组成
    //
    //    第三个参数指定数据类型。
    //
    //    第四个参数定义是否希望数据被标准化(归一化),只表示方向不表示大小。
    //
    //    第五个参数是步长(Stride),指定在连续的顶点属性之间的间隔。
    //
    //    第六个参数表示我们的位置数据在缓冲区起始位置的偏移量。
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
        //以上面的索引值0作为参数,启动顶点属性
        glEnableVertexAttribArray(0);
    }
    
    

    五、绘制三角形

    void onDrawFrame() {
        glClearColor(0.2, 0.3, 0.3, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        LOGD("OpenGL", "onDrawFrame");
    
        glDrawArrays(GL_TRIANGLES,0,3);
    }
    

    此时就依次完成了以下步骤:
    生成并绑定VBO --> 将顶点数组存储到显存 -->编译顶点着色器和片元着色器 --> 链接两个着色器生成着色器程序Program --> 告诉OpenGL如何解析顶点数组 --> 绘制三角形

    优化

    VAO

    以上绑定了一个三角形,但是如果我们有几十几百个物体,这样绑定和配置就太麻烦了
    使用顶点数组对象VAO Vertex Array Object,可以保存所有的VBO对象和配置
    VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。

    VAO
    uint generateVAO(){
        uint  vao = 0;
        //生成VAO
        glGenVertexArrays(1,&vao);
    
        //绑定VAO
        glBindVertexArray(vao);
    }
    

    使用VAO

    // ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
    // 1. 绑定VAO
    glBindVertexArray(VAO);
    // 2. 把顶点数组复制到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 3. 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    
    [...]
    
    // ..:: 绘制代码(渲染循环中) :: ..
    // 4. 绘制物体
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    

    索引缓冲对象IBO

    索引缓冲对象,Element Buffer Object EBO 或 Index Buffer Object IBO。
    OpenGL里绘制各种形状都是由三角形组合而成,比如矩形就是由两个三角形组合而成。
    矩形有4个点,绘制两个三角形需要6个点,这样就有2个点多余了。利用IBO存储绘制顺序,在只存储4个点的情况下,告诉OpenGL调用点的顺序,可以绘制两个三角形。

    //矩形由2个三角形组成,需要6个点。但是有两个点重复了,浪费存储空间
    float rectangleVertices[] = {
            // 第一个三角形
            0.5f, 0.5f, 0.0f,   // 右上角
            0.5f, -0.5f, 0.0f,  // 右下角
            -0.5f, 0.5f, 0.0f,  // 左上角
            // 第二个三角形
            0.5f, -0.5f, 0.0f,  // 右下角
            -0.5f, -0.5f, 0.0f, // 左下角
            -0.5f, 0.5f, 0.0f   // 左上角
    };
    

    使用EBO同样可以使用VAO来存储EBO,因为都是glBindBuffer()。

    //以上绑定了一个三角形,但是如果我们有几十几百个物体,这样绑定和配置就太麻烦了
    //使用顶点数组对象VAO Vertex Array Object,可以保存所有的VBO对象和配置
    //利用VAO可以同时存储VBO和EBO
    uint generateVAO() {
        uint vao = 0;
        //生成VAO
        glGenVertexArrays(1, &vao);
    
        //绑定VAO
        glBindVertexArray(vao);
        //生成VAO
        generateVBO();
        //告诉OpenGL如何解析VBO并启动VBO
        handleVBO();
    
        //生成绑定EBO
        generateEBO();
    
        //解绑VAO
        glBindVertexArray(0);
        return vao;
    }
    
    

    相关文章

      网友评论

          本文标题:OpenGL Hello World

          本文链接:https://www.haomeiwen.com/subject/lcluwctx.html