はじめに

Homeデザイン > Tower of Lights

Tower of Lights (光の塔)

「壁に窓を開ける」という命題をテーマとする建築形態のデザイン(形態生成)に取り組んでみましょう。

図1〜2は,直方体に穴を空けただけの形態ですが,超高層ビルのように見える(見えなくもない)と思います。それは,穴を窓に見立てると,直方体がビルのスケールに見えてくるからだと思います。

単純化して考えれば,建築は,ボリューム(直方体)とエレメント(窓)によって構成されていることが多いと思います。「Twisting Tower(ねじれ立体)」をボリュームのデザインだとすれば,エレメントのデザインとしての「Tower of Lights(光の塔)」のデザインに取り組んでみましょう。

図1 Tower of Lights(光の塔)

 

図2 Tower of Lights(光の塔)の内部

 

図3は,「Twisting Tower(ねじれ立体)」にアーチ状の穴を空けた形態です。この形態は,集落のように見えませんか?

 

図3 Twisting Tower + Tower of Lights

 

図4は,球体を積み上げた形態に穴を空けたものです。これは,集合住宅のように見えませんか?

図4 Design by Spheres + Tower of Lights

 

1. 建築と開口

極端ないい方になりますが,「建築の外形は壁と開口からできている」といえなくはないように思えます。というのは「建築の外形=外観」の話で,もちろん,建築の内部には床や階段やその他いろいろな要素があります。建築には,建築の内部が外観に現れる「透明性」という概念があると思いますが,それはさておき,単純なモデルとしての建築を「壁と開口」からモデル化することはできるかもしれません(単純なモデルであった箱形建築の外形は「壁と開口(入口と窓)」からできていました)。

図5〜7は,イタリア・フィレンツェにあるシニョーリア広場の写真,立面図,鳥瞰図です。シニョーリア広場は,ヴェッキオ宮殿(現在の市役所),ランツィのロッジア,ウフィツィ美術館などが集まるフィレンツェの中心といえる広場で,その姿は,中世を通じて形成され,今日に至ったものです。

図5 シニョーリア広場(イタリア・フィレンツェ)

図6 シニョーリア広場(立面図)

図7 シニョーリア広場(鳥瞰図)

 

広場は,単純化して考えれば,壁に囲まれた空間です。しかし,壁は塀ではありませんから,壁に囲まれた空間であると同時に,窓に囲まれた空間でもあります。地面のレベルに関していえば,入口に囲まれてもいます。

図6の立面図は,壁,窓,入口だけを描いたものです。窓と入口はそのカタチを描いただけで,実際にはあるはずの窓の詳細(サッシなど)は描いていません。壁には素材感(石の目地など)や陰影やヨゴレなどがあるはずですが,それも省略しています。すなわち,図6の立面図は建築の単純化モデルです。それでも,広場を取り囲む建築のカタチは十分に表れていると思えます。

 

2. 閉じた線形状の配置

「建築の外形は壁と開口から表現できるかもしれない」ということを仮説として,壁と開口から構成される建築をデザインしてみましょう。

最初は,正方形の窓を均質に配置するスクリプトを考えます。20×20メートルの壁に1辺2メートルの正方形を均質に配置してみましょう(図8)。

図8 グリッド状に配置

 

スクリプト1

  1. scene = xshade.scene()  #インスタンス
  2. d = 20000  #壁の幅
  3. h = 20000  #壁の高さ
  4. w = 2000  #窓(正方形)の最大サイズ
  5. ox = 1000  #窓の位置のオフセット(X方向)
  6. bx = 2000  #窓の間隔(X方向)
  7. oy = 1000  #窓の位置のオフセット(Y方向)
  8. by = 2000  #窓の間隔(Y方向)

  9. scene.begin_creating()
  10. scene.begin_line("Wall", 1)
  11. scene.append_point([0, 0, 0], None, None, None, None)
  12. scene.append_point([d, 0, 0], None, None, None, None)
  13. scene.append_point([d, h, 0], None, None, None, None)
  14. scene.append_point([0, h, 0], None, None, None, None)
  15. scene.end_line()
  16. for x in range(5):
  17. → for y in range(5):
  18. → → scene.begin_line("Window", 1)
  19. → → x0 = ox + x * (w + bx)
  20. → → y0 = oy + y * (w + by)
  21. → → scene.append_point([x0, y0, 0], None, None, None, None)
  22. → → scene.append_point([x0+w, y0, 0], None, None, None, None)
  23. → → scene.append_point([x0+w, y0+w, 0], None, None, None, None)
  24. → → scene.append_point([x0, y0+w, 0], None, None, None, None)
  25. → → scene.end_line()
  26. scene.end_creating()

上記スクリプトによって,20×20メートルの壁「Wall」と2×2メートルの窓「Window」が25個生成されます。3〜5行目で壁と窓のサイズ,7〜10行目で窓を配置する基準点(壁の右端/下端からのオフセット)と間隔を記述しています。

このスクリプトでは「Wall」と「Window」はすべて「閉じた線形状」ですから,このままでレンダリングしても窓は現れません。「Wall」と「Window」が重なってレンダリングされてしまうからです。窓を表現するためには,窓を「穴」として,壁に「穴」を空けなければなりません。その方法について,次項で解説します。

 

3. ブーリアン演算

「箱形建築のモデリング」でも説明していますが,Shadeでは,ブーリアン演算によってカタチの演算が可能です。壁に窓を空ける場合は,壁から窓を「引き算」します。

Shadeのブーリアン演算は,ブラウザに表示されるオブジェクトの名称の前に,以下の演算子(記号)を付すことで記述されます。

表1 ブーリアン演算

\ 和(交差部分が除去される)
* 差(他の形状を”*”の形状で削り取る。削り取った部分の表面は”*”の形状の表面となる)
$ 積(他の形状との交差部分を取り出す)
= 表面を置き換える
& 他の演算効果をローカルに制限する(演算の効果を同一階層のパート以下に制限する)
+ “*”,”-”の効果を打ち消す
! “*”,”-”,”=”の効果を打ち消す
^ ”=”の効果を打ち消す

 

「壁から窓を引く」場合は,窓のオブジェクトの名称「Window」を「*Window」に変えればいいということです。しかし,5×5=25個もある窓の名称を一つずつ変えるのは面倒なので,パートを使うのが効率的です。すなわち,たとえば「Windows」などといった名称のパートを作成し,25個の「Window」をパート「Windows」の中に入れます。その状態で,「Windows」を「*Windows」にすればOKというわけです(図9)。

図9 ブラウザ

ところで,上記スクリプトは壁と窓を閉じた線形状で描いていますから,壁にも窓にも厚みがありません。厚みゼロの形状から厚みゼロを形状を引き算するのは気味が悪いと思います。そもそも厚みがゼロという形態が存在していいのかどうか迷ってしまうと思います。ゼロからゼロを引く計算にどういう意味があるのか考え始めると悩んでしまうと思います(実際にはちゃんと引き算されるのですが…)。

ここで,3Dモデルらしく壁に厚みを与えましょう。壁の厚さは400ミリとしてみましょう。

窓は引き算に使用するだけなので,窓の厚さがレンダリングされることはありません。壁と同一の厚さでも正しく引き算されることはあります。しかし,400ミリから400ミリの引き算では厚さゼロが残ってしまうかもしれません。あいまいなモデリングは予期せぬレンダリング結果をもたらすことがありますから,400ミリから400ミリの引き算は避けましょう。すなわち,窓は壁よりも厚くして,明確に壁をくりぬきましょう。

図10に例を示しました。ここでは,壁の厚さ400ミリに対して窓の厚さを500ミリとし,窓は前後に50ミリずつ壁から飛び出るようにしています。

図10 窓をグリッド状に配置

 

ここで,ブーリアン演算の演算子について確認をしましょう。

今,図11のように,空間に3つの立方体があるとします。3つの立方体をA(シアン),B(マゼンタ),C(オレンジ)とします。ブラウザは以下のようになっているはずです。

  1. A(シアン)
  2. B(マゼンタ)
  3. C(オレンジ)

図11 ブーリアン演算(演算前)

 

最初に,引き算の演算子である [*] と [-] の違いを確認しましょう。演算子 [*] を用いて,CでA+Bを引き算すると,以下のようになります(図12)。

  1. A(シアン)
  2. B(マゼンタ)
  3. *C(オレンジ)

図12 ブーリアン演算(演算子:*)

 

演算子 [*] の代わりに演算子 [-] を用いると以下のようになります(図13)。

  1. A(シアン)
  2. B(マゼンタ)
  3. -C(オレンジ)

図13 ブーリアン演算(演算子:-)

 

すなわち,演算子 [-] は他の立体の表面に穴を空けるのですが,その穴は立体のヴォリューム(中身の詰まったソリッド)に対してではなく,立体のサーフェス(表面)に対して空けられるものです。

一般に,3次元CGのモデルには,サーフェス・モデル,ソリッド・モデル,ワイヤーフレーム・モデルなどがあるといわれます。Shadeが扱うCGモデルはサーフェス・モデルです。Shadeでは立方体は中身の詰まったソリッドではなく,サーフェス(面)で囲まれた立体(中身はスカスカの空洞)として定義されます。

演算子 [-] による引き算では,立体C(オレンジ)が単に引き算するオブジェクトとして扱われますから,立体Cは残りません。一方,演算子「*」による引き算では,引き算された後に立体Cの表面が残ります。すなわち,[*C]では,立体Cは立体A+Bを削り取り,削り取った面を新たに生成します。

次に,「他の演算効果をローカルに制限する」ための演算子 [&] と,[*] / [-] の効果を打ち消すための演算子 [+] について確認をしましょう。

今,図14のように,「立体A(シアン)から立体C(オレンジ)を引き算したいが,立体B(マゼンタ)からは立体C(オレンジ)を引き算したくない」という状態を考えましょう。

図14 ブーリアン演算(引き算の打ち消し)

 

上記のブーリアン演算は以下のいずれかの記述で実現できます。

(方法1)

  1. A(シアン)
  2. +B(マゼンタ)
  3. *C(オレンジ)

(方法2)

  1. パート
     A(シアン)
     &*C(オレンジ)
  2. B(マゼンタ)

方法1は,引き算の対象とされたくない立体Cに [*],[-] の効果を打ち消すための演算子 [+] を付しています。

方法2は,新たにパート(ここでは「パート」という名称)を作成し,その中に引き算をされる立体Aと引き算をする立体Cを入れています。そして,引き算をする立体Cに「他の演算効果をローカルに制限する」ための演算子 [&] を付しています。演算子 [&] によって引き算の効果は立体A+Cが属するパートにおいてのみ行われ,パートの外にある立体Bには影響を与えません。

このように演算子とパートを適切に記述することによって,目的のブーリアン演算を実現するのがShadeの使い方です。

 

4. Tower of Lights

図15では,20×20メートルの壁に最大2×2メートルの正方形の窓を20個,ランダムに配置しています。ここで,窓のサイズは1〜2メートルの範囲でゆらいでいます。このアルゴリズムはスクリプト2に示しています。

図15 Tower of Lights

 

スクリプトの5〜10行目でパラメーターの設定をしています。コメントにある通り「d:壁の幅,h:壁の高さ,wmax:窓の基本サイズ」です。窓と窓,あるいは壁の境界と窓は,少なくとも「b=200」の間隔をもつようになっています。「wratio」は「窓の細長比」,すなわち,窓の幅と高さの比を定義しています。「wratio=1.0」ならば正方形です。37行目を「wy = wx * wratio * (1.0 + random.random() * 0.5)」とすると,細長比は1.0〜1.5の範囲でゆらぐことになります。

スクリプト 2

  1. import random
  2. scene = xshade.scene()   #インスタンス
  3. d = 20000      #壁の幅
  4. h = 20000      #壁の高さ
  5. wmax = 2000    #窓の最大サイズ
  6. wratio = 1      #窓の細長比
  7. b = 200       #窓のバッファ(窓間の間隔)
  8. num = 20      #窓の数(最大)
  9. maxloop = 10000   #計算回数
  10. xs = []
  11. ys = []
  12. xe = []
  13. ye = []
  14. n = 0
  15. k = 1
  16. xs.append(0)
  17. ys.append(0)
  18. xe.append(0)
  19. ye.append(0)
  20. print(xs[0], ys[0], xe[0], ye[0])
  21. scene.begin_creating()
  22. scene.begin_line("Wall", 1)
  23. scene.append_point([0, 0, 0], None, None, None, None)
  24. scene.append_point([d, 0, 0], None, None, None, None)
  25. scene.append_point([d, h, 0], None, None, None, None)
  26. scene.append_point([0, h, 0], None, None, None, None)
  27. scene.end_line()
  28. while n<num:
  29. → flag = False
  30. → wx = wmax/2 + random.random()*wmax/2
  31. → wy = wx * wratio
  32. → x0 = random.random() * (d - wx - 2 * b) + b
  33. → y0 = random.random() * (h - wy - 2 * b) + b
  34. → x1 = x0 + wx
  35. → y1 = y0 + wy
  36. → for i in range(n+1):
  37. → → if (x0>=(xs[i]-b)) * (x0<=(xe[i]+b)) * (y0>=(ys[i]-b)) * (y0<=(ye[i]+b)) :
  38. → → → flag = True
  39. → → if (x1>=(xs[i]-b)) * (x1<=(xe[i]+b)) * (y0>=(ys[i]-b)) * (y0<=(ye[i]+b)) :
  40. → → → flag = True
  41. → → if (x0>=(xs[i]-b)) * (x0<=(xe[i]+b)) * (y1>=(ys[i]-b)) * (y1<=(ye[i]+b)) :
  42. → → → flag = True
  43. → → if (x1>=(xs[i]-b)) * (x1<=(xe[i]+b)) * (y1>=(ys[i]-b)) * (y1<=(ye[i]+b)) :
  44. → → → flag = True
  45. → → if (xs[i]>=(x0-b)) * (xs[i]<=(x1+b)) * (ys[i]>=(y0-b)) * (ys[i]<=(y1+b)) :
  46. → → → flag = True
  47. → → if (xe[i]>=(x0-b)) * (xe[i]<=(x1+b)) * (ys[i]>=(y0-b)) * (ys[i]<=(y1+b)) :
  48. → → → flag = True
  49. → → if (xs[i]>=(x0-b)) * (xs[i]<=(x1+b)) * (ye[i]>=(y0-b)) * (ye[i]<=(y1+b)) :
  50. → → → flag = True
  51. → → if (xe[i]>=(x0-b)) * (xe[i]<=(x1+b)) * (ye[i]>=(y0-b)) * (ye[i]<=(y1+b)) :
  52. → → → flag = True
  53. →  if k >= maxloop:
  54. → → print("Loop Exceeded the Limit:", k)
  55. → → break
  56. → k = k + 1
  57. →  if flag == False:
  58. → → n = n + 1
  59. → → xs.append(x0)
  60. → → ys.append(y0)
  61. → → xe.append(x1)
  62. → → ye.append(y1)
  63. → → print(xs[n], ys[n], xe[n], ye[n])
  64. → → scene.begin_line("Window"+str(n), 1)
  65. → → scene.append_point([x0, y0, 0], None, None, None, None)
  66. → → scene.append_point([x1, y0, 0], None, None, None, None)
  67. → → scene.append_point([x1, y1, 0], None, None, None, None)
  68. → → scene.append_point([x0, y1, 0], None, None, None, None)
  69. → → scene.end_line()
  70. → → obj=scene.active_shape()
  71. → → obj.has_surface_attributes = 1
  72. → → obj.surface.has_diffuse = True
  73. → → obj.surface.diffuse_color = (random.random(), random.random(), random.random())
  74. scene.end_creating()

このスクリプトのアルゴリズム(手順)の概念を図16〜17に示します。図16の(A)は壁(領域)の中に単に窓(正方形)をランダムに配置したものです。(B)は窓の大きさにゆらぎ(変化)を与えていますが,ゆらぎが大きすぎるように思えます。また,窓同士が重なってしまっています。(C)では,窓の大きさのゆらぎを0.5〜1.0に制限しています。また,窓同士が重ならないようにしています。しかし,窓同士が接近しすぎています。そこで,(D)では,窓同士が一定の間隔をもつようにしています。スクリプトでは,以上の概念を数式でコントロールしているわけです。

図16 ToLのアルゴリズム

 

45〜60行で窓の交差判定を行い,間隔を含んで窓が交差しない場合にだけ窓を生成するようにしています。交差判定の手順を図17に示します。

図17 交差判定のアルゴリズム

5. 広場立面のシミュレーション

さて,窓にはスケールがありますから(めちゃくちゃ小さな窓や大きな窓はありませんから),壁や立体などのヴォリュームに窓を空けると,建築らしさ(建築のスケール感)が表れます。

窓には一定の大きさがありますが,その一方,どの窓も同じ大きさであるはずはなく,窓の大きさはゆらいでいます。小規模な建築ではゆらぎは少ないと思えますが,大規模な建築ではかなりのゆらぎがあるはずです。

冒頭で述べたフィレンツェのシニョーリア広場を囲む立面には350の窓があり,57の入口があります。図18に窓と入口を塗り分けた立面図を示します。

図18 シニョーリア広場立面図(窓と入口)

 

これらの窓/入口/壁の,幅・高さ・細長比・面積の平均値・最小値・最大値を示すと,以下のようになります。

表7-2 シニョーリア広場の窓・入口・壁(単位はメートル)

 

すなわち,シニョーリア広場の350の窓は,平均値として面積が3.7㎡,幅が1.4m,高さが2.6m,細長比が2.0です。また,壁の総面積9614.3㎡に対して,入口と窓を合わせた開口部の総面積は「1293.3+874.6=2167.9㎡」ですから,開口率(開口部の壁に対する比率)は22.5%ということになります。

一方,下図は壁だけの立面図です。

図18 シニョーリア広場立面図(壁)

 

壁の形態にもある変化が見られます。広場には高さが約90メートルの塔が建っています(この高さは簡略な実測による値で正確ではありません)。広場の立面はいくつかの建物の集まりで,それぞれの建物は高さが異なっています。

窓・入口・壁の構成に注目しながら,シニョーリア広場と類似する立面を生成してみましょう。一例として,以下のような特性を考えることにします。

  1. 一つの壁の大きさは「幅=40メートル,高さ=30メートル」と仮定し,その壁に開口部(窓と入口)を配置する。ここで「高さ=30メートル」は「平均値=29.6メートル」に近い数値である。
  2. 命題を単純化して,窓と入口は区別しないで,すなわち,入口の窓の一種と考える。 窓の大きさと細長比にゆらぎを与える。
  3. 窓幅のゆらぎを「1〜3メートル」とする。窓の幅の「最大値ー最小値」は「8.32ー0.66=7.66」であるが,最小値から最大値までの範囲で単純にランダムにゆらぎを与えると過度なゆらぎを与えることになる(幅が8メートルを超える窓や1メートル未満の窓は特殊な窓だと思える)。
  4. 窓の幅の「平均値=1.41」であるが,「最大値ー最小値の中間値=4.49」であることを考慮し,平均値よりやや大きめの窓を出現させることを意図する。なお,実際の自然現象では,ゆらぎは正規分布にしたがい平均値近くに集中することがわかっている。正規分布にしたがうゆらぎを与えることも可能であるが,ここでは単純にランダムに変化させる。
  5. 細長比のゆらぎを「1.0〜2.0」とする。
  6. 開口率(開口部の面積の壁の面積に対する比)が約25%となるようにする。

以上の特性を与えて生成させた壁+窓が図19です。ここで,この壁+窓の特性は以下の通りです。

  1. 窓の数:72
  2. 開口率:24.5%
  3. 窓の幅の平均:1.57メートル
  4. 窓の高さの平均:2.31メートル

図19 広場的な特性をもつ壁

 

6. 光の教会

ここで,ゴシックの教会の光の特性を単純な形態によって再現することを考えてみます。図20がその結果です。

図20 光の教会

この教会は,以下のような考えでつくりました。

図21〜27は,歴史上の代表的な教会である,ハギア・ソフィア(トルコ,イスタンブール),シャルトル大聖堂(フランス,シャルトル),カンタベリー大聖堂(イギリス,カンタベリー),ノートルダム大聖堂(フランス,パリ)の立面における壁と開口部(窓と入口)の面積比の計算結果です。

図21 ハギア・ソフィア(トルコ,イスタンブール)

 

図22 ハギア・ソフィア(トルコ,イスタンブール)

 

図23 ハギア・ソフィア(トルコ,イスタンブール)

 

図24 シャルトル大聖堂(フランス,シャルトル)

 

図25 カンタベリー大聖堂(イギリス,カンタベリー)

 

図26 ノートルダム大聖堂(フランス,パリ)

 

図27 ノートルダム大聖堂(フランス,パリ)

中世初期のビザンチン時代の教会であるハギア・ソフィアの開口率(壁面積に対する開口部面積の割合)が7.8%であるのに対して,中世後期のゴシック時代の教会であるシャルトル大聖堂,カンタベリー大聖堂,パリ・ノートルダム大聖堂の開口率は,10.3〜17.5%となっています。特に,カンタベリー大聖堂とパリ・ノートルダム大聖堂の開口率は,17.1%と17.5%と,よく似た値となっています。

そこで,パリ・ノートルダム大聖堂と同じ壁の開口の面積をもつ形態を,Tower of Lights のアルゴリズムを用いて生成したのが前述の図20および図28に示した光の教会というわけです。

図28 光の教会

7. おまけ

図29〜図30は,正方形の窓をアーチに変えて場合の Tower of Lights です。スクリプトの四角形を描く部分をアーチに変えれば,こんなカタチになります。スクリプトをゼロから書くのは大変だったりしますが,すでにあるスクリプトを変更しながら,その構成を学ぶのもいい勉強です。

図29 光の教会(アーチ)

 

図30 光の教会(アーチ)の内部