在3D游戏中实现光照阴影算法,其实主要问题就是推导出平面阴影矩阵,参考到我的前几章写的博客就是关于平面矩阵的推导与演算,
<a target=_blank href="http://blog.csdn.net/liuchuang_mfc/article/details/49255731">光照阴影平面矩阵</a><pre name="code" class="cpp">http://blog.csdn.net/liuchuang_mfc/article/details/49255731
在这里实现就不是太难了,此外此次环境是WIN32下配置上OpenGL实现光照,再用平面阴影矩阵去计算物体在光照下的阴影,实现了最终结果。
有时候,比较复杂的模型,想获得他的光照阴影,我们不失找到该模型的简单多边形来替代从而得出其大概的阴影效果,这样效率就会提高上、很多。
</pre><pre name="code" class="cpp"><pre name="code" class="cpp">#include "stdafx.h"
#include "OpenGL.h"
#include "math_3d.h"
#include <GL\glut.h>
//
#define KEY_DOWN(vk_code)((GetAsyncKeyState(vk_code)& 0x8000)?1:0) //键盘消息
//实际需要的3dsmodel数量个数
#define _3DSMODEEL_MAX 1
//
static float g_framesPerSecond = 0.0f; // fps
extern int g_screenX, g_screenY; // 客户屏幕宽度
extern HDC hDC; // GDI设备句柄,将窗口连接到 GDI( 图形设备接口)
float g_LigObjRotX, g_LigObjRotY, g_LigObjRotZ;//光照物旋转角度
///光照参数设置//
float LightAmbient[] = { 0.0f, 0.0f, 0.0f, 1.0f };// 环境光参数
float LightDiffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };// 漫射光参数
float LightPosition[] = { 4.0f, 20.0f,-10.0f, 1.0f };// 光源位置
float specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };/*镜面反射光参数*/
float specref[] = { 1.0f, 1.0f, 1.0f, 1.0f };/*材质镜面反射颜色属性*/
int g_fps = 0; // FPS帧率值
char g_fpsStr[10] = { 0 }; // 存放帧率值
float g_time = 0.0f; // 系统运行时间
float g_lastTime = 0.0f; // 持续的时间
//纹理存储
unsigned int g_groundtex;
unsigned int g_mirrorTex;
///阴影矩阵
//声明一个阴影转换矩阵
M3DMatrix44f shadowMat;
//存储平面方程ABCD系数
M3DVector4f vPlaneEquation;
//地面任意三点
M3DVector3f points[3] = { { -3.0f, -0.0f, -2.0f },
{ -3.0f, -0.0f, 2.0f },
{ 4.0f, -0.0f, 2.0f } };
3ds模型///
enum Model_3DS
{
CAR, GUN, HOUSE
};
float g_3dsPlace[10] = {
4.0, 0.5, 0.0, //表示在世界矩阵的位置
0.0005, 0.0005, 0.0005, //表示xyz放大倍数
90, 0, 1.0, 0 //表示旋转
};
OpenGL::OpenGL()
: m_MyDraw(NULL)
, m_MyFont(NULL)
{
/*为对象分配内存空间*/
m_MyDraw = new MyDraw();
m_MyFont = new MyFont();
m_hFont = CreateFont(
24, // nHeight
0, // nWidth
0, // nEscapement
0, // nOrientation
FW_NORMAL, // nWeight
FALSE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
_T("Arial")); // lpszFacename
m_hFontSong = CreateFont(
15, 0, 0, 0, FW_NORMAL, FALSE, FALSE, 0, GB2312_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS,
"宋体"
);
}
/*初始化相应的绘图数据*/
int OpenGL::InitDrawData()
{
// 从三点得到的平面方程
m3dGetPlaneEquation(vPlaneEquation, points[0], points[1], points[2]);
InitLight(); //初始化光照信息
m_MyDraw->InitDrawScene(); //初始化绘图要求如加载纹理等
//创建部分显示列表
m_MyDraw->CreateList();
//创建天空显示列表
m_MyDraw->CreateSkyList();
//创建地板显示列表
m_MyDraw->CreateFloorList();
g_groundtex = m_MyDraw->ATLLoadTexture("data/ground.jpg");
g_mirrorTex = m_MyDraw->ATLLoadTexture("data/mirror.jpg");
//创建3ds模型对象
for (int i = 0; i < _3DSMODEEL_MAX; i++)
{
m_3dsLoad[i] = NULL;
m_3dsLoad[i] = new CLoad3DS();
}
//导入模型 模型的文件夹尽量这样设置
m_3dsLoad[CAR]->Init3DSModel(&m_3dsModel[CAR], "data/3ds/AUDIAVUS.3DS", FALSE, TRUE);
m_3dsLoad[GUN]->Init3DSModel(&m_3dsModel[GUN], "data/3ds/gun.3DS", FALSE, TRUE);
m_3dsLoad[HOUSE]->Init3DSModel(&m_3dsModel[HOUSE], "data/3ds/apple.3DS", FALSE, TRUE);
return 0;
}
OpenGL::~OpenGL()
{
CleanUp();
}
BOOL OpenGL::SetupPixelFormat(HDC hDC0) //检测安装OpenGL
{
int nPixelFormat;
hDC = hDC0;
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // pfd结构的大小
1, // 版本号
PFD_DRAW_TO_WINDOW | // 支持在窗口中绘图
PFD_SUPPORT_OPENGL | // 支持 OpenGL
PFD_DOUBLEBUFFER, // 双缓存模式
PFD_TYPE_RGBA, // RGBA 颜色模式
32, // 32 位颜色深度
0, 0, 0, 0, 0, 0, // 忽略颜色位
0, // 没有非透明度缓存
0, // 忽略移位位
0, // 无累加缓存
0, 0, 0, 0, // 忽略累加位
32, // 32 位深度缓存
0, // 无模板缓存
0, // 无辅助缓存
PFD_MAIN_PLANE, // 主层
0, // 保留
0, 0, 0 // 忽略层,可见性和损毁掩模
};
if (!(nPixelFormat = ChoosePixelFormat(hDC, &pfd)))
{
MessageBox(NULL, "没有找到合适的显示模式", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
SetPixelFormat(hDC, nPixelFormat, &pfd);//设置当前设备的像素点格式
hRC = wglCreateContext(hDC); //获取渲染描述句柄
wglMakeCurrent(hDC, hRC); //激活渲染描述句柄
InitDrawData();/*初始化绘图数据*/
return TRUE;
}
int OpenGL::InitLight()//设置并初始化光源信息
{
glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient); // 设置环境光
glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse); // 设置漫射光
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
//作用是控制多边形的正面是如何决定的。在默认情况下,mode是GL_CCW
//GL_CCW 表示窗口坐标上投影多边形的顶点顺序为逆时针方向的表面为正面
glFrontFace(GL_CCW); // 逆时针多边形面
glEnable(GL_CULL_FACE); // 剔除内部表面影响
启用颜色跟踪
glEnable(GL_COLOR_MATERIAL);
实时设置材料属性遵循glColor值
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
所有材料有全反射
glMaterialfv(GL_FRONT, GL_SPECULAR, specref);/*指定用于光照计算的当前材质属性*/
glMateriali(GL_FRONT, GL_SHININESS, 128);/*高亮显示*/
glEnable(GL_POINT_SMOOTH);//抗锯齿
glEnable(GL_LINE_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); // Make round points, not square points
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
return TRUE;
}
void OpenGL::Init(int Width, int Height)
{
glViewport(0, 0, Width, Height); // 设置OpenGL视口大小。
glMatrixMode(GL_PROJECTION); // 设置当前矩阵为投影矩阵。
glLoadIdentity(); // 重置当前指定的矩阵为单位矩阵
gluPerspective // 设置透视图
(45.0f, // 透视角设置为 45 度
(GLfloat)Width / (GLfloat)Height, // 窗口的宽与高比
0.1f, // 视野透视深度:近点1.0f
3000.0f // 视野透视深度:始点1f远点f
);
// 这和照象机很类似,第一个参数设置镜头广角度,第二个参数是长宽比,后面是远近剪切。
glMatrixMode(GL_MODELVIEW); // 设置当前矩阵为模型视图矩阵
glLoadIdentity(); // 重置当前指定的矩阵为单位矩阵
}
场景截图:
<img src="https://img-blog.csdn.net/20151023200318436?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
void OpenGL::Render() // 游戏场景图形渲染处理
{
CalculateFPS();/*计算fps数据*/
glClearColor(0.0f, 0.0f, 0.5f, 1.0f); // 设置刷新背景色
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();//单位化矩阵
glTranslatef(0, 0, 0);//移入世界物理(0,0,0)即是屏幕坐标系原点
m_MyDraw->CameraRoam();/*游戏场景漫游控制*/
glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);/*实时改变光源的位置*/
// 实时计算投影在一个平面上的阴影矩阵
m3dMakePlanarShadowMatrix(shadowMat, vPlaneEquation, LightPosition);
glPushMatrix();
//绘制平面即阴影体的依附平面
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, g_groundtex);
float lenghtGround = 20.0;//地面长宽度
float heightGround = -0.4;
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-lenghtGround, heightGround, -lenghtGround); // 纹理和四边形的左下
glTexCoord2f(1.0f, 0.0f); glVertex3f(-lenghtGround, heightGround, lenghtGround); // 纹理和四边形的右下
glTexCoord2f(1.0f, 1.0f); glVertex3f(lenghtGround, heightGround, lenghtGround); // 纹理和四边形的右上
glTexCoord2f(0.0f, 1.0f); glVertex3f(lenghtGround, heightGround, -lenghtGround); // 纹理和四边形的左上
glEnd();
glDisable(GL_TEXTURE_2D);
glPopMatrix();
绘制阴影与地面
首先关闭光照和深度测试
glDisable(GL_LIGHTING);
// 绘制光源体
glPushMatrix();
glPushAttrib(GL_CURRENT_BIT);
glTranslatef(LightPosition[0], LightPosition[1], LightPosition[2]);
glColor3f(1.0, 1.0, 0.0);
glutSolidSphere(0.3f, 20, 20);
glPopAttrib();
glPopMatrix();
///3DS模型渲染///加载车的3ds模型
m_3dsLoad[CAR]->Show3DSModel(&m_3dsModel[CAR], g_3dsPlace);
// 保存矩阵状态并进行旋转
glPushMatrix();
// 在绘制物体前将光源放在合适位置
glEnable(GL_LIGHTING);
glRotatef(g_LigObjRotX, 1.0f, 0.0f, 0.0f);
glRotatef(g_LigObjRotY, 0.0f, 1.0f, 0.0f);
DrawaScenes(0);/*绘制物体*/
glDisable(GL_LIGHTING);
// 恢复原矩阵状态
glPopMatrix();
乘以阴影矩阵
glMultMatrixf((float *)shadowMat);
旋转物体
Draw3DShadow();
glRotatef(g_LigObjRotX, 1.0f, 0.0f, 0.0f);
glRotatef(g_LigObjRotY, 0.0f, 1.0f, 0.0f);
// 给予true来绘制阴影
DrawaScenes(1);
// 恢复矩阵
glPopMatrix();
/*光源位置动*/
float speed = 0.2;
if (KEY_DOWN(VK_UP))
{
LightPosition[2] -= speed;
}
if (KEY_DOWN(VK_DOWN))
{
LightPosition[2] += speed;
}
if (KEY_DOWN(VK_LEFT))
{
LightPosition[0] -= speed;
}
if (KEY_DOWN(VK_RIGHT))
{
LightPosition[0] += speed;
}
/*光照物的移动*/
//实用小键盘时注意键盘不能被锁否则2846被当成上下左右控制键
if (KEY_DOWN(VK_NUMPAD8))
{
g_LigObjRotX -= speed;
}
if (KEY_DOWN(VK_NUMPAD2))
{
g_LigObjRotX += speed;
}
if (KEY_DOWN(VK_NUMPAD4))
{
g_LigObjRotY -= speed;
}
if (KEY_DOWN(VK_NUMPAD6))
{
g_LigObjRotY += speed;
}
TextShow(); //场景方位、fps等文字显示
glFlush();
SwapBuffers(hDC); // 切换缓冲区
}
void OpenGL::TextShow() //场景所有文字显示
{
//场景中心点“+”显示
m_MyFont->PrintText(g_screenX / 2, g_screenY / 2, "+", m_hFont, 1.0, 0.0, 0.0);
m_MyFont->PrintText(g_screenX / 2.5, g_screenY / 6, "开启3D--GAMES", m_hFont, 0.0, 1.0, 0.0);
m_MyFont->PrintText(g_screenX / 1.5, g_screenY / 4, "WSAD漫游、8246光照物动、↑↓←→光源移动", m_hFont, 0.0, 1.0, 0.0);
char str_Scene[44]; //存储场景信息方位角度
int rad_direc = abs(int(m_MyDraw->m_tempAngle));//场景方位角
sprintf_s(str_Scene, "[方位=%3i X=%1.0f Y=%1.0f 高=%1.0f 俯仰角=%1.0f]",
rad_direc % 360, m_MyDraw->m_eye[0],
m_MyDraw->m_eye[2],//屏幕上的y坐标就是空间的Z坐标
m_MyDraw->m_eye[1], m_MyDraw->m_elev);
m_MyFont->PrintText(g_screenX / 1.2, g_screenY / 1.5, str_Scene, m_hFontSong, 0.9, 0.2, 0.0);
/*fps显示*/
m_MyFont->PrintText(g_screenX / 1.2, g_screenY / 3, g_fpsStr, m_hFont, 0.0, 1.0, 0.0);
}
void OpenGL::CleanUp()
{
wglMakeCurrent(hDC, NULL);//清除OpenGL
wglDeleteContext(hRC); //清除OpenGL
/*删除对象内存*/
delete m_MyFont;//删除字体指针对象
delete []m_3dsLoad;//删除模型对象
}
void OpenGL::CalculateFPS()
{
// g_time获取操作系统启动到现在所经过的毫秒数,乘以0.001f得到秒数
g_time = GetTickCount() * 0.001f;
// 持续时间是否大于1秒
if (g_time - g_lastTime > 1.0f)
{
// 记录新的持续时间
g_lastTime = g_time;
// 把整数g_fps格式化为一个字符串保存在g_fpsStr中,并输出该字符串
sprintf_s(g_fpsStr, "FPS: %d", g_fps);
// 重置帧率值为0
g_fps = 0;
}
else
{
// 帧率值递增
g_fps++;
}
}
// 绘制光照物以及影子
void OpenGL::DrawaScenes(int nShadow)
{
glPushAttrib(GL_CURRENT_BIT);
// 设置材质的颜色这里我们只需要设置为黑色表示阴影
if (nShadow == 0)
glColor3f(1.0, 1.0, 1.0);/*非阴影时颜色设置为白色等*/
else
{
glColor3f(0.1, 0.1, 0.1);/*阴影时颜色设置为黑色*/
}
/*绘制实体与阴影*/
glPushMatrix();
glTranslatef(0.0, 0.2, 0.0);
glScalef(0.2, 0.2, 0.2);
glBegin(GL_QUADS); // 开始绘制四边形
// 前侧面
glNormal3f(0.0f, 0.0f, 1.0f); // 法线指向观察者
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// 后侧面
glNormal3f(0.0f, 0.0f, -1.0f); // 法线背向观察者
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f);
// 顶面
glNormal3f(0.0f, 1.0f, 0.0f); // 法线向上
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f);
// 底面
glNormal3f(0.0f, -1.0f, 0.0f); // 法线朝下
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// 右侧面
glNormal3f(1.0f, 0.0f, 0.0f); // 法线朝右
glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f);
// 左侧面
glNormal3f(-1.0f, 0.0f, 0.0f); // 法线朝左
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd(); // 四边形绘制结束
/*绘制一个三角形*/
glPushMatrix();
glTranslatef(-8, 0.0, -2.0);
glBegin(GL_TRIANGLES);
glVertex3f(0.0, 0.0, -1.0);
glVertex3f(-1.0, 0.0, 1.0);
glVertex3f(1.0, 0.0, 1.0);
glEnd();
glTranslatef(-5.0, 0.0, 0.0);
glutSolidSphere(1.0, 40, 40);
glPopAttrib();
}
/*绘制3ds模型阴影*/
void OpenGL::Draw3DShadow()
{
glPushAttrib(GL_CURRENT_BIT);
glColor3f(0.1, 0.1, 0.1);/*阴影时颜色设置为黑色*/
底面
glPushMatrix();
glTranslatef(g_3dsPlace[0], g_3dsPlace[1], g_3dsPlace[2]);//3ds空间位置
//3ds简化汽车模型制造阴影
glBegin(GL_QUADS); // 开始绘制四边形
float length = 2.0;
float width = 0.6;
float height = 0.1;
// 顶面
glNormal3f(0.0f, 1.0f, 0.0f); // 法线向上
glTexCoord2f(0.0f, 1.0f); glVertex3f(-length, height, -width);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-length, height, width);
glTexCoord2f(1.0f, 0.0f); glVertex3f(length, height, width);
glTexCoord2f(1.0f, 1.0f); glVertex3f(length, height, -width);
// 底面
glNormal3f(0.0f, -1.0f, 0.0f); // 法线朝下
glTexCoord2f(1.0f, 1.0f); glVertex3f(-length, -height, -width);
glTexCoord2f(0.0f, 1.0f); glVertex3f(length, -height, -width);
glTexCoord2f(0.0f, 0.0f); glVertex3f(length, -height, width);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-length, -height, width);
glEnd(); // 四边形绘制结束
glPopMatrix();
glPopAttrib();
}