Grasshopperで作る自然な樹木形状

Grasshopperで作る自然な樹木形状Natural tree shape created with Grasshopper

発想 idea

先日、家にオリーブの木が届きました。

引っ越し祝いに職場の方から頂いたものですが、育て方を調べている過程で、オリーブというのは1000年以上も生きることができる「長寿の木」であることを知りました。世界最古のオリーブは、ギリシャのクレタ島に生えているOlive tree of Vouvesと呼ばれている古代のオリーブであり、樹齢は少なくとも2000年から3000年であると言われています。我が家のベランダに置いたオリーブの木を眺めながら、「私だけでは1000年も育てることはできないけど、今後どんなふうに成長するのだろうか」と空想にふけりました。

話は変わって、弊社でも使用している3D CADであるRhinocerosでは、再帰性処理による構造作成の例として、良く樹形のモデリングがチュートリアルに使用されます。下図は、サンプルコードがたくさん載っている書籍[1]の例に従って再帰性処理により作成した樹形になります。このように、先端に向けて相似形状を繰り返しながら生成された構造を「フラクタル構造」と呼びます。

フラクタル構造は、幾何的な形状であり、大変美しく見えますが、一方で自然に生えている木と比較すると何か違和感を覚えます。今回は、再帰性処理による構造作成の方法をおさらいしながら、より自然な樹形を目指して、RhinocerosのGrasshopper上で樹木形状のモデリング環境を作成してみたいと思います。

調査 research

自然な樹木の幹形状に関する研究は、コンピュータグラフィックス(CG)の発達とともに、研究が進んできたテーマです。これらの研究のモチベーションは、建築計画における景観シミュレーション、映画、マルチメディアコンテンツ、バーチャルリアリティー等で、見た人にリアルな体験を与えることです。昨今、話題となっているメタバースが普及すれば、人間が違和感なく自然と感じられる要素を表現する方法は、より重要になってくる気がしますね。

自然な樹木生成についての文献としては、以下の論文を参考にさせていただきました。

金丸ら[2]は、葉がなるべく光を受けるように枝先の方向が変化する「向日性」による枝先方向の修正モデルを適用し、自然な樹木に見られる樹冠や枝垂れの性質を再現しました。

吉澤ら[3]は、伸長指向と葉面積拡張指向の2つの分枝モデルを組み合わせるとともに、群生による樹木同士の成長における相互作用を検証しました。

同じく、吉澤ら[4]は、植物の生育する環境である地形や周囲の建物がつくる影日向の分布をマッピングし、生育環境に適応して樹木が成長する様子を再現しました。

大志田[5]は、目に見える地上部だけではなく、地中の根の成長モデルと合わせて成長シミュレーションを行い、相互作用による成長への影響を検討しました。また、質点ーバネモデルを用いた外力による樹木へのストレス値(=応力)により樹木の幹が肥大化する現象を表現し、樹齢の長い樹木の印象を与える方法を模索しました。

これらの文献をもとに重要と思われる考え方を以下の3点に整理しました。

  • 分枝モデル:世代が進むごとに、枝がどのように分岐して伸びていくのかを定義する。
  • 向日性:光の取り合いにより、枝同士、樹木同士が相互に影響を受け自然に分布するとともに、場合によっては枝垂れし、下方向の空間に光を求めて枝が伸びる。
  • 幹の太さ:幹の太さは、その幹に与えられるストレス値(=応力)の大きさによって決定する。

構築 build

それでは、樹木のモデリングを始めていきましょう。モデリングはRhinocerosにデフォルトで付属したプラグインであるGrasshopper上で実行していきます。Grasshopperとはなにか?については、過去に記事で取り上げているので、より深く知りたい方はこちら(【Rhino+Grasshopperを理解して魔改造しよう】)の記事もおすすめです。

Anemone

元の枝の情報を使って新しい枝の形状を決定する再帰処理を、Grasshopper上で実行するために最初にご紹介した書籍[1]でも使用されていた、Grasshopperプラグインである「Anemone」を導入します。

Grasshopperでは、入力に対して一定の処理をして出力する機能を持った、様々なコンポーネントをつないでいくことで複雑な処理を可能にします。これらのコンポーネントは、左から入力して、右から出力するというお約束があり、左から順に処理されていくため、出力を再度利用するループを作ってしまうと自己参照が発生してしまいデフォルトの状態では、エラーになってしまいます。そのため、再帰処理をデフォルトのまま書こうとすると、同じコンポーネントのセットを繰り返し記述する必要があるため、右へ右へとコードが伸びていってしまいます。

「Anemone」を使用すると、以下のサンプル画像のようにループによる自己参照が簡潔に記述可能となり、周回ごとに前ステップの数値を参照することができます。今回は、このAnemoneによるループ1周期を1世代とし、世代ごとに元の枝の先端に新しい枝が分岐して伸びるという処理を繰り返します。

二分枝モデル

実際の植物を観察すると枝分かれの方法は多岐にわたります。枝の生え方にルールなどないように見えますが、根本となる幹が1本であり、先に向かうほど枝の数が増えていくのが自然でしょう。この性質を最も単純に表したのが、2分枝モデルであると言えます(下図、文献[2]より引用)。このモデルでは、元の枝を表すラインが乗る平面を定義し、その平面上で元の枝の先端から一定の角度、一定の長さで新しい枝が2本発生するという方法を繰り返すことで樹木の成長を表現します。

向日性

単純な二分枝モデルを採用して、Anemoneを用いた再帰性処理により8世代まで計算を行って出力した樹形が下図になります。この時点では、まったく自然な樹木には見えません。茶道で使う茶筅のように見えます。原因は、周りに十分なスペースがあるのになぜか枝が密集して規則正しく並んでいるためです。

向日性を追加しない樹木モデル

そこで文献[2]で提案されている向日性モデルを採用します。このモデルでは、新たに生成されたすべての枝の先端に葉球とよばれる日差しを遮る領域を定義し、ある枝先が全方向から受ける光のベクトルを合計して得られる受光ベクトルの方向をもとに、枝先の方向修正します。ある枝先から見た時に、ほかの枝先がある方向は葉球にが光を遮るため、そちらの方向には伸びたくないという力が働きます。これにより、それぞれの枝が、光を多く受けられる位置を探索し、枝先の密集を回避する向日性を表現することができます。

ベースとなる日照量は、上半球の光が強く、周囲や地面からの反射光を想定している下半球は0.7倍程度の光量としています。したがって、周りに何もない自由な空間であれば枝先は徐々に直上に向かって伸びる性質を持ちます。
2分枝モデルに向日性を追加して第8世代まで計算を行って出力した樹形が下図になります。大分マシになりましたが、まだ少し不自然です。

受光ベクトルによる新枝方向修正のイメージ

向日性を追加した樹木モデル

ランダム枝枯れ

向日性を入れたとしても、1本の垂直幹からシミュレーションを開始すると、完全な対照形状の樹木が成形されてしまいます。実際の自然では、風や雨などの外部要因により、傷ついた枝が枯れてしまうこともあるでしょう。ここでは、一定の確率で新枝が枯れてしまうランダム性を導入し、樹木に非対称性を与えて自然な樹形を再現します。枝が枯れることによって、そのスペースには光が余ることになるので、向日性により、周りの枝が集まってきてそのスペースを埋めるような挙動が見られるはずです。

向日性とランダム枝枯れを追加して生成した樹木が下図になります。どうでしょうか、なかなか自然な枝ぶりになってきたのではないかと思います。さらに2本の樹木を同時に生やしてみると、お互いの葉球位置を観測しているために、重なりながらも、枝先はお互いによけるように成長している様子が観測できます。

Tree構造

さてここまで、自然な樹形を目指して、樹木の成長モデルを改良してきましたが、Grasshopperで取り扱うデータの構造も樹木と似ている点が面白いです。

過去記事(【Rhino+Grasshopperを理解して魔改造しよう】)でも触れられているように、Grasshopper上では、GH_Structureとよばれるデータ構造で値が受け渡しされますが、この構造はDataTreeなどとツリー構造になぞらえて説明されることが多いです。下図はここまでのコードで生成された樹木モデルとその情報を保持しているデータ構造を可視化したものです。DataTree内の{}で囲まれた数値はpathと呼ばれており、それぞれのpathにあるデータはbranch、まさに枝と表現されています。branch内のリストが各枝を表しており、ここでは世代ごとにpathが分かれています。一方で、3Dモデルの枝先に描画したのは、各branchにおけるリスト番号です。これを見ると、同一のbranch内に2本の樹木の枝がごちゃ混ぜに格納されていることがわかります。これだと、2本の樹木の識別ができませんし、後処理でもどの枝がどのデータに対応しているのか把握が難しくなります。

樹木のモデリングとそれを保持するデータ構造(整理前)

そこで、データ構造を下図のように整理しました。pathの一桁目は世代を表し、二桁目が樹木の番号を表します。このように整理すると、ある樹木の何世代目の枝がどのpathにあるのか分かりやすく、リストも0から順に並んでおり大変見通しが良くなりました。特に今回のように再帰性処理により、データ数が自動的に増えていく場合は、データ構造を整理し、どこにどの情報があるのか対応させて整理しておくのが良いですね。

樹木のモデリングとそれを保持するデータ構造(整理後)

幹の太さ Karamba 3D

最後に、こちらの過去記事Karamba3dの性能について考えようでも使用した「Karamba 3D」を使用して樹木の幹の太さを決定します。Karamba3dはGrasshopperで動く構造解析プラグインであり、高速に構造解析できる点が強みとなっています。各枝を梁要素でモデル化し、重力を加味して、各枝にかかる軸荷重を計算し、その荷重を支えられる幹太さになるように木が成長するようにします。接続されている枝の数が多いほど負担する荷重が大きく、また幹から遠方に伸びた枝ほどモーメントがかかるために支える幹は太くなければなりません。

Karamba 3Dの「Optimize Cross Section」を使用すると材料リスト(太さの異なる円柱のWood材料をリストとして定義)の中から必要十分な枝径を選択し、樹木全体で各箇所の軸応力が制限値以下になるように最適な枝径(幹径)を決定してくれます。

実行 execute

それでは、作成したGrasshopperコードを用いて、樹木を生成してみます。

もととなる幹は、鉛直上向きに生えた直線の枝で隣接して2本の幹を定義しました。これらの幹を第0世代として、第9世代まで成長させます。余談ですが、1世代ごとに枝の本数が2倍ずつ増えていくとともに、一つの枝先に対して、同世代の葉球すべてに対して計算を行うため、(2^N)^2=2^2Nずつ計算量が増えていくので、世代数Nを上げるとそれなりに計算時間がかかります。このケースでの計算時間は20分ほどでした。

世代ごとに枝を描画したgif画像を作ったのでぜひ下をご覧ください。1世代ごとにKaramba 3Dを用いて幹の太さを計算しているので、成長しながら幹が太っていく様子が再現されていて、とてもリアルです。2本の幹を立てただけで、このように木が生えてくるコードができて大変面白いですね。

生成された木を眺めてみると、文献[2]にあった枝垂れを見て取ることができます。また、2本の木の枝がお互いに干渉を避けるように外側や空いているスペースをめがけて伸びているように見えます。同じ世代の枝でも幹の太さが異なるものが見つけられます。これは、より多くの荷重を支えている枝ほど太く成長しているためです。

さらに、初期値となる第0世代の枝の向きを変えるとまた違った表情を見せてくれます。山中の崖や川沿いでは、幹が斜めに生えている木がありますね。下図は第0世代の2本の幹をXY方向に寝かせて解析をしてみた結果です。特に、右の木の第3~第4世代の枝を見ると、向日性の光量ベクトルの向きに従って鉛直上方向に延びようとしているのがわかります。

振り返り review

本記事では、自然な樹木形状の生成について、3D CADのRhinoceross(Grasshopper)を用いたモデリングの手法を探索しました。Karamba 3Dの解析により自然な幹の太さを与えることで、樹木の成長過程に見えるような、自然な成長モデルを作ることができました。初期値である第0世代幹の配置を変えるだけで色々な成長結果を見せてくれるので観察していてとても面白いです。自分でモデリングした構造でありながら、初期状態からは最終形状は予測できないので、感覚的に自分の制御下を離れた自然物のように感じます。

樹木の生え方はさまざまであり、その数理モデルも色々な表現方法があります。今回作成したモデルは、街路樹のようには見えますが、我が家のオリーブには見えませんね。今回の調査範囲では、オリーブの木に関する成長モデルの文献は見つからなかったので、いつか自分の家の木がどう成長するのか予想できるような成長モデルを見つけたいものです。

最後まで読んで頂きありがとうございました。
皆様にも目に入る木々の枝ぶりを、興味深く楽しんで頂けることを願います。

使用したソフト

参考文献

[1]Parametric Design with Grasshopper 増補改訂版
建築/プロダクトのための、Grasshopperクックブック
[2]向日性による自然な枝振りのシミュレーション,金丸ら,1990
[3]樹木の生長シミュレーション,吉澤ら,2006
[4]光環境を考慮した樹木生長モデリング,吉澤ら,2007
[5]樹木景観のビジュアルシミュレーションに関する研究,大志田,2000

idea

The other day, an olive tree arrived at my house.

It was a housewarming gift from a coworker, and while I was researching how to grow it, I learned that olives are "long-lived trees" that can live for over 1000 years. The oldest olive tree in the world is an ancient olive called Olive tree of Vouves that grows on the Greek island of Crete, and is said to be at least 2000 to 3000 years old. While looking at the olive tree on my balcony, I daydreamed, "I can't grow it for 1000 years by myself, but I wonder how it will grow in the future."

Changing the subject, in Rhinoceros, a 3D CAD that we also use in our company, tree modeling is often used in tutorials as an example of structure creation using recursive processing. The figure below is a tree created by recursive processing following an example from a book [1] that contains a lot of sample code. A structure generated by repeating similar shapes toward the tip like this is called a "fractal structure."

Fractal structures are geometric shapes that look very beautiful, but at the same time, they feel strange when compared to trees that grow in nature. In this article, I would like to review how to create structures using recursive processing and create a modeling environment for tree shapes on Grasshopper in Rhinoceros, aiming for a more natural tree shape.

Research

Research into the trunk shapes of natural trees is a topic that has progressed along with the development of computer graphics (CG). The motivation for these studies is to give viewers a realistic experience through landscape simulations in architectural planning, movies, multimedia content, virtual reality, etc. If the metaverse, which has been a hot topic recently, becomes widespread, I feel that methods for expressing elements that humans feel are natural and natural will become even more important.

We referred to the following papers for literature on natural tree generation.

Kanamaru et al. [2] applied a modified model of branch tip direction based on heliotropy, in which the direction of the branch tip changes so that the leaves receive as much light as possible, and reproduced the crown and drooping characteristics seen in natural trees.

Yoshizawa et al. [3] combined two branching models, elongation-oriented and leaf area expansion-oriented, and examined the interactions between trees growing in groups.

Yoshizawa et al. [4] similarly mapped the distribution of shade and light created by the topography and surrounding buildings, which are the environments in which plants grow, and reproduced the way trees grow in response to their growing environment.

Oshida [5] performed growth simulations not only for the visible above-ground parts, but also for the roots underground, and examined the impact of interactions on growth. In addition, they used a mass-spring model to express the phenomenon of tree trunks becoming enlarged due to stress values ​​on trees caused by external forces, and explored ways to give the impression of a long-lived tree.

Based on these documents, we have organized what we believe to be the most important ideas into the following three points.

  • Branching model: Defines how branches branch and grow with each generation.

  • Heliotropy: Branches and trees influence each other as they compete for light, resulting in a natural distribution, and in some cases the branches may droop and grow downward in search of light.

  • Trunk thickness: The thickness of a trunk is determined by the amount of stress placed on it.

Build

Now, let's start modeling the tree. Modeling will be performed on Grasshopper, a plugin that comes with Rhinoceros by default. What is Grasshopper? I have written about it in a previous article, so if you want to know more, I recommend this article (【Understanding Rhino + Grasshopper and modifying it】.

Anemone

To perform the recursive process that uses the information of the original branch to determine the shape of the new branch on Grasshopper, we will introduce "Anemone", a Grasshopper plugin that was also used in the first book introduced [1].

Grasshopper enables complex processing by connecting various components that have the function of performing a certain processing on the input and outputting it. These components have a convention that input is input from the left and output is from the right, and since they are processed in order from the left, if you create a loop that reuses the output, a self-reference will occur and an error will occur in the default state. Therefore, if you try to write recursive processing with the default settings, you will need to repeatedly write the same set of components, which will result in the code growing further and further.

By using "Anemone", you can concisely write self-references using loops, as shown in the sample image below, and you can refer to the values ​​of the previous step for each lap. In this example, one loop cycle by Anemone is considered to be one generation, and a new branch branches and grows from the tip of the original branch for each generation.

Bifurcation model

When observing actual plants, there are many different ways in which they branch. There seems to be no rule for how branches grow, but it is natural that there is one trunk at the base and the number of branches increases as you go further. The simplest expression of this property is the bifurcation model. In this model, a plane is defined on which the line representing the original branch is placed, and two new branches emerge from the tip of the original branch at a certain angle and a certain length on that plane, and the growth of the tree is expressed by repeating this method.

Heliotropism

The tree shape output by adopting a simple two-branching model and performing calculations up to eight generations using recursive processing with Anemone is shown below. At this point, it does not look like a natural tree at all. It looks like a tea whisk used in the tea ceremony.

The reason is that the branches are densely packed and regularly arranged even though there is plenty of space around them. Therefore, we will adopt the heliotropism model proposed in reference [2]. In this model, a region that blocks sunlight, called a leaf bulb, is defined at the tip of all newly generated branches, and the direction of the branch tip is corrected based on the direction of the light receiving vector obtained by adding up the vectors of light received by a branch tip from all directions. When viewed from a branch tip, the leaf bulb blocks light in the direction of other branch tips, so there is a tendency for the branch tip to not want to grow in that direction. This allows each branch to search for a position where it can receive more light, and expresses heliotropy, which avoids the congestion of branch tips.

The base amount of sunlight is strong in the upper hemisphere, and about 0.7 times the amount of light in the lower hemisphere, which is assumed to be reflected light from the surroundings and the ground. Therefore, if there is free space with nothing around, the branch tip will have the tendency to gradually grow straight up.

Image of branch direction correction by light receiving vector

Random branch dieback

Even if you include heliotropy, if you start the simulation from a single vertical trunk, a mirror image of the tree will be formed because it is in a completely symmetric state. In real nature, damaged branches may die due to external factors such as wind and rain. Here, we introduce randomness in which new branches die with a certain probability, giving the tree asymmetry to reproduce a natural tree shape. When a branch dies, the space becomes full of light, so the surrounding branches will gather and fill the space due to heliotropy.

The tree generated by adding heliotropy and random branch dieback is shown below. What do you think? I think the branches are quite natural. If you grow two trees at the same time, you can see that the tips of the branches grow to avoid each other even though they overlap because they are observing the positions of each other's leaf heads.

Tree structure

So far, we have improved the tree growth model in order to achieve a natural tree shape, but it is interesting to note that the structure of the data handled by Grasshopper is similar to that of trees.

As mentioned in a previous article (【Let's understand Rhino + Grasshopper and tinker with it】, in Grasshopper, values ​​are passed in a data structure called GH_Structure, and this structure is often explained in terms of tree structures such as DataTree. The diagram below visualizes the tree model generated with the code up to this point and the data structure that holds that information. The numbers surrounded by {} in DataTree are called paths, and the data in each path are represented as branches, or branches themselves. The lists within the branches represent each branch, and here the paths are divided by generation. Meanwhile, the list numbers for each branch are drawn at the end of the branches in the 3D model. Looking at this, we can see that the branches of the two trees are stored jumbled together in the same branch. This makes it impossible to distinguish between the two trees, and it will also be difficult to figure out which branch corresponds to which data in post-processing.

Modeling trees and the data structure that holds them (before reorganization)

So, I organized the data structure as shown below. The first digit of the path represents the generation, and the second digit represents the tree number. By organizing it in this way, it is easy to see which generation of branches of a tree is on which path, and the list is also arranged in order from 0, making it very easy to see. Especially in cases like this where the amount of data automatically increases due to recursive processing, it is a good idea to organize the data structure and organize it so that which information is where.

Tree modeling and the data structure that holds it (after organization)

Karamba 3D

Finally, we will use "Karamba 3D", which was also used in this past article Let's think about the performance of Karamba3d, to determine the thickness of the tree trunk. Karamba3d is a structural analysis plug-in that runs on Grasshopper, and its strength is that it can perform structural analysis at high speed. Each branch is modeled with a beam element, gravity is taken into account, the axial load on each branch is calculated, and the tree is grown to a trunk thickness that can support that load. The more branches connected, the greater the load they bear, and the farther the branches extend from the trunk, the greater the moment they are subjected to, so the thicker the trunk must be to support them.

Karamba 3D's "Optimize Cross Section" allows you to select the necessary and sufficient branch diameter from a material list (defined as a list of cylindrical wood materials of different thicknesses) and determine the optimal branch diameter (trunk diameter) so that the axial stress at each point throughout the tree is below the limit value.

Execute

Now, let's try to generate a tree using the Grasshopper code we created.

The original trunk is defined as two adjacent trunks with straight branches growing vertically upward. These trunks are the 0th generation and are grown up to the 9th generation. As an aside, the number of branches doubles with each generation, and calculations are performed for all the leaf heads of the same generation for each branch tip, so the amount of calculations increases by (2^N)^2=2^2N, so increasing the number of generations N takes a considerable amount of calculation time. In this case, the calculation time was about 20 minutes.

I created a gif image of the branches drawn for each generation, so please take a look below. The thickness of the trunk is calculated using Karamba 3D for each generation, so the trunk thickens as it grows, which is very realistic. It's very interesting to be able to create code that grows a tree like this just by setting up two trunks.

Looking at the generated tree, you can see the drooping branches mentioned in reference [2]. In addition, the branches of the two trees appear to be growing toward the outside or toward the open space to avoid interference with each other. Even branches of the same generation can be found with different trunk thicknesses. This is because the branches that support more weight grow thicker.

Furthermore, if you change the direction of the initial value 0th generation branch, it will show a different appearance. On cliffs in the mountains and along rivers, there are trees with trunks that grow at an angle. The figure below shows the results of an analysis of two 0th generation trunks laid down in the XY direction. In particular, if you look at the 3rd to 4th generation branches of the tree on the right, you can see that they are trying to grow vertically upwards according to the direction of the heliotropic light vector.

Review

In this article, we explored a modeling method using the 3D CAD software Rhinoceross (Grasshopper) to generate natural tree shapes. By giving the trunk a natural thickness through analysis in Karamba 3D, we were able to create a natural growth model that looks like the growth process of a tree. It is very interesting to observe the various growth results that can be seen just by changing the placement of the initial value, the 0th generation trunk. Although it is a structure that I modeled myself, the final shape cannot be predicted from the initial state, so it feels like a natural object that is out of my control.

Trees grow in various ways, and there are various ways to express their mathematical models. The model I created this time looks like a street tree, but it doesn't look like the olive tree in my house. In my research this time, I was unable to find any literature on growth models for olive trees, so I hope to find a growth model that will allow me to predict how the tree in my home will grow someday.

Thank you for reading to the end.

I hope that after reading this article, you will also find it interesting and enjoyable to look at the branches of the trees that catch your eye.

Software used

References

[1]Parametric Design with Grasshopper Revised and Expanded Edition
Grasshopper Cookbook for Architecture/Products

[2]Simulation of Natural Branch Patterns by Heliotropy, Kanamaru et al., 1990

[3]Tree Growth Simulation, Yoshizawa et al., 2006

[4]Tree Growth Modeling Considering Light Environment, Yoshizawa et al., 2007

[5]Study on Visual Simulation of Tree Landscapes, Oshida, 2000

Author