OpenGL de プログラミング - 実践編05::まとめコード2_2
現在地: メニュー >> 実践編05 >> 実践編05::ステンシルシャドウ >> 実践編05::まとめコード2_2

問題


ステンシルシャドウを行え。
その他条件:
  • 両面ステンシルを使う(OpenGL 2.0標準機能)

答え


#include <iostream>
#include <cmath>
#include <GL/glew.h> //先に記述する
#include <GL/glut.h>

//------------- 各種外部変数 ----------------//
struct Point4f
{
	float x,y,z,w;
};

Point4f LightPos = {1,3,3, 1.0};
Point4f RoofCoord[3] = { {-1,1,0,1}, {0,1,1,1},{1,1,0,1} };
Point4f ShadowEnd[3];
double eye[3] = {0,7,8};

//無限遠方計算用
const float InfinitePoint = 100;


//-- マウスの状態 --//
struct _MOUSE
{
	int Xstart,Ystart;
	bool flag;
	double weight;
};
_MOUSE MouseStatus={0,0,false,0.5};

//-- 回転関係 --//
struct _ObjectRotate
{
	double xAngle,yAngle;
};
_ObjectRotate ObjRot={0,0};


//------------ プロトタイプ宣言 ---------------//
void Enables();
void display();
void reshape(int w, int h);
void myMouseFunc(int button,int state,int x,int y);
void myMouseMotion(int x,int y);
void DrawRoof();
void DRAW_FLOOR();
void DrawLightPos();
void CalcShadowVolume(const Point4f &LightPos,const Point4f *RoofCoord, Point4f *EndPoint);
void DrawScreen(double fovy,double eyeX,double eyeY,double eyeZ,double znear);



//------------- OpenGLの初期設定 ------------------//
void GLUT_INIT()
{
	glutInitDisplayMode(GLUT_RGBA| GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
	glutInitWindowSize(640,480);
	glutCreateWindow("Stencil Shadow");
}

//glewの設定
bool GLEW_INIT()
{
	GLenum err;
	err = glewInit();
	if (err != GLEW_OK){
		std::cerr << glewGetErrorString(err) << '\n';
		return false;
	}
	return true;
}

void GLUT_CALL_FUNC()
{
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutMouseFunc(myMouseFunc);
	glutMotionFunc(myMouseMotion);
}

void MY_INIT()
{
	glClearColor(1.0, 1.0, 1.0, 1.0);
	Enables(); //各種有効化
	glClearStencil(0);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	CalcShadowVolume(LightPos,RoofCoord,ShadowEnd);
}

//各種有効化
void Enables()
{
	float light_pos[] ={LightPos.x,LightPos.y,LightPos.z,LightPos.w}; 
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_NORMALIZE); //法線の正規化
}

//----------- ここからメイン関数 -------------//
int main(int argc, char **argv)
{
	glutInit(&argc,argv);
	GLUT_INIT();
	if ( GLEW_INIT() == false){
		std::cerr << "Can't init glew\n";
		return -1;
	}
	
	GLUT_CALL_FUNC();
	MY_INIT();
	glutMainLoop();

	return 0;
}

//--------- ここから各種コールバック -------------//
void display()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT| GL_STENCIL_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt(eye[0],eye[1],eye[1], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);


	glRotated(ObjRot.xAngle,1,0,0);
	glRotated(ObjRot.yAngle,0,1,0);


	//床を描画
	static float floorcolor[] = { 0.8, 0.8, 0.8, 1.0 };
	glPushMatrix();
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, floorcolor);
		DRAW_FLOOR();
	glPopMatrix();


	//通常の描画
	static float red[] = { 0.8, 0.2, 0.2, 1.0 };
	glPushMatrix();
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, red);
		glutSolidTeapot(0.7);
	glPopMatrix();


	glDisable(GL_LIGHTING);
	glDisable(GL_LIGHT0);

	
	//ここからステンシルシャドウの処理
	glEnable(GL_STENCIL_TEST);
	glDepthMask(GL_FALSE);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	
	glStencilFuncSeparate(GL_FRONT_AND_BACK, GL_ALWAYS, 0, ~0);
	glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);//表面は「+1」
	glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);//裏面は「-1」

	glBegin(GL_TRIANGLE_FAN);
	glVertex3f(LightPos.x,LightPos.y,LightPos.z);
	for(int loop = 0; loop < 3;++loop)
	{
		glVertex3f(ShadowEnd[loop].x,ShadowEnd[loop].y,ShadowEnd[loop].z);
	}
	glVertex3f(ShadowEnd[0].x,ShadowEnd[0].y,ShadowEnd[0].z);
	glEnd();


	


	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glDepthMask(GL_TRUE);
	glStencilFunc(GL_NOTEQUAL,0, ~0); //ステンシル値が0じゃない部分が影
	glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );

	////ステンシルバッファを可視化
	glPushMatrix();
	glRotated(ObjRot.yAngle,0,-1,0);
	glRotated(ObjRot.xAngle,-1,0,0);
		glDepthMask(GL_FALSE);
		glEnable(GL_BLEND);  //ブレンド有効化
		glColor4f(0,0,0,0.5);
		DrawScreen(30,eye[0],eye[1],eye[1],1); //スクリーン描画(fovy,znearをわたす)
		glColor4f(1,1,1,1);
		glDisable(GL_BLEND);  //ブレンド無効化
		glDepthMask(GL_TRUE);
	glPopMatrix();

	glDisable(GL_STENCIL_TEST);


	//屋根の描画
	glColor3f(0,1,0);
	DrawRoof();

	//光源部分を描画
	glColor3f(1,0,0);
	DrawLightPos();


	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glutSwapBuffers();

}


void reshape(int w, int h)
{
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(30.0, (double)w / (double)h, 1.0, 100.0);
	glMatrixMode(GL_MODELVIEW);
}


//マウス関係
void myMouseFunc(int button,int state,int x,int y)
{
	if(button == GLUT_LEFT_BUTTON &&state == GLUT_DOWN){
		MouseStatus.Xstart = x;
		MouseStatus.Ystart = y;
		MouseStatus.flag = true;
	}
	else
	{
		MouseStatus.flag =false;
	}
}

void myMouseMotion(int x,int y)
{
	int xdis,ydis;

	if(MouseStatus.flag == false)return;

	xdis = x - MouseStatus.Xstart;
	ydis = y - MouseStatus.Ystart;

	ObjRot.xAngle += (double)ydis * MouseStatus.weight;
	ObjRot.yAngle += (double)xdis * MouseStatus.weight;

	MouseStatus.Xstart = x;
	MouseStatus.Ystart = y;
    
	display();
}


//------------ ここから各種関数 ---------------------//
void DrawLightPos()
{
	glPointSize(3);
	glBegin(GL_POINTS);
	glVertex3f(LightPos.x,LightPos.y,LightPos.z);
	glEnd();
	glPointSize(1);
}

//屋根部分の描画
void DrawRoof()
{
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(4, GL_FLOAT, 0, &RoofCoord[0]); //1頂点は3つで構成、float型、オフセット0、データ元
	glDrawArrays(GL_TRIANGLES,0,3);
	glDisableClientState(GL_VERTEX_ARRAY);
}

void DRAW_FLOOR()
{
	glTranslatef(0,-1,0);
	glRotatef(90,-1,0,0);
	glNormal3d(0,0,1);  //法線にもアフィン変換がかかるので注意
	glRectf(-8,8,8,-8);
}


//------------ シャドウボリュームの末端を計算する ----------------//
void CalcShadowVolume(const Point4f &LightPos,const Point4f *RoofCoord, Point4f *EndPoint)
{
	for(int N = 0; N < 3 ;++N)
	{
		EndPoint[N].x = LightPos.x + (RoofCoord[N].x - LightPos.x)*InfinitePoint;
		EndPoint[N].y = LightPos.y + (RoofCoord[N].y - LightPos.y)*InfinitePoint;
		EndPoint[N].z = LightPos.z + (RoofCoord[N].z - LightPos.z)*InfinitePoint;
	}
}

//スクリーンを描画する
void DrawScreen(double fovy,double eyeX,double eyeY,double eyeZ,double znear)
{

	static int viewport[4];//ビューポート取得
	glGetIntegerv(GL_VIEWPORT, viewport);

	static double top =  znear * tan(0.0174532925*fovy/2.0);
	static double left = static_cast<double>(viewport[2])/viewport[3] * top;

	static double theta = atan2(eyeX,eyeZ)/0.0174532925;
	static double fai = atan2(eyeY,sqrt(eyeX*eyeX+eyeZ*eyeZ))/0.0174532925;
	static double length = sqrt(eyeX*eyeX+eyeY*eyeY+eyeZ*eyeZ) - 0.001;//原点からの距離

	glPushMatrix();
	glRotated(theta,0,1,0);
	glRotated(fai,-1,0,0);
	glTranslatef(0,0,length-znear);
	glRectd(-left,top,left,-top);
	glPopMatrix();

}