1、Sierpinski 镂垫
谢尔宾斯基三角形(Sierpinski triangle)是一种分形,由波兰数学家谢尔宾斯基在1915年提出。它是自相似集的例子(自相似是近乎或确实和它的一部分相似)
Sierpinski 镂垫是一个有趣的图形,它有很长的历史,在分形几何这样的领域中是重要的研究对象。我们可以按递归和随机的方式来定义 Sierpinski 镂垫,但当迭代的次数趋于无限时,它的性质是完全确定的,并不随机。我们从Sierpinski 镂垫的二维版本开始。
2、随机法
假定我们从空间中的三个点开始。只要这些点不是共线的,它们就定义了一个三角形,而且也定义了一个平面。假定这个平面是z=0,并且这些点在某个方便的坐标系下的坐标是 v 1 v_1 v1(x1,y1, 0), v 2 v_2 v2(x2, y2, 0)和 v 3 v_3 v3(x3, y3, 0)。
Sierpinski镂垫的构造过程如下(Chaos Game):
- 1)在三角形内随机选择一个初始点 p 0 p_0 p0
- 2)随机选择3个顶点之一。( 比如: v 1 v_1 v1)
- 3)找出 p 0 p_0 p0 和随机选择的这个顶点之间的中点 p 1 p_1 p1
- 4)在显示器上把这个中点 p 1 p_1 p1 所对应的位置画出来
- 5)用这个中点 p 1 p_1 p1替换 p 0 p_0 p0
- 6)转步骤2
图片来自于《交互式计算机图形学 基于OpenGL着色器的自顶向下方法 第6版_中文版》
该书在附录部分有opengl 的源代码,简单用 qt 的类替换修改了下。
2.1 相关代码
在基础 Qt OpengGL 框架(见 【Qt OpenGL(01) 概述】)的代码基础上 修改Widget.cpp的源代码,如下
#include "Widget.h"
#include "qobject.h"
#include "qvector2d.h"
#include <QDebug>
#define qout if( 1 ) qDebug() << __FILE__ << __LINE__ << ": "
#include <QRandomGenerator>
#include <QThread>
#define qRandom QRandomGenerator::global ()
constexpr int NumberPoints = 5000;
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
setWindowTitle ("02_SiepinskiTriangle");
resize (300,300);
}
Widget::~Widget()
{
makeCurrent ();
glDeleteBuffers (1,&VBO);
glDeleteVertexArrays (1,&VAO);
doneCurrent ();
}
void Widget::initializeGL()
{
initializeOpenGLFunctions ();
const char *version =(const char *) glGetString (GL_VERSION);
qout << QString(version);
QVector2D points[NumberPoints] ;
// 为三角形指定顶点
QVector2D vertices[3] = {
QVector2D(-1.0,-1.0),
QVector2D( 0.0, 1.0),
QVector2D( 1.0,-1.0)
};
// 在三角形内部选择一个任意的初始点
points[0] = QVector2D(-0.0,-0.5);
// 计算并存储 N-1 个新顶点
for (int i = 1; i < NumberPoints; ++i) {
int j = qRandom->bounded (3);
// 计算随机选择的顶点和之前的顶点之间的中点
points[i] = (points[i-1] + vertices[j]) / 2.0;
}
glGenBuffers (1,&VBO);
glBindBuffer (GL_ARRAY_BUFFER,VBO);
glBufferData (GL_ARRAY_BUFFER,sizeof(points),points,GL_STATIC_DRAW);
glGenVertexArrays (1,&VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex, ":/shader.vert");
shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment,":/shader.frag");
shaderProgram.link ();
glBindBuffer (GL_ARRAY_BUFFER,0);
glPolygonMode (GL_FRONT_AND_BACK,GL_LINE);
shaderProgram.bind ();
shaderProgram.setUniformValue ("u_color",1.0f, 0.5f, 0.2f, 1.0f);
}
int count = 1;
void Widget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置背景色
glClear(GL_COLOR_BUFFER_BIT); // 填充背景色
// 绘制三角形扇形
glPointSize (5.0f);
glDrawArrays (GL_POINTS,0,count);
count += 50; // 简单加入刷新频率,显示绘制过程,根据需要调整大小
if(count < NumberPoints) {
QThread::currentThread ()->msleep (50);
update ();
}
}
2.2 运行结果如图所示:
3、递归法
构造过程如下(Chaos Game):
-
1.取一个实心的三角形。
-
2.沿三边中点的连线,将它分成四个小三角形。
-
3.去掉中间的那一个小三角形。
-
4.对其余三个小三角形重复1。
取一个正方形或其他形状开始,用类似的方法构作,形状也会和谢尔宾斯基三角形相近。
我们可以用一个递归程序实现上面的这个过程。下面从一个简单的函数开始,这个函数把指定三角形的三个顶点放入points数组中:
void triangle(point2 a, point2 b, point2 c) {
static int i = 0;
points[i] = a; ++i;
points[i] = b; ++i;
points[i] = c; ++i;
}
递归函数用来分型
void divide_triangle(point2 a, point2 b, point2 c, int k) {
if(k > 0 )
{
point2 ab = (a+b)/2.0;
point2 ac = (a+c)/2.0;
point2 bc = (b+c)/2.0;
// 除了中间的那个三角形,继续细分其它三角形
divide_triangle (a,ab,ac,k-1);
divide_triangle (c,ac,bc,k-1);
divide_triangle (b,bc,ab,k-1);
}
else
triangle (a,b,c); // 把三角形的顶点放入数组
}
3.1 相关代码
Widget.cpp
#include "Widget.h"
#include "Helper.hpp"
#include <QThread>
#include <QDebug>
#include <QRandomGenerator>
#define qRandom QRandomGenerator::global ()
#define qout if( 1 ) qDebug() << __FILE__ << __LINE__ << ": "
constexpr int NumTimesToSubdivide = 6;
int NumTriangles;
QVector2D *points=0 ;
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
setWindowTitle ("03_Siepinski_Recursion");
resize (300,300);
NumTriangles = std::pow(3,NumTimesToSubdivide+1);
qout << NumTriangles;
points = new QVector2D[NumTriangles];
}
Widget::~Widget()
{
makeCurrent ();
glDeleteBuffers (1,&VBO);
glDeleteVertexArrays (1,&VAO);
doneCurrent ();
delete [] points;
}
void Widget::initializeGL()
{
initializeOpenGLFunctions ();
const char *version =(const char *) glGetString (GL_VERSION);
qout << QString(version);
// 为三角形指定顶点
QVector2D vertices[3] = {
QVector2D(-1.0,-1.0),
QVector2D( 0.0, 1.0),
QVector2D( 1.0,-1.0)
};
// 对初始的三角形进行细分
divide_triangle (vertices[0],vertices[1],vertices[2],NumTimesToSubdivide);
glGenBuffers (1,&VBO);
glBindBuffer (GL_ARRAY_BUFFER,VBO);
glBufferData (GL_ARRAY_BUFFER,sizeof(QVector2D) * NumTriangles,points,GL_STATIC_DRAW);
glGenVertexArrays (1,&VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex, ":/shader.vert");
shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment,":/shader.frag");
shaderProgram.link ();
glBindBuffer (GL_ARRAY_BUFFER,0);
// glPolygonMode (GL_FRONT_AND_BACK,GL_LINE);
shaderProgram.bind ();
shaderProgram.setUniformValue ("u_color",1.0f, 0.5f, 0.2f, 1.0f);
}
int count = 3;
void Widget::paintGL()
{
static bool firstRun = true;
if(firstRun) {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置背景色
glClear(GL_COLOR_BUFFER_BIT); // 填充背景色
firstRun = false;
}
// 绘制三角形扇形
glDrawArrays (GL_TRIANGLES,0,count);
if(count < NumTriangles) {
count += 6;
QThread::currentThread ()->msleep (50);
update ();
}
}
Helper.hpp
#ifndef HELPER_H
#define HELPER_H
#include <QVector2D>
typedef QVector2D point2;
extern QVector2D *points ;
void triangle(point2 a, point2 b, point2 c) {
static int i = 0;
points[i] = a; ++i;
points[i] = b; ++i;
points[i] = c; ++i;
}
void divide_triangle(point2 a, point2 b, point2 c, int k) {
if(k > 0 )
{
point2 ab = (a+b)/2.0;
point2 ac = (a+c)/2.0;
point2 bc = (b+c)/2.0;
// 除了中间的那个三角形,继续细分其它三角形
divide_triangle (a,ab,ac,k-1);
divide_triangle (c,ac,bc,k-1);
divide_triangle (b,bc,ab,k-1);
}
else
triangle (a,b,c);
}
#endif // WIDGET_H
2.2 运行画面
总结
使用基本图元绘制 2D 的 谢尔宾斯基三角形,至于 3D 部分可以用来另水一篇。
图片来自《交互式计算机图形学 基于OpenGL着色器的自顶向下方法 第6版_中文版》一书中的相关内容
源代码是在上书的基础上修改的,书不错,推荐一波。但不适合初学并且没有领路的人摸索。我也不照搬具体内容了,只是作为读书笔记,方便查询。