OpenGL Hello World

2019-11-26  本文已影响0人  Lucky胡

参考学习资料:
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;
}

上一篇下一篇

猜你喜欢

热点阅读