OGL(教程24)——陰影對映2——程式碼結構梳理
專案地址:[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%[email protected]%d", Width, Height, bpp);
glutGameModeString(ModeString);
glutEnterGameMode();
}
接收的引數是視窗的寬度、高度,是否全屏,視窗的標題,這裡的第三個引數bpp是每個畫素的位數。
如果是全屏那麼開闢長度為64的字元陣列,其全屏模式設定字串為%dx%[email protected]%d,就是[email protected],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方法,