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

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

來自專欄高品質遊戲開發21 人贊了文章

本篇我們對從零開始手敲次世代遊戲引擎(六十四)當中渲染的畫面進行一些調整。

上一篇的最終畫面效果如下:

未進行調節的畫面

這個畫面雖然看起來很清亮,但是平面感較強,且前景與背景的光照條件反差較大,看上去有很強的合成感覺,類似於手機拍攝的照片的效果。

解決畫面平面感的一個辦法是加入景深。我們可以使用一個後處理根據深度緩衝區的內容進行虛化計算,從而模擬鏡頭的景深效果(也稱為DOF效果,Depth Of Field)。但是對於我們現在這個例子,由於遠景來自於天空盒,我們可以有更為簡便的方法:對天空盒的貼圖進行MipsMap計算並採用較低等級的MipsMap層級來渲染天空盒。MipsMap可以在製作貼圖的時候手工生成,也可以讓圖形API層自動生成。我們這裡採用後者。下面是OpenGL當中載入貼圖並生成MipsMap的代碼片段:

for (uint32_t i = 0; i < 6; i++) { auto& texture = scene.SkyBox->GetTexture(i); auto& image = texture.GetTextureImage(); GLenum format; if(image.bitcount == 24) { format = GL_RGB; } else { format = GL_RGBA; } glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, image.Width, image.Height, 0, format, GL_UNSIGNED_BYTE, image.data); } //glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // this is the default and will disable the mip map glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

這裡需要特別注意的是上面被注釋掉的那行,也就是GL_TEXTURE_MIN_FILTER的設置。MipsMap就是對原圖按照2的n次冪進行縮小所產生的一系列縮小圖(所以MipsMap第一層圖的長寬各是原圖一半,面積為原圖1/4;第二層圖長寬各是原圖的1/4,面積為原圖的1/16,依此類推,直到縮小到1x1,也就是一個像素)

然後,我們修改我們的天空盒的PS Shader,讓它使用較低的MipsMap來進行渲染:

// Ouput datalayout(location = 0) out vec4 outputColor;in vec3 UVW;uniform samplerCube skybox;void main(){ //outputColor = texture(skybox, UVW); outputColor = textureLod(skybox, UVW, 2);}

注意上面我們將texture()改為了textureLod(),並且用第三個參數強制參照第二層MipsMap。在本例子當中,天空盒本來的cubemap每個面是512x512像素,那麼第二層MipsMap的每個面其實是128x128像素。這就是說,實際上我們使用了一個更小的圖片進行了天空盒的渲染,相當於對原本的天空盒進行了一個4x4像素的求平均(模糊效果)

渲染的結果如下:

加入了背景模糊之後的效果

接下來我們需要解決前景模型與背景之間在明暗和對比度上的差別的問題。我們可以通過一個被稱為是exposure tone mapping的計算,來模擬攝像機的快門曝光時間的設置,從而改變被拍攝物體的亮度與對比度:

vec3 exposure_tone_mapping(vec3 color){ const float exposure = 1.0f; return vec3(1.0f) - exp(-color * exposure);}vec3 gamma_correction(vec3 color){ const float gamma = 2.2f; return pow(color, vec3(1.0f / gamma));}void main(void){ vec3 linearColor = vec3(0.0f); for (int i = 0; i < numLights; i++) { if (allLights[i].lightType == 3) // area light { linearColor += apply_areaLight(allLights[i]); } else { linearColor += apply_light(allLights[i]); } } // add ambient color linearColor += ambientColor.rgb; // tone mapping //linearColor = reinhard_tone_mapping(linearColor); linearColor = exposure_tone_mapping(linearColor); // gamma correction outputColor = vec4(gamma_correction(linearColor), 1.0f);}

exposure = 0.1時的效果,曝光不足

exposure = 1.0時的效果:曝光適當

exposure = 5.0時的效果:過曝

但是如果僅僅調節前景物體,背景依然顯得對比度過高色彩過艷,所以有必要對天空盒也進行同樣的tone mapping。然而,如果我們對其施以和前景一樣的處理方法,得到的效果並不能令人滿意:

直接對背景進行與前景一樣的tone mapping的效果

可以看到背景的亮度和對比度降低得明顯過多,色彩的丟失也很嚴重。原因在於背景貼圖為實拍照片,實際上是已經包含了gamma矯正。因此,我們必須先將其變換到線性區間,再進行tone mapping,然後再進行gamma矯正:

vec3 inverse_gamma_correction(vec3 color){ const float gamma = 2.2f; return pow(color, vec3(gamma));}void main(){ outputColor = textureLod(skybox, UVW, 2); // inverse gamma correction outputColor.rgb = inverse_gamma_correction(outputColor.rgb); // tone mapping //outputColor.rgb = reinhard_tone_mapping(outputColor.rgb); outputColor.rgb = exposure_tone_mapping(outputColor.rgb); // gamma correction outputColor.rgb = gamma_correction(outputColor.rgb);}

這樣的話,得到的效果如下:

可以看到背景整體的光照感覺與前景比較接近了,而且對比度和色彩也得到了比較好的保留。

但是,前景物體的顏色明顯與環境不符,且暗部過暗。這是因為我們的前景目前只受到了我們放在場景當中的陽光的影響,而沒有考慮周圍環境所反射的光的貢獻。為了計算這部分的貢獻,我們需要一種不同的渲染模型:基於圖像的光照(IBL,Image Based Lighting)。

基於圖像的光照的核心思想是,根據環境貼圖(在我們這裡,就是cubemap)來計算出影響每個被渲染表面的入射光。完整的IBL實現比較複雜,我們在後面導入PBR光照模型之後再進行。這裡首先簡單地用表面的法線對天空盒進行採樣,然後用這個採樣值乘以一定的權重來取代原本固定的ambientColor,也就是環境光照。修改後的shader如下:

void main(void){ vec3 linearColor = vec3(0.0f); for (int i = 0; i < numLights; i++) { if (allLights[i].lightType == 3) // area light { linearColor += apply_areaLight(allLights[i]); } else { linearColor += apply_light(allLights[i]); } } // add ambient color // linearColor += ambientColor.rgb; linearColor += textureLod(skybox, normal_world.xyz, 8).rgb * vec3(0.10f); // tone mapping //linearColor = reinhard_tone_mapping(linearColor); linearColor = exposure_tone_mapping(linearColor); // gamma correction outputColor = vec4(gamma_correction(linearColor), 1.0f);}

注意代碼當中是使用了天空盒的第8層MipsMap來模擬半球面的積分效果。

渲染的結果如下:可以看到色彩已經和背景比較貼合了。

參考引用

LearnOpenGL - HDR?

learnopengl.com圖標
推薦閱讀:

你們天天都在說的「祖傳引擎」,到底能幹嘛?
第五章 數學庫 Part1(From 5.1 To 5.2)
基於Unreal引擎的大地形載入研究
2D精靈Batch系統設計
第三章 System Part2(From3.3 To 3.3.1)

TAG:遊戲引擎 | 3D引擎 | OpenGL |