基于OpenGL的Koch分形雪花实现

在这学期选修了一门计算机图形学,本来是想好好学习一番,但。。。总感觉老师的教学水平有限,一学期下来感觉没学到啥东西,最多就是在几次实战作业中熟悉了一下部分效果的实现。唯一稍微有点收获的就是第一次作业,老师要求实现一种分形图形,最好是雪花。这也算是我开始学习图形学后第一个不是跟着教程实现的小功能,通过自己对API的理解和尝试实现的,虽然很简单,但也是学习路上一点小小的进步吧,在此记录下来(顺便把代码放上来,节省电脑硬盘,代码本身也不多,不想放在GitHub上)

什么是分形

本来是想认真的写个定义,但好像其严格定义有点复杂。。。本人比较懒,就不写了。简单来说,分形图就是以一个“父图”为基准,按照一定规律在“父图”上衍生出“子图”,并以此不断循环下去,放一个简单的图例就知道了(百度直接找的,侵删):

这个图应该能比较好的展现分形雪花的概念。

实现思路

从图形的演变,我们很简单就能想到可以运用递归的思路,现在我们应该考虑的是递归函数的构造——用什么作为递归的参数?以怎样的方式调用下一次递归?

很明显,如果我们直接拿“雪花”本身作为递归的对象,这个递归函数很难实现,通过观察我们可以发现,实际上所谓的“雪花分形”,本质上也是线段的分形,即下图:

每一层次的分形“雪花”,都由相应数量的“线段”构成,而“分形”其实便是所有的线段分形一次,因此我们只需每次递归实现上图中的(1)->(2)即可,我们的递归函数的基本构造便是:输入参数为一条线段的两个端点(的位置),在函数内部计算需要生成的三个端点(的位置),即线段1/3处的端点,线段2/3处的端点,以及用该两点计算出的三角顶点,该算法的实现代码如下如所示:

aPoint v1 = mix(a, b, 1.0f / 3.0f);
aPoint v3 = mix(a, b, 2.0f / 3.0f);
aPoint v2 = caculatev2(v1, v3);
aPoint mix(aPoint a, aPoint b, float length)
{aPoint v;v.x = a.x + (b.x - a.x) * length;v.y = a.y + (b.y - a.y) * length;v.z = 0.0f;return v;
}
aPoint caculatev2(aPoint a, aPoint b)
{aPoint v;v.x = b.x - a.x;v.y = b.y - a.y;v.z = 0.0f;aPoint v2;v2.x = v.x * cos(glm::radians(60.0f)) - v.y * sin(glm::radians(60.0f));v2.y = v.x * sin(glm::radians(60.0f)) + v.y * cos(glm::radians(60.0f));v2.x += a.x;v2.y += a.y;v2.z = 0.0f;return v2;
}

aPoint是我自己写的一个结构体,v1为左边1/3处的点,v2为突出的顶点,v3为右边1/3处的点。左右两边1/3的点很好计算,中间的突出点有点麻烦,不过只要会sin/cos这些三角函数,推一推就好,这里就不细讲了。

现在,计算出的3个点和原来线段的2个端点一共5个点,构成了新的递归调用的参数集合,我们按照线段路线,将这5个点中任意相邻的两个点作为新的递归参数(一共调用下一层递归4次),如下代码:

void dividLine(aPoint a, aPoint b, int Depth)
{if (Depth == 0){//结束递归}else{aPoint v1 = mix(a, b, 1.0f / 3.0f);aPoint v3 = mix(a, b, 2.0f / 3.0f);aPoint v2 = caculatev2(v1, v3);dividLine(a, v1, Depth - 1);dividLine(v1, v2, Depth - 1);dividLine(v2, v3, Depth - 1);dividLine(v3, b, Depth - 1);}
}

当递归结束后,我们就得到了相应递归层次的Koch分形雪花的所有顶点,接下来我们只要调用glBufferData()等OpenGL的函数,就可以把顶点传到顶点着色器,绘制我们的雪花

一个难点

但上述的实现过程有一个问题:OpenGL顶点着色器在处理顶点的时候,是顺序执行,意思是我们在分形过程中产生的顶点,必须要按顺序插入到待传入的数组中,这样一来我们无法确定数组大小,而来对数组进行数据的插入也是十分麻烦的事情(尽管可以用vector,还是麻烦),而且把所有顶点一口气传入到GPU,我也不知道glDrawArrays()函数该怎么设置才能正确把雪花画出来(设置成GL_TRIANGLE,GL_LINES都不对,有兴趣的可以试试看)。我当时在实现的过程中就卡在这个地方上了,后来我仔细想了想,我完全可以利用递归——因为每次递归到最深层次(即Depth == 0),就代表着当前输入参数的两个端点所构成的线段,是不会继续分形的,而是会直接在屏幕上画出来,与其一口气画出整个雪花,不如在每次判定Depth等于0时,画出线段,这样当整个递归结束后,就自然而然的画出了整个雪花。在查询API的过程中,我发现glBufferData在把输出传到显存前,会先清空显存内容,只保留当前glBufferData要传入的数据。利用这一特性,我们可以在每次(Depth == 0)时,将当前两个端点的位置传入到显存中,并直接调用glDrawArrays()函数(设置成GL_LINES),将线段画出来,因此我们的递归函数就变成了如下:

void dividLine(aPoint a, aPoint b, int Depth)
{if (Depth == 0){float vertices[] = {a.x, a.y, a.z, 1.0f, 1.0f, 1.0f,b.x, b.y, b.z, 0.0f, 0.0f, 0.0f,};glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_DYNAMIC_DRAW);glDrawArrays(GL_LINES, 0, 2);}else{aPoint v1 = mix(a, b, 1.0f / 3.0f);aPoint v3 = mix(a, b, 2.0f / 3.0f);aPoint v2 = caculatev2(v1, v3);dividLine(a, v1, Depth - 1);dividLine(v1, v2, Depth - 1);dividLine(v2, v3, Depth - 1);dividLine(v3, b, Depth - 1);}
}

实验结果

最后的生成结果如下图所示

当初始Depth = 1时:

当初始Depth = 2时:

当初始Depth = 3时:

上图的雪花位置、方向有差异,是因为我们这次的作业要求还包括加入雪花的旋转、位移等。完整的代码如下所示(包括着色器):

#include <glad/glad.h>
#include <Glfw/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>#include <iostream>
#include <cmath>const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec3 aColor;\n"
"out vec3 ourColor;\n"
"uniform mat4 transform;\n"
"void main()\n"
"{\n"
"  gl_Position = transform * vec4(aPos, 1.0f);\n"
"  ourColor = aColor;\n"
"}\0";const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"
"void main()\n"
"{\n"
"  FragColor = vec4(ourColor, 1.0f);\n"
"}\n\0";const int WIDTH = 800;
const int HEIGH = 800;int direction = 1;
float speed = 1.0f;
int depth = 1;
bool leftkeytable = false;
bool rightkeytable = false;
bool leftButtonMouse = false;
double Xtranslate = 0.0f;
double Ytranslate = 0.0f;typedef struct Point {float x;float y;float z;
}aPoint;void framebuffer_size_callback(GLFWwindow* window, int width, int heigh)
{glViewport(0, 0, 800, 800);
}void processInput(GLFWwindow* window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS){leftkeytable = true;}if (glfwGetKey(window, GLFW_KEY_LEFT) != GLFW_PRESS && leftkeytable){leftkeytable = false;depth--;if (depth < 0)depth = 0;}if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS){rightkeytable = true;}if (glfwGetKey(window, GLFW_KEY_RIGHT) != GLFW_PRESS && rightkeytable){rightkeytable = false;depth++;if (depth > 5)depth = 5;}if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS){direction = 1;}if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS){direction = -1;}if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS){speed += 0.1f;if (speed > 3.0f)speed = 3.0f;}if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS){speed -= 0.1f;if (speed < 0.5f)speed = 0.5f;}if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS){double X, Y;glfwGetCursorPos(window, &X, &Y);Xtranslate = (X - WIDTH / 2.0) / 800.0;Ytranslate = (HEIGH / 2.0 - Y) / 800.0;}
}aPoint mix(aPoint a, aPoint b, float length)
{aPoint v;v.x = a.x + (b.x - a.x) * length;v.y = a.y + (b.y - a.y) * length;v.z = 0.0f;return v;
}aPoint caculatev2(aPoint a, aPoint b)
{aPoint v;v.x = b.x - a.x;v.y = b.y - a.y;v.z = 0.0f;aPoint v2;v2.x = v.x * cos(glm::radians(60.0f)) - v.y * sin(glm::radians(60.0f));v2.y = v.x * sin(glm::radians(60.0f)) + v.y * cos(glm::radians(60.0f));v2.x += a.x;v2.y += a.y;v2.z = 0.0f;return v2;
}void dividLine(aPoint a, aPoint b, int Depth)
{if (Depth == 0){float vertices[] = {a.x, a.y, a.z, 1.0f, 1.0f, 1.0f,b.x, b.y, b.z, 0.0f, 0.0f, 0.0f,};glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_DYNAMIC_DRAW);glDrawArrays(GL_LINES, 0, 2);}else{aPoint v1 = mix(a, b, 1.0f / 3.0f);aPoint v3 = mix(a, b, 2.0f / 3.0f);aPoint v2 = caculatev2(v1, v3);dividLine(a, v1, Depth - 1);dividLine(v1, v2, Depth - 1);dividLine(v2, v3, Depth - 1);dividLine(v3, b, Depth - 1);}
}int main()
{glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);GLFWwindow* window = glfwCreateWindow(800, 800, "Learnopengl", nullptr, nullptr);if (window == nullptr){std::cout << "Failed to create window!" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);int success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}unsigned int shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success){glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);aPoint a;a.x = -0.433f;a.y = -0.25f;a.z = 0.0f;aPoint b;b.x = 0.0f;b.y = 0.5f;b.z = 0.0f;aPoint c;c.x = 0.433f;c.y = -0.25f;c.z = 0.0f;unsigned int VAO, VBO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);float angle = 0.0f;while (!glfwWindowShouldClose(window)){processInput(window);glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glm::mat4 trans = glm::mat4(1.0f);trans = glm::translate(trans, glm::vec3(Xtranslate, Ytranslate, 0.0));trans = glm::rotate(trans, glm::radians(angle), glm::vec3(0.0f, 0.0f, 1.0f));angle += direction * (speed);glUseProgram(shaderProgram);unsigned int transformLoc = glGetUniformLocation(shaderProgram, "transform");glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));dividLine(a, b, depth);dividLine(b, c, depth);dividLine(c, a, depth);glfwSwapBuffers(window);glfwPollEvents();}glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glfwTerminate();return 0;
}

感谢观看,顺便吐槽一下,网上的Koch雪花实现都是几年前的了,OpenGL的接口早改了

基于OpenGL的Koch分形雪花实现相关推荐

  1. 基于OpenGL的LS分形演示程序

    实现了一个基于LS(L系统)的二维分形演示程序. 实现这个演示程序的第一步当然是理解LS文法.咋看之下似乎有点难度,其实一点也不难. 举个例子就明白了: LS文法先定义了绘图规则: F:以当前方向前进 ...

  2. 计算机可视化仿真技术opengl,基于OpenGL的三维场景可视化仿真

    摘要: 随着计算机可视化技术的发展,计算机可视化应用的领域不断地拓宽,广泛地应用在科学计算.人工智能仿真.三维图形的制作方面等领域.可视化是计算机技术应用的热门领域,而这个热门领域的核心都集中在三维真 ...

  3. 转-基于OpenGL的3D天空仿真

    在进行3D场景渲染时,天空是必不可少的因素.对于3D天空的模拟在视景仿真系统.计算机游戏.三维动画中有着广泛的应用.但是,目前对于天空的仿真还存在很多不足,一些模拟方法中存在实现复杂.计算耗时.图像分 ...

  4. 基于OpenGL的3D天空仿真

    From:http://www.c-cnc.com/dz/news/news.asp?id=18622 在进行3D场景渲染时,天空是必不可少的因素.对于3D天空的模拟在视景仿真系统.计算机游戏.三维动 ...

  5. 基于OpenGL的地形建模技术的研究与实现

    毕业论文 基于OpenGL的地形建模技术的研究与实现 诚信声明 本人郑重声明:本设计(论文)及其研究工作是本人在指导教师的指导下独立完成的,在完成设计(论文)时所利用的一切资料均已在参考文献中列出. ...

  6. matlab画雪花,使用C++ OpenGL,完成Konx 雪花绘图

    首先 在开始之前,先说一下:如何在Viual Studio (我用的2019)中C++项目中安装OpenGl插件 点击安装即可,全程配置不到20s 使用c++ OpenGL,完成Konx 雪花绘图 # ...

  7. java opengl_java基于OpenGL ES实现渲染实例

    这篇文章主要介绍了java基于OpenGL ES实现渲染,实例分析了OpenGL渲染操作的相关技巧,需要的朋友可以参考下 本文实例讲述了java基于OpenGL ES实现渲染的方法.分享给大家供大家参 ...

  8. OpenGL 持久映射分形的实例

    OpenGL 持久映射分形 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <math.h> #include <omp.h&g ...

  9. 基于opengl的3d漫游游戏 - 古堡危机之丧尸围城

    作品名称: <古堡危机> 小组团队名称: 拾荒三人组 日期:2018年12月 目录 第一章 简介 3 前言 3 项目的创意设想.游戏类型.实现的功能.项目意义 3 Opengl 4 作品代 ...

最新文章

  1. 水货刷XT702官方2.21添加google服务包
  2. 第一周——数据分析之表示 —— Numpy 数据存取与函数
  3. 【Java】 5.6 类的继承
  4. Connected to an idle instance问题的小小仇恨
  5. 快速构建ceph可视化监控系统
  6. 汇编语言(六)之输出字符的前导后字符
  7. Linux 下清空或删除大文件内容的 5 种方法
  8. silverlight 上下标
  9. 互联生活:业务模式聚焦
  10. 工厂模式——三个工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)...
  11. linux使用gcc编译报错“undefined reference to `pthread_create'”
  12. 局域网的传输介质、网线水晶头制作图解教程
  13. getting start with storm 翻译 第六章 part-4
  14. 项目风险管理之风险分析
  15. 通信时代的发展与5G未来的发展方向以及面临的业务挑战
  16. 杭州电子科技大学ACM-1001
  17. Material Design(三)--暗色主题设计
  18. C++11介绍之vector::push_back和vector::emplace_back区别
  19. Windows7+CentOS双系统同一硬盘
  20. Word 插入图片后只剩一点?如何解决

热门文章

  1. 营在微博:企业微博营销实战宝典
  2. thinkphp5(thinkcmf5)2018年12月9日getshell漏洞
  3. 重磅!详解阿里研究院互联网+报告
  4. 移动支付技术崛起 多功能集成的趋势
  5. 【狂神说:秦疆老师】Spring5笔记
  6. JZ18_二叉树的镜像
  7. 未来的搜索大战:生态能力成为胜负手
  8. java大文件加密速度_java版AES文件加密速度问题
  9. dynamic web twain java免费下载及使用方法教程功能详解
  10. 想运行游戏,在Pycharm中安装Pygame库一直报错,我只是忽视了它