ワイヤーフレーム法による三次元コンピュータグラフィックス

コンピュータの利用分野の一つとしてグラフィックス処理は非常に重要な分野の一つであり,特に三次元的な画像表示を取り扱う三次元コンピュータグラフィックス(3Dグラフィックス)は,一般に大量で高速な数学的計算に基づく,まさしく「計算機」としてコンピュータをフル活用した典型的な技術の一例と言える.

さて,ここではプログラミング初心者でも可能な3DグラフィックスプログラミングにHandyGraphicライブラリを用いて挑戦してみよう.簡単のため,幾つかの「直線」だけで構成された立体図形を描画する事を想定する.そのようなCGをワイヤーフレームCGと呼ぶ.(実際のワイヤーフレームCGでは,直線だけでなく曲線も使う.)

三次元グラフィックスといっても最終的には二次元的な平面ディスプレイ上に描画する必要があるので,3次元空間の図形を二次元平面上の図形に変換しなければならない.そのような手法は幾つもの手法が存在するが,最も単純な手法に「投影法」がある.

投影法の考え方

投影法の概略は以下のように考える事が出来る.

この図のように空間座標軸X,Y,Zを想定した3次元空間に配置された物体αに対して,その内の2軸(例えばX軸, Y軸)を含む平面Pに対して垂直でない平面Qを考える.

この時,物体αに対してZ軸方向から平行光線をあててQ上に物体を投影した時の「影」を平面Q上に描画するのが投影法(正確には平行投影法)である.この平面Qがディスプレイ画面(又は,描画ウィンドウ)に相当する.また,この図のようにX, Y, Z軸を取る方法を「右手系」と呼ぶ.右手系の座標軸の取り方は,右手の親指,人さし指,中指を開いて直交させた状態で,指の付け根を原点とし,親指をX軸,人さし指をY軸,中指をZ軸の方向に据える.

左手の指を同じように開いた形で座標軸を取る方法を左手系と呼び,右手系の鏡像対称になっている.物理学やコンピュータグラフィックスなど多くの分野において,右手系が標準(主)で左手系は非標準(従)とされているようだ.
ちなみに,さらに違う座標系の説明はこちらのページで(笑)

ここでの実習内容では「直線」だけで構成された立体図形を描画することを想定しているが,投影法において元の三次元空間内の直線は二次元投影面上の直線に変換されるので比較的簡単に描画出来る.

この「元の直線は投影先でも直線になる」考え方は線形代数学における線形写像の概念を用いて説明できるが,その詳細は本演習の守備範囲を超えるので詳細は省略する.

結局,三次元空間中の直線(正確には線分)Lは二次元投影平面Q上の線分Mへ投影されることになる.さらに,三次元空間中のLの端点となる始点・終点は,投影平面上の線分Mの始点・終点にそれぞれ投影される.このことから,三次元空間中の線分Lの投影図を描くにはLの始点及び終点が投影される投影面上の位置をそれぞれ計算し,その2点を結ぶ直線を投影面Q上に描画すればよいことになる.この描画はHandyGraphicの「直線の描画」機能を用いる事で簡単に実現できる.

ここで想定しているグラフィックスは「直線だけで構成された立体図形」であるから,その図形を構成する有限個の各線分に対する投影結果を描画してゆけば,目的とする「立体図形の投影図」が描画できることになる.

投影法によるワイヤーフレームCG

それでは,いきなり3Dグラフィックスを描画するプログラムを作成するのは少しハードルが高いので,順に段階を踏みながら徐々に必要な機能を実現してみよう.

ここで,プログラム作成の前提条件は以下のとおりと想定する.

各種の数学的計算(三角関数)を行う必要があるため,座標値を扱う変数は全て「倍精度実数型」である double 型を使うようにするべし.

また,三角関数や対数関数などの数学関数(標準数学関数)の教科書7.2節に詳しく乗っているが,それらの数学関数をC言語プログラムで用いる場合には,math.hヘッダファイルをインクルードしておかねばならない.(つまり, #include <math.h> という一行をソースプログラムの先頭箇所に付けておく必要がある.尚,数学関数を使ったソースプログラムの例は,こちらのページに例示している.)

一般的には,本課題でのプログラムでは座標値やカウンタ変数など数多くの変数を同時に使うことになるだろう.各変数の「目的・使い道」を上手く反映するように,変数名の付け方を良く考えながら工夫することが欠かせない.また,定数も複数を使い分けると予想されるので,上手にマクロ定義(#define文)を使うようにしてプログラムの修正や一時的変更が簡単になるようにしておく事を勧める.

明日のためにその1. 三次元空間を表す座標軸の表示

いきなり投影図形を描こうとしても,その見掛けから正しいかどうか良く分からない状態になってしまうので,まずはX軸,Y軸, Z軸の3座標軸を描いてみよう.各座標軸は原点から長さ300の直線で表し,その直線の端点には軸名を表すラベルとなるアルファベット(XとYとZ)を表示させるものとする.

但し,正確に言えば,原点を始点として,各座標軸に乗る長さ300の線分が3本直交している図形を描くことを意味しており,3次元空間の座標軸が回転して動くと考えるわけではないことに注意すべし.

三次元空間内の点P(a, b, c)を投影面Qに投影すると点Q(x, y)に投影されるとすると,その座標の関係は次の行列式で表現される.

練習:以下のプログラム xaxis.c を作成せよ.

手始めとして,X軸のみを投影した状態を表示するプログラム xaxis.c を作成してみよ.

HandyGraphicの描画座標はウィンドウ左下角を原点とするので,上記の練習で分かるようにX軸をそのまま描画するとウィンドウ端に重なってしまう.投影面Qの原点が描画ウィンドウの中央に来るように調整したほうが良いだろう.(このように基準点を補正する操作をオフセット (Offset)と呼んだりする.)

この調整は二次元平面上の点の平行移動として表現できる.二次元平面上の点P(x, y)をX座標でdx, Y座標でdyだけ離れた位置Q(X, Y)へ平行移動すると,その関係は次の行列式で表現される.

練習:以下のプログラムxoffset.cをを作成せよ.

投影面の原点が描画ウィンドウ中央にくるように補正してX軸を表示するプログラムxoffset.cをを作成してみよ.

練習:以下のプログラムthreeaxes.cを作成せよ.

上記で作成した xoffset.c を少し拡張すると,X軸,Y軸,Z軸の3軸が画面中央に表示されるように表示するプログラムが作成できるだろう.各自取り組んでみよ.

この練習で分かるように,Z軸に垂直な平面へ投影する場合,Z軸が「点」になってしまって全体の様子がよく分からない投影図になってしまう.さて,それではどうするか?

君たちは,立体を二次元平面上に投影して表示するという例として,既にGNUPLOTを用いた数学的立体グラフの描画というテーマを経験しているはずだ.GNUPLOTでは立体グラフを見やすくするため,2つの軸について少し回転させた状態として描画していた.投影法によるグラフィックス描画でも同じように少し回転させた状態の投影図を描くと,より立体らしく見せる事が出来る.同様にここで想定している「投影図」もX軸とY軸周りに少し回転させた状態で描くようにしてみよう.

ここで,座標軸周りの回転向きにはいわゆる時計回り,反時計回りの二通りがあり,どちらの回転角度が「正」なのか決めておく必要があるが,これも「右手系」では右手を使って簡単に理解できるように決められている.右手系の各座標軸を表す3本指のうち,親指だけを立てたまま残りの指を全部折り曲げて握った状態(いわゆる「グッジョブ」,又は,「いいね!」の形)を見て,親指が伸びる方向を座標軸の正方向とすると,残りの指の根元から指先へ向かう回転の向きが「正回転」となる.

三次元空間内の線分を座標軸周りに回転させても,始点と終点がそれぞれ異なる位置に移動はするが図形的には線分のままである.(実は回転操作も線形写像の一種であるので,直線を回転させた図形は,やはり直線となる.)

従って,元の線分を回転した状態の投影図を描くためには,始点・終点が回転移動した先の点を求めて,それらの点を両端とする空間中の線分を投影面に投影した状態を描けばよい.

ここで,三次元空間内の点P(a, b, c)がX軸周りθx回転によりQ(x, y, z)に移動したとすると,その幾何学的関係を図示するとは以下のようになる.

PとQの座標関係は以下の式で表現される.但し,回転角度は回転軸が指す方向より原点を見た状態で反時計回りが正である.

また同様に,三次元空間内の点R(a, b, c)がY軸周りθy回転によりS(x, y, z)に移動したとすると,RとSの座標関係は以下の式で表現される.

さらに,三次元空間内の点PがX軸周りθx回転,Y軸周りθy回転により点Qに移動した場合は,まずY軸周りに回転させて次にX軸周りに回転させる(つまり「回転操作の合成」)と考えよう.

明日のための提出課題その1

X軸,Y軸,Z軸の3軸を,X軸について29.5度,Y軸について-29.5度回転した状態で表示するプログラムrotateaxes.cを作成しなさい.尚,C言語の各三角関数は引数を弧度法(ラジアン単位)で与える必要があるので注意すること.

尚,作成したプログラムが正しいかどうか確認するには,各軸の回転角度を0度,±30度,±60度,±90度,±180度などと,回転度合いが確認しやすい切りの良い角度で色々試してみて確認おくと良いだろう.

さあ,プログラムを書くべし!書くべし!書くべし!

レポ−ト名: program/wireaxes

提出期限:教員の指示による

実行例

2013/07/18追記: この課題達成のための参考資料を /usr/local/cse/class/oomoto/program/c_source/wireframe/threeaxes_hints.c として置いたので,必要な人は参照してください.

明日のためにその2. 直線描画のための座標値の入力

ここまでにて,座標軸だけを表示させることは出来るようになった.ここのトピックでは「幾つかの直線で構成された立体図形」を表示させたいので,まずは「空間中の一本の直線」をキーボードから座標値を入力して描かせてみよう.

既に説明したように,空間中の直線(線分)の投影図を描くには,空間直線の始点と終点が投影される投影面上の点の座標が計算できれば良いので,元の空間直線の始点と終点の座標値が必要になる.三次元空間中の点はX, Y, Z各座標値3つで表せるから,結局,直線を描くには合計6個の座標値が必要になる.この6個の値をキーボード入力するにはscanf関数を6回実行すれば出来るが,少し煩雑なので,一度に入力できるようにしてみよう.

実はscanf関数は,非常に多彩な使い方が出来る機能を本来持っている.その一つに複数のデータを一行で読み取るというものがある.例えば,

double x, y, z;			
……
……	
……			
scanf("%lf %lf %lf", &x, &y, &z); 			
	 // double型変数にキーボードから値を読み込む時の書式文字は %lf を使う.%d ではないので注意すること.

といったコードを書けば,変数x, y, zに対して,3つの実数値を空白文字(スペース文字,又は,TAB文字)で区切った一行をキーボードから入力すると,一気に変数へ代入出来る.

このscanf関数の機能を使ったサンプルプログラムが

として置いてあるから,各自,ソースプログラムの内容を確認して理解した上で,コンパイル・動作確認してみよ.

このような使い方をすれば,たとえ6個の実数値でも一度のscanf関数の実行にて入力出来るはずだ.

練習: 以下のプログラムspaceline.cを作成せよ.

6個の実数値x0, y0, z0, x1, y1, z1を一行で入力すると,始点(x0, y0, z0),終点(x1, y1, z1)である空間直線Lの投影像Mを描画するプログラムspaceline.cを作成せよ.但し,座標軸も合わせて表示させ,投影状態はX軸について29.5度,Y軸について-29.5度回転した状態で表示すること.また,動作確認は以下の3種類の直線で確認すること.

これらの直線は全て同じ点を通る.それを意識すれば動作確認に繋がるだろう.

明日のためにその3. 座標値の連続入力による立体描画

練習:以下のプログラムmultiLine.cを作成せよ.

上記練習で作成したプログラムspaceline.cを元にして次のようなプログラム multiline.c を作成せよ.

プログラムが実行されると,キーボードから6個の実数値を空白文字で区切った一行で入力する事を求めてきて,それを入力するとその実数値を端点の座標とする空間直線を描画する.描画したら再度同じようにキーボード入力を求めて,入力されたら対応する直線を描画することを繰り返す.尚,このキーボード入力の繰り返しは最大1000回までとする.

尚,入力データの6個全部を0として入力すると繰り返し処理を打ち切ってプログラムが終了するようにすること.また,座標軸は描画する必要はない.

注意 繰り返し処理の打ち切りを表現するには,while文と使うとかbreak文を使うなどの手がある.また,繰り返しを制御するカウンタ変数については整数型を使うのが良い.

さて,上記のmultiline.cを実行して,キーボード入力を間違わずに延々と続けられれば,論理的には直線の組合せである限りどんな複雑な立体図形でも描画できることになるが,そんなことは非現実的である.

しかし,君たちはこのような操作を上手く行う手段を既に学んでいる.前回の「コマンドの連携による問題解決とアニメーション」トピックスにて,プログラムの標準入力へ与えるデータを予めテキストファイルの形で用意しておき,「入力リダイレクション」を使ってファイルからデータを読み取らせて動作させるというものだ.また,6月5日「日本語漢字コ−ド,リダイレクション,パイプライン」トピックスにて,通常の場合,標準入力はキーボード入力に結びついているという事も学んでいる.これらの知識を組合せると,「人間がキーボード入力するべきデータを予めテキストファイルとして用意しておき,入力リダイレクションを使ってファイルからデータを読み取るように動かす」という操作が可能だ.

そのような操作に使える例として,いわゆるダイヤモンド型立体として

というサンプルデータファイルが用意してあるので,これを各自の作業ディレクトリに複製した上でエディタに読み込んで内容を確認せよ.また,このファイルに記載されたデータがどのような立体図形を描くものか動作確認してみよう.

./multiline < diamond.dat

実行例

いわゆるエンゲージリングによく使われる宝石であるダイヤモンドの代表的カット形状として知られるブリリアントカットのデータファイルも置いてあるので,興味ある者は動作テスト用に用いても良い.
/usr/local/cse/class/oomoto/program/c_souce/wireframe/briliant.dat

練習:以下の要領でmultiline.cの動作を再確認せよ.

原点を中心とする一辺200の立方体を表現する図形データファイルcube.datを作成し,これを用いて画面上にその立方体を表示させてみよ.

明日のための提出課題その2

上記で作成した立体描画プログラムに座標軸も合わせて表示する機能を付け加えたプログラム linesandaxes.c を作成して提出せよ.但し,描画図形は青色,座標軸とラベルは黒色で表示させること.(尚,厳密には,図形を表す直線と座標軸をを表す直線の前後関係までを計算して図形と座標軸が重なってみえる点の色を決定しなければ,少し不自然な描画結果になる.これを陰線処理と呼ぶが少し高度な概念になるので,ここでは無視する.)

さあ,くじけてないで,プログラムを書くべし!書くべし!書くべし!

レポ−ト名: program/wireframe

提出期限:教員の指示による

ワイヤーフレームによる三次元CGアニメーション

前回の課題にあるように,CGアニメーションは少しずつ異なる絵を連続して表示する事で実現されている.ここまでで既に立体図形を少し回転させた状態で表示させる事が出来るようになっているはずだ.という事は,その回転度合いを少しずつ変えた描画を連続して行えば簡単な立体図形アニメーションが実現できる事になる.

しかし,ここまでは立体を多数の線分の組合せとして描くためのデータはキーボード入力(或いは,リダイレクションによるファイルからの入力)で与えていた.このアニメーションを実現するには同じ立体を何度も描く必要があるから,そのままでは何度もファイルを読み込ませるようなことが必要になってしまう.

そのようは処理はリダイレクションでは難しいので,考え方を少し変えよう.

君たちは既に沢山の入力データを上手く系統的に扱うやり方として「配列」の考え方を学んでいる.それを使おう.つまり,多数の直線を描くための座標値は配列に格納するようにして,アニメーションの各コマの図形を描くときには,その配列に格納されている値を使うようにする.そうすれば,データ入力については配列に座標値を格納するためにプログラムの初期段階で一度だけ行えば良いことになる.

その配列はどのように用意すれば良いかについて幾つか考え方があるが,例えば,一行に6個の要素が並ぶ二次元配列を使えば,一行分で一本の直線を描くための座標値を格納しておけるだろう.(その他にも表現方法のバリエーションはある.)

明日のための上級課題

上記のdiamond.datなど,予め用意しておいた立体図形データファイルを入力リダイレクションして,その立体図形がY軸を中心として一回転するように見えるアニメーションを表示するプログラム moving.c を作成せよ.座標軸表示は不要であるが,X軸周りについては常に29.5度傾いた状態で表示させること.また,Y軸周りの回転が一周した後はプログラムを終了するようにせよ.

尚,読み込むデータファイルについて,その行数は最大1000行までしかあり得ないものとし,データファイルの内容に間違いは存在しないないものとする.また,HandyGraphicの描画性能はあまり高くないので滑らかに動くアニメーションにはならないが,それはやむを得ないものとする.

さぁ,プログラムが出来ないと挫折している場合ではない.根性で立ち上がりプログラムを書くのじゃ. 立てぇい,立つんだ,ジョぉぉぉぉぉぉぉぉぉぉぉぉ!

レポ−ト名: program/wireanimation

提出期限:教員の指示による

基礎プログラミング演習Iの表紙ページへ戻る