spin紹介されたので、現在のバージョンで最新のCg Toolkit Beta 2.1へ対応をしました。
最初のバージョンとの違いは、頂点宣言やリンクファイル、シェーダの入力構造体になります。
一応古いのをこちらに置いておきます(必要ある人はいないと思いますが…)。
ミーハーなので、「プログラミング言語Cg」に手を出してみました。
結果は特に変わることがないので、いつもと見た目は変わりません(新しいモデルを作ってみましたが)。
具体的なレンダリングとしては、テクスチャーを張った3Dモデルを扱っています。
光源計算はしていません。(ここを見ておられる方なら、すぐに拡張は可能でしょう)。
今回のソースは、次のものです(DirectX8.1用です)。
まぁ、いつものように適当にファイルが入っています。
vs.cg | 頂点シェーダー。「Cg言語」 |
ps.cg | ピクセルシェーダー。「Cg言語」 |
draw.cpp | メインの描画部分。セットアップなどを行う。 |
bg.cpp | メッシュを作ったり描画したり。 |
draw.h | 描画の各関数の定義。特に意味無いので出番無し。 |
main.h | 基本的な定数など。今回も出番無し。 |
main.cpp | 描画に関係しないシステム的な部分。変更が無いので、出番無し。 |
あと、モデルと、実行ファイル及び、プロジェクトファイルが入っています。
今回、トーンの模様は算術計算して求めたので、専用のトーンはありません。
まずは、『Cg Toolkit』を手に入れて、インストールしましょう。
NV Effects Browser が Cg Browser の形であとを引き継がれたりしています。
で、プログラムに入るのですが、インクルードやライブラリをいつものように、追加します(下の画像の一番下のやつね)。
次に「プロジェクト」―「設定」―「リンク」に、ライブラリ「cg.lib, cdD3D.lib」を追加します(Cg Toolkit Beta 2.1 からcg.libが必要になりました。図は古いものなので、cg.libが抜けています)。
とりあえず準備は終わりです。あとは、プログラムの変更になります。
後は、警告が出るのを防ぐために、「プロジェクトオプション」に「/nodefaultlib:"LIBC"」を追加します。
以上で、コンパイルが出来ると思います。
それでは、頂点シェーダープログラムの「vs.cg」を見ましょう。
「Cg」言語では、
1)入力の型の宣言
2)プログラムコード
が必要です。
入力される型は、実際に頂点バッファで確保される型です。
ここでは、入力も出力も4次元の頂点座標と、2次元のテクスチャー座標を指定しました。
但し、頂点シェーダに入力される時点では、4次元ベクトルとして扱います。
テクスチャ座標の残り2次元は、適当に補完されて入力されます(確か1にセットされるはず)。
「float4」など、変わった名前をしていますが、直感に従えばわかるでしょう。
出力するときの型は vf20 で、HPOS は頂点座標に使われるなど、決められています。
プログラム自体は、座標を透視変換して、テクスチャー座標はそのままコピーしています。
mul()は、掛け算命令です(正確には、ベクトルを行列に作用させているのですが)。
引数に行列を渡しているところだけ、後で出てきますから、覚えておいてください。
0001: // 0002: // テクスチャーを張る頂点シェーダープログラム 0003: // 0004: 0005: // ---------------------------------------------------------------------------- 0006: // 入力用構造体 0007: // ---------------------------------------------------------------------------- 0008: struct appdata { 0009: float4 position : POSITION; 0010: float4 texcoord0 : TEXCOORD0; 0011: }; 0012: 0013: // ---------------------------------------------------------------------------- 0014: // 頂点シェーダープログラム 0015: // ---------------------------------------------------------------------------- 0016: vf20 main(appdata I 0017: , uniform float4x4 worldviewproj_matrix 0018: ) { 0019: vf20 O; 0020: 0021: // 行列をかけて、頂点をスクリーン座標に変換する 0022: O.HPOS = mul(worldviewproj_matrix, I.position); 0023: 0024: // テクスチャー座標はそのままコピーする 0025: O.TEX0 = I.texcoord0; 0026: 0027: return O; 0028: }
「uniform」など、まだまだ説明できないところもありますが、それはおいおい。
ピクセルシェーダープログラムですが、入力、出力の型情報とプログラムで成り立っています。
入力の型は、頂点シェーダの出力から座標を取り除いた構造体を指定します。
HPOS は、プログラム中には陽には出てきませんが、これは今までにピクセルシェーダーのプログラムをした人はわかると思いますけど、
そうでない人は、ぜんぜんわかんないんじゃないかな。
プログラム自体は、tex0 のテクスチャーを texcoord0 で読み込んで、その色を出力します。
0001: // ---------------------------------------------------------------------------- 0002: // 頂点シェーダーからの入力構造体 0003: // ---------------------------------------------------------------------------- 0004: struct v2f_simple { 0006: float4 texCoord0 : TEXCOORD0; 0007: }; 0008: 0009: struct myFragment { 0010: float4 col : COLOR; 0011: }; 0012: 0013: myFragment main(v2f_simple I 0014: , uniform sampler2D tex0 0015: ) { 0016: myFragment O; 0017: 0018: // テクスチャーの色を読み込む 0019: O.col = tex2D(tex0); 0020: 0021: return O; 0022: }
(今回は使っていませんが)変数などが気楽に使えるのがいいところです。
実際に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: cgVertexDefinition vertex_attributes[] = { 0056: {D3DVSDT_FLOAT4, "position", 0}, 0057: {D3DVSDT_FLOAT2, "texcoord0", 0}, 0058: CGVERTEXDEFINITIONEND 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言語に移ったように、こちらに流れは進むのでしょうね。