前言

好久没更新博客了,上一篇文章还是在去年6月份,乍一看居然快要过去一年了,不时还能看到粉丝数和排名在涨,可是却一直没有内容更新,怪不好意思的- -(主要是一直没想好要写些什么,中间也有过一些想法,比如从 Python 的 yield 关键字讲到协程、基于蒙特卡洛方法的 Raytracing、ECS等等,但是总觉得不是能够讲的很好就一直没写,然后平时不是在打 Dota2 就是在赶项目,所以就一直咕咕咕了。。。)

回到正题,前段时间看到的 github skyline,就是将过去一年在 github 的 commit 可视化成一个三维的模型,如下图。

当时看到这个第一反应就是好炫!然后看到了 Download STL file 这个按钮,又想到目前正在设计的并将作为学校图形学实验使用的项目(项目地址:https://github.com/leo6033/disc0ver-Engine ),就萌生了将绘制这个 model 做为模型绘制实验的想法。于是便开始了动手实现。(关于 OpenGL 模型加载的文章,我在两年前的从零开始 OpenGL 系列中倒是有写过,不过当时的实现是基于固定渲染管线的,从去年开始接触了 OpenGL 可编程流水线,就打算后面进行逐步更新)

模型绘制

STL 模型

将 STL file 下载下来之后,看到后缀名 stl,头一次见到这种后缀名,打开文件看了看

第一反应,这个格式有那么点点奇怪啊,字符串好像有那么点多。。。不过仔细分析了一下,大概就是这样

solid stlmesh
facet normal n1 n2 n3
outer loop
vertex x1 y1 z1
vertex x2 y2 z2
vertex x3 y3 z3
endloop
end facet
...
...
facet normal n1 n2 n3
outer loop
vertex x1 y1 z1
vertex x2 y2 z2
vertex x3 y3 z3
endloop
end facet
endsolid stlmesh

那么,用一个大循环即可解决,伪代码如下

s = readline()
assert(s == "solid filename")
while(!eof){
normal = readline()
if "facet" in normal{
normal -> (n1, n2, n3)
readline() skip outer loop
for i in range(3){
vertex = readline()
vertex -> (x, y, z)
}
readline() skip end loop
}
}
代码实现
#include <sstream>
#include <fstream>
#include <iostream>
void disc0ver::Model::loadModel(const std::string path)
{
std::cout << "load model " << path << std::endl;
std::ifstream infile;
std::string tmp_str;
infile.open(path); if(!infile.is_open())
{
throw "model not found";
} // 读取头 solid filename
char line[256];
infile.getline(line, sizeof(line));
std::istringstream solid(line);
solid >> tmp_str;
assert(tmp_str == "solid"); while(!infile.eof())
{
infile.getline(line, sizeof(line));
float n1, n2, n3;
float x, y, z;
std::istringstream face(line); face >> tmp_str;
if(tmp_str == "facet")
{
face >> tmp_str >> n1 >> n2 >> n3;
// outer loop
infile.getline(line, sizeof(line));
for(int i = 0; i < 3;i ++)
{
infile.getline(line, sizeof(line));
std::istringstream vertex(line);
vertex >> tmp_str >> x >> y >> z;
vertices.emplace_back(x, y, z, n1, n2, n3, 0.0f, 0.0f);
}
// end loop
infile.getline(line, sizeof(line));
}
// end facet
infile.getline(line, sizeof(line));
} }

模型绘制

在项目中,我封装了个 Mesh 类,将 OpenGL 图形绘制都放置于 Mesh 中进行

void disc0ver::Mesh::Draw(Shader &shader)
{
for(unsigned int i = 0; i < textures.size(); i ++)
{
textures[i].use(i);
shader.setInt(textures[i].getName(), static_cast<int>(i));
} glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0); glActiveTexture(GL_TEXTURE0);
} void disc0ver::Mesh::setupMesh()
{
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO); glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int),
&indices[0], GL_STATIC_DRAW); // 顶点位置
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), static_cast<void*>(0));
// 顶点法线
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, normal)));
// 顶点纹理坐标
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, texCoords))); glBindVertexArray(0);
}

因此,我们需要做的就是如模型读取代码那样将读取到的 normal 和 vertex 存至 vertices 数组中,然后在 Init 函数中创建 indices 数组,作为顶点索引,最后直接调用 mesh.Draw() 来实现模型的绘制。

void disc0ver::Model::Init()
{
std::vector<Texture> tmp;
for (auto it = textures.begin(); it != textures.end(); ++it)
{
tmp.push_back(it->second);
}
for (int i = 0; i < vertices.size(); i++)
{
indices.push_back(i);
}
Mesh mesh(vertices, indices, tmp);
meshes.push_back(mesh);
} void disc0ver::Model::draw(Shader& shader)
{
transform.use();
for (auto& mesh : meshes)
{
mesh.Draw(shader);
}
}

效果展示

加入了简单的光线反射 shader 效果

小节

由于很多 OpenGL 的绘制接口在项目中被封装起来了,所以给的代码片段看起来会比较迷糊,感觉比较单独看比较好理解的也就是模型数据读取的那部分代码了,欢迎大家阅读项目的源码,并希望能给些建议,如果可以点个 star 的话就更好了 _ (:з」∠)_ 。后续将继续更新一些项目内容的实现原理,以及去年做的基于蒙特卡洛方法的 Raytracing。

本文首发于我的知乎

OpenGL 绘制你的 github skyline 模型的更多相关文章

  1. OpenGL绘制自由落体小球

    OpenGL绘制自由落体小球 一.    程序运行的软硬件环境 本次设计在window10系统下进行,运用C++进行编写,在CodeBlocks环境下使用OpenGL进行设计. 所需环境配置分为2部分 ...

  2. OpenGL绘制简单场景,实现旋转缩放平移和灯光效果

    本项目实现了用OpenGL绘制一个简单场景,包括正方体.球体和网格,实现了物体的旋转.缩放.平移和灯光效果.附有项目完整代码.有具体凝视.适合刚開始学习的人熟悉opengl使用. 开发情况 开发环境V ...

  3. Opengl绘制我们的小屋(二)第一人称漫游

    这章我们先讲第一人称漫游的实现.在openTK里,我们用函数Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)来放置摄像机,其中三个参数分别与摄像 ...

  4. WPF绘制深度不同颜色的3D模型填充图和线框图

    原文:WPF绘制深度不同颜色的3D模型填充图和线框图 在机械测量过程中,测量的数据需要进行软件处理.通常测量一个零件之后,需要重建零件的3D模型,便于观察测量结果是否与所测工件一致. 重建的3D模型需 ...

  5. [Modern OpenGL系列(三)]用OpenGL绘制一个三角形

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...

  6. opengl绘制正弦曲线

    利用opengl绘制正弦曲线 ,见代码: #include <windows.h> //#include <GLUT/glut.h> #include <GL/glut. ...

  7. [OpenGL ES 03]3D变换:模型,视图,投影与Viewport

    [OpenGL ES 03]3D变换:模型,视图,投影与Viewport 罗朝辉 (http://blog.csdn.net/kesalin) 本文遵循“署名-非商业用途-保持一致”创作公用协议 系列 ...

  8. OpenGl 绘制一个立方体

    OpenGl 绘制一个立方体 为了绘制六个正方形,我们为每个正方形指定四个顶点,最终我们需要指定6*4=24个顶点.但是我们知道,一个立方体其实总共只有八个顶点,要指定24次,就意味着每个顶点其实重复 ...

  9. OPENGL绘制文字

    OPENGL没有提供直接绘制文字的功能,需要借助于操作系统. 用OPENGL绘制文字比较常见的方法是利用显示列表.创建一系列显示列表,每个字符对应一个列表编号.例如,'A'对应列表编号1000+'A' ...

随机推荐

  1. Ant-design-vue—— 表单输入框输入很卡问题

    参考:https://blog.csdn.net/weixin_43905402/article/details/106074435 我的问题:vue项目中使用ant-design-vue,表单中输入 ...

  2. 最新 Vue 源码学习笔记

    最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...

  3. React Hooks vs React Class vs React Function All In One

    React Hooks vs React Class vs React Function All In One React Component Types React Hooks Component ...

  4. Emmet & VSCode

    Emmet & VSCode Emmet - the essential toolkit for web-developers https://emmet.io/ https://emmet. ...

  5. NGK团队是如何打造超高回报率的BGV项目的?

    NGK旨在激励网络的供给端引导去中心化的节点集群,用于促进网络使用和增加带宽流动. 具体来讲,在未来的24个月内,NGK会将部分生态参与者纳入白名单系统有兴趣的参与者可关注官方信息. 对内,NGK采用 ...

  6. django学习-3.如何编写一个html页面并展示到浏览器,及相关导入错误的解决方案

    1.前言 在django中,视图的概念是:具有相同功能和模板的网页,都可以称为视图.通俗一点来说,就是你平常打开任一浏览器,输入一个地址A后看到浏览器窗口展示出来地址A所对应的页面内容B,页面内容B就 ...

  7. iOS 兼容性处理

    1. scroll滑动层,在iOS中滑动不流畅的处理 -webkit-overflow-scrolling:touch; //在滑动层标签添加这个样式 2. iOS 系统中input标签,去掉圆角效果 ...

  8. 死磕以太坊源码分析之EVM固定长度数据类型表示

    死磕以太坊源码分析之EVM固定长度数据类型表示 配合以下代码进行阅读:https://github.com/blockchainGuide/ 写文不易,给个小关注,有什么问题可以指出,便于大家交流学习 ...

  9. 几个小实践带你快速上手MindSpore

    摘要:本文将带大家通过几个小实践快速上手MindSpore,其中包括MindSpore端边云统一格式及华为智慧终端背后的黑科技. MindSpore介绍 MindSpore是一种适用于端边云场景的新型 ...

  10. C# 使用 Index 和 Range 简化集合操作

    C# 使用 Index 和 Range 简化集合操作 Intro 有的语言数组的索引值是支持负数的,表示从后向前索引,比如:arr[-1] 从 C# 8 开始,C# 支持了数组的反向 Index,和 ...