OpenGL 自学总结

前言:

        本人是工作后才接触到的OpenGL,大学找工作的时候其实比较着急,就想着尽快有个着落。工作后才发现自己的兴趣点。同时也能感觉到自己当前的工作有一点温水煮青蛙的意思,很担心自己往后能力跟不上年龄的增长。因此想在工作之余多学学自己感兴趣的东西,并记录下来。

        本文计划按照模型数据,渲染流水线,顶点着色器,光栅化,片元着色器,其他具体知识点的顺序来梳理自己这段时间自学的内容。

正文:

1、模型数据

        什么是模型数据,从本人目前学习的情况来理解,模型就是一组顶点数据的集合,注意,这里的顶点的数据不仅仅是顶点坐标,还包括纹理坐标,法线向量等等。其实每一项顶点数据都可以看作是广义的纹理,可能是二维的(如纹理坐标),也可能是三维的(如RGB颜色)。

        代码实验使用的是obj格式的模型文件,其格式可以参考本章下文连接,还是比较好理解的。本人目前只解析了obj文件中的“v”(模型顶点坐标)、“vt”(模型纹理坐标)、“vn”(模型顶点法线坐标)。输入是模型文件目录;输出按照OpenGL的格式,为一段float类型的数据流,逻辑上按行划分,每行为一组顶点数据(顶点坐标,法线数据,纹理坐标)。代码如code 1-1、code 1-2所示:

// objloader.h
#ifndef OBJLOADER_H
#define OBJLOADER_H
#include <QString>
#include "qdebug.h"
#include <iostream>
#include <fstream>
#include <QFile>

struct Vnode {
    float x, y, z;
};
struct Vnormal {
    float x, y, z;
};
struct Vtexture {
    float x, y;
};
struct gldata {
    float vx, vy, vz;
    float vnx, vny, vnz;
    float vtx, vty;
};

class objloader
{
public:
    bool ReadOBJFile(QString &fileName);
    bool GetOBJData(float** data, int* dataLen, int** iddata, int* idlen);
    QList<Vnode> Vlist;
    QList<Vnormal> Vnlist;
    QList<Vtexture> Vtlist;
    QList<gldata> glist;
    QList<int> idlist;
};

code 1-1

// objloader.cpp
#include "objloader.h"
#include "qdebug.h"
#include <iostream>
#include <fstream>
#include <QFile>

bool objloader::ReadOBJFile(QString &fileName)
{
    QFile file(fileName);
    if(!file.open(QIODevice::ReadOnly|QIODevice::Text)){
        qDebug()<<"文件打开失败";
    }
    Vlist.clear();
    Vnlist.clear();
    Vtlist.clear();
    glist.clear();
    idlist.clear();
    int id = 0;
    while(!file.atEnd()) {
        QByteArray line = file.readLine();
        QString str(line);
        str = str.trimmed();
        if (str.length() < 2) {
            continue;
        }
        if (str[0] == 'v'){
            if (str[1] == 't'){ //纹理
                QStringList strlist = str.split(" ");
                Vtexture tmp;
                tmp.x = strlist[1].toFloat();
                tmp.y = strlist[2].toFloat();
                Vtlist.append(tmp);
            } else if (str[1] == 'n') { //法线
                QStringList strlist = str.split(" ");
                Vnormal tmp;
                tmp.x = strlist[1].toFloat();
                tmp.y = strlist[2].toFloat();
                tmp.z = strlist[3].toFloat();
                Vnlist.append(tmp);
            } else {
                QStringList strlist = str.split(" ");
                Vnode tmp;
                tmp.x = strlist[2].toFloat();
                tmp.y = strlist[3].toFloat();
                tmp.z = strlist[4].toFloat();
                Vlist.append(tmp);
            }
        } else if (str[0] == 'f') {
            QStringList strlist = str.split(" ");
            for (int i=1;i<strlist.size();i++) {
                QStringList info = strlist[i].split("/");
                if (info.size() < 3) {
                    qDebug()<<"f decode fail";
                    return false;
                }
                gldata node;
                if (info[0].toInt()-1 >= Vlist.size() ||
                    info[2].toInt()-1 >= Vnlist.size() ||
                    info[1].toInt()-1 >= Vtlist.size()) {
                    qDebug()<<"f overflow";
                    return false;
                }
                node.vx = Vlist.at(info[0].toInt()-1).x;
                node.vy = Vlist.at(info[0].toInt()-1).y;
                node.vz = Vlist.at(info[0].toInt()-1).z;
                node.vnx = Vnlist.at(info[2].toInt()-1).x;
                node.vny = Vnlist.at(info[2].toInt()-1).y;
                node.vnz = Vnlist.at(info[2].toInt()-1).z;
                node.vtx = Vtlist.at(info[1].toInt()-1).x;
                node.vty = Vtlist.at(info[1].toInt()-1).y;
                glist.append(node);
            }
            // push绘制点的下标 123和134,目的是确保绘制方向一致(顺时针)
            idlist.append(id);
            idlist.append(id+1);
            idlist.append(id+2);
            idlist.append(id);
            idlist.append(id+2);
            idlist.append(id+3);
            id = id + 4;
        } else if (str[0] == 'o') {
            qDebug()<<"o 解析失败";
        }
    }
    return true;
}
bool objloader::GetOBJData(float** data, int* dataLen, int** iddata, int* idlen)
{
    *dataLen = (sizeof(gldata)*(glist.size()));
    *data = (float*)malloc(*dataLen);
    *idlen = (sizeof(int)*(idlist.size()));
    *iddata = (int*)malloc(*idlen);
    for (int i=0;i<glist.size();i++) {
        if ((int)(i*sizeof(gldata)) >= *dataLen) {
            qDebug() << "GetOBJData out of mem";
        }
        memcpy((*data) + (i*(sizeof(gldata)/sizeof(float))), &glist.at(i), sizeof(gldata));
    }
    for (int i=0;i<idlist.size();i++) {
        memcpy((*iddata)+i, &idlist.at(i), sizeof(int));
    }
    return true;
}

code 1-2

        相关学习:

        3D文件格式之OBJ文件格式

2、渲染流水线

        模型数据加载进内存中后,计算机只有一堆点的数据,如何绘制出模型的“形”呢?这就需要利用OpenGL的渲染流水线了。一般来说,一个渲染流程会分为三个阶段:应用阶段、几何阶段、光栅化阶段。图2-1是这三个阶段的联系。应用阶段是开发者工作的阶段,开发者需准备好要渲染的各种几何信息(包括模型数据、渲染状态、着色器等),即渲染图元;几何阶段通常在GPU上进行,负责处理应用阶段输入的渲染图元,一般是逐点或者逐多边形地操作(例如对每个顶点做光照处理)。最终几何阶段会将模型的顶点数据变换到屏幕空间中,并交给光栅器处理;光栅化阶段会将几何阶段传递下来的数据进行采样,产生屏幕上的像素,渲染出最终的图像。这一阶段也是在GPU进行的。

图2-1

        整个渲染过程中,先是由CPU将数据加载进显存中,并设置渲染状态(例如使用哪些着色器),最后调用渲染命令。之后的工作都在GPU里进行。GPU内部的工作流程如图2-2所示,其中绿色表示该阶段可编程,黄色表示该阶段可配置不可编程,蓝色表示该节点开发者无法控制。实线表示该着色器必须由开发者编程实现,虚线表示该着色器是可选的。本文目前只涉及顶点着色器以及片元着色器。

图2-2

3、顶点着色器

        顶点着色器对输入的每一个模型顶点做同样的处理流程,具体处理流程将由开发者编程实现现,一般为一个用GLSL(OpenGL Shading Language)语言编写的txt文件。code 3-1是一段顶点着色器的代码,作用是将传入的模型坐标变换到摄像机的裁剪空间,并设置模型的颜色和纹理并输出给片元着色器。语法和c语言类似,下面介绍代码中的几个关键字:

        #version 330 core,指定GLSL的版本和配置。在这个例子中,表示使用OpenGL 3.3版本的核心配置。

        layout (location = i) ,这是GLSL接收外部变量的方式之一,其中vec3表示该变量的类型,即3维向量(x,y,z)。后面的aPos则是变量名。外部代码通过code 3-2的方式传入变量,本文用于加载章节1输出的模型数据(顶点坐标、法线坐标、纹理坐标的大数据流)。

        out,指定顶点着色器的输出变量,后面跟着变量类型、变量名。顶点着色器的输出将成为片元着色器的输入。

        uniform,这是GLSL接受外部变量的另一种方式。mat4表示变量类型,是一个4*4的矩阵,model为变量名。本例子用于传入3个变换矩阵(MVP矩阵),外部代码通过code 3-3的方式传入变量。

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 rPos;
layout (location = 2) in vec2 texCoord;
out vec3 normal;
out vec2 TexCoord;
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);
   normal= rPos;
   TexCoord = texCoord;
}

code 3-1

// 加载VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// 加载VBO,data是模型数据(顶点坐标、法线坐标、纹理坐标的大数据流)
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, datalen, data, GL_STATIC_DRAW);
// 加载EBO,iddata表示绘制模型各个三角形面时,每个三角形顶点坐标的索引,顶点坐标来源与上面的data
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idlen, iddata, GL_STATIC_DRAW);

/* glVertexAttribPointer说明:
 * 每个顶点属性从一个VBO管理的内存中获得它的数据
 * 具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的
 */
// 绑定顶点
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);

// 绑定法线
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);

// 绑定纹理坐标
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);

/*
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer)
其中:
index:指定要修改的顶点属性的索引,与顶点着色器中的location对应。
size:指定数据的大小,例如顶点坐标是3维(3个数据),纹理是2维(2个数据)
type:指定每个组件的数据类型,可以是GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT或GL_DOUBLE。
normalized:指定是否应该将非浮点值映射到范围[0,1](如果为GL_TRUE)或[-1,1](如果为GL_FALSE)。
stride:在模型数据流中,两个指定数据之间的步长,本文中每个模型顶点数据由(顶点坐标,法线坐标,纹理坐标)构成,所以每个子数据之间的步长为8个float。
pointer:指定指向第一个顶点属性的指针。如果缓冲区对象绑定到GL_ARRAY_BUFFER,则pointer被解释为首份数据的偏移量;否则,它被解释为指针。
*/
/*
glEnableVertexAttribArray用于激活指定索引的顶点属性数组,使其可以被顶点着色器使用。可以理解为指定一块内存存放中间数据。一般情况下,OpenGL确保至少有16个包含4分量的顶点属性可用。
*/

 code 3-2

/*
*void glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)
*location : uniform的位置
*count : 矩阵个数,一般为1
*transpose : 矩阵是列优先矩阵(GL_FALSE)还是行优先矩阵(GL_TRUE)
*value : 指向由count个元素的数组的指针,一般为矩阵的首地址指针
*/
GLint modelLoc = glGetUniformLocation(shaderProgram, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, model.constData());
modelLoc = glGetUniformLocation(shaderProgram, "view");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, view.constData());
modelLoc = glGetUniformLocation(shaderProgram, "projection");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, projection.constData());

 code 3-3

        相关学习:

        顶点着色器与片元着色器 内置变量

4、光栅化

        这部分由OpenGL自身实现,开发者无法控制,但我认为也需要了解其中的过程。光栅化是将内存中的模型(由若干个顶点组成)投影到屏幕空间,并采样到一个一个像素上的过程。之前说过一个模型在内存中表示为若干个顶点,每三个顶点能够组成一个三角形面,称作一个“图元”。光栅化的操作目标就是模型面上的各个图元。每个图元有哪三个顶点组成是之前加载EBO时确定好的。

        为什么一个图元是三角形呢?原因有:1、三角形是最基础的多边形,所有的多边形都可以打碎成多个三角形的组合;2、光栅化还有一个很重要的一步——插值,即把顶点的一些属性(坐标、颜色、法线等)通过一定的策略附加到三角形内部的“像素”上,这个过程是线性。因此,只有三角形能够完成插值(4个点不一定在同一个平面,像法线、坐标这样的属性无法通过线性插值给到内部“像素”)。

5、片元着色器

        经过光栅化后,一个图元内部就有了若干“像素”(也可以叫片元),而片元着色器就是遍历这些“像素”做统一的处理。一般也是一个用GLSL语言编写的txt文件。code 5-1是一段片元着色器的代码,作用是输出当前片元的纹理值,法线暂时没用到(法线一般用于计算光照)。下面介绍下几个关键字:

        in,接收顶点着色器的输出,后面跟着分别是数据类型和数据名。

        out,片元着色器的输出,一般是颜色数据(RGBA)。

        uniform sampler2D ourTexture,这是GLSL供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler2D、sampler3D。该变量能够获取到之前加载的的纹理数据,和输入的纹理坐标TexCoord结合使用就能够得出纹理值(颜色)。纹理的加载方式如code 5-2所示。

#version 330 core
in vec3 normal;
in vec2 TexCoord;
out vec4 Fcolor;

uniform sampler2D ourTexture;

void main()
{
    Fcolor = texture(ourTexture, TexCoord);
}

code 5-1

// 加载纹理
QImage img;
img.load("D:\\IDE\\QTProject\\opgl\\a.png");
// 改变编码格式,不然颜色对不上
img = img.convertToFormat(QImage::Format_RGB888);
int width = img.width();
int height = img.height();
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
// 加载并生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, img.bits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);


// 绘制时需要加上
glBindTexture(GL_TEXTURE_2D, texture);

code 5-2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/190542.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】类型转换 ② ( C++ 静态类型转换 static_cast | C 语言隐式转换弊端 | 代码示例 )

文章目录 一、静态类型转换 static_cast1、C 静态类型转换 static_cast2、C 语言隐式转换弊端3、代码示例 在之前写过一篇 C 类型转换的博客 【C 语言】类型转换 ( 转换操作符 | const_cast | static_cast | dynamic_cast | reinterpret_cast | 字符串转换 ) , 简单介绍了 C 类…

Linux系统的文件权限

Linux系统权限的相关概念与理解 (xshell下进行演示) 文章目录&#xff1a; 1:linux系统下两种用户 超级用户(root)与普通用户(非root)的理解root与非root用户之间切换的指令非root用户之间进行切换的指令操作 2:linux文件权限管理 文件访问者的介绍文件的类型与文件的访问权…

openpnp - 自动换刀设置 - 使用克隆功能降低风险

文章目录 openpnp - 自动换刀设置 - 使用克隆功能降低风险概述笔记需要注意的地方将一个做好的吸嘴作为这排其他吸嘴的模板END openpnp - 自动换刀设置 - 使用克隆功能降低风险 概述 自动换刀设置时, 很危险, 动不动就撞刀. 如履薄冰啊:( 看到openpnp在自动换刀时, 有个克隆功…

【Vue】记事本

上一篇&#xff1a;Vue的指令 https://blog.csdn.net/m0_67930426/article/details/134599378?spm1001.2014.3001.5501 本篇所需指令&#xff1a; v- for v-model v-on v-show 目录 删除功能 添加功能 统计功能 清空功能 v-show 删除功能 <!DOCTYPE html> …

系列十九、Spring实例化bean的方式

一、概述 所谓实例化bean&#xff0c;大白话讲就是Spring如何把这一个个的普通的Java对象创建为Spring bean的。 二、方式 Spring中实例化bean常用的有以下四种&#xff0c;即&#xff1a; ① 构造器方式&#xff1b; ② 静态工厂方式&#xff1b; ③ 实例工厂方式&#xff1b;…

SQL JOIN 子句:合并多个表中相关行的完整指南

SQL JOIN JOIN子句用于基于它们之间的相关列合并来自两个或更多表的行。 让我们看一下“Orders”表的一部分选择&#xff1a; OrderIDCustomerIDOrderDate1030821996-09-1810309371996-09-1910310771996-09-20 然后&#xff0c;看一下“Customers”表的一部分选择&#xff…

帮管客CRM 文件上传漏洞复现

0x01 产品简介 帮管客CRM是一款集客户档案、销售记录、业务往来等功能于一体的客户管理系统。帮管客CRM客户管理系统&#xff0c;客户管理&#xff0c;从未如此简单&#xff0c;一个平台满足企业全方位的销售跟进、智能化服务管理、高效的沟通协同、图表化数据分析帮管客颠覆传…

cuda magma 构建 使用cmake构建的步骤记录

这不是群论代数软件&#xff0c;而是cuda 矩阵计算软件 1. 生成其他精度的源代码 1.1 复制编辑 make.inc cp make.inc-examples/make.inc.openblas ./make.inc 并修改其中的定义&#xff1a; OPENBLASDIR ? /opt/OpenBLAS 这需要实现安装openblas到此处。文件夹解构&…

JAVA小游戏简易版王者荣耀

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;…

虹科分享 | AR世界揭秘:从二维码的起源到数据识别与位姿技术的奇妙融合!

引言&#xff1a;探索AR的神奇世界&#xff0c;我们将从二维码的诞生谈起。在这个科技的海洋中&#xff0c;二维码是如何帮助AR实现数据获取与位姿识别的呢&#xff1f;让我们一起揭开这层神秘的面纱&#xff01; 一、二维码的由来 二维码是将数据存储在图形中的技术&#xff…

王者荣耀,,,,,

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;…

【VSCode】自定义转换大小写快捷键

文章目录 VSCode 是没有可以直接转换字母大小写的快捷键的&#xff0c;但是可以通过设置去定义 点击左下角设置按钮&#xff0c;并选择键盘快捷方式 在快捷方式里面搜索写&#xff0c;就能找到&#xff1a; 选择要设置的快捷键&#xff0c;并点击左侧的号 在键盘上按住你想设置…

mysql高级知识点

一、mysql架构 连接层&#xff1a;负责接收客户端的连接请求&#xff0c;可以进行授权、认证(验证账号密码)。服务层&#xff1a;负责调用sql接口&#xff0c;对sql语法进行解析&#xff0c;对查询进行优化&#xff0c;缓存。引擎层&#xff1a;是真正进行执行sql的地方&#x…

类与对象——(1)初识对象——C++中的string

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 或许不安或许迷惑&#xff0c;但…

storyBook常见踩坑报错 和 解决

用StoryBook官网的代码&#xff0c;但报错&#xff0c;Unexpected token’<’ 在js文件中// Button.stories.js|jsx import { Button } from ‘./Button’; export default { component: Button, }; /* *&#x1f447; Render functions are a framework specific featur…

Liunx系统使用超详细(一)

目录 一、Liunx系统的认识 二、Liunx和Windows区别 三、Liunx命令提示符介绍 四、Liunx目录结构 一、Liunx系统的认识 Linux系统是一种开源的、类Unix操作系统内核的实现&#xff0c;它基于Unix的设计原理和思想&#xff0c;并在全球范围内广泛应用。以下是对Linux系统的详…

文件的写入和读取操作

题目&#xff1a; 编写一个程序&#xff0c;实现以下功能&#xff1a; 1. 创建一个新的文本文件&#xff0c;并将用户输入的数据写入文件中。 2. 打开已存在的文本文件&#xff0c;并将其中的数据显示在屏幕上。 #include <stdio.h> #include <stdlib.h> void wri…

一文讲明SpringMVC 【爆肝整理一万五千字】

我 | 在这里 &#x1f575;️ 读书 | 长沙 ⭐软件工程 ⭐ 本科 &#x1f3e0; 工作 | 广州 ⭐ Java 全栈开发&#xff08;软件工程师&#xff09; &#x1f383; 爱好 | 研究技术、旅游、阅读、运动、喜欢流行歌曲 ✈️已经旅游的地点 | 新疆-乌鲁木齐、新疆-吐鲁番、广东-广州…

【JVM系列】- 穿插·对象的实例化与直接内存

对象的实例化与直接内存 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f31d;分享学习心得&#xff0c;欢迎指正&#xff0c;大家一起学习成长&#xff01; 文章目录…

Java多线程二-线程安全

1、线程安全问题 多个线程&#xff0c;同时操作同一个共享资源的时候&#xff0c;可能会出现业务安全问题。 2、实例&#xff1a;取钱的线程安全问题 2.1、场景 小明和小红是夫妻&#xff0c;他们有个共同账户&#xff0c;余额是十万元&#xff0c;如果两人同时取钱并且各自取…