回顧并為今天的工作做準備
我們今天繼續昨天開始的工作,現在我們要回到渲染中處理 Z 值的最終環節。我們目前已經有一個我們認為還算合理的排序方式,雖然可能還需要在接下來的過程中進行一些調整,但總體上已經有了一個明確的方向。
我們已經開始草擬出一些接下來的任務,這很可能會是我們下周的主要工作。不過今天也不能浪費時間,能推進多少就推進多少。昨天我們已經開始試著實現,現在可以開始真正深入推進,把自己放在一個更好的位置,為下周的完成打下基礎。
接下來的過程其實沒有什么特別神奇的東西。我們只是要根據現在對 Z 值處理的明確認知,把之前為了摸索而寫下的內容進行整理和清理。之前的做法比較雜亂,是因為藝術風格、代碼實現等多方面因素影響了我們對 Z 值如何工作的判斷。但現在我們已經清楚地知道了 Z 值的職責是什么,也知道了我們希望它如何發揮作用。
因此,我們要做的是整理整個渲染流程中的 Z 值處理邏輯,確保它是清晰、統一且健壯的。否則,后續繼續在這種不穩定的系統上構建,會出現很多潛在的問題,比如表現不一致或者難以調試的問題。我們希望最終能達到這樣一種狀態:對于 Z 值的使用,我們有清晰的理解和信心,不再出現“為什么會這樣”的疑惑。
過去在開發過程中,我們多次調整 Z 值的處理方式,嘗試不同的策略,遇到問題就修改,然后再試。這些過程是必要的學習階段,是摸索方向的一部分。但現在我們已經走出了那個階段,進入到需要把所有東西整理清楚、穩定下來的時候了。
我們要做的,就是清理原本那些零散、不一致的處理方式,讓整個 Z 值系統在渲染層是干凈、明確、可預期的。這樣,在后續開發中我們就不必反復質疑其行為或邏輯,也不會被過去的臨時實現困擾。我們需要的就是一個可靠的基礎系統,然后就可以毫無顧慮地在其上構建更多功能。
運行游戲,查看當前進展
我們現在繼續昨天的工作,主要是處理和樓梯相關的渲染問題。可以看到,在處理樓梯間時,通往上層的樓梯并沒有被正確地包含在底層的渲染中。這說明我們在處理 Z 值的時候,整個流程還不夠一致和清晰。
問題的根源在于我們沒有系統地跟蹤 Z 值。我們并沒有從邏輯上根據所在區塊來計算 Z 值。我們已經決定,Z 軸上的區塊(chunk)應該對應于每一層樓——也就是說,同一層樓的所有內容應該處于同一個 Z chunk 中。這樣渲染的時候才能將同一層的元素作為一個整體進行處理。
接下來要做的第一件事就是讓每個 chunk 的 Z 值真正被用在渲染中。當前我們雖然有傳遞 chunk 的概念,但實際上渲染系統中并沒有用這些 Z 值進行層級劃分。我們需要修復這一點,讓 chunk 的 Z 值真正參與渲染邏輯,使得每一層樓被當作一個獨立的渲染切片(slice)處理。
此外,當前的渲染系統只是把所有東西混在一起,然后依賴排序規則進行排列。而我們的排序規則本身也并沒有設計成處理這種多樓層的情況。這種混合處理是不對的,我們需要明確地區分不同層的數據,并作為單獨的切片傳入渲染流程中。
我們要做的第二件事就是,讓渲染系統開始認識這些不同的切片,讓它們分層傳入并進行單獨處理,而不是像現在這樣全部混在一起后依賴排序算法“勉強”處理。
總體思路如下:
- 首先修正 chunk 的 Z 值處理,確保每一層樓被正確分配一個 chunk,且 chunk 的 Z 值能被渲染系統識別并利用。
- 然后讓渲染系統以“切片”的形式,分別接收這些按層劃分的數據。
- 最終要讓排序系統明確知道這些層級是怎么工作的,避免不明確或錯誤的層間混合渲染。
通過這些步驟,我們就能讓渲染流程在處理多樓層內容時更合理、更清晰、更可靠。現在我們開始動手。
修改 game_entity.cpp:停止 UpdateAndRenderEntities() 調用 ConvertToLayerRelative(),改為按昨天討論的方法計算 RelativeLayer
我們現在在處理廣義相對性的位置轉換邏輯,也就是之前一直使用的 ConvertToLayerRelative
。這個方法之前用于確定某個物體在 Z 軸上的位置。但現在我們不再想沿用這種基于相機視角差異來決定圖層的方式,因為我們不再對圖層做透視變換,所以這種依賴相機 Delta 的方式已經不適用了。
我們真正需要的是基于“chunk Z 值”的圖層邏輯。我們已經開始在代碼中明確了這一點,比如寫下了注釋說明:現在我們關注的是 chunk Z 值的差異,它決定了圖層的高低,而不再關注相機的位置偏移。
問題在于,目前我們在執行重新打包(repack)之前,還無法準確知道某個實體的 chunk Z 值。也就是說,WorldPosition
這個值在當前階段并不可信,因為我們還沒確定目標位置在哪個 chunk。換句話說,我們移動了某個實體,但在進行重新打包之前,我們還不知道它的新 chunk Z 值應該是多少。
雖然在重新打包的階段我們是可以計算出來的,比如在打包函數中,我們會根據實體的位置重新計算對應的 chunk,這時候我們確實能夠知道 Z 的差異。但是我們現在還沒進入這個階段。
因此我們必須審視代碼的執行順序,尤其是在區域數據(region)內的行為。我們可以看到,當我們真正進行“打包實體進世界”這個操作時,比如調用 PackEntityIntoWorld
的時候,代碼中確實傳遞了實體的 WorldPosition
,也就是位置信息。
進一步查看,可以發現 PackEntityIntoWorld
是在 EndSim
的時候被調用的,意味著在模擬結束階段我們會把所有實體重新打包。并且在 PackEntityIntoWorld
中,會根據實體的位置來計算它所在的 chunk,這個計算過程包括圖塊映射與坐標平移。
雖然這種做法會導致在某些地方重復計算 chunk 的代價略高,但當前階段我們還沒有更好的解決方案。當然,也可以考慮將計算出來的 chunk 信息緩存到實體中,以便后續重用,這部分我們可以留個 TODO,將來優化時考慮。
總的來說,當前任務核心內容如下:
- 不再使用基于相機 Delta 的
ConvertToLayerRelative
方法; - 圖層信息應當由 chunk Z 值來決定,這樣才能與我們每層樓一個 chunk 的設計相匹配;
- 當前在重新打包實體前無法得知它準確的 chunk Z,需要等待進入打包流程才能得出;
PackEntityIntoWorld
是我們當前依賴的處理過程,它在EndSim
階段執行,內部根據WorldPosition
計算 chunk;- 盡管存在一定的計算重復,但可以通過后續緩存優化;
- 當前階段的目標是讓邏輯流程保持清晰,讓每個實體的圖層歸屬可以通過 chunk Z 來可靠判斷,為后續渲染處理打下基礎。
修改 game_entity.cpp:讓 UpdateAndRenderEntities() 通過 MapIntoChunkSpace() 獲取 WorldPos 來計算 RelativeLayer
我們現在考慮的問題是,如何在已知某個 chunk(例如 chunk C)的情況下,獲得實體在該 chunk 中的相對位置(entity P)。實際上,我們并不一定需要保存這個轉換的結果,僅僅在需要計算時進行一次轉換就足夠了。
舉個例子,我們可以簡單地調用一次 MapIntoChunkSpace
,直接將實體的世界位置轉換為 chunk 空間下的位置。雖然從邏輯上講我們可以不保留中間結果,但實際上,我們很可能還是需要知道這個轉換所產生的偏移量(offset),因為后續我們可能要利用這個偏移量做進一步處理,所以我們可能還是會保留這個值。
因此,我們可以這樣處理:將世界位置 WorldPosition
傳入 MapIntoChunkSpace
,這時就能獲得在 chunk 空間下的位置(例如 WorldPoster
),從而得知實體當前所處 chunk 的 Z 值。在物理模擬步驟之后,實體已經被移動到新位置,此時再執行這一轉換操作,就能知道它的新圖層(relative layer)。
這樣我們就可以獲得準確的圖層索引,這是非常重要的信息。
不過接下來還有其他問題需要考慮。我們還需要為這個實體設置用于渲染的“身份變換矩陣”(identity transform),用于對 Z 值進行合適的偏移處理,以確保實體在 Z 軸上被正確地渲染。因此,雖然我們現在已經可以正確計算出相對圖層,但對于后續渲染的處理,還需要額外的步驟。
我們知道實體最終會被繪制在某個具體的位置,而這個位置會受到 chunk Z 值的影響,所以最終渲染的位置不僅取決于邏輯坐標,還取決于圖層處理邏輯。我們目前還沒有把這些都建立起來,因此這部分工作還得繼續完善。
目前我們先暫時把這部分邏輯放一邊,因為其他依賴的部分還沒準備好,但這顯然是我們接下來必須推進的方向。
此外還發現一個小問題,我們當前代碼中似乎沒有 World
的定義或引用,看起來像是漏掉了。可能是因為相關模塊引用時沒有傳遞 World
實例,或者有些代碼根本沒有用到它所以沒人注意。我們推測可能是 WorldMode.World
之類的結構,只是沒有在當前作用域里建立本地引用。
這個問題雖然不影響當前的主邏輯構思,但之后處理實體或渲染環境時還是需要修正,確保我們能正確訪問世界狀態信息。
總結:
- 可以通過
MapIntoChunkSpace
獲取實體在 chunk 空間下的位置; - 從中獲得 chunk 的 Z 值,從而確定實體所在的圖層;
- 物理模擬之后再執行該計算,確保使用的是實體的新位置;
- 后續還需要設置 identity transform,以處理 Z 偏移;
- 實體最終在畫面中出現的位置依賴于 chunk Z 的正確處理;
- 當前
World
對象沒有被本地引用,需要檢查并修復; - 接下來的重點工作是建立完整的圖層渲染流程,包括位置變換和正確排序機制。
運行游戲,確認狀態明顯改善
現在整體情況已經明顯好轉。樓梯間的處理已經完成,并且每一層樓的樓梯部分都已經準確地歸類到了對應的樓層中,位置也符合預期。這是一個重要的進展,因為這意味著基礎結構已經開始變得清晰和有序。
雖然仍然存在樓層切分的問題,目前還沒有進行樓層的切片處理,因此相關的錯誤依然存在,但這只是下一步需要解決的問題。當前的主要成果是樓層中的元素已經被正確地分配到了各自應在的樓層,不再出現混亂或歸類錯誤的情況。
這是第一次整理樓層內容的初步結果,雖然還不完整,但方向正確。整個結構開始表現出一定的連貫性,樓層之間的關系也更為合理,不再像之前那樣雜亂無章。接下來需要進一步處理的是將每層樓進行實際的切分,從而徹底消除現有的切片錯誤。但總的來看,目前取得的進展令人滿意,已經邁出了關鍵的一步。
考慮在渲染中引入圖層 Z 和實際 Z 值的概念
接下來需要重點處理的是 Z 軸方向的轉換部分,也就是 z transform 的另一部分,這是接下來工作中最復雜的部分之一。因此,我們必須開始收緊思路,對整個實現方案有一個非常清晰、明確的概念設計,特別是在渲染階段的錯誤處理、圖層管理和 Z 值之間的關系上。
我們需要處理圖層(layer)和實際的 Z 值的關系。具體來說,目前的 chunk dim z(即每塊在 Z 方向上的維度)需要作為一個絕對的排序標準,被渲染系統感知并使用。為了解決當前的問題,可以臨時采用一種折中的方案:把 Z 層的信息直接嵌入到 sort key(排序鍵)中,讓排序系統以此為依據來進行精確排序。
這種方式雖然是可行的權宜之計,但并不一定是最終推薦的做法,因為當前的 sort key 結構已經變得非常龐大,承載了過多邏輯。雖然目前我們可以繼續在其中添加信息來滿足開發需求,但這并不是一個長久之計,遲早我們需要面對復雜度上升所帶來的系統開銷和維護負擔。
目前的重點是先把各部分如何組合的問題理清楚,讓整個流程跑通。在實現階段,可以允許排序邏輯暫時承擔更多的責任,比如在 sort key 中嵌入 Z 層信息,讓排序過程自動把不同層級的數據區分出來。
不過,等到最終版本的渲染階段時,我們很可能需要更聰明、更高效的方案。可能會采用將不同圖層的數據預先分配到獨立的 buffer 中,從而避免后期再進行統一排序。這種方式可能在性能和邏輯清晰度上更具優勢,因此在之后的設計中需要重點考慮是否轉向這種結構優化的路徑。
修改 game_render.h:在 sprite_bound 結構體中添加 ChunkZ
目前雖然還不確定最終方案應該怎么設計,但在現階段,由于排序系統已經搭建完成,因此可以先提出一個臨時性的處理思路。
具體來說,可以引入一個類似于 chunk z 的機制。這個 chunk z 會作為一個具有絕對優先級的排序因素存在,它的優先級甚至高于人工設定的排序順序。這意味著,在排序過程中,只要涉及到 chunk z,它就會主導排序行為,把相關元素從通用排序邏輯中“抽離”出來,優先進行分組排序。
這個機制的核心思想是將 chunk z 作為一種強制的排序層級存在,把它看作是一種“排序組”的概念。凡是具有相同 chunk z 值的元素會被歸為一組,然后在這組內部再根據其他標準進行細致排序。這種方式可以有效地將整個場景在 Z 軸維度上劃分成結構清晰的分區,使得渲染或處理邏輯能夠優先考慮 Z 軸上的分布,而不是完全依賴人工設定的順序或其他混雜條件。
雖然這是一種暫時性的“吸附式”(hoover up)的解決方案,但在當前階段非常實用,可以讓系統在沒有復雜 buffer 管理和最終結構優化之前,依然維持較高的邏輯清晰度和排序準確性。這樣既能保證結構分層正確,也為后續的優化和系統演化打下良好基礎。
修改 game_render.cpp:讓 IsInFrontOf() 測試 ChunkZ
接下來在實現“是否在前方”的判定邏輯時,需要在已有邏輯的基礎上引入一個額外的判斷條件。這個處理的關鍵在于排序對比過程中,我們現在要處理的是一種更復雜的場景,而不僅僅是簡單的幾何對比。每當我們在進行對象之間的排序比較時,邏輯復雜度就會上升,因此這部分的設計也變得更加關鍵。
目前的做法是:在排序判斷階段,我們可以加入一個新的邏輯分支。這個分支基于 chunk z 的值來進行判斷。具體操作是:如果參與比較的兩個對象,其對應的 chunk z 值不相等,那么我們就可以直接根據這個值來判斷前后關系。也就是說,chunk z 值較大的對象會被視為“在前方”,從而在渲染中獲得更高的顯示優先級。
這樣一來,chunk z 就成為了一個天然的分層依據,相當于在所有其他排序邏輯之上,先進行一個大范圍的分組。只要兩個對象處于不同的 chunk z 區域,就不需要再進行更細致的空間對比,可以直接通過 chunk z 的大小關系來決定誰在前誰在后。
這種分層判斷的方式雖然讓排序邏輯變得更復雜,但它也是必須的。尤其是在對象數量增加、場景變得更加復雜的情況下,必須提前意識到哪些部分可能成為性能瓶頸或系統負擔點。雖然目前系統中并不會出現成千上萬個對象同時出現在屏幕上的情況,這在一定程度上給予我們一些“寬容度”,但從長遠看,識別和標記這些潛在風險點仍然是必要的。
總之,這種基于 chunk z 的預分層策略能夠有效簡化部分排序判斷,同時在邏輯上也有助于構建一個更清晰的圖層結構。通過將其視作一種在其他所有排序標準之上的“層級規則”,我們就可以自由地將對象歸入更寬泛的類別之中,從而提高整體系統的可維護性和可擴展性。
修改 game_render_group.h:在 object_transform 結構體中添加 ChunkZ
關于渲染分組的實現,其實在代碼架構層面上并不復雜,這部分的邏輯設計非常直接,也容易實現。真正讓人產生顧慮的并不是實現難度,而是更高層次上的系統整體性與性能代價問題。
當前采用的策略雖然可行,也能滿足現階段的需求,但在心里還是會有所擔憂——不是說這條路徑是錯誤的,而是擔心在系統發展到更復雜或性能要求更高的階段時,這種方案可能存在瓶頸,可能會拖慢整體的效率或者變得不易維護。
也就是說,現在的實現方案雖然暫時能跑通,也邏輯清晰,但未來在更高負載或者更大規模的渲染場景中,我們可能需要對這些結構重新審視,尋找更高效、更優雅的方式。不是說當前不能繼續推進,而是要有一個心理準備:當系統進入后期階段、功能趨于穩定或規模擴大之后,可能必須回頭對某些設計做出優化和重構。
因此,這些擔憂更像是一種預警機制,一種對未來可能問題的預留意識。當前階段繼續使用這些方法是可以接受的,不存在阻礙開發的風險。但同時,也需要在心中標注這些點,防止在后續版本中它們演化成更嚴重的系統問題,或對性能產生難以忽視的影響。這樣可以避免到時候措手不及,而是能夠有計劃、有節奏地進行調整和優化。
修改 game_render_group.cpp:讓 GetBoundFor() 設置 ChunkZ
現在我們所做的事情非常簡單,主要是為了處理手動排序(manual sort)相關的邏輯。具體來說,我們要做的只是確認當前數據中是否存在某個元素,并將其關聯的 chunk z 值提取出來,用于后續的排序判斷。
操作上,我們所做的就是:當找到某個需要排序的對象時,直接從對象中提取其對應的 chunk z 值,并將這個值用于標記或記錄,作為后續排序流程中的一部分。這一步確保了我們能夠明確地知道這個對象在 Z 軸維度上屬于哪個邏輯分層,為后續判斷“是否在前方”以及具體渲染順序提供了基礎依據。
這種做法本身非常直接,目的也很明確,就是為后續排序打好基礎,把對象在 chunk z 上的歸屬清晰地表達出來,避免后續處理時出現混淆或不一致。通過這個操作,我們就能確保在進行手動排序時,能夠與 chunk z 的自動分層邏輯相兼容,不會發生矛盾或邏輯沖突。整體流程簡單但關鍵,是排序系統和渲染系統保持一致性的必要步驟。
運行游戲,確認修改沒有影響現有表現
理論上來說,這個改動不應該對現有的排序結果產生任何影響,因為我們并沒有對任何值進行修改或重新設置。執行這部分代碼時,排序邏輯依舊保持原樣,所以在實際運行時,仍然會看到之前那種奇怪的排序現象。
通過觀察,可以確認排序結果和之前完全一致,說明目前的改動只是做了數據的讀取和確認,沒有改變排序行為本身。這也意味著當前的處理流程正常運行,沒有引入新的錯誤,同時保留了之前的排序狀態,方便后續繼續調試和優化。
修改 game_entity.cpp:讓 UpdateAndRenderEntities() 設置 ChunkZ
現在的問題是,如果我們主動去修改渲染代碼,使其真正設置排序鍵(sort key)和實體(entity),會發生什么情況。之前只是讀取和確認數據,但沒有實際更改排序鍵,這次打算嘗試把手動排序部分真正生效,也就是說,讓排序鍵被賦值并用于渲染排序。
通過這樣做,可以直接觀察手動排序對整體渲染順序的影響,驗證這種修改是否能夠按預期改善或者改變排序結果。這個實驗能讓我們更直觀地看到當前排序機制的實際表現,判斷這種修改是否可行,以及是否需要進一步調整和優化排序邏輯。總之,這是一個動態測試,用來驗證手動設置排序鍵后系統的反應和效果。
查看該修改如何影響排序
手動排序的部分,當我們將實體的變換信息(entity transform)中的 chunk z 和世界坐標(world pos)作為排序依據時,就形成了一種自動排序機制,這個機制會自動生效。經過這種處理后,可以直觀地看到 Z 軸的排序效果幾乎是“免費”實現的,整體畫面變得更加合理、層次分明,排序看起來非常自然,物體之間的遮擋關系也更清晰。
不過,這里也出現了一個潛在的問題,具體影響還不太確定,后續版本還需要進一步觀察和評估。問題主要出現在樓梯的場景里,比如當角色走上樓梯時,出現了渲染遮擋上的小瑕疵,比如角色應該顯示在樓梯頂部某些部分的前面,但實際表現上可能沒有達到預期。
這部分問題可能并不需要通過改變排序邏輯來解決,而更合理的做法可能是在角色移動到樓梯上某個區域后,主動將角色的 chunk z 調整到更高的層級。換句話說,當角色跨越樓梯的不同“塊”時,可以將其歸類到上一個 chunk,從而保證其在視覺上顯示在正確的位置。這種策略比較合理,也容易實現,能夠較好地解決角色與樓梯層級的遮擋關系問題。
總的來說,自動排序機制基本工作正常,帶來了良好的層次效果,但對于角色與復雜幾何體之間的細節遮擋,可能需要通過動態調整 chunk 歸屬來進一步完善。這個方案看起來是個比較合理且可行的解決思路。
修改 game_entity.cpp:讓 UpdateAndRenderEntities() 擴展 traversables 的邊界,使其繪制時像真實的瓦片
現在我們嘗試的目標是想擴展一下可通行區域的邊界(bounds),以便觀察會發生什么變化。說實話,對這部分的繪制細節已經沒有太多印象了,甚至一開始都不確定是哪個部分負責渲染這些可通行區域。看了一下,確認是這段代碼在處理這些 traversable 區域的繪制。
如果希望讓這些區域在視覺上更接近實際游戲中的瓦片(tile)樣式,那么我們可以嘗試稍微擴展它們的顯示方式。這樣就能夠在角色移動過程中,更清楚地看出每一個 tile 的作用和可行走性。
在實際運行中,也能更清晰地看到游戲中的可通行結構,以及角色在其中的移動軌跡。這個過程中,也開始逐漸呈現出一個更接近真實游戲的視覺效果。
但與此同時,也發現了一個新的問題:在某些地方出現了排序閃爍(sorting flicker),這可能意味著在渲染過程中出現了深度排序沖突。初步判斷,這種問題極有可能是由循環依賴導致的排序錯誤,也就是多個對象之間的排序形成了閉環,系統無法穩定判斷前后關系。
為了驗證這一點,有必要重新引入調試工具,用于確認排序邏輯中是否真的存在循環沖突。同時,還需要將調試信息放入一個獨立的 chunk 中,這樣它們在渲染層級上可以總是處于最上層,方便觀察和調試。
這不僅可以幫助修復當前的排序閃爍問題,同時也能為后續可能遇到的類似問題提供一個清晰可控的觀察入口。總之,當前階段的改動雖然小,但卻暴露出系統中深層次的排序邏輯挑戰,需要進一步驗證并加以優化。
運行游戲,思考場景切換問題
現在可以明顯看到出現了嚴重的排序循環問題(cycling),導致畫面中出現非常糟糕的閃爍現象。這種問題很可能是由于當前的重疊檢測邏輯過于寬泛,判定了許多實際并不真正重疊的對象之間存在遮擋關系。
雖然這些元素之間的重疊非常微弱,但在當前的系統中,哪怕是邊緣上的極小接觸,也會被判定為存在深度關系,從而被強行納入排序比較中。這就導致了一種“偽重疊”的排序邏輯,最終形成大量不必要的圖元之間的排序關系,進一步引發了閉環依賴和排序循環。
考慮到這一點,或許一個改進的方向是:在進行重疊檢測之前,適當縮小物體的邊界范圍。也就是說,不使用實際的完整邊界進行比較,而是對每個物體的包圍盒略微進行收縮,從而避免那些僅僅在邊緣輕微重疊的對象被誤判為存在遮擋關系。
從直覺上來說,這應該是一個非常有效的優化策略,因為許多目前被系統判定為需要參與排序的對象,在視覺上其實并沒有真正發生可感知的遮擋。如果能過濾掉這些“幾乎不重疊”的情況,就可以大幅減少排序系統中的無效比較,從而降低循環發生的概率,提高整體排序的穩定性和效率。
另外,也注意到在某些角度,比如角色靠近一些樹木或場景邊緣的時候,會出現樹木閃爍的問題,可能也是由于同樣的循環排序導致的遮擋不確定性。這種現象一旦出現,就意味著當前排序圖中存在大量的環路。實際上,令人驚訝的是在存在如此多的排序環時,系統仍然能在大多數情況下保持運行穩定,說明基礎排序機制具有一定的魯棒性,但這種狀態顯然是不可持續的。
因此,下一步應當重點考慮對重疊檢測邏輯進行優化,從“邊界縮小”入手,有望在不大幅更改排序結構的前提下,顯著提升渲染表現和用戶體驗。
修改 game_render.cpp:在 BuildSpriteGraph() 中縮小傳入 RectanglesIntersect() 的尺寸
現在我們開始關注排序過程中的重疊檢測(overlap test),尤其是“矩形相交”的部分(rectangles intersect),思考它在邏輯層面上的實際行為,以及我們是否可以通過某種方式改進它,以減少排序錯誤或循環的出現。
具體來說,我們對當前的重疊判斷邏輯產生了興趣,想了解當我們在執行矩形相交檢測時,若對矩形進行一些縮小操作(比如將其邊界收縮4個像素),最終會產生怎樣的效果。這是出于一種優化直覺:通過略微收緊物體邊界,過濾掉那些邊緣接觸的偽重疊,從而減少不必要的排序干擾。
當前的矩形相交邏輯是集中在平臺相關的謂詞模塊(platform-specific predicates)中定義的,所以得去那里查看或修改這部分的代碼。
接下來嘗試性的思路是:在進行碰撞或重疊檢測之前,先人為地將兩個待比較的矩形邊界收縮,比如每邊收縮4個像素。這相當于人為設置了一個“容差”或“安全緩沖區”,排除了那些幾乎看不見的邊緣接觸情況。
這樣做的潛在好處是顯而易見的:
- 可以顯著減少那些在視覺上沒有實際遮擋效果的對象之間的排序參與;
- 減少排序比較數量,降低生成排序圖時的復雜度;
- 減小排序循環(cycle)的可能性,提升最終的排序結果穩定性;
- 減少渲染過程中的閃爍現象。
這個方向的嘗試并不需要大幅改動系統結構,僅僅是修改了相交檢測的邊界條件,卻有可能帶來非常直接且明顯的排序行為改進,因此是一個性價比非常高的優化思路。
因此,當前我們著手的重點是將已有的矩形交叉檢測邏輯,包裝進一個帶有可控“縮放”參數的版本,并實際應用到排序過程中的重疊判斷中,觀察是否能顯著改善排序閃爍與環路問題。這個實驗將對我們后續優化方向提供重要參考依據。
修改 game_debug.cpp:讓 DEBUGStart() 設置調試元素的 ChunkZ
我們現在考慮的是是否可以徹底移除排序偏移量(sort bias)這一機制。
起因是我們目前已經引入了 chunk_z
,它作為一個明確的、全局性的排序參考值,已經足以承擔起排序中的層級判斷功能。之前 sort bias
是一種臨時手段,主要用于手動調整物體的前后關系,使其在渲染順序中有所區別,但現在隨著 chunk_z
的引入與正式使用,它已經具備更明確、更絕對的優先級判斷意義。
因此我們著手進行了如下思考與操作:
- 直接查看當前是否還有地方在使用
sort bias
; - 判斷其是否還會對排序產生任何實際影響;
- 初步結論是:有了
chunk_z
之后,再保留sort bias
顯得多余且冗贅; - 于是決定將
sort bias
從系統中完全移除; - 同時在調試系統中也進行相應修改,確保不會再引用這個字段;
- 后續驗證移除
sort bias
后系統的排序行為是否依舊正常,特別是可視化效果、物體遮擋關系等是否如預期一致。
總的來說,這是一次對系統歷史遺留機制的清理優化,也是對當前排序邏輯簡化和集中化的積極舉措。通過移除多余的權重疊加方式,我們可以進一步減少系統中的潛在歧義來源,使排序系統更清晰、更統一、更易維護。未來如果發現仍有特殊情況需要精細微調排序,可以考慮以更通用的方式引入,而不是依賴舊的、命名模糊的機制。
運行游戲,切換調試組顯示
我們現在驗證了調整后的調試覆蓋顯示確實變得更加清晰、美觀了。
通過在重疊判斷中排除那些只是邊緣接觸的元素(即只“輕微接觸”的物體),我們成功避免了不必要的排序判斷,也大幅減少了因排序循環產生的視覺閃爍問題。這種處理邏輯非常合理,因為那些幾乎沒有實質重疊的對象,本就不該強行介入排序關系的比較中。實際效果也印證了這個優化的價值 —— 排序更加穩定,調試信息也更易觀察。
我們當前的系統狀態令人滿意,有幾個積極的結果:
- 排除邊緣接觸的重疊元素后,排序循環問題明顯減輕;
- 原本的視覺閃爍(尤其是在 tile 邊界處)已經被有效解決;
- 排序結構整體更加智能化,不再因為輕微接觸而生成無意義的復雜比較圖;
- 測試中通過啟用 debug group 顯示,能清楚看到排序關系的調整結果,便于確認邏輯正確性;
- 排序系統也更具魯棒性,即使存在大量復雜結構,也仍能較好維持預期行為。
此外,我們也注意到未來仍需處理一項重要問題 —— X軸方向的傾斜(skew)。當前存在一些非法狀態,即某些元素發生了不允許的 X 方向傾斜,這會導致排序邏輯失效,因為現有的排序規則并不能正確處理這種偏斜后的空間投影關系。因此,在繼續推進系統前,還需要對這些異常狀態進行清理和糾正,確保所有渲染實體都符合排序系統的假設前提。
最后,我們還觀察了一些高度跳躍判斷的邏輯,發現系統會自動選擇不那么陡峭的落點,也就是說它會優先選擇下落距離不太大的區域作為落點,這一點看起來也是符合預期的。
總之,當前我們對系統所處的狀態感到滿意,排序邏輯基本成立,調試工具正常工作,錯誤和異常現象顯著減少。下一步將集中精力解決傾斜問題,并確保后續邏輯構建在一個更清晰、穩定的排序結構之上。
思考如何利用渲染器中的層數據
我們目前通過引入 chunk_z
實現了將所有渲染實體分組的能力,完成了對場景中各個對象的層級劃分。這為后續的渲染排序奠定了基礎。但是,到目前為止,渲染系統本身還沒有真正利用這些分組信息,這才是目前的主要問題。
我們早前在渲染管線中實現了剪裁矩形(clip rects)機制,理論上這些剪裁矩形可以用來關聯不同的渲染組,例如用來處理透明圖層(alpha group)的疊加效果。但目前的結構中,各種邏輯交織在一起,導致渲染系統還無法真正基于這些剪裁區域或圖層信息來合理地處理復雜的渲染層級,尤其是透明層。
理想情況下,我們希望能將透明層(alpha group)中的內容單獨渲染到一個離屏緩沖區(offscreen buffer)中,最終再將該緩沖區合成回主畫面。這就需要渲染系統具備更高層次的上下文感知能力,理解哪些對象處于透明圖層、哪些不在,但目前的架構還不足以支撐這一點。
不過有一個方面我們可以先著手處理,那就是**霧效(fog)**的繪制邏輯。霧效本質上是對不同深度圖層的透明度處理,而這部分邏輯其實可以從主渲染流程中剝離出來,單獨處理。我們只需要在繪制時判斷當前是否處于 alpha 漸隱層(alpha fade layer),以及當前層的透明度級別,而不必讓整個渲染系統理解所有的分組細節(例如目前存在的六個 chunk_z
層級)。
也就是說,大部分這些分組和圖層結構其實只是為霧效服務的,并非渲染必須要深入理解的內容。所以我們可以優化思路:
-
把霧效處理從渲染器中抽離出來,由更高層負責提供 alpha 衰減信息;
-
渲染器只需要關心兩件事:
- 當前對象是否屬于 alpha 衰減層;
- 當前 alpha 層的透明度等級是多少;
-
這樣一來,渲染系統只需處理“是否做漸隱”和“做多少”的簡單問題,而不是陷入復雜圖層結構的處理。
通過這種方式,渲染器邏輯可以保持簡單、獨立,同時我們仍然能夠實現正確的層級顯示、透明度控制和霧效渲染。這是目前較為理想的解耦路徑,也有助于未來優化透明圖層合成與性能。
修改 game_entity.cpp:簡化 UpdateAndRenderEntities(),只關注 alpha 和霧層級別
我們現在開始處理的問題是簡化并改進原先用于霧效(fog)和透明層(alpha layer)處理的機制,目標是去掉不必要的復雜結構,比如 clip_rects
,并用更直接的方式控制圖層效果。
首先我們決定統一使用一個叫做 fog_amount
的變量來表示當前圖層的霧效程度。這個值決定了某個層中繪制元素的透明程度,fog_amount = 0
表示完全不透明,fog_amount = 1
表示完全透明。這種方式比使用復雜的剪裁矩形和分組結構要直觀得多。
我們發現,原先的渲染流程中使用了多個剪裁矩形(clip rects),每個圖層一個,但其實這些剪裁區域并沒有真正被利用起來。事實上,渲染邏輯并沒有按剪裁層級處理,而是根據圖層的深度排序。因此我們決定徹底移除與 clip rect 相關的邏輯和數據結構。這些代碼雖然存在,但并沒有帶來實質作用,反而增加了復雜性。
在實際修改過程中,我們進行了以下關鍵步驟:
- 去除 clip rects 和相關結構:包括清理
clip_rect_effects
、剪裁層設置、相關計算和使用邏輯; - 保留關鍵控制參數:我們保留了
fog_amount
和test_alpha
,但test_alpha
也準備后續移除; - 重構透明度計算:使用
clamp_map_to_range
函數簡化透明度的處理邏輯,并將它直接映射到fog_amount
; - 統一處理流程:只使用一個默認剪裁區域,不再有復雜的分層裁剪;
- 保持霧效一致性:通過直接控制透明度值,霧效仍然得以實現,但實現方式更清晰、更易維護。
最終效果是,原本零散而復雜的圖層處理系統被濃縮為一個由 fog_amount
控制的簡單機制,渲染邏輯也變得更清晰。現在的系統只關注“當前圖層的透明度是多少”,而不需要關心任何額外的圖層裁剪信息。這不僅提升了代碼可維護性,也為后續優化透明圖層的渲染效率打下基礎。
修改 game_entity.cpp:關閉 Alpha
我們現在開始處理的目標是將實體的色彩處理(color blending 或稱色盲渲染調整)整合到更系統化的流程中,而不是像之前那樣手動進行操作。
當前我們已經可以設置每個實體的 colorblend
值,這個值會影響所有渲染出來的內容。也就是說,我們已經有了讓實體根據某種設定改變其顏色的機制,不過這個機制目前還是手動控制的。
手動控制帶來一些問題,比如:
- 渲染邏輯不清晰:因為是手動指定的,有時不容易看清楚具體顏色是怎么被最終決定的;
- 一致性差:不同部分可能采用不同的色彩處理方式,容易出錯;
- 維護困難:每次想要修改整體色彩表現,都需要手動找對應代碼,效率低下。
為了解決這些問題,我們考慮將 colorblend
值直接作為變換(transform)的一部分屬性處理。這樣一來,在顏色校正(rectification)階段,統一處理實體的顏色調整邏輯,就不需要在后續渲染流程中手動應用了。這種方式也和之前曾經試過的方案類似,但這次希望將其做得更加干凈徹底。
我們開始嘗試這個重構思路的初步驗證:
- 首先,將手動顏色調整部分(如
piece_colors
的相關邏輯)移除; - 顏色混合的邏輯被整合到了變換流程中,在顏色被“修正”或轉換的時候統一應用;
- 然后通過設置
colorblend
屬性來觀察最終效果,發現上方圖塊還沒有變透明,說明 alpha 值并未降為 0,這與我們的預期一致,因為現在是從統一變換中推導出來的,而不再依賴額外的手動透明處理邏輯。
通過這一系列更改:
- 色彩控制邏輯被統一放入實體變換的屬性中;
- 移除了原先混亂的手動顏色處理代碼;
- 渲染結構更加清晰、可控,色彩的處理更加一致;
- 后續實現色盲模式、風格變換等視覺模式也更方便。
目前系統已經能夠通過實體自身的屬性統一控制其在渲染中的顏色變化,達成了更清晰和高內聚的設計目標。后續可繼續拓展類似 fog、alpha 等效果進入這種機制,實現完全自動化和數據驅動的渲染控制體系。
修改 game_render_group.cpp:讓 StoreColor() 接受 object_transform 指針,并修改其它相關函數以接受指針參數
目前我們正在對渲染系統中的顏色處理方式進行一次結構性優化,目的是將顏色混合(如色盲適配、透明度處理等)從全局狀態轉移到每個對象的局部變換(transform)中,以實現更清晰、模塊化的渲染邏輯。
以下是詳細的處理與重構思路:
整體目標
- 將
store_color
(顏色存儲與混合邏輯)綁定到每個對象的transform
上,而非使用全局變量; - 優化 transform 的傳遞方式,改為以指針形式傳遞,避免結構體過大造成的低效;
- 清理原有的全局狀態和不再使用的片段,統一使用更簡潔、更清晰的方式處理顏色;
- 保證 debug 構建時性能仍保持較優,方便調試與跟蹤。
具體操作與邏輯說明
-
將顏色混合邏輯從全局移入 transform
- 原先使用全局狀態來控制顏色(比如色盲值或 alpha 值),現在我們將其作為
object_transform
的一部分; - 每個對象的顏色混合狀態將與其變換狀態一起傳遞與生效,更加邏輯一致;
- 實際渲染過程中,在調用
store_color
時,改為讀取object_transform
中的相關屬性。
- 原先使用全局狀態來控制顏色(比如色盲值或 alpha 值),現在我們將其作為
-
使用指針方式傳遞 transform 結構體
- 鑒于
object_transform
較大,為避免頻繁復制,改為指針傳遞; - 所有涉及
transform
的調用,如render_layered_scene()
、reserve()
、default_flat_transform()
等函數中,均改為傳遞指針; - 這樣也便于動態修改其狀態或擴展更多臨時渲染控制信息。
- 鑒于
-
清除原本全局變量及雜亂邏輯
- 統一去除舊有的全局混合狀態變量;
- 去掉在
store_color
或其他與顏色相關函數中曾依賴的外部設置邏輯; - 保持
transform
成為唯一權威的顏色變換數據源。
-
考慮編譯器優化與 debug 構建表現
- 雖然現代編譯器可能足夠聰明處理值傳遞,但為了避免 debug 模式下編譯器不優化導致低效,手動選擇更直接的方式;
- 保持調試時可以準確查看 transform 狀態,避免因結構內聯或優化丟失調試信息;
- 即便是在非 release 構建下,也追求良好的可維護性和執行效率。
-
代碼推進與修改方式
- 在具體推進時,采用系統性的替換策略,將原來傳值的地方一律替換為地址傳遞;
- 大量是“體力活”類型的重復勞動,但為了系統干凈性,這一步是必要的;
- 借此也表達出對現代開發工具的不滿(調侃意義),因為這類操作本該由 IDE 或構建工具自動完成。
當前狀態總結
- 渲染中的顏色混合不再依賴全局狀態,而是綁定到每個對象的變換信息;
- 結構體傳遞方式更高效,也更適合進行渲染階段的臨時調整;
- 調試流程變得更清晰,每個對象的顏色邏輯一目了然;
- 下一步可基于此機制拓展其他類型的 per-object 渲染控制,如模糊、反射、特殊 shader 效果等。
整個過程雖繁瑣,但極大提升了渲染系統的模塊化與可維護性,為后續開發打下了堅實基礎。
修改 game_entity.cpp:讓 UpdateAndRenderEntities() 設置頂層和遠處霧的 Color 和 tColor
目前,我們完成了顏色信息從全局狀態遷移到每個實體的 transform
中的改造,使得渲染流水線中的所有顏色處理,包括透明度、顏色混合等,都能在每個實體級別進行控制。接下來我們繼續完善這套機制,使其能自動響應不同圖層的霧效處理。
當前系統的狀態
- 每個實體的
transform
中已可設置顏色信息; - 渲染管線中,顏色計算已正確沿著
transform
信息傳播; - 霧效處理現在也將在
transform
中控制,而非全局控制。
接下來的處理邏輯
1. 每個圖層的處理方式根據其相對層級不同而不同:
-
若為最頂層(即最大層級索引):
- 該圖層屬于 alpha 漸隱層;
- 此時只處理 alpha 混合,不改變顏色;
- 因此設置
tcolor
(臨時顏色)時只影響 alpha 通道; - 不必指定具體顏色值。
-
若為非最頂層(普通圖層):
- 該圖層需要參與霧效混合;
- 此時需要將背景顏色設置為混合基底;
- 并根據該圖層的霧效強度設置
tcolor
的 alpha 值; - 從而實現逐層霧效遞增的視覺效果。
2. 實現邏輯中的變量準備:
-
fog_amount
:- 根據圖層索引直接查表或計算獲取;
- 用作
tcolor.a
(透明度)的輸入; - 表示當前層級的霧化程度(0 為無霧,1 為完全霧化);
-
背景顏色(blend to color):
- 為霧效混合的目標色;
- 應設置為全局統一背景色,以保證層疊一致性;
流程示意(偽代碼形式)
if (relative_layer_index == max_layer_index) {// 頂層圖層,只處理透明度漸隱transform->tcolor = vec4(1.0, 1.0, 1.0, computed_alpha);// 不設置背景混合色
} else {// 霧化層,混合背景色與實體色transform->blend_to_color = background_color;transform->tcolor = vec4(1.0, 1.0, 1.0, fog_amount_for_layer);
}
實現意義
- 我們現在可對每個實體單獨指定顏色與混合方式,無需依賴全局狀態;
- 渲染過程更清晰,邏輯分明,便于維護和調試;
- 霧效與 alpha 混合統一由
transform
控制,避免狀態污染和錯亂; - 不同圖層可以實現獨立視覺效果,如逐漸遠去的層次感或前景透明渲染。
整個重構過程確保了從數據結構到渲染邏輯的高一致性和高內聚性,為后續擴展更多視覺特效(如光照、遮罩、焦距模糊等)打下了穩定基礎。
運行游戲,進入 UpdateAndRenderEntities() 并檢查 EntityTransform 的值
目前,我們完成了顏色混合邏輯的接入和實體 transform
內部的顏色狀態設置,理論上來說,渲染階段應該可以正確處理這些顏色值。然而,實際運行時并未如預期生效,接下來我們針對這個問題進行排查和修正。
當前問題描述
- 已經設置了
tcolor
值(代表當前層的顏色混合信息); - 渲染器應當在寫入顏色時應用該值;
- 但屏幕上并未看到預期的顏色變化效果;
- 初步懷疑:可能是默認的 transform 初始化沒有設置正確值,或顏色值未被正確傳遞。
排查過程與發現
1. 默認 transform 初始化問題
- 默認的 transform 初始化可能將所有字段設為 0;
- 包括
tcolor
、blend_to_color
等關鍵字段; - 如果未顯式設置,可能導致顏色全為黑或 alpha 為 0,最終不渲染出任何內容;
- 因此必須確保在創建 transform 時為這些字段賦合理初值。
2. 測試 alpha 值的問題
- 當前用于控制圖層漸隱的
test_alpha
值看起來出現了邏輯反轉; - 原先的設計是 alpha 趨近于 0 時越透明,但現在呈現出相反效果;
- 推測是在混合計算中進行了
1 - value
之類的變換,導致計算方向反了; - 需確認:
tcolor.a = fog_amount
的方向是否與 shader 中 alpha 混合一致。
修正方向
1. transform 初始化中設置默認值:
transform->tcolor = vec4(1.0f, 1.0f, 1.0f, 1.0f); // 默認完全不透明
transform->blend_to_color = vec4(0.0f, 0.0f, 0.0f, 0.0f); // 默認無混合背景
確保所有創建的 transform 都擁有合理默認值,避免后續未顯式賦值時出現渲染異常。
2. 修正 test_alpha
值方向:
- 如果目標是從完全不透明 → 漸隱為透明;
- 那么應確保
alpha = 1 - fade_value
或在 shader 中明確使用該關系; - 如果已在 CPU 端計算
tcolor.a
,則避免重復反轉。
例如,若當前計算為:
float fade = compute_fog_fade(layer_index);
transform->tcolor.a = 1.0f - fade; // 若 fade 為 0.0 ~ 1.0,表示霧的強度
那就必須確認渲染端是否也是用相同方向的 alpha 混合。
進一步建議
- 可在調試時設置斷點,確認渲染前每個實體
transform
的tcolor
值; - 在 shader 內部打印或用特殊顏色驗證是否 alpha 正常傳入;
- 添加日志機制或可視化輔助線,以便快速發現混合結果異常區域;
- 若調試麻煩,可考慮先將霧效設置為固定值,驗證整體渲染路徑是否通暢。
綜上所述,當前問題主要是由默認初始化和 alpha 值方向反轉共同造成的。在修復這些細節后,顏色混合應可按預期工作,渲染效果也將回歸正常。整個系統將更具可控性和可維護性,后續擴展更加靈活。
修改 game_entity.cpp:讓 UpdateAndRenderEntities() 反轉 TestAlpha 的計算
目前整體渲染流程已經趨于正常,顏色混合部分(尤其是 ti_color
的應用)已回歸預期狀態。我們在渲染階段可以看到顏色值的正確生效,說明渲染管線中的顏色數據傳遞與應用邏輯是連通的。
當前工作狀態總結
-
test_alpha
邏輯回退至之前舊版本實現方式- 當前的
test_alpha
值處理方式已回歸先前設計; - 這意味著它與舊邏輯保持一致性,并能在渲染輸出中產生正確的 alpha 混合效果;
- 渲染輸出中可以觀察到顏色與透明度的變化符合預期。
- 當前的
-
渲染階段顏色混合邏輯正常運行
- 通過檢查 render 內部流程,可以看到
ti_color
的設置已經傳遞到對應的渲染路徑; - 整體顏色混合效果如預期工作,說明顏色變換和混合已修復完成。
- 通過檢查 render 內部流程,可以看到
-
霧效部分已具備初步效果
- 當前霧效(fog)已開始在畫面中可見;
- 可視區域中出現了因圖層高度而造成的顏色混合與漸隱;
- 霧效的起作用區域依賴于圖層高度和 fog 混合邏輯,判斷是否處于最大圖層范圍。
下一步操作建議
-
增加圖層以測試更多霧效變化
- 當前層數可能不足以完全觀察霧效在遠距離的混合過程;
- 建議手動創建更多層級,使視野可以進一步穿透,驗證霧效在不同層高下的效果。
-
觀察并確認霧效是否隨高度正確變化
- 確保隨著相機視角或對象高度提升,霧效的混合程度正確改變;
- 檢查
fog_amount
在每一層的計算是否平滑過渡; - 確認背景顏色是否正常混合進畫面,未出現全黑或未初始化狀態。
-
記錄當前霧效效果基線
- 當前實現狀態已經可作為一個穩定基線;
- 若后續需調整
fog
的參數(如濃度、漸變速率等),可基于當前結果進行微調; - 建議加注注釋標記霧效使用的位置和關鍵點,方便日后維護與擴展。
總結
渲染通道已建立正確的顏色混合流程,test_alpha
和 ti_color
的設置在各個 transform 上被成功傳遞與應用,霧效部分也開始在多圖層結構中產生可視化表現。接下來可通過增加圖層并進行視覺驗證,確保整個霧效系統在縱深方向上具備平滑、合理的漸隱效果,至此整個渲染系統的霧效處理進入一個穩定的階段。
修改 game_world_mode.cpp:讓 AddStandardRoom() 生成多層房間
我們目前將所有相關邏輯整合到了 world_nerd
模塊中。接下來,通過調用 add_standard_room
接口,可以批量添加多個房間堆疊在一起,從而測試霧效、圖層渲染、透明度混合等功能在多層空間結構中的表現是否正常。
當前狀態詳細總結如下:
-
邏輯已集成至 world_nerd 內部
- 所有與圖層、顏色混合、transform、透明度相關的邏輯現已內聚在
world_nerd
中; - 這樣做簡化了系統結構,使后續調試和擴展更清晰。
- 所有與圖層、顏色混合、transform、透明度相關的邏輯現已內聚在
-
通過 add_standard_room 可連續添加多個房間
- 可以一次性堆疊多個房間結構,形成多層地圖或立體場景;
- 每個房間都被放置于不同圖層,從而形成高度差異,用于霧效等視覺效果的驗證。
-
支持霧效深度漸變測試
- 多層房間結構可以直接用來測試霧效隨圖層變動的漸隱與顏色融合;
- 可驗證
fog_amount
與test_alpha
的混合是否隨著高度合理過渡; - 同時可以確認顏色在不同層之間的混合是否與
ti_color
設置保持一致。
-
渲染路徑完整運行
- 添加房間后,相關實體 transform 顏色參數設置已完整接入渲染流程;
- 渲染系統能夠正確響應圖層信息并繪制透明度、霧效、背景顏色等。
建議進一步操作:
- 嘗試構建超過三層以上的房間結構,觀察不同高度下霧效變化;
- 在每層房間內加入視覺錨點(如高對比度貼圖),以便驗證顏色混合是否平滑;
- 可考慮對
add_standard_room
增加位置偏移參數,構造復雜場景布局; - 后續如需更復雜霧效(非線性漸變、基于視角等),可在此基礎上繼續擴展。
總結
當前我們已成功將霧效與圖層透明度邏輯整合進主流程,并通過堆疊房間機制實現了對渲染管線的測試準備。add_standard_room
接口作為核心測試手段,可以靈活創建多層場景,為進一步優化視覺表現提供良好基礎。下一階段可專注于霧效表現細節的調整與性能測試。
運行游戲,觀察多層房間效果,并查看性能分析器
目前我們注意到在堆疊更多房間時,出現了一些有趣的現象,尤其是在與房間渲染和調試模式表現相關的部分。以下是詳細情況整理:
當前觀察到的關鍵現象和問題:
-
close_traversable
與buildsberry
表現差異明顯close_traversable
是目前少數未使用基本空間分區(basic partition)結構的系統;- 相比之下,
buildsberry
已啟用基本分區,并承擔更多空間管理邏輯; - 兩者在運行負載上出現明顯差異。
-
buildsberry
在 debug 模式下表現不佳- 當前
buildsberry
承擔了較大的計算壓力; - 在 debug 模式中性能下降尤為嚴重,可能存在較多斷言檢查、未優化的內存訪問等問題;
- 導致調試體驗較差,尤其在堆疊多個房間后更明顯。
- 當前
-
需要驗證優化編譯下的表現差異
- 初步判斷,debug 模式的低性能并不能準確反映最終表現;
- 因此計劃在優化編譯(optimized build)下進行快速測試,以確認系統是否能在實際使用中維持性能;
- 拍照或快照操作也計劃進行,以便對比渲染輸出或用于后續驗證。
其他補充:
- 多房間堆疊測試中也可能揭示基礎數據結構(如分區管理、實體變換、顏色混合)潛在的問題;
- 如果
close_traversable
在未使用空間分區下表現更流暢,可能提示我們需要重新評估buildsberry
的分區策略或其調試代碼復雜度; - 圖層管理、霧效處理等邏輯已能正確響應不同房間高度變化,進一步支持這類分析。
建議后續操作:
- 在優化模式下運行
buildsberry
并記錄性能指標; - 比較其與
close_traversable
的幀率、內存使用和響應速度; - 分析 debug 模式下哪些部分導致性能瓶頸,考慮是否能局部優化或加以屏蔽;
- 若有必要,構建可視化工具追蹤房間堆疊與渲染開銷的關系。
總結
當前我們已觀察到 buildsberry
在 debug 模式下因壓力過大表現不佳,而 close_traversable
結構相對簡單、無分區邏輯,運行更輕盈。這一對比有助于分析空間分區和調試模式的性能影響,接下來將在優化模式下進一步驗證系統在真實運行時的表現,以確定是否需要調整相關模塊的架構設計。
“我的天啊!為什么有這么多頭在跟著我?”β
目前我們觀察和測試了一些關鍵渲染特性,特別是與霧效、圖層、Z軸偏移和渲染分層相關的內容。以下是當前工作的詳細總結:
渲染和霧效狀態確認:
- 霧效在底層區域渲染結果正確,這意味著顏色混合、圖層透明度、視距遮蔽等邏輯基本正常。
- 當前我們主要關注的目標之一就是驗證這些霧效在特定圖層深度下是否呈現正確,目前結果令人滿意。
屏幕內容混亂與設計目標偏離:
- 當前畫面中存在大量元素(例如多個“頭部”實體)圍繞玩家移動,視覺效果令人不適,且不是預期效果。
- 雖然暫時不會優先處理,但這可能需要后續設計優化,例如減少視覺噪聲、壓縮敵人數量或行為頻率。
圖層與 Z 值的問題:
-
我們現在擁有兩個關于 Z 位置的概念:
- 圖層(Layer)的位置索引;
- 實體在該圖層內的實際 Z 偏移。
-
為了實現正確的遮擋、渲染順序和深度感,需要對 Z 值的處理邏輯進行整合與調整。
-
目前尚未處理該問題,但已經明確必須要完成此步驟,才能確保系統穩定性和正確性。
圖層滲透與可見性異常:
- 目前觀察到有一些圖層滲透的現象,例如本應被遮擋的生命條(health rectangles)在不應出現的圖層中露出;
- 理論上完全不應透過主圖層,但由于所有元素目前都被統一添加到了渲染圖(graph)中,導致可能存在渲染周期或依賴沖突;
- 如果圖中出現循環依賴(cycles),可能導致渲染順序錯誤或渲染結果不符合預期;
- 當前做法違反了最初設計意圖,因此將來必須將不同種類的元素(如HUD、實體、背景等)劃分為不同的渲染階段或通道(passes)。
后續改進思路:
-
明確分離不同類型的渲染元素,例如:
- 實體渲染 Pass;
- HUD 渲染 Pass;
- 后期處理 Pass;
-
避免將所有元素直接插入單一渲染圖結構,以免形成復雜依賴,導致渲染次序錯亂;
-
在分層渲染基礎上增加獨立的遮擋檢測或剪裁策略。
時間限制與開發安排:
- 當前僅剩大約 12 分鐘開發時間;
- 本階段不打算深入實現 Z 處理或分層 Pass 管理;
- 但上述兩者已被確認為后續必做的重點開發項。
其他探索:
- 未來計劃嘗試一些“技巧型”處理方式,簡化圖層渲染依賴或通過批次遮擋策略解決局部問題;
- 這些技巧并不能取代系統級架構調整,但在短期開發周期內可暫時緩解部分問題。
小結:
目前霧效與圖層透明處理已基本正確,但仍存在圖層滲透與Z軸錯位的問題未解決。圖層與實體的渲染順序及結構劃分需進一步完善,并通過多 Pass 渲染架構重構,以徹底解決依賴錯誤和遮擋異常等潛在渲染問題。雖然當下開發時間有限,但整體方向已清晰,接下來的工作將集中在圖層渲染邏輯與Z位置處理的完善上。
考慮始終用實體最初的 ChunkZ 來渲染
我們正在探索一種優化渲染順序的策略,核心思想是:在進行渲染排序時,始終使用實體在本次模擬開始時所處的 chunk Z 值,而不是他們最終移動結束后的 chunk Z 值。
背后的動機與好處:
- 實體是在按 chunk 分塊的結構中流式加載的,因此它們天然已經是按照 chunk Z 值有序排列的;
- 如果我們始終使用它們起始時的 chunk Z 值進行渲染排序,就能完全避免重新排序的開銷;
- 對于大多數靜止或微小移動的實體,這種排序方式不會造成任何視覺錯誤;
- 整體上,這是一個性能友好型方案,能夠極大減少排序邏輯的復雜度和運行時負擔;
- 從使用感受上來看,這種機制“感覺很好”,具有非常吸引人的簡潔性和穩定性。
潛在問題:
- 當實體正在跨越兩個 chunk 邊界時(例如從一個 chunk 移動到另一個 chunk),由于使用的是起始位置的 chunk Z 值,會造成某一幀的渲染順序錯誤;
- 這意味著在極少數幀中,某些實體可能出現在錯誤的深度上,從而影響遮擋關系;
- 不確定這種“錯一幀”的視覺錯誤是否可接受,但初步判斷問題可能比較小,尤其是在場景復雜度較高、動作頻繁時用戶感知不強。
屏幕空間排序的問題:
- 屏幕空間排序的情況無法直接使用相同優化方式;
- 屏幕空間錯誤一旦發生,其可見性和影響非常明顯,會造成嚴重的錯位或遮擋混亂;
- 因此,屏幕空間排序仍然需要更加精確的實時更新與處理邏輯,不能使用類似的“起始狀態緩存”方法。
當前結論:
- 對于 chunk 層級的 Z 值排序,使用模擬開始時的 chunk Z 是一個“幾乎白送的優化”,擁有顯著優勢;
- 即使存在某一幀的錯序風險,其實際影響也可能是可以接受的;
- 暫時決定采用該方法,以換取性能與邏輯上的清晰性;
- 屏幕空間排序方面仍需保持精確的實時處理。
后續可能探索:
- 對“移動中實體”的特殊處理機制,比如在跨 chunk 時標記其狀態,特殊處理渲染順序;
- 對于屏幕空間排序是否存在某種“近似”優化方式的深入測試與實驗;
- 可視化調試支持,幫助開發中觀察是否出現明顯的錯幀渲染錯誤。
整體來說,這是一個兼具工程效率和運行效率的優化策略,適合當前架構在性能和復雜度之間找到平衡點。
黑板討論:排序障礙物
我們正在提出一種改進渲染排序流程的方案,目標是在不影響排序邏輯正確性的前提下,大幅減少排序的開銷并提升組織性。核心構想如下:
基本思路
當前我們已經將渲染條目(slices)以一定的順序寫入渲染緩沖區(render buffer)。現在我們設想通過在這些切片之間插入屏障標記(barrier token),來實現**局部排序(bucket sort)**的能力。每一組條目(每一個邏輯層級)在渲染緩沖區中由一個屏障標記分隔。排序時,只需要在兩個屏障之間做本地排序即可。
渲染排序流程的改造
我們希望在構建渲染圖(build spray graph)時,像這樣處理:
- 渲染條目會按照邏輯層級(layer)順序寫入;
- 在每個層之間插入一個特殊標記,表示這一層的終止點;
- 排序器(sort_entries)在處理時遇到此標記,立即對當前段內條目執行排序,然后開始新一段;
- 這個屏障標記可以使用特殊的 key 值(manual key)來實現,標記其為 barrier 類型。
這樣,整個渲染排序就會像分段流水線一樣處理,而不是整個一次性排序。
優勢
- 減少無效排序:不需要將全部渲染條目都丟到一個排序器中處理;
- 提升可控性:每個邏輯層級內部排序彼此隔離,方便調試與分層渲染;
- 提高性能:局部排序在數據量較小的范圍內完成,排序開銷更小;
- 簡化實現:邏輯上很容易實現,只需添加對特殊 key 的判斷邏輯。
初步實現構想
在 sort_entries
或 sort_right_bounds
這類排序函數中,實現如下邏輯:
for (int i = 0; i < entry_count; ++i) {if (is_barrier_key(entries[i].manual_key)) {sort_current_bucket(start, i);start = i + 1;}
}
其中,is_barrier_key()
判斷是否為插入的特殊標記,sort_current_bucket()
對當前段執行排序。
實際收益場景
- 渲染層級特別多時,減少全局排序代價;
- 動態場景頻繁變化時,提升幀穩定性;
- 調試或設計工具中提供更清晰的結構化渲染輸出。
下一步計劃
這個機制暫時不會立即實施,而是作為后續的改進方向先行構思出來,待到下次繼續開發(例如下周)時再投入實現。
總體來說,這是一個低成本、高回報的渲染排序架構改進方案,能帶來更強的靈活性與性能表現。
修改 game_render.h:定義 SPRITE_BARRIER_OFFSET_VALUE
我們考慮在渲染排序流程中引入一個“精靈屏障值”(sprite barrier value)的機制,用于優化渲染條目的分段排序。這個屏障值將作為一個特殊標記,用來區分渲染緩沖區中不同邏輯分區的邊界,從而實現分段式的排序處理。以下是詳細構想:
精靈屏障值機制
設想創建一個特殊的屏障標識符,比如稱作 sprite_barrier_value
,其作用是:
- 表示排序邊界:一旦渲染隊列遇到這個值,就意味著到達了當前排序塊的末尾;
- 不參與排序本身:這個值僅作為排序觸發點,排序邏輯不會把它視為普通渲染項;
- 使排序塊分割清晰:不同層或不同邏輯區域被隔離開,每段單獨排序。
實現層面考慮
在構建渲染節點時,我們將該值插入到指定位置,作為邏輯層之間的“錨點”:
- 遍歷輸入節點時,記錄
input_nodes_count
; - 在合適位置(如每個邏輯分層結束之后)插入
sprite_barrier_value
; - 在排序階段判斷當前條目是否是該特殊值,如果是則觸發排序操作,并重置段的起始位置;
- 這個特殊值可以是一個不會出現在實際實體中的保留數值,例如負值或特定 bit pattern。
示例邏輯
排序函數偽代碼:
for (int i = 0; i < node_count; ++i) {if (entries[i].manual_key == SPRITE_BARRIER_VALUE) {sort_range(start, i - 1); // 排序當前段start = i + 1; // 下一段開始}
}
系統集成邏輯
- 在 build 階段,處理
input_nodes
并在適當時機插入 barrier; - 在排序函數中根據
manual_key
判斷是否為屏障; - 在渲染時跳過渲染此特殊條目,確保它不被繪制。
意義與優勢
- 排序粒度優化:更高效地控制排序塊大小,避免全局排序;
- 渲染結構清晰:渲染邏輯上更易組織與維護;
- 性能提升:小范圍排序速度遠快于整體排序;
- 支持擴展:后續可擴展為圖層隔離、特效區分等更多用途。
總之,這一機制將顯著提升渲染排序流程的清晰度與效率,是針對當前層級排序瓶頸所設計的切實可行的解決方案。
修改 game_render.cpp:讓 BuildSpriteGraph() 接收并返回 NodeIndexA,由 SortEntries() 傳遞
我們現在需要做的事情是,在構建渲染排序系統時引入一個控制排序分段起點的機制,避免全局排序帶來的開銷。目標是更靈活地控制每段排序的開始與結束位置。以下是詳細內容:
核心目標
不再從 0
開始遍歷所有輸入節點 (input_node_count
),而是:
- 接收一個明確指定的起始值;
- 通過該值控制每次排序從哪里開始;
- 實現排序邏輯的分段式執行。
實現步驟
-
設定起始值
在外部控制邏輯(如構建精靈圖的函數)中設定一個初始的first_index
值,初始為 0;int first_index = 0; while (first_index < total_count) {first_index = build_sprite_graph(..., first_index); }
-
在
build_sprite_graph
中處理 barrier
在遍歷過程中,判斷當前節點是否是“排序屏障”,若是,則提前返回當前索引,表示本輪排序終止點:for (int i = start; i < count; ++i) {if (offsets[i] == SPRITE_BARRIER_OFFSET_VALUE) {return i + 1; // 下次從這里繼續}// 處理正常節點 }
-
將屏障值放入
offsets
中
為避免污染manual_key
字段,使用offsets
來存儲特殊屏障值(如-1
或其他非法偏移):if (offsets[i] == SPRITE_BARRIER_OFFSET_VALUE) {// 不進行排序處理 }
-
測試機制
在測試階段可以插入偽 barrier 值用于驗證分段是否正確:if (offsets[i] == SPRITE_BARRIER_OFFSET_VALUE) {// 停止本輪排序,測試分段是否有效 }
效果和意義
- 支持多輪分段構建排序圖:每次從上次 barrier 后開始處理;
- 顯著提高排序效率:避免無意義的全量排序;
- 邏輯清晰可控:通過 barrier 分隔各個圖層或區域;
- 屏障信息存儲更合理:利用
offsets
避免干擾manual_key
等核心字段。
這個方案使我們可以靈活地構建圖層隔離、優化渲染處理流程,同時也方便后續擴展,例如支持動態圖層、區域遮擋裁剪等。通過精確控制每段渲染數據的邊界,進一步優化性能并降低維護復雜度。
運行游戲,確認沒有變化
現在運行的話,基本上處于穩定階段,暫時不需要做任何改動,系統應該一切正常。但接下來,我們可以回頭對之前的方案做出調整和完善,這部分工作計劃在周一進行。到時候會根據目前的情況,把之前的改動落實并優化,使整體流程更加合理和高效。
問答環節
那些召喚獸都從哪兒來的?!
突然出現了很多隨從,我們完全不清楚它們是從哪里來的。每個層級都會生成一個隨從,具體原因不明。觀察了隨從的行為,它們似乎只是簡單地沿直線移動,代碼里它們只檢查目標位置是否被阻擋,其他情況似乎沒有判斷。這些隨從行為怪異,讓人覺得有點詭異。既然沒有更多疑問,我們可以提前結束了。
蛇看起來壞掉了?
蛇的表現很奇怪,只有一節身體出現,像是爬上了樓梯到更高的層級,但其他蛇都正常,沒有出現這種情況,這個bug挺詭異的。之前好像沒見過這種情況。雖然蛇爬樓梯本身沒問題,只要它能及時避開其他物體,但這只蛇跳到了頂層,我們根本不知道它是怎么做到的。估計是代碼里關于z軸跳躍高度的邏輯有問題,沒對爬升做限制,導致它能直接選擇高樓層。場景里還有些斷頭隨從漂浮,畫面顯得怪異,雖然這些代碼不是我們主要關注的,但整體行為怪異,讓人感覺詭異和不安。