描画処理の基礎
Last update: <2004/03/13 17:31:40 +0900>
ここでは,3次元空間におけるモデルを計算機で描画表示するまでの処理について説明しています.また説明に伴って出てくる用語についても,できるだけ詳しく記述するようにしています.用語は日常的に使うものから専門的なものまであります.専門書などを読む場合,こうした語彙の有無が理解に影響しますので,知識として覚えておきましょう.
計算機で3次元モデルを描画する場合,手順は大きく4つに分けられます.すなわち,
- 映像の視点の位置を設定すること
- モデルの配置を設定すること
- 視野の大きさを設定すること
- 画面の大きさを設定すること
です.この手順を実際のカメラ撮影になぞらえて説明していきます.
まず視点の位置を設定することとは,三脚を組み立ててカメラを配置することです.普段なにげなく構えるカメラですが,これを算術的にあらわそうとすると,空間的な位置(x,y,z)と方向(ヨー,ピッチ,ロール)の6つのパラメータによって定めることができます.つまり,視点を設定するとはこの6つのパラメータを決めることに他なりません.
OpenGLでは,右手の親指と人差し指,中指をそれぞれ直交させ,その方向にそれぞれx,y,z軸をとる右手系の空間座標系を使用しています.ここではz軸(中指)負の方向がカメラの向いている方向(写真の通り)で,y軸が頭上方向(写真の通り)だと考えて下さい.この座標系は基準となり,とても重要です.
図:右手系の座標系(中指の指先から付け根に向かうのが視線方向)
次に描画対象となるモデルを配置します.モデルとは立方体であったり球であったりというような,表示の対象となる物体を意味します.モデルの配置も視点と同様に6つのパラメータによって決められます.
ところで,視点の配置とモデルの配置は非常に密接な関係があります.視点をモデルに近づけることとは,モデルをカメラに近づけることと同じ効果があるからです.このことは後で触れますが,視点の移動とモデルの移動は常に反対の関係にあることを念頭において下さい.視点とモデルの位置姿勢が決定すると視点とモデルの配置関係が定まることになります.
3番目は,視体積の形状を設定することです.視体積とは視野のことで,カメラの場合ならレンズの画角に相当します.広角レンズだと広い範囲の映像を撮影でき,望遠レンズだと狭い範囲を拡大して撮影できます.*5
視体積は通常,四角錘の形をとります.カメラマンのフレーミングを思い出して下さい.視点が四角錘の頂点で,指でフレームを作っているのが四角錘の底面になります.この四角錘が撮影の範囲で,底面が大きく視点に近ければ広角,底面小さく視点から遠いほど狭角になります.
視体積が四角錘の場合,近くにあるものは大きく,遠くにあるものは小さく見えます.これはperspective*6(遠近画,透視図)といいます.カメラでは視体積にあるものをすべてフィルムに投影しますが,コンピュータグラフィクスでも同様で,定義された視体積に入っているモデルはすべて2次元に射影されます.なお,視体積の大きさを定義するパラメータについては後述します.
4番目は画面の大きさを設定することです.カメラの場合,現像したフィルムを印画紙にプリントする必要がありますが,コンピュータグラフィクスでは印画紙の代わりにモニタを使います.4番目の設定は,モニタに表示される大きさを設定することです.
上記のような手順により,計算機上で3次元モデルを描画することが可能となります.
ここでは計算機によるモデルの表現手法について述べていきます.
まず,モデルの中で最も単純なものは点(point)です.
点は3次元空間上の位置を与えることによって表現されます.
点は他の幾何学的形状の基本要素となりますが,このときは点のことを頂点(vertex)と呼びます.
頂点が2つ集まると線,線分(line)ができます.線は数学的定義によると無限に続くものですが,描画では無限長の線などはあまり意味がありませんので,ここでは2点を結ぶ線分をイメージしてください.
頂点が3つ集まると平面が定まり,三角形が構成されます.描画においては,3点を通る平面などにあまり意味がないので,3点を結ぶ三角形をイメージしてください.
三角形になると面積を持つため,面に様々な属性が導入されます.
例えば面の法線(normal)を決めることができます.法線は面に対する照光処理に用いられます.
頂点が3つ以上集まると,多角形(polygon)や多面体を構成することができます.あらゆる多面体は多角形から構成されていると考えることができます.多角形より構成されている多面体モデルをポリゴンモデル(polygon model)といいます.
またポリゴンモデルのように面だけで構成されているモデルを,特にサーフェスモデル(surface model)ということもあります.サーフェスモデルでは面の頂点を定義することで様々な形状を表現できます.
一方,モデルの表面だけでなく中身を数式で表現することもあります.物体の領域を範囲で指定するようなモデルは,中身が詰まっており,論理演算に適用できます.こうしたモデルはソリッドモデル(solid model)と呼ばれています.サーフェスモデルではあらゆる形状でも微小面で近似することで表現できますが,ソリッドモデルでは数式で表わせる形状しか表現できません.
OpenGLではサーフェスモデルを扱っています.
サーフェスモデルでは面による形状の表現をおこなっていますが,面の稜線だけを表示する表現をワイヤフレーム(wire frame)といいます.計算機でワイヤフレームや線分を表示する場合,画面の解像度が粗いとギザギザが生じます.このギザギザはジャギー(jaggy)といいます(図).ジャギーをなくすための工夫として,線の色と背景色の中間色をジャギーに埋めていく方法があります.近くから見ると線がぼやける感じがしますが,遠めで見るとジャギーが消えて滑らかな線に見えます.このようなジャギーをなくす描画手法をアンチエリアシング(anti aliasing)といいます.
図:ジャギーありの線(左)とアンチエリアシングありの線(右)
ところで,点や線,面には色の属性をつけることもできます.
色の指定は光の三原色であるRGBでおこなうのが一般的です.通常の色指定は三原色で十分ですが,4番目の要素として透明度が導入されることもあります.透明度の要素はアルファ(alpha)と呼ばれています.また半透明物質どうしによる色混合をアルファブレンディング(alpha blending)といいます.三原色にアルファ値が加わった場合RGBAと記述することもあります.
面の各頂点に色の属性をつけると,頂点間で補間されながら色がつきます.これをシェーディング(shading)といいます.
面には法線が定義されますが,照光処理をおこなうと法線と光源の位置関係によって明暗が発生します.
各面にそのまま明暗をつけると,隣り合った面同士でも異なった明るさとなります.フラットシェーディング(flat shading)では各面にそれぞれの明るさを割り当てるため,面の境界がはっきり見えてしまいますが,グーローシェーディング(gouraud shading)では隣り合った面の明暗の境界をなくすように明るさのグラデーションをつけています.グーローシェーディングはスムースシェーディング(smooth shading)とも呼ばれます.
図:フラットシェーディング(左)とグーローシェーディング(右)
テクスチャマッピング(texture mapping)とは,面に対して画像を貼り付ける過程のことです.複雑なモデルの設計をおこなわなくとも,表面の画像を平面モデルにテクスチャマッピングすることで,より質感の高いシーンを作成することができます.
前節の視点やモデルの設定は3次元空間でおこなわれますが,グラフィックとして表示されるのは2次元の映像です.ここでは3次元のシーンを2次元に射影する処理について述べていきます.
3次元空間内にあるオブジェクトを2次元平面に射影するのは,ちょうどカメラでフィルム撮影をすることに似ています.
カメラマンが写真の構図を考える場合,両手指でフレームを作って前後に動かします.ここで画面の範囲を決定してレンズを選択します.例えば指のフレームが目から遠いほど,見える範囲が小さく拡大率が高いので,望遠レンズを選択することになります.
計算機でグラフィックを生成するときも,見える範囲を決めることになります.見える範囲のことを視体積(view volume)といいます.計算機の場合,カメラの場合と違って視体積は任意の大きさにすることができます.視体積の形状は2通りあります.
図:透視射影変換と正射影変換
(a)透視射影変換(perspective transformation)
カメラや人間の目と同じ視界を持ちます.視体積は四角錐になるため,視点から物体が遠ざかるほど,物体は小さく射影されます.また,四角錐の底面積が四角錐の高さに対して大きくなるほど,広い画角の映像を生成することができます.
(b)正射影変換(orthographic transformation)
四角錐は直方体になるため,視点から物体が遠ざかっても物体は同じ大きさに射影されます.この射影では物体の大きさや角度が常に一定に表示されるため,CADの図面などに用いられます.
視体積が幾何学的に決定されると,3次元空間における物体を射影面に変換することができます.この変換の際に問題になってくるのは,各物体の前後関係に関することです.物体同士の位置関係によっては,近くの物体が遠くのものを覆い隠す(occlusion)必要が出てきます.
もっとも,ワイヤーフレームで物体が表現される場合は,手前も奥も関係ありませんので問題はありません*7.しかしポリゴンモデルなど物体が面で構成される場合,面や物体の奥行き判定をおこなって,視点に近い側の物体を表示しなければなりません.こうした操作を隠線消去(hidden-line removal),隠面消去(hidden-surface removal)といいます.
隠線消去を実現する具体的な方法は,デプスバッファ(depth buffer)を使用することです.デプスバッファとは名前の通りデプス(深度)のバッファ,つまり視点からの距離画像のことを指します.
図:フレームバッファとデプスバッファ
処理の手順を簡単に説明すると上図のようになります.これは平面物体が重なって表示されている例です.
3次元空間における物体を射影面に変換した結果,平面像ができます.この像はRGB情報として1画素ごとにフレームバッファ(frame buffer)に格納されていきます.
隠線(隠面)消去の処理を有効にする場合,フレームバッファと同じサイズのデプスバッファを用意しておきます.射影計算の結果,画素がフレームバッファに書き込まれる前に,デプスバッファで遠近判定をおこないます.例えばd1,d3にある平面の描画後に平面d2を描く場合,デプスバッファの遠近判定でd2より値が大きい場所だけが上書きを許されます.上書きできる場合は,フレームバッファにd2の情報が書き込まれ,デプス値が更新されます.
デプスバッファを無効にする場合は,物体の奥行き情報が完全になくなるため,すべての物体が重畳されて描画されます.これは半透明の物体を表現したい場合に有効です.
完成した映像を表示するための大きさを設定します.
表示領域はビューポート(viewport)ともいいます.このビューポートにフレームバッファの内容が表示されるため,フレームバッファの大きさはビューポートの大きさに依存します.
画面の大きさを設定する場合,カメラフィルムの感光のように相似的に拡大縮小するだけでなく,上下や左右に伸縮した映像を作成することも可能です.
例えば,視体積の底面が横n:縦mの比率だったとします.ビューポートの大きさを横n:縦mの比率にすると,正常な映像を得ることができますが,横2n:縦mにすると画面横方向に2倍伸張した映像が得られます.
なお,画面の縦横比のことをアスペクト比(aspect ratio)と呼びます.
正常な比率で映像を生成するには,常に画面表示と視体積の底面のアスペクト比が一致している必要があります.例えばウインドウに画像を表示していてウインドウサイズが変更された場合,ビューポートのアスペクト比が変化してしまいます.このとき同時に視体積のアスペクト比も変化させなければならないことになります.
- ここで出てきた各描画方式について実際に試してみましょう.
- 第1回でサンプルとして実行した,c:\glut-3.7\prog\demos\newave\newave.exeをもう一度実行してみます.
- 画面を左クリック&ドラッグすることで,回転操作できることを確認します.
- 右クリックするとメニューがでます.
- Wireframe(ワイヤーフレーム),HiddenLine(隠線消去),Flat Shaded(フラットシェーディング),Smooth Shaded(スムースシェーディング),Texture Mapping(テクスチャマッピング)がそれぞれどんな描画かを確認しましょう.
- メニューのOtherを選択し,Antialiasingを選んでアンチエリアシングを試してみましょう.
- これから実際にプログラムを書いていきますが,VisualC++でプログラムを管理する方法については,こちらに書いてありますのでよく読んで置いてください.
- 2次元物体の描画を試してみましょう.
- 前回作成したプログラムを用いて演習をおこないます.前回の作成したコンパイル環境を読み込んで下さい.VisualC++では「ファイル=>ワークスペースを開く」で前回のプロジェクト状態を読み込むことができます.
- プログラム中にあるglRectf(-25.0, -25.0, 25.0, 25.0)という命令を探して下さい.このglRectf(x1, y1, x2, y2)は,(x1, y1)と (x2, y2)が対角となる四方形を表示します.サンプルプログラムの状態は(-25.0, -25.0)と(25.0, 25.0)の2頂点が指定されていますので,一辺50.0の正方形が表示されます.これらの数字の単位は特に決まっていません.ただし,プログラム全体で統一しておく(cmならcm,mmならmmと自分で決めておく)必要があります.
一辺が100.0となる四方形を表示してみましょう.glRectf(x1,y1,x2,y2);の値を変更します.
- ウインドウのサイズを500×500にしてみましょう.初期ウインドウの大きさを決めるのはglutInitWindowSize(width, height);という関数です.ここのwidth, heightの値を変更します.
- ウインドウを大きくしても,相似的に拡大されるだけです.見える範囲を広げる場合は視野を大きくします.ここでは,glOrtho(left, right, bottom, top, near, far);の値を変更します.glOrthoは正射影変換の命令で,下図の通り視点から 左left, 右right, 下bottom, 上top, 手前near, 奥行きfarの視野を設定します.視野の大きさを単純に2倍にしてみましょう.
図:正射影変換の関数glOrtho(left, right, bottom, top, near, far) [1]より
- 四角形の代わりに,三角形などの自由形状を表示します.
- 三角形など任意の形状のポリゴンを指定する場合は,頂点座標を以下のように指定します.
glBegin(GL_POLYGON);
glVertex3f(-50.0,-28.8, 0.0); /* 左下 */
glVertex3f( 50.0,-28.8, 0.0); /* 右下 */
glVertex3f( 0.0, 57.6, 0.0); /* 上 */
glEnd();
glRectの部分を上記と差し替えて,三角形を表示してみましょう.
- glBeginとglEndで囲まれた部分で,頂点座標を指定することが出来ます.このglVertex3f(x,y,z)は頂点の座標を3次元で指定しています.
現在はポリゴン表示ですが,ワイヤーフレームにすることも出来ます.glBegin(GL_LINE_LOOP)と変更してみましょう.
- glBegin()で指定できる引数には次のようなものがあります.
図:glBegin()で指定できる引数と形状[1]より
図のv0, v1, v2...は,glBeginとglEndにはさまれている,glVertex3f()で順に指定された頂点です.
これを使って,最初にでてきたglRectf(-25.0, -25.0, 25.0, 25.0);と同じものを描いてみましょう.
以下に参考例を隠しておきますが,他にもやり方があるので考えてみましょう.
glBegin(GL_POLYGON);
glVertex3f(-25.0,-25.0, 0.0); /* 左下 */
glVertex3f( 25.0,-25.0, 0.0); /* 右下 */
glVertex3f( 25.0, 25.0, 0.0); /* 右上 */
glVertex3f(-25.0, 25.0, 0.0); /* 左上 */
glVertex3f(-25.0,-25.0, 0.0); /* 左下 */
glEnd();
- ポリゴンの色を自由に変更します.
- glColor3f(1.0, 1.0, 1.0)では表示色を設定しています.この関数の引数はglColor3f(float red,float green,float blue)で,赤,緑,青の三原色が指定でき,それぞれ0.0〜1.0の値をとります.
赤い三角形を表示してみましょう.
- 各頂点に色を持たせることも可能です.例えば三角形の頂点に色をつける場合は以下のように指定します.
glBegin(GL_POLYGON);
glColor3f(1.0, 0.0, 0.0); /* 赤 */
glVertex3f(-50.0,-28.8, 0.0); /* 左下 */
glColor3f(0.0, 1.0, 0.0); /* 緑 */
glVertex3f( 50.0,-28.8, 0.0); /* 右下 */
glColor3f(0.0, 0.0, 1.0); /* 青 */
glVertex3f( 0.0, 57.6, 0.0); /* 上 */
glEnd();
このように,色をつける場合には,色をつけたい物体の直前でglColor3fを指定することになります.このとき,一度glColor3fを指定したら,その色はあとで描画する物体すべてに引き継がれます.よって,同じ色の物体を描くときに,何度もglColor3fで指定する必要がありません.色を変更するときだけglColor3fで指定します.
上記のプログラムを試してみましょう.(うまくいきません)
- 各頂点に配色したはずなのに,指定の表示にはならなかったと思います.これは,現在のポリゴン描画モードがフラットシェーディングになっているからです.各頂点間の色を補完して表示するには,フラットシェーディングではなく,スムースシェーディングに変更する必要があります.
glShadeModel()という関数では描画モードを指定できます.現在はGL_FLATとなっており,フラットシェーディングが指定されています.これをGL_SMOOTHに変更してみて下さい.
- 白い線によるグリッドを引いてみましょう.
- for文を使用して,一辺が100の四方形に10おきの罫線を引いてみましょう
サンプルは下に隠しておきます.
float x;
...
glBegin(GL_LINES);
for(x=-50.0;x<=50.0;x+=10.0){
glVertex3f(x, 50.0, 0.0);
glVertex3f(x, -50.0, 0.0);
glVertex3f(50.0, x, 0.0);
glVertex3f(-50.0, x, 0.0);
}
glEnd();
ここで注意しなければならないことは,glBeginとglEndを必ず対で使用することです.対になっていなくてもエラーはでませんが,おかしな表示を招きます.エラーが出ないので,誤りに気づきにくいBugになります.必ず対にする習慣をつけましょう.
- glBeginとglEndは,不必要に多用しないようにしましょう.例えば先の例題で,ループの中にglBeginとglEndを入れてしまうと,本来なら1回でよいのに,繰り返しで11回呼ぶことになります.(11回入れても間違いではありませんが)これは描画パフォーマンスの低下を招きます.
- グリッドの回転方向を変更してみましょう.glRotatef(degree, x, y, z);で,回転をおこなっています.degreeが回転角度,x,y,zが回転軸をあらわす3次元のベクトルです.現在の回転はglRotatef(spin, 0.0, 0.0, 1.0);となっていますので,z軸周り(視線方向)にspin度回転となっています.y軸やx軸周りに変更してみましょう.
- 実際に回転させてみると,上図のようにうまく描画されないかもしれません.glOrtho(left, right, bottom, top, near, far);のnearとfarの値が小さすぎて,奥行き方向の視野が小さすぎることに起因していると考えられます.正射影変換のnearとfarの値を大きくすると,正しく描画されるようになります.
glBegin(),
glEnd(),
glVertex3f(x, y, z)を使用して,何か自由な形状を作成してみましょう.
glutWireSphere(double radius,int slices,int stacks)を使用してみましょう.なお,radiusは半径,slicesは経線数,stacksには緯線数が入ります.
glutWireTeapot(double size)を使用してみましょう.sizeで大きさを指定します.
ここで勉強したOpenGLの関数は,
glRectf(float x1,float y1,float x2,float y2)
glutInitWindowSize(int width, int height)
glOrtho(double left,double right,double bottom,double top,double near,double far)
glBegin(GL_*****)
glEnd()
glVertex3f(float x, float y, float z)
glColor3f(float r, float g, float b)
glShadeModel(GL_FLAT or GL_SMOOTH)
glRotatef(float degree, float x, float y, float z)
glutWireSphere(double radius,int slices,int stacks)
glutWireTeapot(double size)
です.赤本を利用して,復習しておきましょう.