標籤:

從零開始手敲次世代遊戲引擎(二十九)

好的,我們繼續進行場景結構的編寫。

如同上一篇預告的,首先讓我們來編寫存儲材質的數據結構。首先上代碼∶

class SceneObjectMaterial : public BaseSceneObjectn {n protected:n Color m_BaseColor;n Parameter m_Metallic;n Parameter m_Roughness;n Normal m_Normal;n Parameter m_Specular;n Parameter m_AmbientOcclusion;nn public:n SceneObjectMaterial() : BaseSceneObject(SceneObjectType::kSceneObjectTypeMaterial) {};n };n

材質的模型有很多種,在遊戲當中常用的是經典的高光漫反射模型,還有近年流行的基於物理的PBR模型。上面這個例子就是PBR模型。

計算機圖形學當中嚴格意義上的材質模型是指物體表面對於來自各個方向的光線的反射折射以及吸收的數學模型。多為解析形式或者隱函數模式。遊戲當中使用的材質的含義則更為寬泛,而且一般為離散採樣樣本數據和合成公式的模式,有的時候也被稱為是基於圖像的渲染方式(Image Based Rendering)

這是因為遊戲作為一種互動式數字藝術作品,並不嚴格追求所渲染的圖像的正確性。而基於解析計算的渲染方法目前計算量都很大,而且包含很多邏輯判斷與分支,難以實現GPU的批量化計算。而基於圖像的渲染則是將事先準備好的圖片(貼圖)按照一定的規律進行組合輸出結果,這種方式特別適合併行計算,而且結果「看起來」也能夠相當地不錯。

使用過近代DCC工具的人應該都知道,DCC工具當中的材質,除了極少的一部分之外,大多也是基於圖像的。這種材質的特點是,可以為材質的每個屬性(或者說通道)指定一個固定的值,或者是一張貼圖。通過巧妙地組合這些貼圖和通道,可以形成各種各樣十分有趣或者十分逼真的材質效果,比如生鏽、浸潤、污漬、蝕刻、風化、毛絨、傷疤等等。所以我們的材質屬性也需要支持單值輸入的情況,和貼圖輸入的情況。這是通過定義如下的複合數據類型來實現的∶

template <typename T>n struct ParameterMapn {n bool bUsingSingleValue = true;nn union _ParameterMap {n T Value;n std::shared_ptr<Image> Map;n };n };nn typedef ParameterMap<Vector4f> Color;n typedef ParameterMap<Vector3f> Normal;n typedef ParameterMap<float> Parameter;n

好,接下來讓我們看看燈光。遊戲當中常用的燈光有泛光燈(也叫點狀光源,白熾燈)、射燈、天光等。泛光燈是向其周邊360度球形空間均勻輻射光線的燈,也就是無指向的燈;射燈是向特定方向進行一個光錐照射的燈,比如舞台上打的那種追光燈,或者汽車的大燈;天光則是平行光源。

無論是哪種燈,首先都有個自身的亮度或者說光線密度屬性,然後會有一個光線衰減函數。雖然在物理學當中,光線是按照距離的平方倍進行衰減的,但是在遊戲製作當中這樣的衰減很不容易控制,而且往往衰減得過快。所以,如同我們在很多DCC工具當中看到的,我們往往是通過指定一個近裁剪平面,一個遠裁剪平面和一個近似的光衰減函數來控制光照的效果。所以我們的光照基類的定義如下∶

class SceneObjectLight : public BaseSceneObjectn {n protected:n Color m_LightColor;n float m_Intensity;n AttenFunc m_LightAttenuation;n float m_fNearClipDistance;n float m_fFarClipDistance;n bool m_bCastShadows;nn protected:n // can only be used as base class of delivered lighting objectsn SceneObjectLight() : BaseSceneObject(SceneObjectType::kSceneObjnectTypeLight) {};n };n

這裡面上面沒有提到的就是m_bCastShadows。這是用來標識光源是否會產生陰影的。陰影的形成因為要做以光源為視點的場景投影與排序,對於實時渲染來說是很昂貴的。

我們如果用過近代的一些遊戲引擎,我們會看到除了上面這些屬性之外,還會有諸如光源是否會移動等諸多其它屬性。這些屬性更多的是為了優化光照計算性能存在的,我們在今後相應的地方再進行拓展,這裡先不介紹了。

接下來我們就可以從這個基類進行派生,定義各種具體的光源類型了∶

class SceneObjectOmniLight : public SceneObjectLightn {n public:n using SceneObjectLight::SceneObjectLight;n };nn class SceneObjectSpotLight : public SceneObjectLightn {n protected:n float m_fConeAngle;n float m_fPenumbraAngle;n public:n using SceneObjectLight::SceneObjectLight;n };n

可以看到泛光燈最簡單,基本就是基類本身;射燈則多了兩個光錐頂角相關的參數。

這裡可能有人會問,射燈的方向的定義是不是被遺漏了?回答∶是的,但是是故意的。我們的之前的設計是,場景對象當中只保存場景對象固有的屬性,而與場景結構或者空間位置相關的參數是保存在場景節點而不是場景對象當中的。在遊戲當中往往會採用追光燈的設計,也就是跟著某個對象跑的射燈。但是被跟隨的對象的空間位置只有場景節點和場景圖這一級的信息當中才有,將這個信息寫到場景對象裡面是不合適的。

關於光照函數我們則是如下定義的∶

typedef float (*AttenFunc)(float /* Intensity */, float /* Distance */);n

就是一個輸入為光源的強度和到光源的距離,輸出為光照強度的函數的指針。

最後是攝像機的部分了。攝像機和射燈某種意義類似,位置和方向我們放在節點當中處理,作為場景對象主要需要記錄的是縱橫比,近裁剪距離和遠裁剪距離。對於透視相機還需要記錄fov,即視角。

class SceneObjectCamera : public BaseSceneObjectn {n protected:n float m_fAspect;n float m_fNearClipDistance;n float m_fFarClipDistance;n public:n SceneObjectCamera() : BaseSceneObject(SceneObjectType::kSceneObjectTypeCamera) {};n };nn class SceneObjectOrthogonalCamera : public SceneObjectCameran {n };nn class SceneObjectPerspectiveCamera : public SceneObjectCameran {n protected:n float m_fFov;n };n

正交相機是指沒有透視到相機,一般用於在3D空間當中製作2D遊戲。而透視相機的fov則是表示了相機的視場角,大的視場角相當於廣角相機,小的則相當於長焦。當然,僅僅是這樣的話是沒有景深效果的。景深效果是由於凸透鏡成像造成的,而透視相機只是相當於在正交相機上多乘一個仿射矩陣(透視矩陣)P,每個像素依然是單根光線採樣決定其顏色值,是和理想的小孔成像一樣的。

好了,到這裡場景對象的主要結構就完成了。對比前面的設計,我們還有諸如變形對象,貼圖對象,動畫對象,矩陣對象等沒有完成。不過這些對於渲染一個基本的場景來說是不需要或者可以用別的方法代替的,就不在這裡一一表述了。下面我們進入場景結構(場景圖),並且對接OpenGEX,導入一個實際的場景並進行畫面的渲染。

本作品採用知識共享署名 4.0 國際許可協議進行許可。


推薦閱讀:

Unity3D 學習路線?
在 Ubisoft 工作是什麼樣的感覺?
如何邀請一個程序員幫我做遊戲?
如何為Nintendo Switch開發遊戲?

TAG:游戏开发 |