TOP

ABOUT

PROGRAM

DIALY







GPUのお勉強(2)

 1.はじめに…
 Cg言語(C for Graphics)を使って最初のプログラムを組んでみます。
今回は,頂点プログラムのみを利用してキューブを表示するだけの単純なプログラムです。





 2.とりあえず…
とりあえず,まずはCg言語を使わない状態でキューブを表示するプログラムを作りましょう。
頂点ごとに色をつけたかったので,頂点ごとに色を指定してみました。
GLfloat CubeVertices[8][3];  
GLfloat CubeNormals[8][3];
GLint CubeFaces[6][4] = {
    {3, 2, 1, 0}, {7, 6, 2, 3}, {4, 5, 6, 7},
    {0, 1, 5, 4}, {1, 2, 6, 5}, {3, 0, 4, 7}
};

//----------------------------------------------------------------------------------------------------
//  InitializeCube
//  Desc : キューブの初期化
//----------------------------------------------------------------------------------------------------
void InitializeCube(GLfloat v[8][3])
{
    v[0][0] = v[1][0] = v[2][0] = v[3][0] = 1;
    v[4][0] = v[5][0] = v[6][0] = v[7][0] = -1;
    v[0][1] = v[1][1] = v[4][1] = v[5][1] = 1;
    v[2][1] = v[3][1] = v[6][1] = v[7][1] = -1;
    v[0][2] = v[3][2] = v[4][2] = v[7][2] = 1;
    v[1][2] = v[2][2] = v[5][2] = v[6][2] = -1;
}

//----------------------------------------------------------------------------------------------------
//  DrawCube
//  Desc : キューブを描画する
//----------------------------------------------------------------------------------------------------
void DrawCube()
{
    for(int i=0; i<6; i++) 
    {
        glBegin(GL_QUADS);  

        glColor3f(
            0.5*CubeVertices[CubeFaces[i][0]][0]+0.5,
            0.5*CubeVertices[CubeFaces[i][0]][1]+0.5,
            0.5*CubeVertices[CubeFaces[i][0]][2]+0.5 );
        glNormal3fv(&CubeNormals [CubeFaces[i][0]][0]);             
        glVertex3fv(&CubeVertices[CubeFaces[i][0]][0]);             

        glColor3f(
            0.5*CubeVertices[CubeFaces[i][1]][0]+0.5,
            0.5*CubeVertices[CubeFaces[i][1]][1]+0.5,
            0.5*CubeVertices[CubeFaces[i][1]][2]+0.5 );
        glNormal3fv(&CubeNormals [CubeFaces[i][1]][0]);
        glVertex3fv(&CubeVertices[CubeFaces[i][1]][0]);

        glColor3f(
            0.5*CubeVertices[CubeFaces[i][2]][0]+0.5,
            0.5*CubeVertices[CubeFaces[i][2]][1]+0.5,
            0.5*CubeVertices[CubeFaces[i][2]][2]+0.5 );
        glNormal3fv(&CubeNormals [CubeFaces[i][2]][0]);
        glVertex3fv(&CubeVertices[CubeFaces[i][2]][0]);

        glColor3f(
            0.5*CubeVertices[CubeFaces[i][3]][0]+0.5,
            0.5*CubeVertices[CubeFaces[i][3]][1]+0.5,
            0.5*CubeVertices[CubeFaces[i][3]][2]+0.5 );
        glNormal3fv(&CubeNormals [CubeFaces[i][3]][0]);
        glVertex3fv(&CubeVertices[CubeFaces[i][3]][0]);
    
        glEnd();    
    }
}


ここまでは,いつもと何ら変わりません。


 3.Cg言語を使ってみる(CPU側)
 普通にキューブを表示するプログラムが作れたら,いよいよCg言語を使ってみます。
まず,Main.cppの先頭でCg言語を使うためにcg.hとcgGL.hをインクルードし,cg.libとcgGL.libのファイルを呼び出せるようにソリューションなどに追加するか,#pragma commentを使って呼び出せるようにしておいてください。とりあえず#pragma commentを用いることにします。
//
// include
//
#include <iostream>
#include <cmath>
#include <GL/glut.h>
#include <Cg/cg.h>
#include <Cg/cgGL.h>
#include "Mouse.h"
using namespace std;


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

今回はマウスで視点を動かせるように自作ヘッダーのMouse.hもインクルードしてあります。
次に,Cg用の変数をグローバルで用意しておきます。

//
// global
//
ViewCamera camera(10.0);
CGcontext context;
CGprofile vertexProfile;
CGprogram vertexProgram;
CGparameter modelview;
コンテキスト用の変数と頂点プロファイル用の変数,頂点プログラム用の変数,モデルビュー行列用のパラメータを用意しました。
次にCg言語を使うために,Cgの初期化をInitialize_Cgという関数の中で行います。
//----------------------------------------------------------------------------------------------------
//  Initialze_Cg
//  Desc : Cgの初期化
//----------------------------------------------------------------------------------------------------
bool Initialize_Cg()
{
    // エラーコールバック関数の設定
    cgSetErrorCallback(CheckCgError);

    // コンテキストを作成
    context = cgCreateContext();

    // 可能な限り高度な頂点プロファイルを使用
    vertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);

    // プロファイルが不明あるいはサポートされていないかどうかチェック
    if ( vertexProfile == CG_PROFILE_UNKNOWN 
        || vertexProfile == CG_INVALID_PROFILE_ERROR )
        return false;

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

    // 頂点プログラムのロード
    cgGLLoadProgram(vertexProgram);
    
    // プログラムのパラメータを直接取得
    modelview = cgGetNamedParameter(vertexProgram, "modelViewMatrix");
    
    return true;
}

//----------------------------------------------------------------------------------------------------
//  CheckCgError
//  Desc : Cgエラーのチェック
//----------------------------------------------------------------------------------------------------
void CheckCgError()
{
    CGerror lastError = cgGetError();

    if ( lastError )
    {
        cout << "Error : Cgエラーが発生しました\n";
        cout << cgGetErrorString(lastError) << endl;
        cout << cgGetLastListing(context) << endl;
        exit(0);
    }
}
C/C++側で普通にエラーが起こらずにビルドできた場合でも,プログラムの実行ができない場合があります。Cg言語側でプログラムのエラーが起きた場合などはこれにあたります。 なので,まずcgSetErrorCallBackでCgエラーが発生した場合にアプリケーション側でコールバックする関数を引数にしてセットしておきます。 次にコンテキストの作成を行います。Cgにはコンテキストを作成・破棄および問い合わせする関数が用意されているので,これらを利用します。コンテキストを作成するには,上のようにcgCreateContext()を呼び出してあげます。 プロファイルですが,Cg言語ツールキットのドキュメントによると,「使用できるOpenGL拡張機能に応じて,頂点プログラムまたはフラグメントプログラムに可能な限り高度なプロファイルを提供する便利な関数が用意されています。」とのことなので,便利な関数とやらを使います。その関数が
CGprofile cgGLGetLatestProfile( CGGLenum profileType);

だそうです。パラメータprofileTypeは,CG_GL_VERTEXまたはCG_GL_FRAGMENTになるのですが,今回は頂点シェーダーを使ってプログラムを作成するので,CG_GL_VERTEXを引数にしておきます。 取得したプロファイルがおかしくないかを if文を使ってチェックしておきます。  プロファイルのチェックが終了したら頂点プログラムの作成を行います。頂点プログラムは別シートのvertex.cgというファイルから作成を行いたいので,cgCreateProgramFromFileを呼び出して作成します。 関数の詳しい説明はCg言語ツールキットのドキュメントの36ページを見ると載っています。 頂点プログラムの作成が終わったら,プログラムのロードをします。あとは先ほど宣言したmodelviewを使いたいのでCgからパラメータの取得をします。プログラムのパラメータはcgGetNamedParameterでパラメータの名前を使用することで直接取得することができるそうです。
CGparameter cgGetNamedParameter( CGprogram program, const char* name );

頂点プログラムの名前がvertexProgramで,Cgファイルで宣言している変数の名前がmodelViewMatrixなので,上のようになっています。ちなみにnameに対応するパラメータがプログラムにない場合は0を返すとのことです。
次に,描画処理ですが,以下のようにDisplay関数で行っています。
//---------------------------------------------------------------------------------------------------
//  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);

    // キューブの描画
    DrawCube();

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

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

    glPopMatrix();

    glutSwapBuffers();
}
いつもとちがうところは,描画したいものを書く前にCgをバインドするということと描画したいものを書き終えた後にCgのプロファイルを無効にするという作業が付け加わることです。
まず,モデルビュー行列を使いたいのでCg側から取ってきた変数をcgGLSetStateMatrixParameter関数を使ってセットします。つぎにcgGLEnableProfile関数で頂点プロファイルを有効にします。そして頂点プロファイルを有効にしてからcgGLBindProgramにより頂点プログラムをバインドします。 Cg言語ツールキットのドキュメントによると「プログラムのバインドは,プロファイルが有効になっている場合にのみ動作します」とのことなのでプロファイルは先に書くようにしておいた方がいいでしょう。 書きたいものが書き終わったらcgGLDisableProfile()関数でプロファイルを無効化します。 最後にCgの後片付けを行いましょう。これはClenup_Cg()関数を作ってそこで行うことにします。
//----------------------------------------------------------------------------------------------------
//  Cleanup_Cg
//  Desc : Cgの後片付け
//----------------------------------------------------------------------------------------------------
void Cleanup_Cg()
{
    cgDestroyProgram(vertexProgram);

    cgDestroyContext(context);
}
cgDestroyProgramとcgDestroyContextで作成した頂点プログラムとコンテキストの破棄を行います。これでCPU側での処理は一段落です。 あとはGPU側の処理です。"vertex.cg"というCgファイルを作成し,そこにコーディングしていきます。 


 4.Cg言語を使ってみる(GPU側)
//
// VertexInputs
//
struct VertexInputs
{
    float4 position : POSITION;
    float4 color : COLOR;
};

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

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

    // 行列を掛けてスクリーン座標にする
    output.position = mul(modelViewMatrix, input.position);

    // 色
    output.color = input.color;

    return output;
}

GPU側ではが必要だそうで,まず入力と出力用の構造体を作成します。VertexInputsとVertexOutputがそれにあたります。今回は座標用にfloat型の4次元ベクトルとカラー用にfloat型の4次元ベクトルを用意しました。次にコードなんですが,これはmain関数の中でやっています。 CやC++の場合はmain関数からプログラムが始まるので,わかりやすいように同じようにメインエントリーポイントの関数名をmainにしました。mainではなく別の名前をつけたいときは cgCreateProgramFromFile関数の5番目の引数のところを"main"ではなく自分でつけた関数名に変更するのを忘れないでください。 main関数の中は,入力してきた座標に行列をかけてスクリーン座標にしてGPU側に送ってやってます。色の方はそのままGPU送るという処理をしています。
一応こんな感じでCg言語が使えるそうです。


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