[程式] Unheard Engine - 自製Vulkan小引擎

看板GameDesign作者 (獅子心)時間1年前 (2022/11/24 03:46), 1年前編輯推噓14(1406)
留言20則, 14人參與, 1年前最新討論串1/1
大家好, 不才名字Squall 目前在英國從事Graphics Programmer, 參與研發AAA專案 即使平常在工作上已經收穫許多 我個人是那種不用別人催也會想辦法變強的個性 所以平常是會開一些Side Project給自己練習的 這次的Vulkan小引擎 - Unheard Engine便是其中之一了 https://i.imgur.com/WygBvq1.jpg
利用空閒時間實作約57天, 目前的功能還沒有很多 - Deferred Rendering Pass (PBR) - Ray Tracing Shadow - Lighting Pass - Skybox Pass - Motion Vector Pass - Tone mapping - Temporal AA 但從物件渲染、後製特效、光追的使用 應該涵蓋各種基本的用法了 希望能幫到剛好也在學習Vulkan的人:) 然後也不用問為什麼沒有半透明物件, 為什麼沒有XXX之類的 就只是還沒實作而已, 一切從0開始做是需要點時間的 任何沒實作的功能都是Future Work, no ETA 網頁好讀版 (我發在GameDev.net的文章): https://tinyurl.com/5t9824xu 由於篇幅有點過長, 我不會在這提所有的細節 (尤其是程式碼) 那如果你是那種喜歡直接觀察程式碼的人 可以直接下拉到置底連結 ● 引擎名稱 由於過往使用過的引擎剛好都Un-開頭 (Unity, Unreal) 我就隨便抓個Un開頭的字來用了XD 以下我會簡單稱為UHE 如果你對Vulkan程式有興趣, 我建議還是從官方教學開始 ● 環境 如同多數的學習者一樣, 我從下列兩個網站開始: https://vulkan-tutorial.com/ https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html 所以你大概預期UHE是Vulkan+GLFW+GLM這種組合, 但,恰好相反! UHE的組合為Vulkan + Win32 Window + DirectX math + HLSL + WIC texture loader + FBX importer 主要是因為我已經有DirectX 12的開發經驗了 我想最大化利用學習時間, 盡量維持在我熟悉的環境 跨平台?以後想做的時候再改就行了 所以這邊只有Vulkan / FBX SDK對我來說是陌生的 然後我是在筆電上開發的 i7 - 11375H, RTX 3060 Laptop, 16GB RAM ● Debug vs Release Build 我有特別區分這兩種Build 一些功能像是Editor不會出現在Release Build ● Vulkan的誤報錯誤 如果真的有把UHE載下來跑, 可能會遇到兩種誤報 1. VUID-VkSwapchainCreateInfoKHR-imageFormat-01778(ERROR / SPEC) VUID-VkImageViewCreateInfo-usage-02275(ERROR / SPEC) 這個問題, 我是只有在開啟RTSS的時候才遇到的 我猜其他FPS測量工具可能也會造成此問題 2. Microsoft C++ exception: MONZA::IgcThreadingContext::msg_end 在呼叫vkCreateSwapchainKHR()時有可能會存在的例外 以上這兩種誤報都不影響UHE的運行 如果有人對於此兩種誤報有解決經驗, 還請不吝指點! ● 素材匯入 由於還沒有實作一些編輯器, 目前UHE將會自動匯入RawAssets/下的素材 然後加入自製的Renderer架構裡 素材匯入有著簡單的快取機制, 匯入過的素材會生成.uhxxxx檔案 下次再啟動時, 已匯入過的素材不會再匯入(除非有變) 不然說真的匯入FBX可是很卡的.. ● Shader編譯 DirectXShaderCompiler才是真的MVP! Vulkan的shader模組其實採用了他們的Spir-V語言 所以HLSL是需要轉換成Spir-V的 只要在呼叫dxc.exe時傳入 -spirv 參數就能轉換 另外也為了光追shader傳入了-fspv-target-env=vulkan1.1spirv1.4 UHE也有簡單的shader變體管理, 同個shader但使用不同keyword的 我會給他們產生不同的hashcode https://i.imgur.com/KoJyvp6.jpg
● Culling 目前完全沒有做culling, 也還沒有做draw call batching 另外要提的是為了最佳深度精度, 使用了reversed infinite z 所以物件離鏡頭再遠都絕對不會被剔除 效能優化是之後才要做的事 目前Release Build大概是跑280~340 FPS在跑 (不開Vsync) ● Pool機制 UHE會盡量重複使用性質相同的物件 例如目前的維京小屋測試雖然有著747個Draw Calls 但實際上只用到了16個VkPipeline物件 以及只有5個VkSampler物件等等 相信我, 如果每個Renderer都生不一樣的物件, 效能絕對降 ● 渲染流程 目前很簡單, 一個Main Thread + Render Thread 所有渲染工作都是在RT上完成 物件Constant Buffer或Storage Buffer的更新 採用了Dirty Flag機制, 也就是說大多數靜態物件 根本不需要每個物件每個Frame都做Buffer Copy的動作! 流程和開頭說得差不多: https://i.imgur.com/HbVBFIG.jpg
1. Base Pass - Filling GBuffer 要點在於: 如何建立MRT (Multiple Render Target) Vulkan教學網站只教了單一RT的使用 但其實很簡單, 在建立vkFrameBuffer物件時 所使用的VkAttachmentDescription, VkAttachmentReference 改成複數就好了 2. 更新Top Level Acceleration結構 對於DXR光追不熟的人可以參考這個連結: https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html 簡單來說Acceleration Structure是光追必備的結構 分成Top Level, Bottom Level兩種 前者是基於Instance的, 後者是基於Mesh的 可以簡單把他們的關係想成Renderer跟Mesh 一個Mesh可以被不同的Renderer使用 一個Bottom Level AS可以被不同Instance Level AS使用 UHE給模型建立Bottom Level AS (無Transform) 然後給Instance建立Top Level AS (包含Transform) 當然這只是UHE的做法, 你要給Bottom Level AS施加Transform也是可以的 詳細的建立過程請參閱程式碼, 真的不好轉過來 3. 光追陰影 https://i.imgur.com/yy7uYgv.jpg
https://i.imgur.com/Ggq38g9.jpg
多重陰影有考慮到, 取樣時也有施加PCSS 所以距離遮擋物較近的影子比較銳利, 遠的比較柔和 這邊建立了一個Ray Generation Shader跟Closest Hit Shader 以及Any Hit Shader 並沒有第一條試探Ray, 而是直接把深度轉World Posion 直接從該位置進行光追就好了, 省掉試探Ray的損耗 重點在於, 如何獲得擊中物的材質/模型資訊? 由於Vulkan不像D3D12有Local Descriptors可以使用 我只好把資料都集中為Descriptor Array了: Texture2D UHTextureTable[] : register(t0, space1); SamplerState UHSamplerTable[] : register(t0, space2); StructuredBuffer<VertexInput> UHVertexTable[] : register(t0, space3); ByteAddressBuffer UHIndicesTable[] : register(t0, space4); 把Texture2D, StructuredBuffer再定義成Array!? 對, 這在D3D12, Vulkan都是做得到的 但一定要Shader Model 5.1以上 如此一來, 就能利用光追Shader的內建函式InstanceIndex() 或InstanceID()來取得正確資料了 在UHE, 我用InstanceID()來對應材質 用InstanceIndex()來對應模型 4. 光源Pass 結合GBuffer的燈光計算步驟 目前僅實作了Directional Light 沒什麼好講, 陰影取樣, BRDF計算都在這 Indirect Lighting只簡單使用了GroundColor + Normal.up * SkyColor 未來會考慮SH9 5. Skybox Pass 更沒什麼好講, 就是畫Skybox 不過Vulkan初學者大概會關心: 怎麼建立TextureCube? 由於UHE並沒有匯入dds, 一種可以直接存TextureCube的格式 (也是很舊的格式了) 所以必須用6張Texture2D來建立 也就是將Texture2D複製到TextureCube不同的Slice, 以及MipLevel 記得在建立Vulkan物件時使用VK_IMAGE_VIEW_TYPE_CUBE 以及VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT 然後Layer Count一定要是6這樣 6. Motion Vector Pass 由於實作了TAA, 移動向量是一定要用來解決一些問題的 UHE分成兩種Pass: Camera Motion, Object Motion Camera Motion簡單利用轉換Depth Buffer -> World Position 並計算移動向量, 適合靜態物件 Object Motion當然就是為了移動物件了 一樣用了Dirty Flag機制, 不會移動的物件, 沒有pass的必要! 7. Tone Mapping 採用了Stephen Hill (@self_shadow)的ACES https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl 簡單來說這位老兄將ACES需要的轉換統整成只要兩個矩陣 相當的好用! 8. TAA 也是近代引擎的AA常客了, 但有幾個問題要處理: - 鬼影: 只要取得正確的History UV就行了 - Disocclusion: 前一個Frame被遮擋的物件, 在這個Frame突然出現 , 也一樣會產生鬼影, 我採用了"motion rejection"來解決 若history motion與motion的差值大過一定門檻, 就不取樣history - Missing Depth: 像是skybox這種沒有深度值的像素, 在算motion vector 時是會有問題的, 簡單做了3x3檢查, 範圍內如果沒有深度值, 也不取樣history 如此一來, 鬼影問題便解決了 9. SwapChain Present 直接利用vkCmdBlitImage()來將後製後的結果present到swap chain 這個函式同時也會幫你做linear-sRGB轉換 另外UHE的機制是分開了Swap Chain跟渲染解析度 例如視窗尺寸如果是1600x900, 還是可以渲染在1920x1080 為何要這麼做呢? 理由是為了全螢幕模式 我發現vkAcquireFullScreenExclusiveModeEXT() 並沒有像IDXGISwapChain::SetFullscreenState()那樣 呼叫了就進入全螢幕模式 這邊必須自己resize視窗成桌面解析度 再使用vkAcquireFullScreenExclusiveModeEXT獲得獨佔權 也就是說, 表現起來更像"無痕視窗"全螢幕 我不希望解析度被桌面解析度綁住呀! ● 總結 終於打完了, 如果你真的耐心地看到了最後, hold my beer! 個人幾個對於Vulkan的想法 - 比D3D12更冗長的實作, 尤其是物件管理方面, 每個vk物件都有 對應的vkDestroyXXX()... - Vulkan似乎沒有Thread-Safe vkQueue, vkCommandBuffer也不能 執行後馬上reset再重複利用, ID3D12CommandQueue則是Thread-Safe ID3D12CommandList也能執行後馬上再錄製, 這種特性差異對平行化 設計有著重大影響 - Vulkan沒有Local Descriptor的樣子 - 整體來說, Vulkan幾乎跟D3D12一樣強大, 我猜它在Linux平台會更強 很高興現在自己是D3D12/Vulkan雙刀俠了! The GitHub Link (code only): https://github.com/EasyJellySniper/Unheard-Engine The Full Project Link (including assets): https://mega.nz/file/p0ICFJbJ#TL5Vdu6FEyCFdCwd_7rhT3N_UaLfb4HkG1XMP9cYMzA 我十分建議下載完整專案, 畢竟有一些素材 感謝收看! -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 80.47.253.201 (英國) ※ 文章網址: https://www.ptt.cc/bbs/GameDesign/M.1669232776.A.469.html ※ 編輯: Renzokuken (80.47.253.201 英國), 11/24/2022 03:48:26

11/24 04:52, 1年前 , 1F
真猛 有生之年我想我都不會做到開發引擎這種事
11/24 04:52, 1F

11/24 07:42, 1年前 , 2F
讚欸感謝分享 XD
11/24 07:42, 2F

11/24 07:42, 1年前 , 3F
感覺對 Unity 失望透頂的時候會想要來試試看(?
11/24 07:42, 3F
對Unity失望先試試UE吧! 自主製作的話 一堆"理所當然"的元件都得自己寫 不過確實是挺好玩的:)

11/24 07:43, 1年前 , 4F
強大
11/24 07:43, 4F

11/24 07:56, 1年前 , 5F
Vulken教學還躺在我的書籤裡面XD,感謝分享
11/24 07:56, 5F

11/24 11:38, 1年前 , 6F
Unreal表示:都在我的source code裡 去找吧
11/24 11:38, 6F

11/24 12:00, 1年前 , 7F
Respect
11/24 12:00, 7F

11/24 13:02, 1年前 , 8F
請手下我的膝蓋 m(_"_)m
11/24 13:02, 8F

11/24 13:03, 1年前 , 9F
11/24 13:03, 9F

11/24 17:37, 1年前 , 10F
太強了,你跟另一個wicked engine的作者還滿像了
11/24 17:37, 10F
wicked也不錯, 有幾次google到他的東西

11/24 18:55, 1年前 , 11F
好強
11/24 18:55, 11F

11/24 20:34, 1年前 , 12F
最近在學 webgl,對所有寫底層的人感到敬佩 XD
11/24 20:34, 12F
GL team真的讚, 不畏強權微軟 ※ 編輯: Renzokuken (80.47.253.201 英國), 11/25/2022 02:38:09

11/25 07:17, 1年前 , 13F
感謝分享 網誌有不少感興趣的文章
11/25 07:17, 13F

11/25 08:21, 1年前 , 14F
好猛 推
11/25 08:21, 14F

11/25 15:02, 1年前 , 15F
我已經對 Unity 的可靠度失望到開始自己寫很多理所當然
11/25 15:02, 15F

11/25 15:02, 1年前 , 16F
的東西了 XD
11/25 15:02, 16F

11/25 15:02, 1年前 , 17F
但當然自製引擎就主要是順便學習,如果目的是做商業遊
11/25 15:02, 17F

11/25 15:02, 1年前 , 18F
戲的話應該也是轉 Godot 不是轉 UE(
11/25 15:02, 18F

11/26 09:59, 1年前 , 19F
11/26 09:59, 19F

11/26 10:21, 1年前 , 20F
推,自幹過引擎,真的很累但很好玩
11/26 10:21, 20F
文章代碼(AID): #1ZVdY8Hf (GameDesign)