「ゲームプログラミング/3Dグラフィック」の版間の差分

削除された内容 追加された内容
→‎現状: 「ゲームエンジンとの関係の現状」にタイトル変更。なんの「現状」なのか明記。
M 整備
9 行
ゲームエンジンは、個人では無料で使えるものもあるが、しかしフリーソフトではない(自由ソフトではない)。特に3Dゲームエンジンは、オープンソース'''ではない'''。UnityもUnreal Engine も、ソースコードは非公開である(非オープンソース)。
 
ゲーム業界にかぎらず、こういった非オープンソースの製品のことを一般に「プロ プライエタリ」と言います。たとえば、マイクロソフトのWindowsもOfficeソフトもプロプライエタリです。
 
 
29 行
== 3Dグラフィック ==
=== 計算の原理 ===
3D-コンピュータグラフィックの計算の方式は、投影面(スクリーン)の形状(おおまかに2種類)と、平行投影か透視投影かの2種類の違いによって、2×2=42×2{{=}}4種類ていど程度に分かれる。
 
投影面
122 行
 
 
現在では、GPUなどのグラフィックボードにより、このような面ごとのZバッファ法ていど程度の計算なら、ある程度の新しいパソコンならば、瞬時で可能であろう。
 
なお、カメラから被写体のあいだの距離のことを「z値」という。Zバッファ法のZとは、そのZ値のことだろう。
132 行
Zソート法は 点ごと ではないので、コンピュータの計算の負担は少ないが、アルゴリズムが複雑化しやすい欠点がある。また、くぼんだ部分のある被写体など、複雑な形状の被写体では、不正確な前後判定になる。
 
(Zソート法だろうZバッファ法だろうがなんだろうが、数値の大きさにもとづいた並び替えが必要になるが、このような処理は[[ゲームプログラミング/RPG#素早さ順行動のアルゴリズム]]』などにもあるので、考になるかと。
 
 
138 行
カメラの向きを変えたり、被写体をどこかを中心軸にして回転したあとの座標を計算したい場合は、
 
単に、高校数学~大学1年ていど程度の行列の理論の、回転行列の公式をつかえばいい。
 
 
224 行
また、 z > zs に位置する被写体だけを描画するものとしている。
 
なお、この仮定の場合、z軸そのものの座標は x=0x{{=}}0y=0y{{=}}0 である。
 
==== 欠点と対策 ====
239 行
 
 
なので、どんなに真横で何十メートルも離れた位置にいても、真横だとz=0z{{=}}0なので倍率は 1/0 {{=}} ±∞ となり、被写体は無限大に拡大して見えることになる。
 
 
311 行
3D-CGのプログラムは、かならずしもC言語系の言語である必要は無い。
 
最低限度に必要なのは、画像表示の機能と、逆三角関数と平方根ていど程度の数学計算の組み込み関数さえ出来ればよい。
 
ただし、実際には、キーボードなどからの入力の機能なども必要なので、自分の使用したいプログラム言語で、それらのプログラムの方法も調べる必要がある。
587 行
鏡にうつりこむ風景や、金属の光沢などは、正確にシミュレーションして求めるにはレイトレーシングが必要になります。
 
しかし、レイトレーシングによる計算量は上述のように結構、膨大になります。そで、計算量を減らす工夫としてため、アクションゲームなど速度の要求されるゲームでは、あらかじめ制作者がゲーム機以外の計算機で、ゲーム機中の映像について光学計算のシミュレーションをしておき、そのシミュレーション結果にもとづく2次元テクスチャ画像によって光を表現する場合も多くあります。
 
このような技術を[[w:環境マッピング]]といいます。よく、鏡や光沢のシミュレーションで、上述のようにテクスチャによる代替を行うことについて、「[[w:環境マッピング]]」といいます。
 
しかし、なにも鏡だけにかぎらず、この技術の本質は'''静的な'''レイトレーシング結果をテクスチャで置き換えることですので、照明などによる光の明暗のシミュレ-ションも、環境マッピングのようなテクスチャによる代替が可能です。
 
しかし、環境マッピングの苦手分野として、鏡が移動するような場合は、難しくや主人公や敵キャラどのような移動する物体が存在する場合が挙げられます。
 
そのため別途、移動する物体による鏡面映像などの描画処理を追加する必要がある。
環境マッピングの苦手分野として、鏡が移動するような場合は、難しくなります。
 
 
また、主人公や敵キャラなどのような移動する物体による光への影響は、追加的な処理が必要になってしまう。なぜなら、静止物だけをテクスチャとしているからである。
 
なので、たとえば移動する主人公や敵などのキャラクターが、鏡や照明などに近づいた場合は、環境マッピングだけでは表現できない。
 
別途、移動する物体による鏡面映像などの描画処理を追加する必要がある。
 
 
709 ⟶ 702行目:
しかし、実は、これらのソフトを使わなくても、3Dプログラミングは可能です。
 
実際、1980年代のマイコンBASICなどのプログラム入門書などを読むと、中学生~高校生向けにワイヤーフレーム式の3Dプログラミングのソースコードが書かれていたりする場合もありました。その程度の初歩的な知識でも、3Dプログラミングは可能です。
 
 
近年のインターネット上には、「3Dプログラミングには、理系の大学専門レベルの高度な数学の知識が必要! たとえば四元数が~~(以下略)」などというタワゴトがありますが、それは間違った情報ですので、だまされないようにしてください。
 
もちろん、数学の知識があるのに越したことはないですが、しかし、高校レベルの三角関数に毛が生えた程度の知識でも、3Dプログラミングは可能ですし、1980年代のマイコンBASICのブームの時代から、そういう高校数学レベルで分かる3Dプログラミングの入門書は存在しています。
 
 
とはいえ、いまさら3Dソフトをゼロから自作するのは(個人でも不可能ではないが)調べることも多く手間が掛かるので、たいていは、DirectXやOpenGLなど既存のツールを使って、3Dグラフィックを表示するためのプログラム作成します。
 
 
下記の3Dプログラミングの解説でも、いろいろな数式が出てきますが、数式のひとつひとつは、高校レベルのベクトルや行列、三角関数といった数式です。
 
行列は、高校カリキュラムの学習指導要領が数年ごとに変更すのでため、年代によっては高校で習ってない場合もありますが、ここでいう行列とは単に、ベクトルを並べたものです。
 
行列の計算法について詳しくは[[高等学校数学C/行列]]を参照してください。
794 ⟶ 784行目:
プレイヤーが動かすキャラクタの位置と向いている方向を表す構造体をcameraとし、その値を次のようにおくと(cはcamera型の変数で、この変数がプレイヤーキャラクタの位置と方向を保持しているものとします)、
 
<sourcesyntaxhighlight lang="c">
typedef struct {
double x,y,z,dx,dy,dz;
} camera;
camera c;
</syntaxhighlight>
</source>
 
カメラの位置をプレイヤーキャラクタの位置に変更する方法は次のようになります。
 
<sourcesyntaxhighlight lang="c">
void set_camera(){
glMatrixMode(GL_PROJECTION);
809 ⟶ 799行目:
gluLookAt(c.x,c.y,c.z,c.x+c.dx,c.y+c.dy,c.z+c.dz,0,0,1);
}
</syntaxhighlight>
</source>
 
ただし、ここでは最後の引数(0,0,1)で、上方向がz方向であると定義しました。
815 ⟶ 805行目:
上の変換では、カメラの位置を変更したのですが、これだけだと変更された後の位置を中心として、長さ2で表される立方体の中の物体しか描画されません。これはもともと投影行列が単位行列だった時には、原点を中心として長さ2の立方体内の物体しか描画されないことと対応しています。より遠方の物体を描画するには、glOrtho, glFrustumの両関数を利用することができます。一般的なゲームでは"遠方のものは小さく見える"といった表現がなされるので、ここではglFrustumを用います。glFrustumの引数は状況によりますが、x,y方向に関係する引数を大きく取ると視界が広くなり、z方向の座標を大きく取ると遠くのものまで見えるようになります。ここでは
 
<sourcesyntaxhighlight lang="c">
glFrustum(left,right,bottom,top,near,far);
</syntaxhighlight>
</source>
 
をgluLookAtの1つ前にいれます。
823 ⟶ 813行目:
実際にこの関数を使ってプレイヤーキャラクタが動く様子を書くことができます。処理の様子は、
 
<sourcesyntaxhighlight lang="c">
int main(){
init_gl();
835 ⟶ 825行目:
return 0;
}
</syntaxhighlight>
</source>
 
のようになります。set_camera以外の関数は説明していないので、以で説明します。
# init_glは、OpenGLの初期化を行う関数です。この関数は使っている環境によりますが、glutInit (GLUT) , SDL_SetVideoMode (SDL) などが対応する関数です。
# init_cameraは、プレイヤーキャラクタの位置を設定する関数です。cをグローバル変数としておけば、camera型の変数を、引数として渡す必要は無くなります。
855 ⟶ 845行目:
ここで、init_cameraとdraw_sceneの内容を紹介します。init_cameraは、カメラの位置と方向を定める構造体cに、初期値を与える関数です。ここでは次のようにしています。
 
<sourcesyntaxhighlight lang="c">
static void init_camera(){
c.x = 10;
864 ⟶ 854行目:
c.dz = 0;
}
</syntaxhighlight>
</source>
 
単にカメラの座標を(10,0,1)とし、方向を-x方向に定めているだけです。ここでz座標が0でないのは、FPSでカメラの位置がキャラクタの顔の辺りにおかれることを意識したものです。c.zを0とすると地面を這うような表現になります。
870 ⟶ 860行目:
draw_sceneは、次のような関数です。
 
<sourcesyntaxhighlight lang="c">
static void draw_scene(){
glClear(GL_COLOR_BUFFER_BIT);
879 ⟶ 869行目:
draw_cone(5,-2,1);
}
</syntaxhighlight>
</source>
 
glClearは、画面の表示をクリアする関数です。watch_from_cameraとdraw_coneはそれぞれ次のように与えています。
 
<sourcesyntaxhighlight lang="c">
static void watch_from(double x, double y, double z, double dx, double dy, double dz){
glMatrixMode(GL_PROJECTION);
893 ⟶ 883行目:
watch_from(c.x, c.y, c.z, c.dx, c.dy, c.dz);
}
</syntaxhighlight>
</source>
 
ここで、watch_fromが視点変換を行う関数の本体であり、watch_from_cameraは、引数を与えることを目的とした関数です。ここでは
903 ⟶ 893行目:
という仮定をおいています。更に、draw_coneは、
 
<sourcesyntaxhighlight lang="c">
#define A (glVertex3f(x,y,z));
#define B (glVertex3f(x+0.5,y,z));
920 ⟶ 910行目:
#undef C
#undef D
</syntaxhighlight>
</source>
 
としています。頂点と面を指定して多角形を書くときには、頂点と面のそれぞれをGLfloat型と、GLint型の2次元配列を使う方が普通です(例えばRed Book3章)。ここでは簡単のためマクロを使いました。
941 ⟶ 931行目:
実際にイベントの監視を行うには、メインループの中で、
 
<sourcesyntaxhighlight lang="c">
while(SDL_PollEvent(&e))
process_event(&e);
</syntaxhighlight>
</source>
 
などとします。ここで、eはSDL_Event型の変数で、main関数内で定義します。実際にイベントを扱うのはprocess_event関数で行います。SDLが扱うイベントは、キーボード、マウス、ジョイスティックなどの入力機器からの要請の他に、スクリーンからのexposeイベントやresizeイベントがあります。これらは使っていたウィンドウが他のウィンドウで隠された時や、扱うウィンドウの大きさを変更したときに供給されるイベントです。これらの様々なイベントを扱うため、SDL_Event構造体には、
 
<sourcesyntaxhighlight lang="c">
Uint8 type;
</syntaxhighlight>
</source>
 
という要素が含まれています。これは各々のイベントの種類を表す要素で、process_event内ではこの値に従って処理を分ける必要があります。具体的には
 
<sourcesyntaxhighlight lang="c">
static void process_event(SDL_Event *e){
switch(e->type){
967 ⟶ 957行目:
}
}
</syntaxhighlight>
</source>
 
cb_keydown(up)の関数では実際に押された(離された)キーを取得します。このときにはSDL_KeyboardEvent内の、
 
<sourcesyntaxhighlight lang="c">
e->keysym.sym
</syntaxhighlight>
</source>
 
要素を利用します。詳しくは、SDLのインストール先から、SDL/SDL_keyboard.hや、SDL/SDL_keysym.hなどを参照してください。cb_keydown(up)内での具体的な処理は対応するpressed関係の変数を書き換えることです。この処理は2Dの時の例と同じなので省略します。
988 ⟶ 978行目:
ここでは"前"を表すキーとして上キーを使い、"後ろ"を表すキーとして下キーを用います。具体的なset_cameraは次のようになります。
 
<sourcesyntaxhighlight lang="cpp">
static void set_camera(){
if (up_is_pressed()){
999 ⟶ 989行目:
// 回転のための処理
}
</syntaxhighlight>
</source>
 
ここで、aはキャラクタの移動速度を調整するための定数です。実際には人間が地面を蹴って移動するとき人間が得るのは[[w:力積|力積]]なので、キャラクタの移動では位置ではなくキャラクタの速度を変更するべきです。ここでは簡単のため移動にかかる時間は無視できるものとしました。力積については[[高等学校理科 物理I]]を参照してください。
1,007 ⟶ 997行目:
通常キャラクタの方向を変更するときには&phiだけを変更します。ただし、キャラクタの視点に立って辺りを見回す表現(主観視点と呼ばれる)では、&thetaも含めて変更する必要があります。ここでは&phiだけを変更します。具体的には左回転をするときには、
 
<sourcesyntaxhighlight lang="c">
phi += b;
</syntaxhighlight>
</source>
 
右回転では
 
<sourcesyntaxhighlight lang="c">
phi -= b;
</syntaxhighlight>
</source>
 
とします。ここで、bはキャラクタの回転速度を表す定数です。
1,035 ⟶ 1,025行目:
1度の回転の角度が十分小さいときには、
 
<sourcesyntaxhighlight lang="c">
static void turn_left(){
c.dx -= m*c.dy;
1,041 ⟶ 1,031行目:
normalize(c);
}
</syntaxhighlight>
</source>
 
で左回転を、
 
<sourcesyntaxhighlight lang="c">
static void turn_right(){
c.dx += m*c.dy;
1,051 ⟶ 1,041行目:
normalize();
}
</syntaxhighlight>
</source>
 
で右回転を表すこともできます。ここで、mは定数であり、normalize関数は、cの方向ベクトルを正規化する関数としました。これは、回転の角度が小さいとき、回転による方向ベクトルの変更を元のベクトルに直交するベクトルで近似できることを利用した変換です。方向角を使わないで回転を表現したい場合に利用するとよいでしょう。
1,057 ⟶ 1,047行目:
これらの関数を用いると、set_camera内のif - else if文に、
 
<sourcesyntaxhighlight lang="c">
} else if (left_is_pressed()){
turn_left();
1,063 ⟶ 1,053行目:
turn_right();
}
</syntaxhighlight>
</source>
 
を付け加えることになります。
1,095 ⟶ 1,085行目:
ここからは先に述べた3つの要素について解説します。xjumpでは、ジャンプによって飛び移るための床は、乱数を用いて生成していました。ここでは簡単のため床の位置は定数の2次元配列で与えることにします。更に、床の大きさを固定することにすると、各々の床を得るために必要な情報は、床のうちの任意の1点です。ここではx座標とy座標が最も小さい部分とします。これらの情報は、例えば、
 
<sourcesyntaxhighlight lang="c">
double planes [N_PLANES][3] = {{0,0,2},{4,0,4}};
</syntaxhighlight>
</source>
 
のように与えることができます。
1,103 ⟶ 1,093行目:
次に、キャラクタが落下する処理については、カメラの位置を動かす処理で、
 
<sourcesyntaxhighlight lang="c">
c.z -= vz;
vz -= g;
</syntaxhighlight>
</source>
 
などとします。gはここでは下向きの加速度を表す定数です。これらの処理は2Dの場合と同じなので詳しく述べません。
1,112 ⟶ 1,102行目:
最後に、キャラクタが床の上にいるかを判定するためには、次のような関数を使います。
 
<sourcesyntaxhighlight lang="c">
static int on_the_ground(){
int i = 0;
1,128 ⟶ 1,118行目:
...
}
</syntaxhighlight>
</source>
 
if文で行っていることはキャラクタの位置(PL_X,PL_Y,PL_Zで与えられる)が各々の床の範囲内にあるかどうかを確かめる作業です。3Dの時の違いは、x座標とy座標の2つについて判定が必要になった点だけです。また、実際にキャラクタが床にいるときに、キャラクタのv_zを0にするなどの処理も行っていますが、2Dの時と変わらないので省略します。
1,165 ⟶ 1,155行目:
: ここではOpenGLを用いるためにGLUTを利用しますが、PLIB自身もOpenGLの設定を行う機能を持っている様です。
 
<sourcesyntaxhighlight lang="c">
#include <plib/ssg.h>
#include <GL/glut.h>
1,184 ⟶ 1,174行目:
glutMainLoop();
}
</syntaxhighlight>
</source>
 
ここまでが、PLIBとGLUTを用いるときの基本的な描画を行う部分です。今までと違うのは、物体の描画にssgCullAndDraw関数を用いていることです。PLIBは3DモデルのデータをssgRootクラスを用いて管理します。ssgCullAndDraw関数は3Dモデルのデータを実際に描画する関数です。ここでは描画する3Dモデルに対するポインタをrootとしました。実際にデータをrootに与える部分はdata_init関数としており、次のように与えられます。
 
<sourcesyntaxhighlight lang="c">
static void data_init(){
root = new ssgRoot;
1,194 ⟶ 1,184行目:
root->addKid(snowman);
}
</syntaxhighlight>
</source>
 
ただし、ここでは"snowman.ac"というACファイルが同じディレクトリ内にあるものとしました。ここまでで物体の描画は行われますが、物体が実際に見えている事を知るために、カメラの位置を動かす必要があります。その作業はcamera_init関数で行います。
 
<sourcesyntaxhighlight lang="c">
static void camera_init(){
sgCoord campos;
1,204 ⟶ 1,194行目:
ssgSetCamera(&campos);
}
</syntaxhighlight>
</source>
 
ここではカメラの位置を(0,-10,0)とし、カメラの向きを(0,1,0)としました。何も設定しないとファイルから読み出された物体の位置は(0,0,0)となるので、これで設定は完了です。