Cg言語?


~nVidiaの野望は達成されるか?~




■はじめに

ミーハーなので、「プログラミング言語Cg」に手を出してみました。
結果は特に変わることがないので、いつもと見た目は変わりません(新しいモデルを作ってみましたが)。
具体的なレンダリングとしては、テクスチャーを張った3Dモデルを扱っています。
光源計算はしていません。(ここを見ておられる方なら、すぐに拡張は可能でしょう)。

今回のソースは、次のものです(DirectX8.1用です)。

まぁ、いつものように適当にファイルが入っています。

vs.cg頂点シェーダー。「Cg言語」
ps.cgピクセルシェーダー。「Cg言語」
draw.cppメインの描画部分。セットアップなどを行う。
bg.cppメッシュを作ったり描画したり。
draw.h描画の各関数の定義。特に意味無いので出番無し。
main.h基本的な定数など。今回も出番無し。
main.cpp描画に関係しないシステム的な部分。変更が無いので、出番無し。
earth.bmp (地球テクスチャー)
tile.bmp (床デカール)
sky.bmp (空デカール)

あと、モデルと、実行ファイル及び、プロジェクトファイルが入っています。
今回、トーンの模様は算術計算して求めたので、専用のトーンはありません。

■準備

まずは、『Cg Toolkit』を手に入れて、インストールしましょう。
NV Effects Browser が Cg Browser の形であとを引き継がれたりしています。
で、プログラムに入るのですが、インクルードやライブラリをいつものように、追加します(下の画像の一番下のやつね)。




次に「プロジェクト」―「設定」―「リンク」に、ライブラリ「cdD3D.lib」を追加します。


とりあえず準備は終わりです。あとは、プログラムの変更になります。


後は、警告が出るのを防ぐために、「プロジェクトオプション」に「/nodefaultlib:"LIBC"」を追加します。
以上で、コンパイルが出来ると思います。

■頂点シェーダー

それでは、頂点シェーダープログラムの「vs.cg」を見ましょう。
「Cg」言語では、
 1)入力の型の宣言
 2)出力の型の宣言
 3)プログラムコード
が必要です。

ここでは、入力も出力も4次元の頂点座標と、2次元のテクスチャー座標を指定しました。
「float4」など、変わった名前をしていますが、直感に従えばわかるでしょう。
入力される型は、実際に頂点バッファで確保される型です。
出力するときの型は、HPOS は頂点座標に使われるなど、決められている名前もありますが、 ピクセルシェーダーとの関係で適当に決められます(自信なし。勉強不足なので後で書き換えるかも)。
プログラム自体は、座標を透視変換して、テクスチャー座標はそのままコピーしています。
mul()は、掛け算命令です(正確には、ベクトルを行列に作用させているのですが)。
引数に行列を渡しているところだけ、後で出てきますから、覚えておいてください。

0001: //
0002: // テクスチャーを張る頂点シェーダープログラム
0003: //
0004: 
0005: // ----------------------------------------------------------------------------
0006: // 入力用構造体
0007: // ----------------------------------------------------------------------------
0008: struct appdata : application2vertex {
0009:     float4 position;
0010:     float2 texcoord0;
0011: };
0012: 
0013: // ----------------------------------------------------------------------------
0014: // 出力用構造体
0015: // ----------------------------------------------------------------------------
0016: struct vfconn : vertex2fragment {
0017:     float4 HPOS;
0018:     float2 texcoord0;
0019: };
0020: // ----------------------------------------------------------------------------
0021: // 頂点シェーダープログラム
0022: // ----------------------------------------------------------------------------
0023: vfconn main(appdata I
0024:         , uniform float4x4 worldviewproj_matrix
0025: ) {
0026:     vfconn O;
0027:     
0028:     // 行列をかけて、頂点をスクリーン座標に変換する
0029:     O.HPOS = mul(worldviewproj_matrix, I.position);
0030: 
0031:     // テクスチャー座標はそのままコピーする
0032:     O.texcoord0 = I.texcoord0;
0033: 
0034:     return O;
0035: } 

「uniform」など、まだまだ説明できないところもありますが、それはおいおい。

■ピクセルシェーダー

ピクセルシェーダープログラムですが、入力の型情報とプログラムで成り立っています。
どうも入力の型は、頂点シェーダーの出力とまったく同じにはいかないようで、「float2」から「float4」に修正が必要でした。
プログラム自体は、tex0 のテクスチャーを texcoord0 で読み込んで、その色を出力します。
HPOS は、プログラム中には陽には出てきませんが、これは今までにピクセルシェーダーのプログラムをした人はわかると思いますけど、 そうでない人は、ぜんぜんわかんないんじゃないかな。

0001: // ----------------------------------------------------------------------------
0002: // 頂点シェーダーからの入力構造体
0003: // ----------------------------------------------------------------------------
0004: struct v2f_simple : vertex2fragment  {
0005:     float4 HPOS;
0006:     float4 texcoord0;       // float2 では、なくなっているのに注意
0007: };
0008: 
0009: fragout main(v2f_simple I
0010:                 , uniform sampler2D tex0
0011: ) {
0012:   fragout O;   
0013: 
0014:     // テクスチャーの色を読み込む
0015:     O.col = tex2D(tex0);
0016: 
0017:   return O;
0018: } 

(今回は使っていませんが)変数などが気楽に使えるのがいいところです。

■呼び出し方法

実際にcプログラムからの呼び出し方ですが、次のオブジェクトの準備が必要です。

0015: #include "Cg/cgD3D.h"
0016: 
0017: // Cg 環境
0018: cgDirect3D cg;
0019: cgContextContainer * pContextContainer = 0;
0020: // 頂点シェーダー
0021: cgProgramContainer *pVertexProgramContainer = 0;    // 頂点シェーダーを扱うコンテナ
0022: cgBindIter * vertex_mat_iter = 0;                   // ローカル/射影変換を扱う反復子
0023: // ピクセルシェーダー
0024: cgProgramContainer  *pPixelProgramContainer = 0;    // ピクセルシェーダーを扱うコンテナ
0025: cgBindIter * tex0_iter = 0;                         // テクスチャーを扱う反復子

まぁ、最初は、インクルードですが、Cg言語を使うのに、cgDirect3D と、cgContextContainer が必要です。
シェーダープログラムを使うには、コンテナ、cgProgramContainer が必要になります。
あと、引数を使うのに、cgBindIter (Cg言語バインドイテレータ)が必要になります。
なんか、Cg言語はSTL方面の方言が多いようです。
詳細は、おいおい考えていくとして、とりあえず使ってみましょう。

初期化ですが、黄色い部分が、頂点シェーダーでオレンジ色の部分がピクセルシェーダーです。
多いように見えますが、コメントが多いです。
っていうか、コメントが面白いんですよ。
コメントをはずすと、コンパイル結果をメモ帳で開いてくれます。
いやー、最初見た時はほんとに驚きましたよ。
あと、最適化が完全でないのもわかります。

0039: //-----------------------------------------------------------------------------
0040: // Name: InitRender()
0041: // Desc: 初期化
0042: //-----------------------------------------------------------------------------
0043: HRESULT InitRender(LPDIRECT3DDEVICE8 lpD3DDev)
0044: {
頂点バッファの生成など
0047: 
0048:     // プログラマぶるシェーダーを使えるようにする
0049:     cg.AddFilePath("..");
0050:     pContextContainer = cg.CreateContextContainer(lpD3DDev);
0051: 
0052:     // 
0053:     // 頂点シェーダー
0054:     // 
0055:     cgVertexAttribute vertex_attributes[] = {
0056:         {4, "position", 0},
0057:         {2, "texcoord0", 0},
0058:         {0, 0, 0}
0059:     };
0060: 
0061:     pVertexProgramContainer = pContextContainer->LoadCGProgramFromFile(
0062:         "vs.cg", "Vertex Shader", cgDX8VertexProfile, vertex_attributes);
0063: 
0064:     if (pVertexProgramContainer == NULL) {
0065:         // エラー表示
0066:         const char * listing = pContextContainer->GetLastListing();
0067:         if (listing == 0) listing = "Could not find cgc.exe.";
0068:         cg.NotePad("頂点シェーダープログラムの生成に失敗しました\n\n", listing);    // メモ帳に出力
0069: 
0070:         exit(1);    // 終了
0071:     }
0072:     // 頂点データの反復子と、.cg ファイルの引数を関連付ける
0073:     vertex_mat_iter = pVertexProgramContainer->GetParameterBindByName("worldviewproj_matrix");
0074: 
0075: // ここのコメントをはずすと、生成されたソースコードが表示される
0076: //  const char * object_code = pVertexProgramContainer->GetProgramObjectCode();
0077: //  cg.NotePad("", object_code);
0078:         
0079:         
0080: // ここのコメントをはずすと、入力すべき頂点情報が表示される
0081: //  const char * vert = pVertexProgramContainer->GetVertexDeclaration(); 
0082: //  cg.NotePad("cg program expects the vertex be defined like this:\n", vert);
0083: 
0084:     // 
0085:     // ピクセルシェーダー
0086:     // 
0087:     pPixelProgramContainer = pContextContainer->LoadCGProgramFromFile(
0088:                                 "ps.cg", "test", cgDX8PixelProfile);
0089:     
0090:     if (NULL == pPixelProgramContainer) {
0091:         // エラー表示
0092:         const char * error_text = pContextContainer->GetLastListing();
0093:         cg.NotePad("ピクセルシェーダープログラムの生成に失敗しました\n\n\n", error_text);
0094: 
0095:         exit(1);    // 終了
0096:     }
0097: // ここのコメントをはずすと、生成されたソースコードが表示される
0098: //  const char *pixel_object_code = pPixelProgramContainer->GetProgramObjectCode();
0099: //  cg.NotePad("", pixel_object_code);
0100: 
0101:     // テクスチャーの反復子と、.cg ファイルの引数を関連付ける
0102:     tex0_iter = pPixelProgramContainer->GetTextureBindByName("tex0");
0103:     int t0 = pPixelProgramContainer->GetTexturePosition(tex0_iter);
0104: 
0105:     
0106:     // 不変なレジスタの設定
0107:     lpD3DDev->SetRenderState( D3DRS_ZENABLE, TRUE );
0108:     lpD3DDev->SetRenderState( D3DRS_LIGHTING,  FALSE );
0109:     lpD3DDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE);
0110: 
0111:     return S_OK;
0112: }

具体的には、「LoadCGProgramFromFile」でファイルを読み込みます。
次に、「GetParameterBindByName」で、Cgプログラム中の変数名の名前とイテレータを関係付けます。
また、テクスチャーを指定するために、「GetTexturePosition」でテクスチャーのポインタが使えるような準備をします。
要は、テクスチャーを使うのに、2つの関数がいるということです。
これで、プログラムの外から引数として、パラメータを渡すことができるようになります。

あとは、レンダリングするときに、

まずは、draw.cpp
0139:     // プログラマぶるシェーダーを有効にする
0140:     pVertexProgramContainer->SetShaderActive();
0141:     pPixelProgramContainer->SetShaderActive();
以下、bg.cpp
0244:     //
0245:     // 地球
0246:     //
0247:     D3DXMATRIX m1, m2;
0248:     D3DXMatrixRotationY( &m1, ~0-timeGetTime()/1000.0f );
0249:     D3DXMatrixTranslation(&m2, 0,EARTH_R,0);
0250:     m = m1*m2*mVP;
0251:     D3DXMatrixTranspose( &m, &m );
0252:     pVertexProgramContainer->SetShaderConstant( vertex_mat_iter, &m  );
0253:     pPixelProgramContainer->SetTexture(tex0_iter, pEarthTex);
0254:     lpD3DDev->SetTextureStageState(0,D3DTSS_ADDRESSU,   D3DTADDRESS_WRAP);
0255: 
0256:     lpD3DDev->SetStreamSource(0, pEarthVB, sizeof(MyVertex));
0257:     lpD3DDev->SetIndices(pEarthIB,0);
0258:     lpD3DDev->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, NUM_EARTH_VERTICES, 0 , NUM_EARTH_POLYGONE );

で、シェーダープログラムを使用するように設定します。
SetVertexShader などの関数が不必要になって、その代わりに一般的な「SetShaderActive()」関数で使う宣言をします。
将来的にシェーダープログラムを統合したり、テセレーション用シェーダーを考えてこうしてるんでしょうね。
あと、違いは、「SetShaderConstant」で、イテレータを使って、変数を入れたり、「SetTexture」がイテレータ指定になっています。
あとは、今までのプログラムがそのまま使えるので、楽勝でしょう。

■最後に

PC Watchの「後藤弘茂のWEEKLY海外ニュース」の後藤さんとお話したときに、 「高水準グラフィックプログラミング言語がでれば、今のレンダリングプログラミングの世界がかわりますよ(かなり意訳。言葉ずらは完全に忘れました)」と おっしゃってられたので、早めに挑戦してみました。
手続きは面倒くさいですが(慣れか?)、プログラム内で、変数が使えるのがいいですね。
やっぱり、最適化の余地があるので、追及する人は現在の言語で高速化をねらうのでしょうが、 アセンブラからC言語に移ったように、こちらに流れは進むのでしょうね。




もどる

imagire@gmail.com