TOP

ABOUT

PROGRAM

DIALY







射影テクスチャリング

 1.はじめに…
 投影テクスチャあるいは射影テクスチャとかいわれるものをやってみました。




 2.射影テクスチャリング
 まず原理ですが,2次元のスクリーンを3次元の物体に投影するというものです。


上の図を見るとわかるように基本的に透視変換と同じ原理です。よって,ライトを視点とみなして透視変換行列を作成して,その行列を掛けてやればよいということになります。
ここで復習ですが…。
皆さんご存じの通り,普通画像は左上が原点となりますが,これをテクスチャ空間に持ってくると座標系が違うためのこのままテクスチャマッピングを行うと上下が反転してしまいます。このままだとおかしいのでテクスチャ座標のt成分(DirectXやXNA GSだとv成分)をマイナス1倍して座標を反転させて,向きを合わせる処理をどこかでやる必要があります。


これを踏まえた上で話に戻ります。
射影テクスチャリングで気をつけなければいけないことはテクスチャ座標を計算するところです。
ライトからみた射影空間とテクスチャ座標が若干異なる点です。具体的には射影空間の中心が(0, 0)なのに対して,テクスチャ座標系は左下が(0, 0)になります。また射影空間の幅は2.0なのに対して,テクスチャ座標系は1.0の幅になっています。



上記のことから射影空間(X, Y)とテクスチャ座標系(S, T)の関係は下のようになります。



しかし,このままでは画像の上下が反転したままなので,ここで上下を反転させて画像の向きが合うように式を次のように直します。



ここで,気をつけてほしいのは上記の式はw=1の空間に射影して射影空間の座標が[0, 1]に収まったときの話です。w成分で割っていない場合の射影空間(x, y, z, w)とテクスチャ座標系(S, T)の関係は次のようになります。



一応,上記の式の変換は行列で計算することができます。テクスチャ座標を(s/w, t/w)で計算する同次座標系において計算する場合の変換行列は次のようになります。



ここで,テクスチャ座標系のz成分は必要ないので無視です。4次元ベクトルで計算するとシェーダなどで色々と都合がよいそうで,こうするのが慣習になっているそうです。NVIDIAのサンプルなんかでもこのやり方を使っていたりします。
上で述べてきたように通常の透視変換と異なるのはライトから見た射影空間とテクスチャ座標系がことなるため,上記の行列を考慮して頂点座標を変換することです。
物体のローカル座標系からテクスチャ座標系へ変換するための行列をまとめると次のようになります。




 3.実装
 実装ですが,上記で述べてきたように,ワールド行列を設定して,ライトを視点とみなしてビュー行列を作成して,ライトの射影行列を作成して,あとはテクスチャ座標系へ変換する行列を作って,それらをかけてシェーダ側に送るだけです。
00350:  //--------------------------------------------------------------------------------------------------
00351:  // Name : Render3D()
00352:  // Desc : 3次元シーンの描画
00353:  //--------------------------------------------------------------------------------------------------
00354:  void Render3D()
00355:  {
00356:  	Matrix4f mV, mP, mVPT;
00357:  	Matrix4f mT(
00358:  		0.5f, 0.0f, 0.0f, 0.0f,
00359:  		0.0f, -0.5f, 0.0f, 0.0f,
00360:  		0.0f, 0.0f, 1.0f, 0.0f,
00361:  		0.5f, 0.5f, 0.0f, 1.0f );
00362:  
00363:  	// ライトのビュー行列
00364:  	CreateLookAt(g_lightPosition.xyz(), Vector3f().Zero(), Vector3f().One(), mV);
00365:  	
00366:  	// ライトの射影行列
00367:  	CreatePerspectiveFieldOfView(DegToRad(45.0f), 1.0f, 0.25f, 20.0f, mP);
00368:  
00369:  	// 変換行列を送る
00370:  	mVPT = mV * mP * mT;
00371:  	cgSetMatrixParameterfc(g_hTextureMatrix, mVPT);
00372:  	
00373:  	// ライトの位置を送る
00374:  	cgSetParameter3fv(g_hLightPosition, g_lightPosition.xyz());
00375:  
00376:  	// エフェクトを使用して描画
00377:  	for ( CGpass pass = cgGetFirstPass(g_Technique); pass != NULL; pass = cgGetNextPass(pass) )
00378:  	{
00379:  		cgSetPassState(pass);
00380:  
00381:  		// キューブを描画
00382:  		glPushMatrix();
00383:  		glTranslatef(0.0f, -1.0f, 0.0f);
00384:  		cgGLSetStateMatrixParameter(
00385:  			g_hWorldViewProjection,
00386:  			CG_GL_MODELVIEW_PROJECTION_MATRIX,
00387:  			CG_GL_MATRIX_IDENTITY);
00388:  		glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
00389:  		glutSolidCube(2.0f);
00390:  		glPopMatrix();
00391:  
00392:  		// 箱を描画
00393:  		glPushMatrix();
00394:  		cgGLSetStateMatrixParameter(
00395:  			g_hWorldViewProjection,
00396:  			CG_GL_MODELVIEW_PROJECTION_MATRIX,
00397:  			CG_GL_MATRIX_IDENTITY);
00398:  		RenderBox(20, 15, 20);
00399:  		glPopMatrix();
00400:  
00401:  		cgResetPassState(pass);
00402:  	}	
00403:  
00404:  	// ライトを描画
00405:  	glPushMatrix();
00406:  	glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
00407:  	glTranslatef(g_lightPosition.x, g_lightPosition.y, g_lightPosition.z);
00408:  	glutSolidSphere(0.5f, 10, 10);
00409:  	glPopMatrix();
00410:  	
00411:  }
00412:  

頂点シェーダ側では,通常の座標変換やライティングのほかに射影テクスチャリングするために入力で入ってきた頂点座標に,送られてきたテクスチャ座標系への変換行列をかけて,テクスチャ座標として出力します。
ピクセルシェーダ側では,頂点シェーダから送られてきたテクスチャ座標とtex2Dproj()を使って色を出して,色を掛けあわせて出力します。
そこまで複雑な処理はないので大丈夫でしょう。
00037:  //-----------------------------------------------------------------------------------------------------
00038:  // Name : VertexShaderFunction()
00039:  // Desc : 頂点シェーダ
00040:  //-----------------------------------------------------------------------------------------------------
00041:  VertexOutput VertexShaderFunction(VertexInput input)
00042:  {
00043:  	VertexOutput output;
00044:  
00045:  	// 座標変換
00046:  	output.Position = mul(WorldViewProjection, input.Position);
00047:  
00048:  	// 射影テクスチャ用
00049:  	output.TexCoord = mul(TextureMatrix, input.Position);
00050:  
00051:  	// ライティング
00052:  	float4 Ambient = 0.3f;
00053:  	float3 N = normalize(input.Normal);
00054:  	float3 L = normalize(LightPosition - input.Position.xyz);
00055:  	output.Diffuse = Ambient + 1.0 * max(dot(N, L), 0);
00056:  
00057:  	return output;
00058:  }
00059:  
00060:  //-----------------------------------------------------------------------------------------------------
00061:  // Name : PixelShaderFunction()
00062:  // Desc : ピクセルシェーダ
00063:  //-----------------------------------------------------------------------------------------------------
00064:  float4 PixelShaderFunction(VertexOutput input) : COLOR
00065:  {
00066:  	float4 output;
00067:  
00068:  	// テクスチャカラー
00069:  	float4 TextureColor = tex2Dproj(ProjectiveMap, input.TexCoord);
00070:  	
00071:  	// 出力カラー
00072:  	output = TextureColor * input.Diffuse;
00073:  	
00074:  	return output;
00075:  }
00076:  

いままで,ちゃんと勉強していなかったので射影テクスチャリングがわからなかったのですが,ようやくすっきりしました。
これでようやく凝った演出ができそうです。


 Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。
プログラムの作成にはMicrosoft Visual Studio 2005 SP1 Professional, Cg Toolkit 2.0 May 2008を用いています。