前言

好久没更新博客了,上一篇文章还是在去年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. JVM系列之一 JVM的基础概念与内存区域

    前言 作为一名 Java 语言的使用者,学习 JVM 有助于解决程序运行过程中出现的问题.写出性能更高的代码. 可以说:学好 JVM 是成为中高级 Java 工程师的必经之路. 有感于从未整理归纳 J ...

  2. js uppercase first letter

    js uppercase first letter const str = `abc`; str.slice(0, 1).toUpperCase(); // "A" str.sli ...

  3. php 安装 yii framework notice-error 的解决方案!

    1 问题描述: 2 解决方案: error_reporting(0); //解决error_notice 的最简单最有效的方法在每一个php文件的头部都加上error_reporting(0); 3. ...

  4. how to updating Node.js and npm

    how to updating  Node.js and npm 1 Installing Node.js and updating npm How do I update Node.js ? Not ...

  5. Keep Fitness

    Keep Fitness 健身 keep health 训练流程 Part 1 热身 5-10分钟 Part 2 肌肉力量训练 30分钟 大肌群包括:胸.背.腿.臀: 小肌群包括:肩.二头肌.三头肌. ...

  6. js 如何获取某一个月的第一天是周几

    js 如何获取某一个月的第一天是周几 calendar ??? padding dates // day = 1 const firstMonthDate = new Date(year + mont ...

  7. NGK 路演美国站,SPC空投与NGK项目安全

    最近,NGK全球巡回路演在美国最大城市纽约市落下帷幕,本次路演有幸邀请了NGK方面代表迈尔逊,纽约当地区块链大咖维克多以及美国当地社群意见代表乔治等人. 路演一开始,美国当地路演师Viko首先致辞,V ...

  8. Renice INC:解密干型葡萄酒

    市场上,干型葡萄酒往往对比甜型葡萄酒(如甜红.甜白)受到更多葡萄酒爱好者的青睐.在葡萄酒界,大部分的红葡萄酒和白葡萄酒也都是干型的,而且它们的口感往往各有特色,并非千篇一律.今天,就跟随Renice ...

  9. Spring系列.Resource接口

    接口简介 JDK中提供了java.net.URL这个类来用于获取不同种类的资源(根据不同前缀的url可以获取不同种类的资源).但是URL这个类没有获取classpath和ServletContext下 ...

  10. JVM系列(四):java方法的查找过程实现

    经过前面几章的简单介绍,我们已经大致了解了jvm的启动框架和执行流程了.不过,这些都是些无关痛痒的问题,几行文字描述一下即可. 所以,今天我们从另一个角度来讲解jvm的一些东西,以便可以更多一点认知. ...