提供:Japanese Scratch-Wiki

3Dプロジェクトとは、x-y座標による平面的な世界 (縦と横) ではなく、さらに奥行きが存在する3次元の世界 (3D空間) を表現したプロジェクト全般のことを指す。 Scratchのステージは、x座標とy座標から構成される2次元平面であるため、この上で奥行き (z座標) を表現するにはさまざまな工夫が必要になる。
3D空間とは
3D空間とは、x軸とy軸に、z軸が加わった3次元座標空間のことである。 z軸は、x軸、y軸と同じような座標の軸であり、通常、x-y平面に対する奥行きを表す。われわれの周囲の空間 (現実世界) は、3次元であるため奥行き (z軸) が存在するが、Scratchにはデフォルトではz軸が存在しない。そこで、Scratchで3D空間を表現するときは、3Dモデルのサイズと動きを組み合わせて、奥行きが存在するように見せかけることになる。
この記事では、比較的簡単なスクリプトで3Dのような見た目を実現する例を2つ紹介したあと、最後に、本格的な3D空間を扱う例について取り上げる。 ここで紹介した以外にも、3Dの表現方法としてはレイキャスティング も効果的である。目的に応じて、それぞれの手法を使い分けると良いだろう。
オブジェクトと背景を準備する
![]() |
この項目は、書きかけの項目です。この項目に加筆・訂正などをしてくださる協力者を求めています。
|
3Dでスクロールする場合や、全面展望の運転ゲームを使う場合に3Dのオブジェクトを作成する場合がある。その場合の方法を説明する。
一点透視図法
ある一点を基準として、そこに向かってすべての物(を延長した線)が向かっていくように描画する方法である。
点の位置は任意だが、通常目線の高さに設定する。
二点透視図法
一点透視図法を拡張したものである。通常、中央から見て左右に点を設定する。
例えば左右に点を打ったとき、右に向かっていく物は右の点に、左に向かう物は左の点に向かって線が延びていく。
三点透視図法
現実の世界はこのような見え方をしており、この描画方式が一番自然である。 1. 点(消失点)を決める(図中赤丸)
2. 灰色の線を引く
3. 2の線が交わって四角状になっている部分を濃くし、不要部分を削除する。
サイズ変更によって3D効果を得るスクリプト
準備
次の変数を使用する:
(x位置) (y位置) (z位置)
チュートリアル
このスクリプトでは、スプライトのサイズを変更して、奥行きを表現している。
@greenFlag が押されたとき::events hat [x位置 v] を [-30] にする [y位置 v] を [0] にする [z位置 v] を [60] にする ずっと 大きさを (z位置) %にする x座標を (x位置) にする y座標を (y位置) にする もし <[上向き矢印 v] キーが押された> なら [z位置 v] を ((大きさ) / (45)) ずつ変える [x位置 v] を ((x座標) / (55)) ずつ変える end もし <[下向き矢印 v] キーが押された> なら [z位置 v] を ((大きさ) / (-45)) ずつ変える [x位置 v] を ((x座標) / (-55)) ずつ変える end もし <[左向き矢印 v] キーが押された> なら [x位置 v] を ((大きさ) / (-55)) ずつ変える end もし <[右向き矢印 v] キーが押された> なら [x位置 v] を ((大きさ) / (55)) ずつ変える end もし <<(z位置) < [20]> または <(z位置) > [217]>> なら 隠す でなければ 表示する end end
"x位置"と"y位置"は、スプライトの縦横方向の位置を表す変数である (通常のx座標とy座標にあたるもの)。 "z位置"は、スプライトの位置を変えずにスプライトの大きさを変えるための値である。
この例では、3次元のうち、左右の位置、上下の位置については、通常どおりx座標とy座標を使って表しているが、奥行きとなるz座標を、スプライトの大きさで表している。(奥にある物体は小さく、手前にある物体は大きく見えるため、z座標の位置は物体の大きさと比例していると考えられるからだ。
Scratchでリアルな3Dゲームをつくるには複雑な手順が必要になるが、このスクリプトのようなやり方で、ステージを3D空間のように見せかけるのは比較的簡単である。 ただし上記は、3Dスクリプトとしてはかなり初歩的であり、物体同士の衝突を調べるしくみなどが含まれていない。また、動いている物体については、そのままでは使用できない。しかし、工夫すれば、この方法でも奥行きのある3D世界を表現したゲームなどが実現できるはずだ。
スタンプを使ったシンプルな3D効果スクリプト
次に、よりシンプルに3D効果を得る方法を紹介する。この方法では、「スプライトの座標を少しずらしてスタンプ」を何度か繰り返すことで、スプライトの奥行きを表現している。
@greenFlag が押されたとき::events hat ずっと 全部消す::pen x座標を (0) 、y座標を (0) にする スタンプ y座標を (2) ずつ変える スタンプ y座標を (2) ずつ変える スタンプ y座標を (2) ずつ変える スタンプ y座標を (2) ずつ変える スタンプ y座標を (2) ずつ変える スタンプ y座標を (2) ずつ変える スタンプ end @greenFlag が押されたとき::events hat ずっと [マウスのポインター v] へ向ける end
座標計算による3Dグラフィックスの基礎
コンピューターのディスプレイのような平面上で3D空間を表現するときは、3次元上の座標を2次元の平面に変換 (投影) する必要がある。 ここでは、計算によって、3次元空間に置かれた3次元オブジェクトを、2次元平面に投影する基本を説明する。
3次元空間上に置かれた3次元オブジェクト (モデルという) を操作するときは、変換行列を使って計算することが多い。 変換行列には、回転、スケール、平行移動という3種類の変換の情報が含まれている。
3D空間を実装するときは、一般に、次の3つのステップを踏まえる必要がある。
- モデルの各頂点が3次元マップ (ワールド空間) 上のどこに位置するかを計算する (モデル変換)
- 次に、ワールド空間におけるそれらの座標を、視点 (カメラ位置) からの相対位置に変換する (ビュー変換)(これを行うには、各頂点に、カメラの位置の逆行列を適用すればよい) 。このとき、カメラ視野 (クリッピング空間) の外にある情報は脱落する。
- 最後に、クリッピング空間を画面平面に投影する (投影変換)
データ表現
ここでは、3Dモデルは各頂点の配列と各面の配列で構成されるものとする。各頂点は、x, y, z座標を値としてもっており、各面は、その面を構成する頂点の情報を持つ。
変換
次に各頂点の変換に使用するコードを記載する。
スケール変換
スケール変換とは、対象の変えることである。Scratchの2次元平面であれば、大きさを () %にするブロックや大きさを () ずつ変えるブロックでスプライトのサイズを変更できるが、これに当たるものと考えればよい。
[new x v] を ((old x) * (サイズ::variables)) にする [new y v] を ((old y) * (サイズ::variables)) にする [new z v] を ((old z) * (サイズ::variables)) にする
平行移動
平行移動とは、座標軸と平行に対象を動かすことである。Scratchの2次元平面であれば、x座標を () ずつ変えるブロックやy座標を () ずつ変えるブロックでスプライトを移動できるが、これに当たるものと考えればよい。
[new x v] を ((old x) + (x 平行移動)) にする [new y v] を ((old y) + (y 平行移動)) にする [new z v] を ((old z) + (z 平行移動)) にする
回転
回転とは、対象の向きを変えることである。Scratchの2次元平面であれば、() 度回す ブロックでスプライトを回転できるが、これに当たるものと考えればよい。
3次元空間での回転は、計算が少々複雑だ。まず、1つの回転をロール (roll)、ピッチ (pitch)、yaw (ヨー)、という3つの回転に分割し、それぞれに対し、2次元での回転を適用していると考えることができる。
三角関数の加法定理
回転には、次のような、三角関数の加法定理を使用する:
- sin(A1 + A2) = sin(A1) * cos(A2) + sin(A2) * cos(A1)
- cos(A1 + A2) = cos(A1) * cos(A2) - sin(A1) * sin(A2)
- sin(A1 - A2) = sin(A1) * cos(A2) - sin(A2) * cos(A1)
- cos(A1 - A2) = cos(A1) * cos(A2) + sin(A1) * sin(A2)
2次元の回転
2次元平面でスプライトを回転するコードは次のとおり:
[new x v] を (((old x) * ((角度)の[cos v]::operators)) - ((old y) * ((角度)の[sin v]::operators))) にする [new y v] を (((old x) * ((角度)の[sin v]::operators)) + ((old y) * ((角度)の[cos v]::operators))) にする
ロール
ロールは、ここでは、y軸に対する回転を表す。
[new x v] を (((old x) * ((角度)の[cos v]::operators)) - ((old z) * ((角度)の[sin v]::operators))) にする [new z v] を (((old x) * ((角度)の[sin v]::operators)) + ((old z) * ((角度)の[cos v]::operators))) にする
ピッチ
ピッチは、ここでは、x軸に対する回転を表す。
[new y v] を (((old y) * ((角度)の[cos v]::operators)) + ((old z) * ((角度)の[sin v]::operators))) にする [new z v] を (((-1) * ((old y) * ((角度)の[sin v]::operators))) + ((old z) * ((角度)の[cos v]::operators))) にする
ピッチでは角度を加算ではなく減算している。ここではピッチを反時計回りではなく時計回りで考えているからである。
ヨー
ヨーは、ここではz軸に対する回転を表す。
[new x v] を (((old x) * ((角度)の[cos v]::operators)) - ((old y) * ((角度)の[sin v]::operators))) にする [new y v] を (((old x) * ((角度)の[sin v]::operators)) + ((old y) * ((角度)の[cos v]::operators))) にする
これらの変換を適用する順番は、次のとおり:
頂点 → ワールド空間に配置 (モデル変換):
- スケール
- 回転
- 平行移動
ワールド空間 → クリッピング空間 (ビュー変換):
- 平行移動
- 回転

投影変換
3次元空間の座標を2次元平面に投影するには、次のコードを使用する:
[2D x v] を (((3D x)/(3D z))*(焦点距離)) にする [2D y v] を (((3D y)/(3D z))*(焦点距離)) にする
これは、遠くにいけばいくほど (z軸が大きくなればなるほど)、小さく見えるという関係を表している。
変換スクリプト例
ここまでに、3Dモデルを3D空間に配置して、それらをディスプレイ平面上で表すための計算方法をひととおり説明した。それでは、これらをスクリプトにまとめてみよう。
このチュートリアルでは、ワールド空間の周りをぐるっと回転するカメラ位置 (視点位置) を想定している。このカメラは、回転だけでなく平行移動も可能である。カメラ位置が回転、平行移動した場合は、ビュー変換を行うときに、この移動の値を逆にして適用することを忘れないでほしい。
準備
以下の変数を使用する
(2D x) //2D変換後のx座標 (2D y) //2D変換後のy座標 (focal length) //焦点距離 (撮影できる範囲 (カメラでいう画角)が変わる) (x) //3Dモデルのx座標 (y) //3Dモデルのy座標 (z) //3Dモデルのz座標 (scale factor) //スケール因子。3Dモデルのサイズ (roll) //3Dモデルのロール角度 (pitch) //3Dモデルのピッチ角度 (yaw) //3Dモデルのヨー角度 (temp)// 作業用変数 (座標を一時待避するのに使う) (translation x) //3Dモデルのx方向への移動距離 (translation y) //3Dモデルのy方向への移動距離 (translation z) //3DモデルのZ方向への移動距離 (camera x) //カメラの3D空間でのx座標位置 (camera y) //カメラの3D空間でのy座標位置 (camera z) //カメラの3D空間でのz座標位置 (cam roll) //カメラのロール角度 (cam pitch) //カメラのピッチ角度 (cam yaw) //カメラのヨー角度
頂点のレンダリング
次に、ここまでに説明したすべての変換を行い、各頂点の2次元画面での位置を決定するカスタムブロックのスクリプトを記載する。
定義 頂点計算 [x v] を ((x) * (scale factor)) にする [y v] を ((y) * (scale factor)) にする [z v] を ((z) * (scale factor)) にする [temp v] を (x) にする [x v] を (((x) * ((roll)の[cos v]::operators)) - ((z) * ((roll)の[sin v]::operators))) にする [z v] を (((temp) * ((roll)の[sin v]::operators)) + ((z) * ((roll)の[cos v]::operators))) にする [temp v] を (y) にする [y v] を (((y) * ((pitch)の[cos v]::operators)) + ((z) * ((pitch)の[sin v]::operators))) にする [z v] を (((-1) * ((temp) * ((pitch)の[sin v]::operators))) + ((z) * ((pitch)の[cos v]::operators))) にする [temp v] を (x) にする [x v] を (((x) * ((yaw)の[cos v]::operators)) - ((y) * ((yaw)の[sin v]::operators))) にする [y v] を (((temp) * ((yaw)の[sin v]::operators)) + ((y) * ((yaw)の[cos v]::operators))) にする [x v] を (translation x) ずつ変える [y v] を (translation y) ずつ変える [z v] を (translation z) ずつ変える [x v] を ((-1) * (camera x)) ずつ変える [y v] を ((-1) * (camera y)) ずつ変える [z v] を ((-1) * (camera z)) ずつ変える [temp v] を (x) にする [x v] を (((x) * ((cam roll)の[cos v]::operators)) + ((z) * ((cam roll)の[sin v]::operators))) にする [z v] を (((-1) * ((temp) * ((cam roll)の[sin v]::operators))) + ((z) * ((cam roll)の[cos v]::operators))) にする [temp v] を (y) にする [y v] を (((y) * ((cam pitch)の[cos v]::operators)) - ((z) * ((cam pitch)の[sin v]::operators))) にする [z v] を (((temp) * ((cam pitch)の[sin v]::operators)) + ((z) * ((cam pitch)の[cos v]::operators))) にする [temp v] を (x) にする [x v] を (((x) * ((cam yaw)の[cos v]::operators)) + ((y) * ((cam yaw)の[sin v]::operators))) にする [y v] を (((-1) * ((temp) * ((cam yaw)の[sin v]::operators))) + ((y) * ((cam yaw)の[cos v]::operators))) にする [2d x v] を (((x) / (z)) * (focal length)) にする [2d y v] を (((y) / (z)) * (focal length)) にする
モデルの各頂点に対し、このカスタムブロックを適用して、得られた2次元の座標 (2d x, 2d y)を使って描画すれば3Dグラフィックが完成する。
魚眼レンズについて考えなくていい理由
目に映る風景は、魚眼レンズのように曲がっているため、遠近法は実際には成り立たないと考える人もいるかも知れないが、それは間違っている。
右に示す画像を考えるヒントにしてほしい。
重要なのは、目と頂点を結ぶ直線と、画面の交点である。
画面上で魚眼レンズのように捻じ曲げてしまったら、目に入ってくるときに、さらに捻じ曲がってしまう。
(画像内にある「等間隔でない」は無視してよい。遠近法を表現するとき、Zで引くのではなく割る理由を説明しているものである)