3D の球面を模様で彩るには?
今回は Processing で球面上に模様を描いてみます。
この記事を書こうと思ったきっかけは Rui ChenーUIDesigner(@kimforever2008)さんのこのツイートです。
アジサイの花びらの模様がとっても綺麗ですね!
ツイートの中で『球体できないですかね』とおっしゃってます。これはいいお題が出ました!
この記事は全文無料でお読みいただけます。もしお気に召しましたら投げ銭お願いしますね。😉✨
球面を描くには
まず、今回 2D ではなく 3D での描画を行いますので size() の中で P3D の指定を入れます。
size(980, 980, P3D);
単純に球を描くだけなら sphere() を使えばいいです。
球面の粗さは sphereDetail() で変更できます。
表面の線を消すには noStroke() を入れればよいですが、そのままだとただの円と見分けが付かないのでライトを工夫しないといけません。
それはまた別に機会に。
模様が描けないよね?
このように単に球を描くなら sphere() を使うだけなのですが、これだと球の表面に模様を描くことはできません。
自在に模様を描くには自分で球を形作らなければなりません。
2D で自分で円を描きたければ三角関数を使って座標を計算して点を打つなり線を引いていきますよね?
x = r * cos(a);
y = r * sin(a);
それと同じ考えで、3D でも三角関数を使って座標を計算すれば球を描くことができます。
lat : 緯度(latitude)
lon : 経度(longitude)
x = r * cos(lon) * sin(lat);
y = r * sin(lon) * sin(lat);
z = r * cos(lat);
これだけ!
この計算式を使って、試しに立方体を球状に並べてみましょう。
緯度と経度はわかりやすくラジアンじゃなくて 度 を使ってみます。
/**
* 3D の球面を模様で彩る
* 立方体を球状に並べてみる
*
* Creative Commons CC0
* @author @deconbatch
* @version 0.1
* Processing 3.2.1
* 2019.05.29
*/
void setup() {
size(800, 800, P3D);
colorMode(HSB, 360, 100, 100, 100);
smooth();
noLoop();
}
void draw() {
background(0.0, 0.0, 0.0, 0.0);
translate(width * 0.5, height * 0.5, 0.0);
camera(500, 500, 1000,
0, 0, 0,
0, 1, 0);
float radius = 500.0;
float divLat = 10.0;
float divLon = 10.0;
// 緯度は 0 から 180度まで
for (float lat = 0.0; lat <= 180.0; lat += divLat) {
float radLat = radians(lat);
// 経度は 360度ぐるっと回せば球になりますね
for (float lon = 0.0; lon <= 360.0; lon += divLon) {
float radLon = radians(lon);
float cX = radius * cos(radLon) * sin(radLat);
float cY = radius * sin(radLon) * sin(radLat);
float cZ = radius * cos(radLat);
pushMatrix();
// 座標は translate で設定
translate(cX, cY, cZ);
fill(lon, abs(lat - 90.0), 60.0, 100.0);
stroke(0.0, 0.0, 20.0, 100.0);
box(50.0);
popMatrix();
}
}
saveFrame("frames/####.png");
exit();
}
ね?
box() を ellipse() に変えれば円で球を描けます。
// 座標は translate で設定するからここは 0.0, 0.0
ellipse(0.0, 0.0, 50.0, 50.0);
これはこれでいいんだけれど…
それなりに球状に模様を描くことができましたが、気になる点が2点あります。
1.面の向きが全部同じ方向
地球に例えると ellipse の面が南北を向いてるので、赤道方向から見るとこうなってしまいます。
2.極付近が密集、赤道付近がスカスカ
わかりやすくするために球の上半分だけ見ると、球のてっぺんでは ellipse が重なっているのに、そこから離れるにしたがって隙間が開いてしまっています。
面の向きを変えるには?
面の向きは球面に沿った向き、つまり常に球の中心を向くようにするとよいですね。
それには rotate() で回転です。
3D の場合は軸ごとに rotateX(), rotateY(), rotateZ() を使います。
translate() の後に以下の回転を加えると球の中心方向を向くようになります。
rotateZ(radLon);
rotateY(radLat);
これは順番が大事で、逆にすると思った結果になりません。
// NG
rotateY(radLat);
rotateZ(radLon);
極付近が密集、赤道付近がスカスカは?
これは経度の分割が固定値になっているのでこうなってしまいます。
float divLon = 10.0;
これを極付近では大きく、赤道付近では小さくするとよいです。
緯度方向の円と円の間隔と経度方向のそれを同じにするという考えで式を考えるとこうなると思います。
float divLon = 180 / constrain(((180 / divLat) * sin(radLat)), 0.5, 180.0 / divLat);
ちょっと経度 0度 と 360度のところが一部重なってしまっている部分があるのでイマイチなのですが、間隔を狭めるとあまり気にならなく…
float divLat = 5.0;
やっぱり気になりますね!
ここはちょっと課題です。
ここまでやればもう気にならないかな?
float divLat = 2.0;
結論:つまりこう!
最終的なコードはこうなりました。
/**
* 3D の球面を模様で彩る
* 円で球面を描く
*
* Creative Commons CC0
* @author @deconbatch
* @version 0.1
* Processing 3.2.1
* 2019.05.29
*/
void setup() {
size(800, 800, P3D);
colorMode(HSB, 360, 100, 100, 100);
smooth();
noLoop();
}
void draw() {
background(0.0, 0.0, 0.0, 0.0);
translate(width * 0.5, height * 0.5, 0.0);
camera(500, 500, 1000,
0, 0, 0,
0, 1, 0);
float radius = 500.0;
float divLat = 10.0;
// 緯度は 0 から 180度まで
for (float lat = 0.0; lat <= 180.0; lat += divLat) {
float radLat = radians(lat);
float divLon = 180 / constrain(((180 / divLat) * sin(radLat)), 0.5, 180.0 / divLat);
// 経度は 360度ぐるっと回せば球になりますね
for (float lon = 0.0; lon <= 360.0; lon += divLon) {
float radLon = radians(lon);
float cX = radius * cos(radLon) * sin(radLat);
float cY = radius * sin(radLon) * sin(radLat);
float cZ = radius * cos(radLat);
pushMatrix();
// 座標は translate で設定
translate(cX, cY, cZ);
// 必ず Z -> Y の順番で
rotateZ(radLon);
rotateY(radLat);
// ここに描画したいものを持ってくる
fill(lon, abs(lat - 90.0), 60.0, 100.0);
stroke(0.0, 0.0, 20.0, 100.0);
ellipse(0.0, 0.0, 50.0, 50.0);
popMatrix();
}
}
saveFrame("frames/####.png");
exit();
}
描画の角度を変えればドームも描けます。
for (float lat = 0.0; lat <= 60.0; lat += divLat) {
for (float lon = 0.0; lon <= 90.0; lon += divLon) {
こんな感じです。
よかったら使ってみてください。
あとがき
以前、円を並べて球面を描く作品を作ったことがあり、今回の記事はこのコードを元に書きました。
描画したときの色からアジサイを連想したので作品のタイトルに Hydrangea (アジサイ)を入れてたんです。
記事のきっかけになった Rui ChenーUIDesigner さんのツイートがアジサイだったので、「あ、そういえばあの作品!」と思い出したのでした。
ここでこの記事はおしまいです。もしこの記事がお気に召しましたら投げ銭お願いします。😉✨
ここから先は
¥ 100
この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕