前言

好久没更新博客了,上一篇文章还是在去年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. 从GitHub Jobs! 看技术发展趋势! 程序员进阶必备!

    0. https://jobs.github.com/positions GitHub Jobs: 1. https://jobs.github.com/positions/38bb8dc8-b5b4 ...

  2. 知乎 bug

    知乎 bug shit zhihu https://zhuanlan.zhihu.com/p/111809590 无法展开评论 https://unpkg.zhimg.com/@cfe/sentry- ...

  3. ES6 进制字面量 All In One

    ES6 进制字面量 All In One 二进制 & 八进制 & 字面量 https://developer.mozilla.org/en-US/docs/Web/JavaScript ...

  4. c++ 获取和设置 窗口标题

    有些窗口标题中含有中文,可能识别不出来,可以改一下 SetWindowTextW GetWindowTextW 设置: SetWindowTextW( (HWND)0x00370868/*窗口句柄*/ ...

  5. DBA 的效率加速器——CloudQuery v1.3.0 上线!

    好久不见! 自 CloudQuery v1.2.1 发布至今,已有月余,在此期间我们收到了很多朋友对 CloudQuery 的反馈和建议,很多朋友表达了对 v1.3.0 的期待,非常感谢. Cloud ...

  6. [转]c++使用thread类时编译出错,对‘pthread_create’未定义的引用

    转载地址:https://blog.csdn.net/wuhui20091515/article/details/52531202 例子1 #include <iostream> #inc ...

  7. Java 线程间通信 —— 等待 / 通知机制

    本文部分摘自<Java 并发编程的艺术> volatile 和 synchronize 关键字 每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合 ...

  8. css故障文字动画

    免费分享95套java实战项目,不仅有源码还有对应的开发视频,关注公众号『勾玉技术』回复"95"即可获取 首先给内容上hover和before, .glitch:hover:bef ...

  9. 力扣1052. 爱生气的书店老板-C语言实现-中等难度

    题目 传送门 文本 今天,书店老板有一家店打算试营业 customers.length 分钟.每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开. 在某些时 ...

  10. Android获取OneNET云平台数据

    尝试HttpURLConnection "get"方式获取了www.baidu.com的数据后,试着获取OneNET云平台的设备数据(设备数据已成功上传至云平台) .java文件 ...