今回はnVIDIAのサンプルにあるプログラムです。
頂点プログラムとフラグメントプログラムを組み合わせて乱視効果をやってみます。考え方としては,同じテクスチャを少しずらしたテクスチャ座標で2回サンプリングして,それを半分ずつブレンドするということになります。
GPUのお勉強(5)★ 1.はじめに…
今回はnVIDIAのサンプルにあるプログラムです。
頂点プログラムとフラグメントプログラムを組み合わせて乱視効果をやってみます。考え方としては,同じテクスチャを少しずらしたテクスチャ座標で2回サンプリングして,それを半分ずつブレンドするということになります。 ★ 2.コーディング(C/C++側)
まず,CPU側ですが,必要な変数を用意しておきます。
CGcontext context; CGprofile vertexProfile; CGprofile fragmentProfile; CGprogram vertexProgram; CGprogram fragmentProgram; CGparameter decal_map; CGparameter left_separation; CGparameter right_separation; DDSImage dds_texture; static float separation = 0.1; static float separation_velocity = 0.005; 用意したのは,いつもどおりのコンテキストとプロファイルとプログラム用の変数,あと今回テクスチャを使用するので,テクスチャ用にdecal_mapという名前で変数を用意します。 あとで,テクスチャ座標も動かすのでleft_separationとright_separationっていう変数も用意しておきます。 初期化のところでテクスチャをセットしたり,パラメータを取得したりします。まぁ,今まであんまし変わりません。 //---------------------------------------------------------------------------------------------------- // Initialize_Cg // Desc : Cgの初期化 //---------------------------------------------------------------------------------------------------- bool Initialize_Cg() { // エラーコールバック関数の設定 cgSetErrorCallback(CheckError_Cg); // コンテキストの作成 context = cgCreateContext(); // プロファイルの設定 vertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX); fragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT); // プロファイルのチェック if ( vertexProfile == CG_PROFILE_UNKNOWN || vertexProfile == CG_INVALID_ENUMERANT_ERROR ) return false; if ( fragmentProfile == CG_PROFILE_UNKNOWN || fragmentProfile == CG_INVALID_ENUMERANT_ERROR ) return false; // プログラムの作成 vertexProgram = cgCreateProgramFromFile( context, CG_SOURCE, "vertex.cg", vertexProfile, "main", NULL ); fragmentProgram = cgCreateProgramFromFile( context, CG_SOURCE, "fragment.cg", fragmentProfile, "main", NULL ); // プログラムのチェック if ( !vertexProgram ) return false; if ( !fragmentProgram ) return false; // プログラムの設定 cgGLLoadProgram(vertexProgram); cgGLLoadProgram(fragmentProgram); // パラメータを直接取得 left_separation = cgGetNamedParameter(vertexProgram, "leftSeparation"); right_separation = cgGetNamedParameter(vertexProgram, "rightSeparation"); decal_map = cgGetNamedParameter(fragmentProgram, "decalMap"); // パラメータをセット cgGLSetTextureParameter(decal_map, dds_texture.ID); return true; } 描画のところでは,テクスチャをずらすのに用いている変数sperationの値によって送る値を変えます。あとは,プロファイル有効にしたりとかはいつもどおりです。 //--------------------------------------------------------------------------------------------------- // Display // Desc : ウィンドウへの描画 //--------------------------------------------------------------------------------------------------- void Display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if ( IsMove ) MoveTexture(); // パラメータをセット if ( separation > 0 ) { cgSetParameter2f(left_separation, -separation, 0); cgSetParameter2f(right_separation, separation, 0); } else { cgSetParameter2f(left_separation, 0, -separation); cgSetParameter2f(right_separation, 0, separation); } // 頂点プログラムをバインド cgGLBindProgram(vertexProgram); // 頂点プロファイルを有効化 cgGLEnableProfile(vertexProfile); // フラグメントプログラムをバインド cgGLBindProgram(fragmentProgram); // フラグメントプロファイルを有効化 cgGLEnableProfile(fragmentProfile); // テクスチャを有効化 cgGLEnableTextureParameter(decal_map); // 四角形を描画 DrawRectangle(); // テクスチャを無効化 cgGLDisableTextureParameter(decal_map); // フラグメントプロファイルを無効化 cgGLDisableProfile(fragmentProfile); // 頂点プロファイルを無効化 cgGLDisableProfile(vertexProfile); // ダブルバッファ glutSwapBuffers(); } CPU側はこんな感じで,テクスチャ読み込んで,テクスチャ座標に使う値を送っているだけです。 ★ 3.コーディング(Cg側)
頂点プログラムの方ですが,こちらは送られてきた値をもとにテクスチャ座標を足して,ずらす処理をします。
// // VertexInputs // struct VertexInputs { float2 position : POSITION; float2 texcoord : TEXCOORD0; }; // // VertexOutputs // struct VertexOutputs { float4 position : POSITION; float2 leftTexcoord : TEXCOORD0; float2 rightTexcoord : TEXCOORD1; }; //---------------------------------------------------------------------------------------------------- // main // Desc : メインエントリポイント //---------------------------------------------------------------------------------------------------- VertexOutputs main ( VertexInputs input, uniform float2 leftSeparation, uniform float2 rightSeparation ) { VertexOutputs output; output.position = float4(input.position, 0, 1); output.leftTexcoord = input.texcoord + leftSeparation; output.rightTexcoord = input.texcoord + rightSeparation; return output; } フラグメントの方では,頂点プログラムの方から送られてきたテクスチャ座標をもとにしてテクスチャを2回サンプリングします。すると,テクスチャ座標がずれているため,異なる色が結果としてでてきます。結果として出てきた2つの色の平均を求めて出力するようにします。 平均を求めるのに,線形補間の関数が用意されているのでそれを使って求めます。 VECTOR lerp(VECTOR a, VECTOR b,TYPE weight); ちなみにlerpというのは「linear interpolation(線形補間)」の略だそうです。 知っているとは思いますが,線形補間の式は次の式です。 result = (1 - weight)×a + weight×b それでは,フラグメントプログラムのコードです。 // // FragmentInputs // struct FragmentInputs { float2 leftTexcoord : TEXCOORD0; float2 rightTexcoord : TEXCOORD1; }; // // FragmentOuputs // struct FragmentOutputs { float4 color : COLOR; }; //---------------------------------------------------------------------------------------------------- // main // Desc : メインエントリポイント //---------------------------------------------------------------------------------------------------- FragmentOutputs main ( FragmentInputs input, uniform sampler2D decalMap ) { FragmentOutputs output; float4 leftColor = tex2D(decalMap, input.leftTexcoord); float4 rightColor = tex2D(decalMap, input.rightTexcoord); output.color = lerp(leftColor, rightColor, 0.5); return output; }ちなみにですね,Pocolの使っているグラボはGeForce 8600GTなんで,上のフラグメントプログラムで動きますが。プロファイルによってはテクスチャ座標セットは,セットに対応するテクスチャユニットでしか使うことができません。ちょっと古いGPUなんかはこれにあたることがあるそうです。もし,GPUによる制限により動かないときはuniform sampler2D decalMapのところを一つではなく,2つにして FragmentOutputs main ( FragmentInputs input, uniform sampler2D decalMap0, sampler2D decalMap1 ) { FragmentOutputs output; float4 leftColor = tex2D(decalMap0, input.leftTexcoord); float4 rightColor = tex2D(decalMap1, input.rightTexcoord); output.color = lerp(leftColor, rightColor, 0.5); return output; } という風にすると動くそうです。ですので注意してください。 ★ Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。 プログラムの作成にはMicrosoft Visual Studio 2005, Cg Toolkit 1.5 Septemberを用いています。 |