DepthShadow

今回は、もっとも基本的な影生成を行います。

深度バッファを使った、影生成の中でも比較的簡単なものです。
深度値を比較して、影かどうか比較するだけなので、
影の境界がはっきりとしており、深度バッファの解像度によってはシャギーが出てしまいます。
また、深度バッファの解像度も、GPUの世代や性能によって確保できるサイズが限られますので注意が必要です。

今回は1024*1024のサイズでバッファを確保していますので、GeForce7シリーズ以降なら動作は軽いと思います。

以下実装

深度バッファの生成シェーダー
//グローバル変数宣言
float4x4 world;			// ワールド行列
float4x4 view;			// ビュー行列
float4x4 projection;	// 透視変換行列
 
//入力頂点構造体
struct VS_INPUT
{
	float4 position : POSITION; //頂点座標
	float3 normal   : NORMAL;	//法線ベクトル
	float4 color	: COLOR00;	//色
};
 
//出力頂点構造体
struct VS_OUTPUT
{
	float4 position  : POSITION;  //頂点座標
	float3 normal	 : TEXCOORD1; //法線ベクトル
	float4 color	 : COLOR0;	  //色
	float4 depth	 : TEXCOORD2; //深度
};

//頂点シェーダー
VS_OUTPUT vs_main(
	VS_INPUT input,
	uniform float4x4 world,
	uniform float4x4 view,
	uniform float4x4 projection
)
{
	VS_OUTPUT output;
	
	float4x4 WLV = mul( world, view );
	
	output.normal = mul( input.normal, (float3x3)WLV );
	
	float4x4 WLPV = mul( WLV, projection );
	
	output.position = mul( input.position, WLPV );
	
	output.depth = mul( input.position, WLPV );
	
			
	return output;
}

//入力ピクセル構造体
struct PS_INPUT
{
	float4 color	: COLOR0; //色
	float3 normal	: TEXCOORD1; //法線
	float4 depth	: TEXCOORD2;
};

//ピクセルシェーダー
float4 ps_main( 
	PS_INPUT input
) : COLOR0
{
	float color = input.depth.z / input.depth.w;
	return float4( color, color, color, 1 );
}

//テクニックの指定
technique create
{	
	//パスの指定
	pass Pass_0
	{
		CullFaceEnable = true;
		CullFace = Back;
		VertexProgram = compile arbvp1 vs_main( world, view, projection );
		FragmentProgram = compile arbfp1 ps_main();
	}
}

影生成シェーダー
//グローバル変数宣言
float4x4 world;
float4x4 view;
float4x4 projection;
float3 light;

float4x4 lightview;
float4x4 lightprojection;
float4x4 texture;

sampler2D depth;

float4 Ambient = float4( 0.2f, 0.2f, 0.2f, 1.0f );
float4 Diffuse = float4( 1.0f, 1.0f, 1.0f, 1.0f );
float4 Specular = float4( 1.0f, 1.0f, 1.0f, 1.0f );
float4 Ka = float4( 1.0f, 1.0f, 1.0f, 1.0f );
float4 Kd = float4( 1.0f, 1.0f, 1.0f, 1.0f );
float4 Ks = float4( 1.0f, 1.0f, 1.0f, 1.0f );
float4 Ke = float4( 0.0f, 0.0f, 0.0f, 0.0f );
float  Shininess = 30.0f;

//入力頂点構造体
struct VS_INPUT
{
	float4 position : POSITION; //頂点座標
	float3 normal   : NORMAL;	//法線ベクトル
	float4 color	: COLOR00;
};

//出力頂点構造体
struct VS_OUTPUT
{
	float4 position  : POSITION;  //頂点座標
	float3 normal	 : TEXCOORD1; //法線ベクトル
	float3 light	 : TEXCOORD2; //ライト方向のベクトル
	float3 view		 : TEXCOORD3; //視点方向のベクトル
	float3 halfAngle : TEXCOORD4; //二等分ベクトル
	float4 color	 : COLOR0;	  //色
	float4 shadowUV  : TEXCOORD5; //シャドウ用UV
	float4 depth	 : TEXCOORD6; //深度
};

//頂点シェーダー
VS_OUTPUT vs_main(
	VS_INPUT input,
	uniform float4x4 world,
	uniform float4x4 view,
	uniform float4x4 projection,
	uniform float3 light,
	uniform float4x4 lightview,
	uniform float4x4 lightprojection,
	uniform float4x4 texture
)
{
	VS_OUTPUT output;
	
	float4x4 WV = mul( world, view );
	//視点座標系の頂点座標を求める
	float3 PosEye = (float3)mul(input.position,WV);
	
	float3 LPE = mul( light, (float3x3)view );
	//ライト方向のベクトルを求める
	float3 L = normalize(LPE - PosEye);
	//視点方向のベクトルを求める
	float3 V = normalize(-PosEye);
	//二等分ベクトルを求める
	float3 H = normalize( L+V );
	
	//法線ベクトルの座標変換
	output.normal = mul(input.normal,(float3x3)WV);
	//ライト方向のベクトルを出力
	output.light = L;
	//視点方向のベクトルを出力
	output.view = V;
	//二等分ベクトルを出力
	output.halfAngle = H;
		
	float4x4 WVP = mul( WV, projection );
	//頂点座標の座標変換
	output.position = mul(input.position,WVP);
	
	float4x4 WLP = mul( mul( world, lightview ), lightprojection );
	output.depth = mul( input.position, WLP );
	
	float4x4 WLPB = mul( WLP, texture );
	output.shadowUV = mul( input.position, WLPB );
	
	//色を設定
	output.color = input.color;
	
	return output;
}

//入力ピクセル構造体
struct PS_INPUT
{
	float2 texcoord : TEXCOORD0; //テクスチャ座標
	float3 normal	: TEXCOORD1; //法線ベクトル
	float3 light	: TEXCOORD2; //ライト方向のベクトル
	float3 view		: TEXCOORD3; //視点方向のベクトル
	float3 halfAngle: TEXCOORD4; //二等分ベクトル
	float4 color	: COLOR0;
	float4 shadowUV  : TEXCOORD5; //シャドウ用UV
	float4 depth	 : TEXCOORD6; //深度
};

struct PS_OUTPUT
{
	float4 color0 : COLOR0;
	float4 color1 : COLOR1;
};

//ピクセルシェーダー
PS_OUTPUT ps_main(
	PS_INPUT input,
	uniform float4 Ambient,
	uniform float4 Diffuse,
	uniform float4 Specular,
	uniform float4 Ka,
	uniform float4 Kd,
	uniform float4 Ks,
	uniform float4 Ke,
	uniform float  Shininess,
	uniform sampler2D depth
)
{
	//各ベクトルを正規化する
	float3 N = normalize(input.normal);
	float3 L = normalize(input.light);
	float3 V = normalize(input.view);
	float3 H = normalize(input.halfAngle);
	
	//拡散反射光、鏡面反射光を計算する
	//float diffuse = max(dot(N,L),0.0f);
	float diffuseLight = max(dot(N,L),0.0f)*0.5f+0.5f; //ハーフランバートシェーダー
	float diffuse = diffuseLight * diffuseLight;
	float specluar = pow(max(dot(N,H),0.0f),Shininess);
	
	//環境光、拡散反射光、鏡面反射光を計算する
	float4 totalAmbient = Ka * Ambient * input.color;
	float4 totalDiffuse = Kd * Diffuse * diffuse * input.color;
	float4 totalSpecular = Ks * Specular * specluar;
		
	float shadow = tex2Dproj( depth, input.shadowUV ).x;
	
	//最終的なカラーを計算する
	PS_OUTPUT output;
	
	if( (shadow * input.depth.w) < (input.depth.z-0.05f) ){
		output.color0 = totalAmbient;
		output.color1 = totalAmbient;
	}else{
		output.color0 = totalAmbient + totalDiffuse + totalSpecular;
		output.color1 = float4( 1, 1, 1, 1 );
	}
	
	return output;
}

technique shadow
{
	//パスの指定
	pass Pass_0
	{
		CullFaceEnable = true;
		CullFace = Back;
		DepthTestEnable = true;
		VertexProgram = 
		 compile arbvp1 vs_main(
		  world,view,projection,
		  light,lightview, lightprojection,
		  texture
		 );
		FragmentProgram = 
		 compile arbfp1 ps_main(
		  Ambient, Diffuse, Specular,
		   Ka, Kd, Ks, Ke, Shininess,
		   depth
		 );
	}
}

メインプログラム
#include "Matrix4.h"
#include "Color4.h"
#include "Vector2.h"
#include "Vector3.h"
#include "MathUtility.h"
#include "Shader.h"
#include <iostream>
#include <GL/glew.h>
#include <GL/glut.h>

#pragma comment( lib, "glew32.lib" )

static void display();
static void idle();
static void key( unsigned char state, int x, int y );
static void initializeGL();
static void initializeCg();
static void drawRect( float x, float y, float w, float h );

namespace {
	//深度バッファ生成シェーダー
	Shader* create = NULL;
	//シャドウシェーダー
	Shader* shadow = NULL;
 
	//ワールド行列
	Matrix4 World;
	//透視変換行列
	Matrix4 Projection;
	//ビュー行列
	Matrix4 View;
	//ライト用透視変換行列
	Matrix4 LightProjection;
	//ライトビュー行列
	Matrix4 LightView;
	//ライト位置
	Vector3 Light( 5, 10, 5 );
	//テクスチャ行列
	Matrix4 Texture;
	//回転角度
	float angle;
	//FBOID
	unsigned int fbo;
	//TextureID
	unsigned int texture;
	//バッファサイズ
	const float BUFFER_SIZE = 1024.0f;
}

//メイン関数
void main( int argc, char* argv[] )
{
		glutInit( &argc, argv );
	glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );
	glutInitWindowPosition( 0, 0 );
	glutInitWindowSize( 640, 480 ); 
	glutCreateWindow( "Shader Test" );

	int err = glewInit();
	if( err != GLEW_OK ){
		std::cout << glewGetErrorString( err ) << std::endl;
		exit(0);
	}

	//初期化
	initializeGL();
	initializeCg();

	//関数の登録
	glutDisplayFunc( display );
	glutIdleFunc( idle );
	glutKeyboardFunc( key );

	glutMainLoop();
}

void initializeGL()
{
	glClearColor( 0.0f, 0.0f, 1.0f, 1.0f );

	glViewport( 0, 0, 640, 480 );
	//OpenGLでは何もさせない
	//透視変換行列の設定
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();

	Projection.setIdentity();
	Projection.setPerspective( 90.0f, 640.0f/480.0f, 0.1f, 100.0f );
		
	LightProjection.setIdentity();
	LightProjection.setPerspective( 90.0f, BUFFER_SIZE/BUFFER_SIZE, 5.0f, 40.0f );

	//ビュー行列の設定
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();

	View.setIdentity();
	View.setLookAt( 
		Vector3( 0.0f, 5.0f, -5.0f ),
		Vector3( 0.0f, 0.0f, 0.0f ),
		Vector3( 0.0f, 1.0f, 0.0f )
	);

	LightView.setLookAt( 
		Light,
		Vector3( 0.0f, 0.0f, 0.0f ),
		Vector3( 0.0f, 1.0f, 0.0f )
	);

	float offsetx = 0.5f + ( 0.5f / BUFFER_SIZE );
	float offsety = 0.5f + ( 0.5f / BUFFER_SIZE );

	//テクスチャ行列の設定
	Texture = Matrix4( 0.5f, 0.0f, 0.0f, 0.0f,
					   0.0f, 0.5f, 0.0f, 0.0f,
					   0.0f, 0.0f, 0.0f, 0.0f,
					   offsetx, offsety, 0.0f, 1.0f );

	//回転角度の初期化
	angle = 0.0f;

	//ワールド行列の設定
	Matrix4 translate, scale, rotate;
	translate.setIdentity();
	translate.setTranslate( Vector3( 0.0f, 0.0f, 0.0f ) );
	scale.setIdentity();
	scale.setScale( Vector3( 1.0f, 1.0f, 1.0f ) );
	rotate.setIdentity();
	rotate.setRotateY( angle );
	World = translate * scale * rotate;

	glDisable( GL_DEPTH_TEST );
	glDisable( GL_LIGHTING );
	glDisable( GL_CULL_FACE );
		
	//テクスチャのバインド
	glGenTextures( 1, &texture );
	glBindTexture( GL_TEXTURE_2D, texture );
		
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
		
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, BUFFER_SIZE, BUFFER_SIZE, 0, GL_RGBA, GL_FLOAT, 0 );
	
	glBindTexture( GL_TEXTURE_2D, 0 );
	
	glGenFramebuffersEXT( 1, &fbo );
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fbo );
	glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture, 0 );
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}

void initializeCg()
{
	create = new Shader( "CreateDepth.cgfx" );
	shadow = new Shader( "DepthShadow.cgfx" );

	create->setTechnique( "create" );
	shadow->setTechnique( "shadow" );

	//パラメータの設定
	create->setParameter( "world", World );
	create->setParameter( "view", LightView );
	create->setParameter( "projection", LightProjection );

	shadow->setParameter( "lightview", LightView );
	shadow->setParameter( "lightprojection", LightProjection );
	shadow->setParameter( "texture", Texture );
	shadow->setParameter( "world", World );
	shadow->setParameter( "view", View );
	shadow->setParameter( "projection", Projection );
	shadow->setParameter( "light", Light );
	shadow->setTexture( "depth", texture );

}

//描画
void display()
{
	Matrix4 ground;
	ground.setIdentity();

	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fbo );
	glPushAttrib( GL_VIEWPORT_BIT );
	glViewport( 0, 0, BUFFER_SIZE, BUFFER_SIZE );
	glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
               //パスの開始 
	create->begin();

	create->setParameter( "view", LightView );
	create->setParameter( "projection", LightProjection );

	for( unsigned int pass = 0; pass < create->getPassNum(); pass++ ){
		create->setPass( pass );
		//地面用
		create->setParameter( "world", ground );

		glBegin(GL_QUADS);
		glNormal3f( 0.0f, 0.0f, 1.0f );
		glVertex3f( 10.0f, 0.0f, 10.0f );
		glVertex3f( 10.0f, 0.0f,-10.0f );
		glVertex3f( -10.0f, 0.0f, -10.0f );
		glVertex3f( -10.0f, 0.0f, 10.0f );
		glEnd();

		//トーラス用
		create->setParameter( "world", World );

		//トーラスの描画
		glutSolidTorus( 0.5f, 1.0f, 64, 64 );

	}
               //パスの終了		
	create->end();
	
	glPopAttrib();
	glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );

	glClearColor( 0.0f, 0.0f, 1.0f, 1.0f );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
               //パスの開始
	shadow->begin();

	shadow->setParameter( "lightview", LightView );
	shadow->setParameter( "lightprojection", LightProjection );
	shadow->setParameter( "texture", Texture );
	shadow->setParameter( "world", World );
	shadow->setParameter( "view", View );
	shadow->setParameter( "projection", Projection );
	shadow->setParameter( "light", Light );
	shadow->setTexture( "depth", texture );

	for( unsigned int pass = 0; pass < shadow->getPassNum(); pass++ ){

		shadow->setPass( pass );

		//トーラス用
		shadow->setParameter( "world", World );

		//トーラスの描画
		glutSolidTorus( 0.5f, 1.0f, 64, 64 );

		//地面用
		shadow->setParameter( "world", ground );
 
		glColor4f( 1.0f, 0.0f, 0.0f, 1.0f );
		glBegin(GL_QUADS);
		glNormal3f( 0.0f, 0.0f, 1.0f );
		glVertex3f( 10.0f, 0.0f, 10.0f );
		glVertex3f( 10.0f, 0.0f,-10.0f );
		glVertex3f( -10.0f, 0.0f, -10.0f );
		glVertex3f( -10.0f, 0.0f, 10.0f );
		glEnd();
		glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
		
	}
               //パスの終了
	shadow->end();

	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, texture );
	drawRect( 0, 200, 200, -200 );
	glBindTexture( GL_TEXTURE_2D, 0 );
	glDisable( GL_TEXTURE_2D );

	//バッファの切り替え
	glutSwapBuffers();
}

//更新
void idle()
{
	//回転角度の更新
	angle += 1.0f;

	//ワールド行列の更新
	Matrix4 translate, scale, rotate;
	translate.setTranslate( Vector3( 0.0f, 2.0f, 0.0f ) );
	scale.setScale( Vector3( 1.0f, 1.0f, 1.0f ) );
	rotate.setRotateY( angle );
	World = translate * scale * rotate;

	//パラメータの設定
	//cgSetMatrixParameterfr( w, (const float*)&World );
	//cgSetMatrixParameterfr( v, (const float*)&View );
	//cgSetMatrixParameterfr( p, (const float*)&Projection );

	//再描画
	glutPostRedisplay();
}

//キー状態
void key( unsigned char state, int x, int y )
{
	switch( state ){
		//エスケープが押されたら終了
		case '\033':
			delete create;
			delete shadow;
			exit( 0 );
			break;
	}
}

void drawRect( float x, float y, float w, float h )
{
	//レンダリングモードの退避
	glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT | GL_COLOR_BUFFER_BIT );

	glDisable( GL_LIGHTING );
	glDisable( GL_DEPTH_TEST );
	glDisable( GL_CULL_FACE );

	//透視変換行列の退避
	glMatrixMode( GL_PROJECTION );
	glPushMatrix();

	//透視変換行列の設定
	glLoadIdentity();
	gluOrtho2D( 0, 640, 480, 0 );

	//モデルビュー変換行列の退避
	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();

	//変換行列の初期化
	glLoadIdentity();

	//四角形の描画
	glBegin( GL_QUADS );
		glTexCoord2f( 0, 0 );
		glVertex2f( x,  y);
		glTexCoord2f( 0, 1);
		glVertex2f(  x,  y+h);
		glTexCoord2f( 1, 1);
		glVertex2f(  x+w,  y+h);
		glTexCoord2f( 1, 0);
		glVertex2f(  x+w,  y );
	glEnd();
 
	//モデルビュー変換行列の復帰
	glPopMatrix();
 
	//透視変換行列の復帰
	glMatrixMode( GL_PROJECTION );
	glPopMatrix();
 
	//モデルビュー変換行列に設定
	glMatrixMode( GL_MODELVIEW );
 
	//レンダリングモードの復帰
	glPopAttrib();
}

今回は、空のプロジェクトの更新があったのでメインプログラムもすべて載せました。

メインプログラムの部分をmain.cppに上書きしてください。
全角スペースが混じってるかもしれないのでコピペする際は注意してください。

実行結果は以下のようになると思います。

スクリーンショットでは分かりにくいでしょうが、影にぎざぎざが発生しているはずです。


//バッファサイズ
const float BUFFER_SIZE = 1024.0f;

この部分の数値を変更してみてください。
数値を小さくするとギザギザが目立つようになります。
逆に数値を大きくするとギザギザは目立たなくなりますが、
メモリ・速度的にも厳しくなるのでほどほどにしておいたほうが良いです。

こういった欠点を解決した影生成の方法もあるので実装できるようになったらアップしたいと思います。
最終更新:2009年04月05日 18:02
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。