C# Async Task 學習紀錄
2025年的現在,對於Unity的開發者來說,在C#透過Async Task寫非同步執行的程式在已經是一個不得不學的技能了,Unity對於C# async task的支援甚至能夠回推到Unity 2017,但當時實際上使用的人還不像現在這麼多,這篇部落格是我透過看Stephen Toub的Deep .NET: Writing async/await from scratch in C# with Stephen Toub and Scott Hanselman以及同樣是Stepen Toub寫的部落格 - How Async/Await Really Works in C#,實作一個MyTask類別以及在Unity做了一些不同的實驗,來理解C#是如何透過async task來達成非同步執行的紀錄。
Warm Up 這部分會簡單複習一下同步/非同步執行程式以及Corutine與Async Task的寫法,如果你已經熟悉Coroutine與Async Task的區別,只是跟我一樣好奇C# Aync Task的實作原理,你可以跳過下面這部分。
同步執行 V.S 非同步執行 對於Unity的開發者來說,非同步執行不是什麼稀奇的事情,我們從很久以前就再用Unity專為非同步執行打造的類別 - Coroutine,來實作非同步執行與橫跨多個Frame的邏輯了。
簡單回顧一下同步與非同步在Unity中的差異,下面是一段讓物件的透明度從0變化成1的程式碼,沒有使用Coroutine、同步執行的版本:
[ContextMenu("Perform Fading Synchronously")] public void PerformFade() { Debug.Log("Start"); Debug.Log("====Code execute synchronously , main thread will only execute other codes after Fade() complete===="); Fade(); Debug.Log("Exit"); } public void Fade() { Color c = _renderer.
2024 Reflection
今天是唯一一天可以寫年度Reflection的日子,就來回顧一下今年的進度,以及明年的計畫。
達成的事情 到日本念語言學校 JLPT N2合格 (2023年7月是N3) 卡通渲染Shader插件的製作與上架/開賣 FF9 Shader模組開發 Shader相關 Side Projects (SSR、參考Hi-Fi Rush的Unity延遲渲染管線修改、GPU Path Tracer,Probe-based的GI) Unity 6 RenderGraph API學習 在日本找到工作(2025開工) 未達成的事情 JLPT N1 (還沒放榜,但應該是不會合格了) 流利地用日文溝通、充實單字量、程式中常用專有名詞量 實際上認真玩的遊戲不夠多(認真玩過的 - FF9, Unicorn Overlord、Tales of Arise、Tactic Ogre、Ys VIII) 我的Python還是很廢 對日本的印象 到日本之後也是深刻的體會到台灣與國外的差距,雖然日幣已經貶到一般日本上班族的薪水與台灣人差不多的程度了,但資源的差距仍然是巨大的 - 針對娛樂、文化,或者是學習,日本的資源,不論是實體的還是網路的,相對於台灣都豐富許多。 比如說在台灣時想搜尋Unity、遊戲引擎、渲染相關的技術文章時,常常需要從知乎之類的中國網站才有辦法找到內容足夠深入的文章,而日本在這方面的網路資源其實也很多,卷的人夠多R。(雖然我現在只能看懂不到3成,常常必須回頭找英文的解說) 包含專門書的大型書店也不少,買書(還有站著看漫畫)的風氣也是滿盛行的。
💡 日本的天氣也比台灣好很多………起碼沒那麼常下雨
而在先天資源優勢下的結果就是,日本工程師在遊戲引擎上平均的實力也台灣高,這裡的實力並不是說寫code的能力,而是指針對特定領域的know-how(ex:渲染)了解的人數比較多。
💡 單論寫code與解決問題的能力,台灣的強者也很多
關於2024 不論是日文還是程式,我覺得自己進步的幅度都與理想上有些差距,尤其是跟其他的大佬們比較之後。不過如果只跟自己比較的話,仍然是一天比一天進步,每一天都比昨天的自己還要厲害一點點,我想這是最重要的。
關於2025 目前正在處理從大阪搬去東京的各種雜事,2025除了集中於工作的事情之外,有些額外的項目想要做 :
買台PS5+威猛的投影機,玩FF7:Rebirth、Astro Bot JLPT N1合格 繼續強化 C# 掌握度,研究如UniTask或R3的source code 寫一個DirectX Renderer與強化C++基本功 製作Blender add-ons(Python) 研究實務上AI工具與遊戲開發的可行性與流程 更頻繁的發blog逼自己寫學習筆記
煥然一新的經典 - 動手修改FF9的Shader
前言 這篇文章記錄我透過Final Fantasy 9(簡稱FF9)的模組 - Memoria,修改遊戲中執行的shader,讓遊戲中的人物與場景可以有完全不一樣的體驗。文中包括分析FF9的shader與在模型面數、realtime光照方法受限的場景中導入per-pixel光照的過程與想法。
8歲時在家裡第一次玩了FF9,成為了讓我最終踏入遊戲業的契機,目前我正開心的透過自己修改後的模組重新破FF9中。希望這一次除了石中劍II之外的武器與成就都能全入手。
比較模改前後的畫面 先來比較一下2016年移植到PC的版本與套用Memoria模組+新的shader的畫面-
非戰鬥時,人物使用了卡通渲染+描邊效果,背景則使用Moguri模組的AI Upscale圖片。
而在全3D的戰鬥場景時,敵人與玩家隊伍的人物則會套用blinn-phong光照模型,並且根據不同環境產生對應的skybox來計算出對應的環境光,順便新增了有更生動的雲效果的背景shader。
PS1時期還沒有pixel shader這種東西,所以大部分的遊戲都是使用純貼圖(或使用vertex lighting),而FF9也是一樣,人物身上的的明暗變化都是直接畫在貼圖上的,視覺上會比較扁平,導入per-pixel lighting會凸顯出3D的立體感,至於使用Toon Shading或是Blinn-Phong就看個人偏好了,我的模組中不論是戰鬥或是非戰鬥的shader都可以從這兩種風格中選擇。
關於FF9 PC版、模組與現況 FF9代最早於1999年在PS1發售(好懷念阿),PC版於2016年在Steam上釋出,這個PC移植版是透過Unity引擎製作的,不過並不是全部遊戲中全部的邏輯都在Unity重寫,內部有很大一部分仍然是依賴舊引擎的程式碼去跑。像是魔法粒子特效的演出、相機的運鏡、渲染時的投影矩陣…etc,不過因為移植時使用的是Unity,也開啟了社群去模改它的大門。
Memoria專案是一個可以完全取代FF9 PC版C#邏輯的模組,基於Memoria,玩家社群導入了不同的功能,比如說將遊戲使用的背景圖片全都利用AI-Upscale+手繪修改的Moguri Mod是目前最多人用的,還有近期Memoria也加入了將遊戲中的人物動畫從PS1/PC版的15 FPS提升到可最高120FPS的功能也有人製作了提升遊戲難度、新的可遊玩角色的模組…等等。
這些模組都大幅度地提高了遊玩體驗,但畫面的部分則仍與大多的老遊戲模組一樣都侷限在替換貼圖的部分。
而我則是在前陣子重玩FF7時,安裝了修改光照的模組(cosmos lighting)後,有了修改FF9中使用的shader的想法。
竟然能夠修改遊戲中的Shader !? 結論是 - 可以,但比起一般寫shader的方式更硬核一些。
首先,可以看這份文件先複習一下Unity是如何編譯與使用shader的 :
用白話文來講Unity的shader的生命從製作到真正在遊戲中使用大約是這樣:
在Unity編輯器中透過node editor或者使用HLSL語法寫Shader build版本→Shader compiler將Shader檔編譯為不同目標平台的shader asset。 在遊戲進行時讀取shader asset,將asset中的shader assembly傳給GPU dirver,gpu driver會在將shader assembly進行一次編譯,並將編譯好的program cached住,下一次使用時就不會重複編譯。 在玩遊戲時,第3步如果過於費時,就會導致著名的shader stutter,近期常有一些大作在第一次遊玩時總是卡卡的,而在重新玩一次的時候就變順了,原因是第一次遊玩時gpu driver需要編譯的shader數量太龐大+第二次遊玩時gpu driver不用再重複編譯。
而透過Memoria模組執行FF9的時候,會從指定Path將需要的shader asset(就是…一大坨string)讀取至遊戲中,接著就執行上方第3步驟。也就是說,雖然透過模組沒辦法重新build整個專案,但可以透過修改原本要讀取的shader asset的字串,來加入需要的功能。只要沒有編譯錯誤,那GPU driver就能順利的將shader編譯並且在遊戲中執行。
遊戲內的Shader Asset提供了哪些資訊? FF9移植版雖然不是整個遊戲都在C#端重寫,但像是渲染的部分就是完全透過Unity,因此雖然畫面看起來還是ps1的那個老遊戲,背後使用的所有shader都是重新在Unity中寫的。
而這也代表了幾件事 -
Unity的shader都包含Pixel Shader Stage,也就是說可以對3D物件做per-pixel的上色, 使用者能夠透過第三方工具將遊戲的asset輸出,包含以文字檔類型儲存的Shader 接著,以Unity自帶的UI用shader來舉例,讓我們來看看遊戲資料夾中包含的shader檔案會長什麼樣子(部分) -
Shader "UI/Default" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" { } _Color ("Tint", Color) = (1,1,1,1) _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 } SubShader { Tags { "QUEUE"="Transparent" "IGNOREPROJECTOR"="true" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="true" } Pass { Tags { "QUEUE"="Transparent" "IGNOREPROJECTOR"="true" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="true" } ZTest [unity_GUIZTestMode] ZWrite Off Cull Off Stencil { Ref [_Stencil] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] Comp [_StencilComp] Pass [_StencilOp] } Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Program "vp" { SubProgram "d3d9 " { Bind "vertex" Vertex Bind "color" Color Bind "texcoord" TexCoord0 Matrix 0 [glstate_matrix_mvp] Vector 5 [_Color] Vector 4 [_ScreenParams] "vs_2_0 def c6, -1, 1, 0, 0 dcl_position v0 dcl_color v1 dcl_texcoord v2 dp4 oPos.
2023下半年的回顧
關於下半年 8月初的時候,離開了待了將近八年的公司,離開的原因是 - 我明年3月底(沒出意外的話)要去日本念語言學校一年,雖然多少會捨不得(跟少賺了些錢),但提早將近半年離職是因為想去做一些這些年來在累積的 【重要但不緊急】 的事情。
回家體感沒過多久,一眨眼2023就要結束了,每天的行程也是自己排到近乎爆滿的狀態(早8晚9),這篇blog就回顧一下2023下半年,各方面的進度。
積年累月的【重要但不緊急】事項們 這些年來,總是會不斷冒出一些 對我來說好像很重要 但當下又沒空去處理的事情, 最終主要整理出幾個大項目 -
身體健康 健康檢查報告一年比一年慘,前年耳朵出問題, 連續兩年極度缺乏缺維生素D +今年胃潰瘍 + 頸椎有骨刺。
強化Graphics Programming的基礎 從Unity開始接觸shader大概四年了(2019年9月開始),過去四年下班後有很大量的學習時間,不是在寫Unity的C#,就是在摸索,嘗試各種Shader相關的功能。
從一開始的surface shader,到後來寫custom shader做出各種不同的效果、到後來開始玩compute shader、研究SRP…等等,過程中是對graphics programming產生滿大的興趣,而且也發現寫面向GPU的code時,很多東西是可以帶回cpu端改良自己的gameplay logic的,讓我覺得很實用。(還順便做了個插件 )
但是,隔著一個遊戲引擎學computer graphics,始終是多了好幾層封裝過後的API,舉例來說 : Unity中寫custom shader時就已經有各種引擎提供的built-in variable、macro能用,想進行offscreen rendering則多了個RenderTexture api要學,而且網路上有大量blog的關係,這也讓我一直可以不用去重新實那些對一個現代的renderer來說較為最基本的東西,像是shadow map、TAA、FBO、skinned mesh。直接用現成的結果是 - 我覺得我過度依賴API還有網路上的forum或是一些blog,因此也缺乏做出一個優於市場上其他競品以及處理不同的edge case,所需的實作經驗。
而且過於依賴引擎的API也會有另一個問題 - 換一個開發環境的時候,陣痛期會特別長,雖然遊戲開發中大部分的觀念都是通用的,但對底層的實作不夠熟悉時、仰賴引擎提供的API的結果就是得用「引擎A的API在引擎B叫應該會叫做..…」的角度,而不是「某API應該會在引擎中的某個時機點去實作」去思考,比較起來就是很沒效率。所以我一直都很想找機會來個重新造輪子,自己實做一個3D場景的渲染專案。除了充實自己的基礎知識外,我相信也可以有很多解決不同面向問題的收穫。
數學 我是我認識的寫code的人之中,數學最爛的,沒有之一。從小對數學就是有一個障礙,但我很清楚一件事情 - 「如果未來要認真搞graphics,數學是躲不掉的」,gameplay也許用向量跟線性代數就能挺過幾乎所有的狀況,但graphics的東西扯到渲染方程跟光照就是滿滿的微積分,GDC/SIGGRAPH上很多paper上是沒有程式碼的,舉例來說 - Ready at Dawn Studios發表的The Order 1886這款遊戲的渲染paper,計算皮膚間接光的次表面散射是沒有code可以參考的,網路上也是幾乎找不到這部分的實作。
只有下面這坨東西 -
因此,將數學的算式拆解並自己轉化為程式碼的能力是必須的,加上還看到網路上這張troll圖,
我就是suck,直接被激到。於是【學數學】,就是一件一直放在自己心裡但沒去做的事情。
打電動 說來諷刺,在遊戲公司寫了將近8年code,這期間玩的遊戲是是越來越少….一年可能就一兩款,但我心底是很熱愛遊戲的,於是我決定這段空窗期打電動是一項很重要的事情。
日文 去補習班從50因從頭念完N4之後,就考過N3了,後來也把N3的課程上完了。但其實檢定都不重要,最重要的是明年到語言學校的能力分班,還是希望到時候能分到程度中上一點的班過得比較精實一些。所以出發前還是要盡量充實單字量。這項其實滿緊急的XD。
【重要但不緊急】事項的進度 身體健康 開始吃維生素D,吃飯時間在公園曬太陽、吃表飛鳴、戒咖啡因、8、9月天天跑復健科治骨刺,還順便第中了第一次的covid。
但我還是覺得我很不健康,我覺得就隨緣吧,生死有命,富貴在天,時候到了別走的太痛苦就很感恩了。
強化Graphics Programming的基礎 為了對graphics有更深入的了解,直接接觸Graphics API我想是個很好的方式,於是我就用C++/OpenGL做了個 💩 💩等級的renderer,這個renderer就當成我的練功房,沒事就在裡面實做一些心血來潮想寫的功能。
如何在Unity中使用AMD FSR2.0
AMD的FSR與NVIDIA的DLSS都是Upscaling的功能,讓遊戲能用更低的原生解析度渲染的同時仍然保留高解析度般的畫面品質,在這個realtime RT/SSGI/SSR滿街跑的時代,能夠大幅度的提升遊戲效能。
Unity內建只有支援FSR1.0與DLSS1.0,但DLSS 2跟 FSR 2.0還原出的畫面品質都遠遠超過前一代(但FRS 2.0本身的基礎效能消耗會大於FSR 1),要從頭去接這些功能需要有寫Graphics backed API像是vulkan或是DX12的能力,剛好AMD很佛心有釋出一個已經寫好,For Unity DX11的patch,於是就來試用看看,這篇就紀錄一下build所需的DLL,跟打patch到unity中的過程。
事前準備: git clone這兩個repository
FidelityFX-FSR2 https://github.com/GPUOpen-Effects/FidelityFX-FSR2-Unity-URP Unity 2021.3f13 (URP12.1.7) 其他需要的工具 CMake 3.16(或以上) 在visual studio installer中安裝 Windows 10 SDK 10.0.18362.0 在visual studio installer中安裝 “Desktop Development with C++” Visual Studio 2019 Git 編譯FSR2 lib 為了要讓Unity能用FSR 2.0的library我們需要先把FSR編譯之後包成一個DLL檔, clone好兩個所需要repo之後,先到_FSR-Unity-URP_的資料夾中找到0001-fsr-2.2-dx11-backend.patch這一個git patch檔,git patch檔其實是將兩個git commit之間的差異輸出成的檔案。
接著把這patch檔複製到FidelityFX-FSR2所在資料夾下,在terminal中輸入
git appply 0001-fsr-2.2-dx11-backend.patch 接著到FidelityFX-FSR2資料夾/build中,會看到GenerateSolutionDX11.bat,執行這個.bat檔 執行GenerateSolutionDX11.bat之後,會多出一個DX11資料夾,進去把FSR2_Sample.sln打開(Visual Studio 2019),打開之後確認好Project→Properties→General→Platform Toolset使用的是v142版。
沒意外的話就直接build這個專案,build好之後在FidelityFX-FSR2_/bin/ffx_fsr2_api_資料夾下會看到兩個.lib檔 編譯FSR2-Unity-DLL build好lib檔之後回到_FSR-Unity-URP__資料夾下,我們需要使用cmake來建立一個visual studio專案,這個專案會build出最終的dll。打開cmake,source code路徑就指向你的FSR-Unity-URP_的資料夾位址,戳一下左下角的Configure, cmake會把一些設定值讀取近來,
這裡name跟value對應的路徑很重要,設定錯的話等一下會build失敗,
這裡說明一下每個name對應的路徑是做啥的 :
FFX_FSR2_API_INCLUDE_DIR : 這裡要對應的是FidelityFX-FSR2_/src_資料夾路徑,裡面包含了FSR2 API需要的.