基于BSP、L-System的树木模拟

基于BSP、L-System的树木模拟
——《基于分形、DEM、DOM和LOD自适应渲染
策略的三维虚拟现实系统.net》项目研发报告
华南理工大学微软俱乐部虚拟现实开发团队 梁成 刘青 张嘉华 罗文杰

摘要:在虚拟现实系统中,要实现各种自然场景的生成,其中树木、花草等自然景物的生成和模拟,是整套虚拟现实系统的重要组成部分,本文介绍了二叉树、L-System两种算法,对两种算进行改进、合并,用以生成树木,并给出了基于Visual Studio.net、FrameWork1.1和GDI+的关键代码。

关键词:二叉树、L-System、递归、字符串替换、GDI+
1. 引言
在虚拟现实系统中,用户可选择“树木编辑”让计算机在生成的场景中加入树木。因此,系统要求用较为简单的方法生成树木,并且尽量节省时间和空间。我们研究了最基本的二叉树算法及比较流行的L-System算法,并对两种算法进行了改进和合并,生成了几种较为逼真的树木。

2. 二叉树随机植物生成
二叉树(BSP)是迭代和递归中最基本的算法之一。简单描述如下:
void tree(iter)
{
  画树干;
  左转;
tree(iter-1); //画左子树
右转;
  tree(iter-1); //画右子树
}
n=0   n=1     n=2     n=3   n=4

 
其中画子树的过程即递归的过程。iter表示递归的深度(从n递减到0)。

将二叉树进行一些修改,也能生成简单的二维树。

 
首先,生成的枝干更加复杂(如图)。其中侧枝较多的分枝出现在树干的左边还是右边是随机决定的。
其次,分枝的偏转角得一定的随机幅度;当iter较大,即处于较为底层的状态时,分枝的偏转角的随机幅度也应较小,使树木看起来长势集中些。
再次,应考虑树木生长时的顶端优势。当分枝与最初主干之间的夹角较小时,可以在这个分枝上画两个子树,使分枝看起来更长。
最后,考虑树干的粗细及叶子。随着iter的递减,树干逐渐变细;当iter小于1时,画叶子。

图1

图2

 
在末端画上叶子,还可以有其他效果。

3. L-System随机植物生成
L-System是指美国生物学家Lindenmayer提出的植物生长的数学模型。它描述了树枝的生长过程:从一条树枝(种子)开始,发出新的芽枝,而发过芽枝的枝干又都发新芽枝……
L-System的核心思想是“字符串的替换”。它通过产生一系列字符串来描述分形树木图形特征。我们以一种灌木的L-System为例:
该类灌木的L-System的原表达式为
L=〈G,W,P〉
其中G是字符集,这里G={F,+,-,[,]};W是起始符号元,用以确定字符串的初始状态,且W∈G, 此处W=“F”;P为该类植物的生成规则集,即替换法则,这里
P={F→FF+[+F-F-F]-[-F+F+F],+→+,-→-,[→[;]→]} 
以”F”为种子,经过多次迭代,可以生成描述一颗树的字符串:
 
深度 L-System产生的字符串   
1 F   
2 FF+[+F-F-F]-[-F+F+F]   
3 FF+[+F-F-F]-[-F+F+F]FF+[+F-F-F]-[-F+F+F]+[+FF+[+F-F-F]-[-F+F+
F]-FF+[+F-F-F]-[-F+F+F]-FF+[+F-F-F]-[-F+F+F]]-[-FF+[+F-F-F]-[-F
+F+F]+FF+[+F-F-F]-[-F+F+F]+FF+[+F-F-F]-[-F+F+F]]   
… … 

下面为每个字符定义命令:
F——当前方向向前走一步;
[——将当前状态压栈保存;
]——将栈中状态弹出,即恢复原来状态;
+——由当前方向顺时针转δ角;
-——由当前方向逆时针转δ角.
有了字符串,我们可以用简单的递归把树画出来,算法简述如下:
void tree(iter)
{
read a character to ch;
while(ch!=')'} and (ch!=NULL) do
{
switch (ch)
case 'F':画线;
case '+':顺时针转;
case '-':逆时针转;
case '[':turtle (iter-1);
}
}

 
其中的iter表示深度,在该类灌木中与迭代次数相同。
n=0     n=1      n=2      n=3

可是这样生成的树效果并不理想,因为它的树枝和树叶没有区别(也可以说只有树枝没有树叶),生成的树也千篇一律。其它论文中一般的解决办法是:为每次迭代生成字符赋上不同的参数,但那样要耗去大量的时间和空间,不符合要求。

为此,我们对以上的算法做了一些小修改。
首先,在字符串“FF+[+F-F-F]-[-F+F+F]”中,我们把前两个“F”看成树干,用“G”表示,字符串就成了“GG+[+F-F-F]-[-F+F+F]”,“F”和“G”的替换相同。在接下来画树的过程中,把“F”画成树叶,把“G”画成树干,并且让树干随iter的递减而变细。
在样式方面,我们准备了三种替换方案,在字符串替换时随机选择方案。
这样,生成的树就生动多了。
图3

 

4. 两种算法的合并
仔细研究,不难发现:L-System算法实际上就是二叉树算法的推广,L-System的树也可以用二叉树式的递归生成。

把两种算法的思想合在一起,可以用以生成一些规律性较强且较复杂的树,例如一颗类似圣诞树的树。
算法简述如下:
void tree(iter)

 
{
 画树干;
 将画笔位置移到树干顶部;
for i=1 to layer
 {
左转;
tree(iter-1); //画左子树
右转;
  tree(iter-1); //画右子树
  将画笔位置沿树干下移;
 }
}

 
其中的iter表示深度,layer表示树的层数。

同样,我们也可做些改进:
首先,树干随着iter的递减逐渐变细;当iter小于1时画叶子。
其次,在左子树与右子树这间多添加几个子树,即tree(iter-1)。
还有,当i较小即正在画上层较小的树时,少递归一层,写为tree(iter-2),因为上层较小,可以画得简单些。
图4

5. 基于GDI+的树木绘制
我们采用Visual Studio.net作为集成环境,采用GDI+作为图形接口,FrameWork1.1把GDI+封装为Windows.Drawing命名空间。在我们的实验代码中,我们的算法是基于直角坐标系的,在具体的画线过程中,由于屏幕坐标系相对与我们生成的树木是倒立的,所以我们需要自己编写一个基于正立坐标系的DrawLine函数,在函数体内调用GDI+的内置DrawLine函数之前,根据屏幕大小,把树木图形翻转。具体的DrawLine函数代码如下:
Private Sub DrawLine(ByVal width As Short, ByVal x1 As Short, ByVal y1 As Short, ByVal x2 As Short, ByVal y2 As Short, ByVal col As Color)

       y1 = Height - y1
       y2 = Height - y2

        Dim pen As New Pen(col)
        pen.Width = width
        gdi.DrawLine(pen, x1, y1, x2, y2)

End Sub
6. 具体的实现代码
以上方法已经应用在我们的实践项目之中,语言为visual basic.net,需要framework1.1支持,下面给出代码的关键部分:

    Dim gdi As Graphics
    Const pi = 3.1415926

    Private Function Rand(ByVal min As Single, ByVal max As Single) As Single
        '返回min至max之间的随机数
        'On Error resume next
        Rand = Rnd() * (max - min) + min
    End Function

    Private Sub DrawLine(ByVal width As Short, ByVal x1 As Short, ByVal y1 As Short, ByVal x2 As Short, ByVal y2 As Short, ByVal col As Color)

       y1 = Height - y1
       y2 = Height - y2

        Dim pen As New Pen(col)
        pen.Width = width
        gdi.DrawLine(pen, x1, y1, x2, y2)

    End Sub


    Private Sub leaf(ByVal x As Short, ByVal y As Short, ByVal angle As Single, ByVal xstep As Single)
        Dim i As Short
        Dim x1 As Single, y1 As Single
        Dim col1 As Color

        angle = angle / 180 * pi
        Dim t As Single = -10 / 180 * pi
        For i = 1 To 3
            col1 = Color.FromArgb(255, 90 - i * 15, 80 + i * 30, 130 - i * 30)
            x1 = x + Math.Cos(t + angle) * xstep
            y1 = y + Math.Sin(t + angle) * xstep
            DrawLine(1, x, y, x1, y1, col1)

            x1 = x + Math.Cos(-t + angle) * xstep
            y1 = y + Math.Sin(-t + angle) * xstep
            DrawLine(1, x, y, x1, y1, col1)

            t = t - 20 / 180 * pi
        Next

    End Sub


'------------------------------------------------------------------------------
'用二叉树算法递归生成如图1的树,其中树叶用线段代替
'------------------------------------------------------------------------------
    Friend Sub GenerateTree(ByVal iter As Short, ByVal angle As Single, ByVal xstep As Single, ByVal x As Single, ByVal y As Single)

        Dim col1 As Color
        '区别树干和树叶的不同颜色
        If iter <= 2 Then

            If iter = 1 Then
                col1 = Color.FromArgb(200, 60, 50, 70)

            Else
                col1 = Color.FromArgb(255, 30, 170, 50)
            End If

        Else
            col1 = Color.FromArgb(200, 200, 80, 50)
            '若树干角度过大或过小,则剪枝
            If (angle > 180) Or (angle < 0) Then
                Exit Sub
            End If
        End If

        Dim rt As Single, x1 As Single, y1 As Single, t As Single
        '画树叶
        If iter = 0 Then
            t = angle / 180 * pi
            x1 = x + Math.Cos(t) * xstep
            y1 = y + Math.Sin(t) * xstep
            '画两种粗细不同的树叶
            If xstep > 2 Then
                DrawLine(4, x, y, x1, y1, col1)
            Else
                DrawLine(1, x, y, x1, y1, col1)
            End If

            Exit Sub
        End If

        '画主干
        t = angle / 180 * pi
        x1 = x + Math.Cos(t) * xstep
        y1 = y + Math.Sin(t) * xstep
        DrawLine(iter, x, y, x1, y1, col1)
        x = x1
        y = y1

        Dim style As Short = 0

        '随机决定较多侧枝杈在主干的左边或右边
        Dim rstyle As Single
        rstyle = Rand(0, 1)
        If rstyle < 0.5 Then
            style = 1
        Else
            style = -1
        End If

        'Do While style = 0
        'style = Int(Rnd() * 2 - 1)
        'Loop

        '最开始时枝杈的偏转角随机度较小
        '画较少侧枝的枝干
        If iter >= 5 Then
            GenerateTree(iter - 1, angle + (30 * style) + Rand(-15, 15), xstep * 0.6, x1, y1)
        Else
            GenerateTree(iter - 1, angle + (30 * style) + Rand(-30, 30), xstep * 0.6, x1, y1)
        End If

        If (angle > 80) And (angle < 100) And (iter > 4) Then
            '有顶端优势
            GenerateTree(iter - 1, angle, xstep * 0.7, x1, y1)
            t = angle / 180 * pi
            x1 = x + Math.Cos(t) * xstep * 0.8
            y1 = y + Math.Sin(t) * xstep * 0.8
            DrawLine(iter, x, y, x1, y1, col1)
            x = x1
            y = y1
            GenerateTree(iter - 1, angle, xstep * 0.7, x1, y1)

        Else
            GenerateTree(iter - 1, angle, xstep * 0.7, x1, y1)
        End If

        '侧枝较小
        xstep = xstep * 0.75

        '最开始时枝杈的偏转角随机度较小
        If iter >= 5 Then
            rt = angle - 30 * style + Rand(-15, 15)
        Else
            rt = angle - 30 * style + Rand(-30, 30)
        End If

        '画较多侧枝的枝干
        t = rt / 180 * pi
        x1 = x + Math.Cos(t) * xstep
        y1 = y + Math.Sin(t) * xstep
        DrawLine(iter, x, y, x1, y1, col1)
        GenerateTree(iter - 1, rt, xstep * 0.5, x, y)
      
        GenerateTree(iter - 1, rt, xstep * 0.7, x1, y1)
        GenerateTree(iter - 1, rt - 30 * style + Rand(-15, 15), xstep * 0.7, x1, y1)

    End Sub


'------------------------------------------------------------------------------
'用二叉树算法递归生成如图2的树,其中树叶leaf函数生成针叶状
'------------------------------------------------------------------------------
    Friend Sub GenerateTree1(ByVal iter As Short, ByVal angle As Single, ByVal xstep As Single, ByVal x As Single, ByVal y As Single)
        'on error resume next


        Dim col1 As Color = Color.FromArgb(200, 200, 80, 50)

        If iter <= 1 Then
            '画树叶
            leaf(x, y, 90 + (angle - 90) / 2, 8 + xstep / 10)

            Exit Sub

        Else
            If (angle > 180) Or (angle < 0) Then
                Exit Sub
            End If
        End If

        Dim rt As Single, x1 As Single, y1 As Single, t As Single

        t = angle / 180 * pi
        x1 = x + Math.Cos(t) * xstep
        y1 = y + Math.Sin(t) * xstep
        DrawLine(iter, x, y, x1, y1, col1)
        x = x1
        y = y1

        Dim style As Short = 0
        Dim rstyle As Single
        rstyle = Rand(0, 1)

        If rstyle < 0.5 Then
            style = 1
        Else
            style = -1
        End If

        'Do While style = 0
        'style = Int(Rnd() * 2 - 1)
        'Loop

        If iter >= 4 Then
            GenerateTree1(iter - 1, angle + (40 * style) + Rand(-15, 15), xstep * 0.6, x1, y1)
        Else
            GenerateTree1(iter - 1, angle + (30 * style) + Rand(-30, 30), xstep * 0.6, x1, y1)
        End If

        If (angle > 80) And (angle < 100) And (iter > 3) Then
            GenerateTree1(iter - 1, angle, xstep * 0.7, x1, y1)
            t = angle / 180 * pi
            x1 = x + Math.Cos(t) * xstep * 0.8
            y1 = y + Math.Sin(t) * xstep * 0.8
            DrawLine(iter, x, y, x1, y1, col1)
            x = x1
            y = y1
            GenerateTree1(iter - 1, angle, xstep * 0.7, x1, y1)

        Else
            GenerateTree1(iter - 1, angle, xstep * 0.7, x1, y1)
        End If

        xstep = xstep * 0.75

        If iter >= 4 Then
            rt = angle - 40 * style + Rand(-15, 15)
        Else
            rt = angle - 30 * style + Rand(-30, 30)
        End If

        If (rt > 180) Or (rt < 0) Then
            Exit Sub
        End If

        t = rt / 180 * pi
        x1 = x + Math.Cos(t) * xstep
        y1 = y + Math.Sin(t) * xstep
        DrawLine(iter, x, y, x1, y1, col1)
        GenerateTree1(iter - 1, rt, xstep * 0.5, x, y)

        GenerateTree1(iter - 1, rt, xstep * 0.5, x1, y1)
        GenerateTree1(iter - 1, rt - 30 * style + Rand(-15, 15), xstep * 0.5, x1, y1)

    End Sub

 

'------------------------------------------------------------------------------
'用二叉树和L-system算法递归生成如图4的树
'------------------------------------------------------------------------------
    Friend Sub GenerateTree2(ByVal iter As Short, ByVal angle As Single, ByVal xstep As Single, ByVal x As Single, ByVal y As Single)
        'on error resume next


        Dim col1 As Color
        Dim rt As Single, x1 As Single, y1 As Single, t As Single, left As Single, right As Single
        Dim i As Short
        '若角度过大或过小则剪枝
        If (angle > 170) Or (angle < 10) Then
            Exit Sub
        End If
        If iter <= 1 Then
            '控制叶子的长度
            xstep = xstep * 0.8
            If xstep > 13 Then xstep = xstep * 0.8

            col1 = Color.FromArgb(200, 30, 150, 80)
            t = angle / 180 * pi
            x1 = x + Math.Cos(t) * xstep
            y1 = y + Math.Sin(t) * xstep
            DrawLine(3, x, y, x1, y1, col1)
            Exit Sub

        Else

            col1 = Color.FromArgb(200, 200, 80, 50)

        End If

        Dim pp As Single = 0.75 '每层缩小的比例
        '计算缩小的总比例
        Dim prop As Single = 1
        For i = 1 To 5
            prop = prop * pp + pp
        Next

        prop = prop + 0.5
        '根据步长和比例求出第一层的长度
        Dim brach As Single = xstep / prop
        '画主干
        For i = 1 To 5
            t = angle / 180 * pi
            '最底层较短
            If i = 1 Then
                x1 = x + Math.Cos(t) * brach * 0.4
                y1 = y + Math.Sin(t) * brach * 0.4
            Else
                x1 = x + Math.Cos(t) * brach
                y1 = y + Math.Sin(t) * brach
            End If
            DrawLine(iter - i + 3, x, y, x1, y1, col1)
            x = x1
            y = y1
            brach = brach * pp
        Next
        '最顶的分枝
        GenerateTree2(iter - 2, angle, brach * 1.5, x, y)

        For i = 1 To 4
            brach = brach / pp
            t = angle / 180 * pi
            x = x - Math.Cos(t) * brach
            y = y - Math.Sin(t) * brach
            left = angle + 60 '最左枝的角度
            right = angle - 60 '最右枝的角度
            '从左往右画
            While left > right
                '较顶的分枝较简,较短
                If i <= 2 Then
                    GenerateTree2(iter - 2, left, brach * 1.7, x, y)
                Else
                    GenerateTree2(iter - 1, left, brach * 2, x, y)
                End If
                left = left - 40 + Rand(-15, 15)
            End While
            '画最右枝
            If i <= 2 Then
                GenerateTree2(iter - 2, right, brach * 1.7, x, y)
            Else
                GenerateTree2(iter - 1, right, brach * 2, x, y)
            End If
        Next

    End Sub
    Private strtree As String
    Private str_len As Long
    Private piont As Long = -1

'------------------------------------------------------------------------------
'用L-system算法递归生成如图4的树
'------------------------------------------------------------------------------
    Private Sub GenerateLSystemTree()
        gdi = Me.PictureBox1.CreateGraphics
        Dim pen As New Pen(Color.Black)
        Dim depth As Integer = 4
        Dim xStep As Single
        Dim Width As Single
        Dim Height As Single
        Dim Visable As Boolean
        Dim pic As Bitmap

        '生成字符串

        '三种替换方案,其中G为树干,F,为树叶
        Const re11 As String = "GG+[+F-F-F]-[-F+F+F]"
        Const re21 As String = "GG-[-F+F+]+[+F-F-F]"
        Const re31 As String = "GG-[-F+F+F]+[+F-F-F]"
        strtree = re31 '种子"F",想限定树的生长偏向同一侧则可规定第一次替换的方案
        Dim style As Single '生成随机样式
        Dim i As Byte, j As Long, ch As Char, change As Char
        For i = 1 To depth
            str_len = strtree.Length
            For j = 0 To str_len - 1
                ch = strtree.Chars(j) '每次读一个字符
                If (ch = "F") Or (ch = "G") Then
                    '先用ABC代替替换方案
                    style = Rand(0, 3)
                    Select Case style
                        Case 0 To 1
                            change = "A"
                        Case 1 To 2
                            change = "B"
                        Case 2 To 3
                            change = "C"
                    End Select
                    strtree = strtree.Insert(j, change)
                    strtree = strtree.Remove(j + 1, 1)
                End If

            Next
            '字符替换
            strtree = strtree.Replace("A", re11)
            strtree = strtree.Replace("B", re21)
            strtree = strtree.Replace("C", re31)
            'strtree = strtree.Replace("G", "GG")

        Next

        piont = -1 '字符串指针初始化
        '画树干
        Dim x As Single = 200
        Dim y As Single = 50
        Dim x1 As Single, y1 As Single
        Dim col1 As Color = Color.FromArgb(255, 200, 80, 60)
        x1 = x + Math.Cos(pi / 2) * 60
        y1 = y + Math.Sin(pi / 2) * 60
        DrawLine(6, x, y, x1, y1, col1)
        x = x1
        y = y1

        Lsystemtree(depth - 1, 90, x, y - 20)

    End Sub

    Friend Sub Lsystemtree(ByVal iter As Short, ByVal angle As Single, ByVal x As Single, ByVal y As Single)
        'on error resume next
        Dim col1 As Color
        Dim x1 As Single, y1 As Single, t As Single
        Dim ch As Char
 
        While (ch <> "]") And (piont < str_len - 1) '推栈或字符串已读完
            '读一个字符
            piont = piont + 1
            ch = strtree.Chars(piont)

            Select Case ch
                Case "F" '画树叶
                    col1 = Color.FromArgb(255, 30, 150, 90)
                    t = angle / 180 * pi
                    x1 = x + Math.Cos(t) * 4
                    y1 = y + Math.Sin(t) * 4
                    DrawLine(3, x, y, x1, y1, col1)
                    x = x1
                    y = y1
                Case "G" '画树干
                    col1 = Color.FromArgb(255, 200, 80, 60)
                    t = angle / 180 * pi
                    x1 = x + Math.Cos(t) * 4
                    y1 = y + Math.Sin(t) * 4
                    DrawLine(iter, x, y, x1, y1, col1)
                    x = x1
                    y = y1
                Case "+"
                    angle = angle + 30
                Case "-"
                    angle = angle - 30
                Case "[" '压栈
                    Lsystemtree(iter - 1, angle, x, y)
            End Select

        End While

    End Sub

'------------------------------------------------------------------------------
'各函数的调用,括号内为建议实参
'------------------------------------------------------------------------------
GenerateTree(6, 90, 60, 200, 50)           '生成如图1的树
GenerateTree1(5, 90, 60, 200, 50)          '生成如图2的树
GenerateTree2(4, 90, 250, 200, 50)         '生成如图4的树
GenerateLSystemTree()                      '生成如图3的树

 


你可能感兴趣的:(基于BSP、L-System的树木模拟)