Qt OpenGL(03)Sierpinski 镂垫

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版_中文版》一书中的相关内容

源代码是在上书的基础上修改的,书不错,推荐一波。但不适合初学并且没有领路的人摸索。我也不照搬具体内容了,只是作为读书笔记,方便查询。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值