项目地址:[email protected]:yichichunshui/ShadowMap2.git
本节主要是分析之前章节中使用的类、方法,以及如何连接shader、如何编写摄像机类,本文以及阴影映射的例子为入口,详细分析下OpenGL中如何加载模型、如何使用shader渲染一个物体,这将对今后的进一步的学习起到至关重要的作用。
在梳理代码结构之前,首先要回答下为什么选择OGL(教程24)——阴影映射2的项目进行分析。首先随着翻译的进行,一节一节音效不够深刻这是主要原因,其问题就是在于理解了对原理稍微了解,但是对于用程序实现还是十分陌生,OGL教程翻译,共计53节,如果继续翻译下去,势必理解还是不够深刻,所以还是应该以实际程序为参考进行编码层次的分析,这样才能更加深刻的理解原理。其次,随着我的翻译进行,发现阴影映射中包含了比较完整的、清晰的代码结构了。包括模型加载、图片加载、摄像机类、shader管理类,这些初步已经成为后面代码的工具类,所以此时如果能够对代码有清晰的认识,那么势必基础打的更加牢靠。
1、文件的个数统计:
头文件:13个
源文件:11个
2、头文件的功能介绍:
callbacks.h——接口类,里面包含的是方法的声明
camera.h——摄像机类
glut_backend.h——GLUT的一些初始化的工作,单独提到一个类中
lighting_technique.h——包括三类光源,平行光、点光、聚光灯,处理灯光的一些方法
math_3d.h——处理向量和矩阵的一些运算
mesh.h——包括顶点的定义、网格的定义
pipeline.h——渲染光线的操作,比如旋转、缩放、透视等
shadow_map_fbo.h——帧缓冲对象的初始化、绑定操作等
shadow_map_technique.h——阴影映射实现
technique.h——把shader的添加、链接、获取shader属性位置等封装为一个类
texture.h——贴图处理类
unistd.h——unix用到的头文件
util.h——工具类
3、main.cpp分析
int main(int argc, char** argv)
{
GLUTBackendInit(argc, argv);
这个函数在glut_backend.cpp中,方法如下:
void GLUTBackendInit(int argc, char** argv){
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
}
这个在第一节窗口中已经介绍过,它是GLUT的初始化函数,接收的参数可以从命令传入。glutInitDisplayMode设置了显示的设置,这里使用双缓冲,颜色模式为RGBA,开启深度缓冲,由于我们的阴影映射需要开启深度缓冲。glutSetOption设置了GLUT_ACTION_ON_WINDOW_CLOSE和GLUT_ACTION_GLUTMAINLOOP_RETURNS,它保证了glutMainLoop()退出后,继续执行其后的代码,防止glutMainLoop造成的内存泄漏。
总结:在初始化GLUT的其他方法调用之前,首先是初始化、设置显示模式、设置一些选项用以防止内存泄漏。
if (!GLUTBackendCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, 32, false, "OpenGL tutors")) {
return 1;
}
WINDOW_WIDTH和WINDOW_HEIGHT是宏,其值为你想要的数值。这里指窗口的实际大小。GLUTBackendCreateWindow的内容如下:
bool GLUTBackendCreateWindow(unsigned int Width, unsigned int Height, unsigned int bpp, bool isFullScreen, const char* pTitle){
if (isFullScreen){
char ModeString[64] = {0};
snprintf(ModeString, sizeof(ModeString), "%dx%d@%d", Width, Height, bpp);
glutGameModeString(ModeString);
glutEnterGameMode();
}
接收的参数是窗口的宽度、高度,是否全屏,窗口的标题,这里的第三个参数bpp是每个像素的位数。
如果是全屏那么开辟长度为64的字符数组,其全屏模式设置字符串为%dx%d@%d,就是1280x1024@32,32位真彩色。然后调用glutGameModeString设置全屏模式。设置全屏模式之后,就进入游戏模式:glutEnterGameMode()。
else {
glutInitWindowSize(Width, Height);
glutCreateWindow(pTitle);
}
如果不是全屏模式,那么直接初始化窗口:glutInitWindowSize(Width, Height);然后指定窗口的标题,也只有非全屏模式才有窗口的标题。
总结:在创建窗口的时候,区分了全屏和非全屏。全屏需要设置游戏字符串,然后进入游戏模式;非全屏创建窗口,然后设置标题。
GLenum res = glewInit();
if (res != GLEW_OK){
fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
return false;
}
return true;
}
glewInit是OpenGL的扩展库,用以正确加载OpenGL函数。然后是检测是否初始化成功。成功返回true,否则直接返回false。
回到main函数。
Main* pApp = new Main();
new出一个Main对象。
Main的成员变量9个,分别是:
LightingTechnique指针
ShadowMapTechnique指针
Camera指针
float m_scale缩放
SpotLight 聚光灯对象
Mesh网格指针
Mesh地面网格指针
ShadowMapFBO对象
Texture指针
无参数构造函数:
Main()
{
m_pLightingEffect = NULL;
m_pShadowMapEffect = NULL;
m_pGameCamera = NULL;
m_pMesh = NULL;
m_pQuad = NULL;
m_scale = 0.0f;
m_pGroundTex = NULL;
m_spotLight.AmbientIntensity = 0.1f;
m_spotLight.DiffuseIntensity = 0.9f;
m_spotLight.Color = Vector3f(1.0f, 1.0f, 1.0f);
m_spotLight.Attenuation.Linear = 0.01f;
m_spotLight.Position = Vector3f(-20.0, 20.0, 1.0f);
m_spotLight.Direction = Vector3f(1.0f, -1.0f, 0.0f);
m_spotLight.Cutoff = 20.0f;
}
析构函数:
virtual ~Main()
{
SAFE_DELETE(m_pLightingEffect);
SAFE_DELETE(m_pShadowMapEffect);
SAFE_DELETE(m_pGameCamera);
SAFE_DELETE(m_pMesh);
SAFE_DELETE(m_pQuad);
SAFE_DELETE(m_pGroundTex);
}
六个指针的析构。
Main* pApp = new Main();
此句代码目前只执行到了Main的无参数构造函数。
if (!pApp->Init()) {
return 1;
}
重点来了,进入Main的Init方法。
bool Init()
{
Vector3f Pos(3.0f, 8.0f, -10.0f);
Vector3f Target(0.0f, -0.2f, 1.0f);
Vector3f Up(0.0, 1.0f, 0.0f);
if (!m_shadowMapFBO.Init(WINDOW_WIDTH, WINDOW_HEIGHT)) {
return false;
}
m_pGameCamera = new Camera(WINDOW_WIDTH, WINDOW_HEIGHT, Pos, Target, Up);
定义了摄像机的位置、目标、向上向量。
if (!m_shadowMapFBO.Init(WINDOW_WIDTH, WINDOW_HEIGHT)) {
return false;
}
m_shadowMapFBO是阴影映射对象,它是对象,不是指针,所以不为空。调用其Init方法。
ShadowMapFBO的成员两个:
GLuint m_fbo;
GLuint m_shadowMap;
构造函数:
ShadowMapFBO::ShadowMapFBO()
{
m_fbo = 0;
m_shadowMap = 0;
}
然后我们进入ShadowMapFBO的Init方法。
bool ShadowMapFBO::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
glGenFramebuffers(1, &m_fbo);
glGenTextures(1, &m_shadowMap);
glBindTexture(GL_TEXTURE_2D, m_shadowMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
创建一个fbo对象,创建一个贴图m_shadowMap,绑定贴图到GL_TEXTURE_2D纹理目标。glTexImage2D,设定纹理的格式、大小等。
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
总结:对图片做了三步:创建、绑定目标、设定格式、设置偏移和裁剪方式。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
绑定m_fbo到目标GL_DRAW_FRAMEBUFFER。
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap, 0);
绑定铁路到目标:GL_FRAMEBUFFER,最后一个参数是贴图的mipmap等级0是原始尺寸。第二个参数用以说明此贴图用来存储的是深度信息。
总结:创建fbo对象、创建贴图对象、绑定贴图对象到2D目标、设置贴图格式、设置贴图偏移模式、绑定fbo对象到GL_DRAW_FRAMEBUFFER目标、绑定贴图到GL_DRAW_FRAMEBUFFER。
这个部分参考:http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html
glDrawBuffer(GL_NONE);
阴影映射,不需要绘制颜色缓冲,所以设置为GL_NONE。
最后检查FBO对象绑定状态:
GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (Status != GL_FRAMEBUFFER_COMPLETE){
printf("FB error, status: 0x%x\n", Status);
return false;
}
return true;
}
再次回到Main的Init方法:
m_pGameCamera = new Camera(WINDOW_WIDTH, WINDOW_HEIGHT, Pos, Target, Up);
这个是初始化摄像机。
摄像机类的成员8个:
Vector3f m_pos; //位置
Vector3f m_target; //目标
Vector3f m_up; //向上向量
int m_windowWidth; //窗口宽度
int m_windowHeight; //窗口高度
float m_AngleH; //水平角度
float m_AngleV; //垂直角度
Vector2i m_mousePos; //鼠标位置
有参数构造函数1:
Camera::Camera(int WindowWidth, int WindowHeight, const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up)
{
m_windowWidth = WindowWidth;
m_windowHeight = WindowHeight;
m_pos = Pos;
m_target = Target;
m_target.Normalize();
m_up = Up;
m_up.Normalize();
Init();
}
最后的Init方法,