Large Display Size Middle Display Size Small Display Size
印刷用 概要 キーワード 著者

原理で3DCG 第1回の補足説明

原理で3DCG 第1回 基本のプログラム

『原理で3DCG』 第1回で、読者の方が疑問に思うかもしれない部分について補足説明を作成しました。

Contents

  1. ちょっと複雑に見える var x = tens[ i ].x;
  2. なぜ 1 や -1 なのか?
  3. v = -v; これがよくわからない。
  4. var h = x * ( s / z );は x * s / zでいいんじゃ?
  5. テクスチャマッピングできる?
  6. ハイクオリティなゲームはできる?

ちょっと複雑に見える var x = tens[ i ].x;

以下の赤い部分が、ちょっと複雑に見えると思うので説明します。

function onloadx() {

    var canvasTag = $( "canvasID" );

        canvas = canvasTag.getContext( '2d' );



    //頂点1つ1つ取り出す
    for( var i = 0; i < tens.length; i++ ) {


        //1:空間座標について

        var x = tens[ i ].x;
        var y = tens[ i ].y;
        var z = tens[ i ].z;

この3行のうちの var x = tens[ i ].x; について説明します。

説明の過程で、「配列」「オブジェクト」についても説明します。

1行を3つに分ける

この1行は、 var x と、 = tens[ i ].x 、そして  の3つに、意味として分割できます。


var x は、「変数 x をここで宣言します」という意味です。

= tens[ i ].x は、「宣言と同時に、変数に最初の値として tens[ i ].x を入れます」という意味です。

最後の  は、「文の終わりです」という意味です。

var x をさらに2つに分ける

var x はさらに2つに分けられます。 var と x です。

var は「変数宣言をする」という意味です。

x は変数の名前 です。(プログラマーが自由に決められる名前です)

= tens[ i ].x も2つに分ける

= tens[ i ].x も2つに分けられます。 = と tens[ i ].x です。

= は 「左の変数に、右の値を入れる」 という意味です。「代入」と呼ばれています。

左の変数に、tens[ i ].x の値が入るということです。

そして、tens[ i ].x は 今回一番複雑に見える部分 です。特別に説明しましょう。

一番複雑に見える tens[ i ].x

tens[ i ].x は2つに分けられます。 tens[ i ] と、 .x です。

tens[ i ] は 「配列変数 tens の、 i 番目の箱」 という意味です。

この意味自体が難しいので、分けて説明します。

『配列変数』、『tens』、『i 番目の箱』の3つに分けます。


『配列変数』 とは、たとえばこのようなものです。(茶色は段ボール箱をイメージして)

0 1 2 3 …つづく
メンクン スコテシ アメショ スフィン …つづく


番号のついた箱に何かが入っています。

それに対して、普通の変数はこうです。

neko
ミケネコ


neko という名前のついた箱に何かが入っています。

つまり配列変数とは「普通の変数を横に並べて 番号で管理しよう」というものです。

プログラムではたくさんの箱をこのように番号で管理すると、とても便利になります。


つぎに、 『tens』 は「配列変数につけた名前」です。


そして、 『i 番目の箱』 は「配列変数の i 番目の箱」という意味です。

この i は変数です。この3DCGのプログラムでは以下のような for 文 で用意されています。

//頂点1つ1つ取り出す
    for( var i = 0; i < tens.length; i++ ) {

i は 0 から 箱の数の1個前 まで、順に変わります。


あらためて、tens[ i ] とは、「配列変数 tens の、 i 番目の箱」 という意味ですが、わかりますか?

.x

最後に tens[ i ].x の .x の説明です。

tens[ i ] とは、上で説明したとおり、 「配列変数 tens の、 i 番目の箱」 という意味です。

この3DCGのプログラムでは、その箱の中には「オブジェクト」が入っています。

「オブジェクト」とは、たとえばこのようなものです。


tabemono toire omocha kazari …つづく
まぐろ すな ねこじゃらし くびわ …つづく

配列の時は 「番号のついた箱」 が並んでいましたが、オブジェクトでは 「名前のついた箱」 が並んでいます。

(ちなみに、配列は「番号順」に並んでいますが、オブジェクトでは特に順序を付けて並んでいません)

このオブジェクト自体の名前が neko_goods だとすれば、オブジェクトの中の tabemono という箱は、以下のように書きます。

neko_goods.tabemono

この中には まぐろ が入っています。

このような使い方をするものが「オブジェクト」です。


3DCGのプログラムで tens[ i ] にはオブジェクトが入っていますが、それはこのようなものです。

x y z
-1 -1 -1


.x は、このオブジェクトの箱 x のことを指しています。

tens[ i ].x と書いたとき、その値は x という名前の箱の中の -1 です。


tens[ i ]  は箱であると説明しました。また、その中に入っているオブジェクトも箱の並びだと説明しました。

だから、以下のように「箱の中にさらにいくつかの箱」という形になっています。

tens[ 0 ] tens[ 1 ] tens[ 2 ] tens[ 3 ] …つづく
オブジェクト
x y z
-1 -1 -1
オブジェクト
x y z
+1 -1 -1
オブジェクト
x y z
+1 +1 -1
オブジェクト
x y z
-1 +1 -1
…つづく


一番複雑に見える、tens[ i ].x は、tens[ i ] の部分がオブジェクトであり、そのオブジェクトの x という名前の箱を .x と書いて表している、と言えます。

説明をまとめると

var x = tens[ i ].x; とは、

『変数 x を宣言します。最初の値として tens[ i ].x を入れます。tens[ i ] はオブジェクト "3D空間の中のある1点を表す座標"です。そして .x はそのうちの x 座標です』

という意味になります。

難しかったでしょうか。

なぜ 1 や -1 なのか?

第1回のプログラムの最初のほうで、以下のように3Dモデルのデータを設定しています。

「なぜ -1 や +1 を使っているのか?」と思いませんでしたか?

//正六面体の頂点データ
var tens = new Array();
    var a;
    a = new Object();    a.x = -1;    a.y = -1;    a.z = -1;        tens[ 0 ] = a;
    a = new Object();    a.x = +1;    a.y = -1;    a.z = -1;        tens[ 1 ] = a;
    a = new Object();    a.x = +1;    a.y = +1;    a.z = -1;        tens[ 2 ] = a;
    a = new Object();    a.x = -1;    a.y = +1;    a.z = -1;        tens[ 3 ] = a;
    a = new Object();    a.x = -1;    a.y = -1;    a.z = +1;        tens[ 4 ] = a;
    a = new Object();    a.x = +1;    a.y = -1;    a.z = +1;        tens[ 5 ] = a;
    a = new Object();    a.x = +1;    a.y = +1;    a.z = +1;        tens[ 6 ] = a;
    a = new Object();    a.x = -1;    a.y = +1;    a.z = +1;        tens[ 7 ] = a;


実はこれは 1 でなくても構いません。

3Dモデルの頂点データは 15 や、125 や、30000 のような数字でも構いません。

たとえばShade3Dで大きめの正六面体の頂点データをファイルに出力すると、以下のようになります。

Vertices:
1000,-1000,-1000,
1000,1000,-1000,
1000,-1000,1000,
1000,1000,1000,
-1000,-1000,1000,
-1000,1000,1000,
-1000,-1000,-1000,
-1000,1000,-1000

Verticesとは「頂点」という意味で、数値は x, y, z, の順に点の数だけ繰り返しています。


第1回のプログラムで、3Dモデルのデータを設定した後に以下のような変数を設定しています。

//正六面体の大きさデータ
var scale = 50;

scaleと書いてスケールと読みます。目盛り、規模、程度といった意味です。

先ほどの、1や-1をscale倍するということになります。

scaleが50のときは、50倍で、50や-50になります。

そして、1や-1は正六面体の中心を0としたときの値なので、正六面体の大きさは-1~1で2です。これが50倍なので、-50~50で、結局100の大きさとなります。

v = -v; これがよくわからない。

- を付けるだけで、図形が逆になる、というのがわからないかもしれません。

縦軸のvの値に - を付けると、原点を境にして上下反転されます。

以下のグラフの説明ではv座標ではなくアルファベットをyにしてy座標で説明します。

図の黒い点( 5, 4 )のyの値4に-を付けると、赤い点( 5, -4 )になります。



では、図のように黒い点を3つ用意して、三角形を作ります。

上と同じように、yの値に - を付けると、赤い点の三角形になります。

三角形は原点を境にして上下反転されています。



プログラムと同じように原点が図形の真ん中(付近)にある場合も見てみましょう。

この場合も同じで、yの値に - を付けると、原点を境にして上下反転されます。



そして、プログラムの動きはこうなっています。


最初にコンピューターグラフィクスの画面の座標は、左上が原点になっています。



プログラムの途中で3Dの座標 x, y, z を画面の座標 h, v に変換( h=x*(s/z)、v=y*(s/z) )していますが、その計算結果のままの h, v で画面に描くとこのようになってしまいます。

描いたものがさかさまです。

モデルを作成したときの縦軸が上向きだったからです。

画面は素直に、そのとおりに描いてしまいます。


画面に描く際にv座標に - を付けると、先のグラフで説明したように原点を中心に上下反転されます。

(グラフの上で考えたことが、コンピューターの画面で再現されるというのは、もしかしたら慣れないことかもしれませんが…)



ついでですが、プログラムでは上下反転した後、h, v それぞれ、画面の幅の半分を足して、描きたいものを画面の中心に持ってきています。

これで正常に描かれます。



なお、図のさかさまを直したり、原点を画面の中心に移動したりする以上の計算は、「ソフトで対応する」みたいな言い方をします。

画面座標は、ソフト的には左図のようになっています。

これは…




モデルを作成したときのx軸、y軸と方向が同じになっています。

だから正常に描かれるわけですね。



var h = x * ( s / z );は x * s / zでいいんじゃ?

そのとおりです。本当はカッコは不要です。

ただ、意味として強調したいのでカッコを付けてあります。


余談ですが、コンピューターは四則演算のうち、割り算があまり得意ではないそうです。

四則演算の中で唯一、「推測」みたいな処理をするから、らしいです。

125 ÷ 31 は? えーっと、31がだいたい、4つ入るかな?31 * 4 = 124 いけそうだな!

125 ÷ 26 は? えーっと、26がだいたい、5つ入るかな?26 * 5 = 130 だめだな!

じゃあ、26 * 4 = 104 これでいけるか!

…のように人間が行う割り算と同じことをしているそうです。同じじゃなくても、同じくらい面倒な処理をしているそうです。

そのため、もしかしたら、

var h = x * ( s / z );
var v = y * ( s / z );

この式は、

var f = s / z;
var h = x * f;
var v = y * f;

こうしたほうが多少速くなるかもしれません。

FirefoxやGoogleなど、ブラウザ同士はシェア争いで競争しているので、上のような速度アップは「自動的に」やってるかもしれません。(最適化という)

参考: http://fast-programming.aglk.net/division/

テクスチャマッピングできる?

たとえばRPGのキャラクターを3DCGで作るとき、その顔はどうやって描いたらいいでしょうか?

多くの場合はテクスチャマッピングという方法を使います。

テクスチャマッピングとは、3Dモデルに画像を貼りつけることです。

fig.
※この顔は落書きです
fig. fig.


基本的には、WebGLを使わないで、難しい計算もしないで…という条件では、テクスチャマッピングはたぶんできません。

難しい計算は必須でしょう。

代わりにこんな方法はどうでしょうか。

fig.
▲パスの3D化

左の画像をクリックすると、サンプルのJavaScriptが動きます。

正六面体を表した8つの点●が描かれ、その正六面体の1つの面に顔が描かれます。

正六面体が移動すると顔も移動し、回転すると顔も回転します。

これってテクスチャマッピングみたいですよね。

これはCANVASのパスという機能を使って描いています。座標をいくつか指定すると座標に沿って線を描いてくれる機能です。

その指定する座標を3Dモデルの座標と同じように3DCG計算すれば、正六面体と同じように3DCGとして描かれるというわけです。

例によって難しい計算はしていないので、なかなかいいんじゃないでしょうか。

(回転だけはsin, cosを使っていて難しい計算です)


その他の案も考察

顔をモデリングする

3DCGで人物の顔を作ろうというとき、普通だったら、まず、「顔をモデリングしよう」と思うのではないでしょうか。

でも顔をモデリングするのは大変です。

美術の時間の彫刻のように器用さが必要になります。

また、顔に段差が増える分、データ量が増えて、ゲームで動かしたときの処理時間も増えてしまいます。

顔のモデリングはやめておいたほうが良いでしょう。


WebGLを使う

JavaScriptでテクスチャマッピングを使った3DCGをやるんだったら、普通はWebGLを選択するでしょう。

パソコンやスマートホンには3DCGを描くためのハードウェアが内蔵されていて、そのハードウェアを使う機能のことをOpenGLと呼びます。さらにインターネットブラウザのJavaScriptからOpenGLを使う機能のことをWebGLと言います。

WebGLなら、テクスチャマッピングの機能が用意されています。

今ここでそれを使わない理由はなんでしょうか。

  • WebGLはどこか難しい。簡単に行う方法が必要だ。
  • WebGLはWebGLが使える機械だけでしか使えません。WebGLで作った3DCGプログラムをNintendo3DSのプチコン(SmileBASIC)に移植しようというとき、WebGLの描画コマンドはプチコンにはないので移植できません。また、自作の電子回路+液晶パーツというマニアックな環境でも同じで、WebGL(OpenGL)は用意されていないことが多いです。
  • ほかには「自分の力で全部計算したい」というニーズもあると思います。

WebGLはきれいなんですけど、まだ簡単ではないので、別のときにでもとっておきましょう。


自分でテクスチャマッピングのプログラムを作る



下記のサイトでは、WebGLなどハードウェアの力なしで、実用的な速度でテクスチャマッピングを実現しています。

http://d.hatena.ne.jp/gyuque/20090211#1234364019

このサイトによると、JavaScriptのcanvas の transform メソッドで、"描画そのものを変形させる設定"(正方形を描くと自動的に平行四辺形になるような設定)にしてから、drawImageメソッドで画像を描けば、面に画像をマッピングできるそうです。

サイトに掲載されたデモの速度も遅い感じではありませんでした。

ただ、行列などの難しい計算を使っているので、理解するのは大変でしょう。


以上の通り、その他の案は難しいので、先に提案した「パスの3D化」が手軽で良いでしょう。

ハイクオリティなゲームはできる?


fig.
▲ハイクオリティなゲームはできるか

「原理を使用した3DCG」で、左のような画面のゲームはできるでしょうか?

ハイクオリティの程度によりますが、少なくとも左のような画面とまったく同じ質のゲームはできないでしょう。

樹木の美しい陰影と、滑らかさを表現する多数のポリゴン(面)、クリスタルの透明処理と反射処理、地面に落ちる影。

最新のゲーム機はこれらをアニメーションで描画し続ける処理能力があるようですが、「原理を使用した3DCG」では静止画でなら描ける可能性はありますが、ぶっちゃけ無理でしょう。


ただ「ハイクオリティに近づくような性質」がいくつかあるので紹介します。

逆に「ハイクオリティにならない理由」と、「きれいに見せる工夫」も挙げておきます。

ハイクオリティに近づく性質

構図が売り物の3DCG描画ソフトと同じになる。

左が Shade3D で描いたモデル、右が同じモデルを「原理を使用した3DCG」で描いたものです。

売り物のようなクオリティの高い色付けはできませんが、構図は同じにできます。


時間当たりのポリゴン(描画面)数

以下の環境(条件)で「原理を使用した3DCG」の処理能力を計測してみました。

計測環境:

  • ノートPC、core i5 2.3GHz, Intel HD Graphics 3000のPC性能
  • 60fpsになるようJavaScriptをプログラム
  • 陰面処理なし、陰影処理なし。(三角形のべた塗りのみ)
  • 著者が作成したベンチマークJavaScriptで性能を計測


著者が作成したベンチマークを信用して良いなら、500~1000枚程度の面(ポリゴン)を速度低下せずに描けました。

これで、どの程度のゲームソフトができるかはわかりませんが、人によっては期待ができる数字です。

fig.
▲大量のポリゴンを動かして処理低下するか

左の画像リンクをクリックすると、筆者が作成したベンチマークJavaScriptを開きます。

実用に耐えるかどうかはわかりません。

使用方法は、リンク先のreadme.txtをお読みください。

このソフトは「処理が低下するポリゴン数」を知るためのソフトウェアです。著者は無闇に1万ポリゴンを表示するとちょっと不安になります。CPU使用率が100%になり、CPUファンが最速で回るからです。

大量のポリゴンを動かしっぱなしにすると、CPUが高使用率のままになり、高温になります。責任は持てませんので、注意してください。使用後はベンチマークのウィンドウを閉じてください。このソフトは自動的には止まりません。


ハイクオリティにならない理由

ポリゴン数が少ない


GPU(3DCGを描くためのハードウェア)を使わないために描画が低速となり、その結果キャラクタ数やキャラのポリゴン数を、少なくせざるをえなくなる。

表現力

テクスチャマッピングできないので表現力が低い。

ポリゴン数が少ないと、光沢(スペキュラ)の表現ができない。

fig.
▲光沢表現に無理がある

ポリゴン数が少ないので、光沢(スペキュラ)部分がカクカクしている。ポリゴン(面)数をもっと多くできれば、自然な光沢になるだろう。




陰面処理の矛盾

GPUを使わないので良好な陰面処理(Zバッファ法)が使えず、一部、面と面の重なりが不自然になる。


きれいに見せる工夫案

  • 塗りつぶしの時に、CANVASのグラデーション機能を使って塗る。
  • 塗りつぶしの時に、CANVASの半透明色を使って塗る。
  • 「ランバート反射」(陰影の色計算)による陰影付けを行う。
  • 通常、テクスチャで表現する顔などは、代用で「パス」を使って描く。

▲「ランバート反射」による陰影付け



以上、第1回の補足説明でした。他にわからないことがありましたら、メールや掲示板でお知らせください。


ページ制作 homepage6047


ページの上端へ (もくじ開く)