OpenGL 绘制你的 github skyline 模型
前言
好久没更新博客了,上一篇文章还是在去年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 模型的更多相关文章
- OpenGL绘制自由落体小球
OpenGL绘制自由落体小球 一. 程序运行的软硬件环境 本次设计在window10系统下进行,运用C++进行编写,在CodeBlocks环境下使用OpenGL进行设计. 所需环境配置分为2部分 ...
- OpenGL绘制简单场景,实现旋转缩放平移和灯光效果
本项目实现了用OpenGL绘制一个简单场景,包括正方体.球体和网格,实现了物体的旋转.缩放.平移和灯光效果.附有项目完整代码.有具体凝视.适合刚開始学习的人熟悉opengl使用. 开发情况 开发环境V ...
- Opengl绘制我们的小屋(二)第一人称漫游
这章我们先讲第一人称漫游的实现.在openTK里,我们用函数Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)来放置摄像机,其中三个参数分别与摄像 ...
- WPF绘制深度不同颜色的3D模型填充图和线框图
原文:WPF绘制深度不同颜色的3D模型填充图和线框图 在机械测量过程中,测量的数据需要进行软件处理.通常测量一个零件之后,需要重建零件的3D模型,便于观察测量结果是否与所测工件一致. 重建的3D模型需 ...
- [Modern OpenGL系列(三)]用OpenGL绘制一个三角形
本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...
- opengl绘制正弦曲线
利用opengl绘制正弦曲线 ,见代码: #include <windows.h> //#include <GLUT/glut.h> #include <GL/glut. ...
- [OpenGL ES 03]3D变换:模型,视图,投影与Viewport
[OpenGL ES 03]3D变换:模型,视图,投影与Viewport 罗朝辉 (http://blog.csdn.net/kesalin) 本文遵循“署名-非商业用途-保持一致”创作公用协议 系列 ...
- OpenGl 绘制一个立方体
OpenGl 绘制一个立方体 为了绘制六个正方形,我们为每个正方形指定四个顶点,最终我们需要指定6*4=24个顶点.但是我们知道,一个立方体其实总共只有八个顶点,要指定24次,就意味着每个顶点其实重复 ...
- OPENGL绘制文字
OPENGL没有提供直接绘制文字的功能,需要借助于操作系统. 用OPENGL绘制文字比较常见的方法是利用显示列表.创建一系列显示列表,每个字符对应一个列表编号.例如,'A'对应列表编号1000+'A' ...
随机推荐
- 最新 Markdown for GitHub教程
Markdown 教程 Markdown 是什么? Markdown 是一种方便记忆.书写的纯文本标记语言,用户可以使用这些标记符号以最小的输入代价生成极富表现力的文档:譬如您正在阅读的这份文档. 它 ...
- SCSS 复用 class 样式
SCSS 复用 class 样式 @mixin & @include 复用的变量名称,不可以是 .class 开头️ css-varibale-name .css-class-name Mix ...
- 使用 HTML5 & CSS 实现一个自定义开关按钮 switch button
使用 HTML5 & CSS 实现一个自定义开关按钮 switch button switch button <div class="switch"> < ...
- Xcode show whitespace
Xcode show whitespace Xcode 11.5 how to config Xcode show whitespace 如何配置 Xcode 显示空白字符 Editor -> ...
- 如何用 js 实现一个类似微信红包的随机算法
如何用 js 实现一个类似微信红包的随机算法 js, 微信红包, 随机算法 "use strict"; /** * * @author xgqfrms * @license MIT ...
- c++ string split function
#include <string> #include <vector> #include <regex> struct SplitListItem { std::s ...
- js 触发长按事件
为网站添加触摸功能 <button id="btn1">长按触发</button> <button id="btn2">长按 ...
- socket通信框架——boost asio
boost asio是一个封装了基本socket的跨平台通信框架.它支持异步访问,并支持tcp的自动封闭控制等操作. 一个简单的通信协议可以为: header body body长 数据 通过boos ...
- 系统错误,MSVCP100D.dll找不到或丢失!
文章首发 | 公众号:lunvey 今日研究c++,找了一些示例程序,发现无法打开.弹出如下的报错提示: 作为新时代人类,遇见问题第一件事情就是问度娘.然而眼花缭乱的检索数据,大家众说纷纭,不知道如何 ...
- Django Admin 实现三级联动的示例代码(省市区)===>小白级
一 使用环境 开发系统: windows IDE: pycharm 数据库: msyql,navicat 编程语言: python3.7 (Windows x86-64 executable in ...