TOP

ABOUT

PROGRAM

DIALY







GPUのお勉強(3)

 1.はじめに…
 今回は,フラグメントプログラムも使ったCgプログラムを作成してみます。
内容はフラグメントプログラムを使ってテクスチャを表示する単純なものです。





 2. コーディング(C/C++側)
まずは,前回と同じように,ヘッダーファイルをインクルードしたり,変数を用意したりしてやります。
//
// include
//
#include <iostream>
#include <cmath>
#include <GL/glew.h>
#include <GL/glut.h>
#include <Cg/cg.h>
#include <Cg/cgGL.h>
#include "Mouse.h"
#include "DDSLoader.h"
using namespace std;

//
// link
//
#pragma comment (lib, "cg.lib")
#pragma comment (lib, "cgGL.lib")


//
// global
//
int WindowPositionX = 100;
int WindowPositionY = 100;
int WindowWidth = 512;
int WindowHeight = 512;
char WindowTitle[] = "First Fragment Program";
ViewCamera camera(5.0);
DDSImage decal_map;
CGcontext context;
CGprofile vertexProfile;
CGprofile fragmentProfile;
CGprogram vertexProgram;
CGprogram fragmentProgram;
CGparameter modelview;
CGparameter texture;
GLfloat lightColor[4]  = { 1.0, 1.0, 1.0, 1.0 };
GLfloat lightPosition[4] = { 100.0, 100.0, 100.0, 1.0 };


前回と違うところは,フラグメントも利用するので,CGprofile fragmentProfileとCgprogram fragmentProgramを用意しているところと,テクスチャ用にCGparameter textureという変数を用意していることです。
つづいて,Cgの初期化ですが,下のような感じです。
//----------------------------------------------------------------------------------------------------
//  Initialze_Cg
//  Desc : Cgの初期化
//----------------------------------------------------------------------------------------------------
bool Initialize_Cg()
{
    // エラーコールバック関数の設定
    cgSetErrorCallback(Check_CgError);

    // コンテキストを作成
    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;

    // Cgソースファイルから頂点プログラムを作成
    vertexProgram = cgCreateProgramFromFile(
        context,            // コンテキストハンドル
        CG_SOURCE,  // Cgソースコードを使用
        "vertex.cg",        // Cgソールコードが格納されているファイル名
        vertexProfile,  // コンパイルする対象プロファイルの指定
        "main",             // Cgのメインエントリポイント関数名
        NULL );             // コンパイラに引数として渡されるnull終了文字列

    // Cgソースファイルからフラグメントプログラムを作成
    fragmentProgram = cgCreateProgramFromFile(
        context,                // コンテキストハンドル
        CG_SOURCE,      // Cgソースコードを使用
        "fragment.cg",      // Cgソースコードが格納されているファイル名
        fragmentProfile,    // コンパイルする対象のプロファイルの指定
        "main",                 // Cgのメインエントリポイント関数名
        NULL );                 // コンパイラに引数として渡されるnull終了文字列

    // エラーチェック
    if ( !vertexProgram ) return false;
    if ( !fragmentProgram ) return false;

    // プログラムのロード
    cgGLLoadProgram(vertexProgram);
    cgGLLoadProgram(fragmentProgram);
    
    // プログラムのパラメータを直接取得
    modelview = cgGetNamedParameter(vertexProgram, "modelViewMatrix");
    texture = cgGetNamedParameter(fragmentProgram, "decalMap");

    // 読み込んだテクスチャを送る
    cgGLSetTextureParameter(texture, decal_map.ID);
    
    return true;
}

やはし,違うところは前回の頂点プログラムにフラグメントプログラムが加わるということです。基本的な流れは,プロファイルの設定→プロファイルのチェック→Cgソースファイルからプログラムを作成→プログラムのチェック→プログラムのロードと,前回の頂点プログラムとほぼ同じです。
ロードが終わったら次にパラメータの設定をします。
まずはモデルビュー行列のパラメータをcgGetNamedParameter関数で取得します。ここは前回と同じです。 次に,今回はテクスチャを使うのでcgGetNamedParameter関数でテクスチャ用のパラメーターの取得します。 フラグメント用のCGファイルでdecalMapという宣言をしてあるので,引数をdecalMapにしておきます。 最後にcgGLSetTextureParameter関数で取得したパラメータにOpenGLテクスチャオブジェクトを割り当てます。この関数はCg Toolkitを見ると次のように書いてあります。
void cgGLSetTextureParameter( CGparameter parameter, GLuint textureName );
textureNameはOpenGLテクスチャ名だそうです。OpenGLではテクスチャを番号として管理するそうなので,引数の型がGLuintになっています。 プログラムでは読み込んだDDSファイルのテクスチャ番号が格納されているdecal_map.IDを引数にしています。
つづいて,描画関数です。
//---------------------------------------------------------------------------------------------------
//  Display
//  Desc : ウィンドウへの描画
//---------------------------------------------------------------------------------------------------
void Display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glViewport(0, 0, WindowWidth, WindowHeight);

    glPushMatrix();

    // 視点の描画
    camera.Set();

    // OpenGL 4x4ステート行列を設定(モデルビュー行列と射影行列)
    cgGLSetStateMatrixParameter(
        modelview,
        CG_GL_MODELVIEW_PROJECTION_MATRIX,
        CG_GL_MATRIX_IDENTITY
    );

    // 頂点プロファイルを有効化
    cgGLEnableProfile(vertexProfile);

    // 頂点プログラムをバインド
    cgGLBindProgram(vertexProgram);

    // フラグメントプロファイルを有効化
    cgGLEnableProfile(fragmentProfile);

    // フラグメントプログラムをバインド
    cgGLBindProgram(fragmentProgram);

    // テクスチャを有効化
    cgGLEnableTextureParameter(texture);

    // 四角形を描画
    DrawRectangle();

    // テクスチャを無効化
    cgGLDisableTextureParameter(texture);

    // フラグメントプロファイルを無効化
    cgGLDisableProfile(fragmentProfile);

    // 頂点プロファイルを無効化
    cgGLDisableProfile(vertexProfile);

    glPopMatrix();

    // 補助軸の描画
    glPushMatrix();
    camera.RenderSubAxis(WindowWidth, WindowHeight);
    glPopMatrix();

    glutSwapBuffers();
}
OpenGL側で計算したモデルビューと射影行列をcgGLSetSteateMatrixParameter()関数を使って,Cg側に送ってやります。
そのあとで,頂点プロファイルを有効にして,頂点プログラムをバインドします。つぎにフラグメントプロファイルを有効にして,フラグメントプログラムをバインドします。
ここまでは前回とほぼ同じです。
つぎにcgEnableTextureParameter()関数でテクスチャを有効にします。ここで注意してほしいのは,いつもつかっているglEnable(GL_TEXTURE_2D)とglBindTexture(GL_TEXTURE_2D, textureID)という関数を使っていない点です。テクスチャを無効にするときもglDisable(GL_TEXTURE_2D)とglBindTexture(GL_TEXTURE_2D, 0)を使わずcgGLDisableTextureParameter()関数を使ってあげます。


 3.コーディング(Cg側)
 まず,vertex.cgとfragment.cgという2つのファイルを新規作成します。わかりやすいようにvertex.cgとfragment.cgにしましたが別の名前でも構いません。 別の名前にした場合はcppファイルでcgCreateProgramFormFile関数内の記述したCgソースコードが格納されているファイル名を一致させておくのを忘れないでください。
まずは,頂点プログラム側の処理について記述していきます。
//
// VertexInputs
//
struct VertexInputs
{
    float4 position : POSITION;
    float4 color : COLOR;
    float2 texcoord : TEXCOORD0;
};

//
// VertexOutputs
//
struct VertexOutputs
{
    float4 position : POSITION;
    float2 color : COLOR;
    float2 texcoord : TEXCOORD0;
};


//----------------------------------------------------------------------------------------------------
//  main
//  Desc : Cgメインエントリポイント関数
//----------------------------------------------------------------------------------------------------
VertexOutputs main ( VertexInputs input, uniform float4x4 modelViewMatrix)
{
    VertexOutputs output;

    output.position = mul(modelViewMatrix, input.position);
    output.color = input.color;
    output.texcoord = input.texcoord;

    return output;
}

頂点プログラム側は,入力された位置座標とモデルビュー射影行列を掛けてスクリーン座標にして出力し,色とテクスチャ座標は入力をそのまま出力に送ります。
つづいて,フラグメントプログラムです。
//
// FragmentInputs
//
struct FragmentInputs
{
    float4 color : COLOR;
    float2 texcoord : TEXCOORD0;
};

//
// FragmentOutputs
//
struct FragmentOutputs
{
    float4 color : COLOR;
};


//-----------------------------------------------------------------------------------------------------
//  main
//  Desc : Cgメインエントリポイント関数
//-----------------------------------------------------------------------------------------------------
FragmentOutputs main ( FragmentInputs input, uniform sampler2D decalMap )
{
    FragmentOutputs output;

    // テクスチャマッピング関数(2次元の非射影)
    output.color = tex2D(decalMap, input.texcoord);

    return output;
}
フラグメントでは頂点プログラムの方から送られてきた色とテクスチャ座標を入力としておきます。
あとは,CPUの方から送られてきたテクスチャデータがsampler2D decalMapに入ってくるので,これとテクスチャ座標からテクスチャマッピング関数を使って出力する色を計算します。
2次元のテクスチャで深度比較とかはしないので,Cg Toolkitにあるtex2D関数を使いました。
tex2D(sampler2D tex, float2 s)
2次元座標なので,sampler2D型とfloat2型の引数を持つtex2D関数を使っています。texはテクスチャオブジェクトへのハンドルで,sにはテクスチャ座標が入ります。 このtex2D関数を使って出力用のカラーを作成し,出力します。 これでフラグメント側の処理も終了です。
一応こんな感じでプログラムを組んでやると,一番上にあるような画像の実行結果がでてくると思います。


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