1. 程式人生 > >OGL(教程24)——陰影對映2——程式碼結構梳理

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方法,