見出し画像

【Blender Python】数式で表現される曲面を描く①

今回は Blender を使って数式で表現される曲面を描いてみようと思います。

xy 平面をマス目に区切り、各区間の面の四隅の xy 座標に対して z 座標を設定し、そうやってできた4つの xyz 座標で平面を張っていきます。

その平面を繋げていけば様々な曲面が描けるだろうと考えました。

Blender のバージョンは 2.93 LTS です。

1. 平面を繋げたオブジェクトを作る

まずは簡単な形を作ってみましょう。

コード

import bpy


# 既存要素削除
for item in bpy.data.meshes:
   bpy.data.meshes.remove(item)


verts = [[0, 0, 0], [1, 0, 0], [0, 1, 0.5], [1, 1, 0.5], [0, 2, 0], [1, 2, 0]]
faces = [[0, 1, 3, 2], [2, 3, 5, 4]]

msh = bpy.data.meshes.new("cubemesh") #Meshデータの宣言
msh.from_pydata(verts, [], faces) # 頂点座標と各面の頂点の情報でメッシュを作成
cube_obj = bpy.data.objects.new("cube_001", msh) # メッシュデータでオブジェクトを作成
bpy.context.scene.collection.objects.link(cube_obj) # シーンにオブジェクトを配置

解説

このコードでは x 軸方向に 0, 1 、y 軸方向に 0, 1, 2 と刻み、その交点の xyz 座標を verts に格納しています。

画像2

このコードでは左上の原点から開始して x 軸方向に進み、端まで来たら y 軸方向に 1 つ進みまた上から順番に頂点座標を verts に格納しているので、verts 内の頂点のインデックスは下図のようになります。

画像3

次にどの頂点の組み合わせで面を張るのかを faces で設定しています。
verts に格納した頂点のインデックスを使います。

1 枚目の面は頂点 [0, 1, 3, 2] で張られ、2 枚目の面は頂点 [2, 3, 5, 4] で張られています。

実行結果

画像3

2 つの平面が繋がったオブジェクトになっていますね。

2. 一般化する

もっと大きな曲面を作ろうと思ったときに頂点座標や面を張る頂点の組み合わせを 1 つずつ入力するのは大変ですよね。

ということでコードをもっと一般化してみましょう。

コード

import numpy as np
import bpy


# 既存要素削除
for item in bpy.data.meshes:
   bpy.data.meshes.remove(item)


# x,y座標に応じてz座標を返す関数
def calc_z(x, y):
   z = (np.cos(x) + np.sin(y)) / 3
   return(z)


# 分割数
x_len = 4
y_len = 5

# 範囲
x_min = 0
x_max = 3
y_min = 0
y_max = 4

lattice_x = np.linspace(x_min, x_max, x_len)
lattice_y = np.linspace(y_min, y_max, y_len)

verts = [[x, y, calc_z(x, y)] for y in lattice_y for x in lattice_x]
faces = [[x_len * y + x, x_len * y + x + 1, x_len * (y + 1) + x + 1, x_len * (y + 1) + x] for y in range(y_len - 1) for x in range(x_len - 1)]

msh = bpy.data.meshes.new("cubemesh") #Meshデータの宣言
msh.from_pydata(verts, [], faces) # 頂点座標と各面の頂点の情報でメッシュを作成
cube_obj = bpy.data.objects.new("cube_001", msh) # メッシュデータでオブジェクトを作成
bpy.context.scene.collection.objects.link(cube_obj) # シーンにオブジェクトを配置

解説

x 軸方向と y 軸方向の範囲と分割数(x_len, y_len)を設定できるようにしました。
Numpy の linspace 関数を使って x 軸方向と y 軸方向の範囲を設定した分割数で均等割りしています。

下図の数字は x 軸方向と y 軸方向のインデックスを表しています。

画像4

頂点のインデックスは下図のようになります。

画像5

この順番で xyz 座標をリストに格納しているのが

verts = [[x, y, calc_z(x, y)] for y in lattice_y for x in lattice_x]

の部分になります。

また、xy 座標に応じて z 座標を返す関数 calc_z を定義しています。

# x,y座標に応じてz座標を返す関数
def calc_z(x, y):
   z = (np.cos(x) + np.sin(y)) / 3
   return(z)

この calc_z 関数の中身はもっと複雑な計算式にカスタマイズすることもできます。

次に面を張る頂点の組み合わせを考えます。
上図の上から x 番目、左から y 番目の平面の四隅の頂点は下図のように表すことができます。(x は 0 から x_len - 2 まで、y は 0 から y_len - 2 の範囲で考えます。)

画像6

分割した全ての面に対応する四隅の頂点のインデックスを格納しているのが

faces = [[x_len * y + x, x_len * y + x + 1, x_len * (y + 1) + x + 1, x_len * (y + 1) + x] for y in range(y_len - 1) for x in range(x_len - 1)]

の部分になります。

今回のコードでは x 軸方向も y 軸方向も開始位置を 0 にしていますが、別の値にしても動作します。

実行結果

画像7

3. もっと複雑な形を描く

上の例では説明のため分割数をかなり少なく設定していました。
分割数を増やすともっと滑らかな形のオブジェクトになります。
(その分多くのメモリが必要になりますので増やし過ぎには注意して下さい。)

コード

import numpy as np
import bpy


# 既存要素削除
for item in bpy.data.meshes:
   bpy.data.meshes.remove(item)


# x,y座標に応じてz座標を返す関数
def calc_z(x, y):
   z = np.sin(x * y) * np.cos(y)
   return(z)


# 分割数
x_len = 2000
y_len = 2000

# 範囲
x_min = -10
x_max = 10
y_min = -10
y_max = 10

lattice_x = np.linspace(x_min, x_max, x_len)
lattice_y = np.linspace(y_min, y_max, y_len)

verts = [[x, y, calc_z(x, y)] for y in lattice_y for x in lattice_x]
faces = [[x_len * y + x, x_len * y + x + 1, x_len * (y + 1) + x + 1, x_len * (y + 1) + x] for y in range(y_len - 1) for x in range(x_len - 1)]

msh = bpy.data.meshes.new("cubemesh") #Meshデータの宣言
msh.from_pydata(verts, [], faces) # 頂点座標と各面の頂点の情報でメッシュを作成
cube_obj = bpy.data.objects.new("cube_001", msh) # メッシュデータでオブジェクトを作成
bpy.context.scene.collection.objects.link(cube_obj) # シーンにオブジェクトを配置

解説

範囲と分割数を増やしました。
分割数が多いので、私の PC では実行が終わるまでに数十秒かかりました。

calc_z 関数内の計算を

z = np.sin(x * y) * np.cos(y)

としました。
特に何かを表している関数ではないですが、適当に sin と cos を組み合わせて計算させると思いもよらないような形ができて面白いです。

実行結果

画像8

不思議な形ができましたね!
是非皆さんも Blender を使っていろんな形のオブジェクトを作ってみて下さい。

この記事が気に入ったらサポートをしてみませんか?