游戲引擎學習第303天:嘗試分開對Y軸和Z軸進行排序

成為我們自己的代碼精靈α

所以現在應該可以正常使用了。不過,這兩周我們沒辦法繼續處理代碼里的問題,而之前留在代碼里的那個問題依然存在,沒有人神奇地幫我們修復,這讓人挺無奈的。其實我們都希望有個神奇的“代碼仙子”,能幫我們把代碼問題一揮而就,回來時發現所有問題都解決了,但現實是我們必須自己動手,自己想辦法解決問題。既然如此,那我們就得自己動手“灑代碼魔法粉”,開始修復工作。今天準備好鍵盤,準備開始繼續努力了。

回顧并為今天的內容做準備

我們繼續探討精靈排序的問題。之前我們已經基本確定,單一的統一排序方法行不通,至少需要比統一排序更復雜的方案。我們開始嘗試構思如果采用完整的圖排序(graph sort)會是什么樣子,但其實不確定是否真需要完整的圖排序。離開期間,在論壇上有人提出了各種建議,有幾種不同的解決方向,但目前還不清楚哪種方法才是最合適的。唯一可以確定的是,簡單的統一排序無法滿足需求,需要更復雜的處理方式。

Z緩沖不適合二維內容

從直覺上來說,我們認為使用Z緩沖區(Z-buffering)并不是一個好主意。原因在于,我們需要處理一些語義上的特殊排序,比如英雄的頭部應該畫在身體前面,盡管從技術上講頭部可能應該在身體后面。如果用Z緩沖,我們就得不斷和它“作斗爭”來實現這些特定效果,這會非常麻煩和復雜。

我們希望找到一種不用Z緩沖的解決方案,因為雖然Z緩沖在真正的3D環境下表現出色,但在2D或偽3D情況下,它往往需要很多“假裝”的技巧來調整,確保它能按照我們想要的方式工作,同時避免出現錯誤的繪制順序。沒有真實的3D模型和形狀時,想讓Z緩沖準確無誤地運作非常困難。

目前的代碼和渲染器還在排序部分工作,具體實現還在調整中。我們開始考慮建立一個精靈圖(sprite graph)的結構來處理排序問題,但具體細節還在探索階段。接下來還要升級開發環境,解決一些版本問題,然后繼續完善排序邏輯。

quartertron的排序建議,或者用插入排序實現簡易的二叉空間劃分法1

聊天中有人建議,可以先分別對Z軸精靈和Y軸精靈進行排序,因為這兩類精靈各自內部的排序都是可行且正確的。然后再把這兩個已排序的列表合并。這種思路可能解決一些排序問題,但具體是否正確還不確定,還需要進一步驗證。

另外,有人提議可以用插入排序來模擬一種簡易的二叉空間分割(BSP)樹。插入排序不同于現在用的排序方法,它更像是逐個比較并找到元素合適插入位置,類似在一棵二叉樹中逐層測試并定位。這種方法可能有助于更準確地對數據進行排序,形成一種類似BSP樹的結構。

不過,這種方式的問題在于如何保持樹的平衡,否則排序性能可能退化到平方級別(N2),這需要特別考慮和處理。

總體來說,采用分別排序Y精靈和Z精靈再合并的方法比較簡單易行,但是否能解決現有的所有問題還不明確。為了進一步理解,也考慮查看當前的項目進展和數據,但還沒有升級到最新的開發環境版本,這需要盡快完成。

黑板講解:分別對Z軸精靈和Y軸精靈排序,然后合并排序這些已排序的精靈

我們現在討論的方案,基本上是先把精靈分成兩類:一類是平躺在地面的精靈,這些沿著Z軸排列,稱為Z精靈;另一類是直立的精靈,面向我們,沿Y軸排列,稱為Y精靈。

對于Z精靈來說,因為它們平躺且不會相互穿透,所以可以用Z軸坐標來排序,理論上不用管Y軸排序。即使兩個Z精靈一個在另一個后面,只要它在空間中更高,也不會真正發生重疊。

對于Y精靈來說,它們直立且只沿Y軸排序即可,因為Y精靈之間不會因為Z軸位置變化而影響相互排序,只有前后關系決定繪制順序。

這樣,我們可以分別對Z精靈和Y精靈進行排序,保證兩組各自有正確的繪制順序。問題是排序好后,如何合并這兩個排序列表?即在繪制時,如何決定先繪制哪類精靈,什么時候切換到另一類。

聊天中有人建議,先用各自的排序規則(Z軸和Y軸)對兩組精靈分別排序,然后用一個合并步驟來將兩個排序列表融合成一個最終繪制順序。這個合并步驟可以用類似歸并排序中的合并操作,利用一個簡單的“假定”排序規則來決定哪個精靈先畫。

這個方法的優點是靈活:我們可以用任意合適的排序算法(比如基數排序)分別對Z精靈和Y精靈排序,最后只需一個歸并操作完成最終排序,拓展了排序算法的選擇空間。

此外,這個方案雖然巧妙,但還未完成:我們還需要改進渲染部分,特別是考慮精靈的實際邊界(范圍),以避免繪制順序錯誤。目前的邊界處理還不完善,可能導致錯誤的繪制結果。

具體實現上,可以先在合并排序的第一步,將所有精靈分成兩個桶(Y精靈桶和Z精靈桶),分別排序后再合并。如果發現這樣做效率不高,可以考慮在生成精靈時就把它們分別放入不同的桶,以減少排序時的開銷。

總的來說,這個方案思路清晰,易于擴展且有一定算法美感,值得嘗試實現和測試。

game_sort.cpp:引入MergeSort()的兩個版本——MergeSortY()和MergeSortZ()

我們現在討論的合并排序算法,主要是基于“誰在前面”的排序標準來實現的。為了實現這種排序,首先需要對精靈的邊界信息進行跟蹤,比如每個精靈的最小Y值。排序的目標是確保繪制時,離觀察者更遠的精靈先繪制,離觀察者更近的精靈后繪制,從而實現從后到前的正確渲染順序。

具體來說,排序時會比較兩個條目(例如兩個精靈)的最小Y值。如果條目A的最小Y比條目B更小,說明A離觀察者更近,應該后繪制,因此需要交換它們的位置,保證繪制順序正確。排序過程中,在進行分區操作時也是基于同樣的邏輯:優先繪制最大最小Y值的精靈。

類似地,對Z軸的排序也是用同樣的合并排序邏輯實現,只是排序的依據變成了Z軸相關的邊界值。對于X軸也可以這樣處理,但目前重點還是在Y軸和Z軸的合并排序。

目前代碼的實現比較冗長,很多功能是重復的,比如Y軸和Z軸的排序代碼幾乎一模一樣,感覺有些重復,可能會考慮用模板或泛型來簡化代碼結構,減少重復代碼。不過這樣做會在編譯時生成重復的代碼,沒辦法完全節省最終輸出代碼體積,只是讓源碼更簡潔。

總體來說,這個合并排序方案的核心是在排序時結合精靈的邊界信息(最小Y或最小Z)來判定繪制順序,保證視覺效果的正確。實現時需要克隆部分排序代碼以便跟蹤精靈邊界,暫時沒有辦法用單一通用排序函數處理所有情況,但后續可能會尋找更優雅的代碼合并方案。

先改一下tile_sort_entry

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

引入MergeSort()的兩個版本——MergeSortY()和MergeSortZ()

在這里插入圖片描述

game_sort.cpp:引入IsInFrontOf()的兩個版本——IsInFrontOfY()和IsInFrontOfZ()

我們現在的目標是完成一種合并排序的方案,分別對Y軸和Z軸的精靈進行排序,然后再將它們合并為一個最終的渲染順序。整個流程依賴于一個“誰在前面”的判斷邏輯,即根據精靈的位置,決定哪個精靈應當先繪制。

核心邏輯如下:

1. 拆分排序

我們將所有的精靈分為兩類:一種是以Y軸為主的精靈(站立的,比如角色、柱子),另一種是以Z軸為主的精靈(貼地的,比如地面效果、陰影)。對這兩類分別進行排序:

  • Y軸排序規則

    • 若精靈A的最小Y值(ymin)小于精靈B的ymin,說明A靠近屏幕下方,也就是離觀察者更近,應該后繪制。
    • 因此:如果 A.ymin < B.ymin,A 就應排在后面,發生交換。
  • Z軸排序規則

    • Z軸方向與屏幕朝向相反,因此Z值越大,代表越靠近觀察者。
    • 因為精靈只有zmax(沒有zmin),所以比較的是zmax值。
    • 若A的zmax大于B的zmax,說明A更靠近觀察者,應后繪制。
    • 因此:如果 A.zmax > B.zmax,A 就應排在后面,也發生交換。

通過上述兩個判斷,我們就可以分別對Y軸精靈和Z軸精靈使用合并排序來獲得兩個有序列表。

2. 合并階段

在完成Y軸和Z軸的獨立排序之后,我們需要一個“最終合并排序”的階段,將這兩個有序列表組合成一個總的繪制順序。

此合并過程需要一個統一的“誰在前面”的判斷準則來決定從哪個列表中取下一個元素。例如:

  • 若當前Z軸精靈A應在當前Y軸精靈B之前繪制,那么就從Z列表中取出A;
  • 否則,就從Y列表中取出B。

這個邏輯可能仍舊基于類似于前述的“誰在前面”比較函數,也可能需要一個更高級的合并策略來處理交叉渲染的邊界情況(比如一個Z軸地面元素壓在一個靠前的Y軸角色腳下的情況)。

總結:

  • 我們實現了兩個合并排序邏輯,分別基于Y軸最小值(ymin)和Z軸最大值(zmax);
  • 通過這種分開排序,再進行最終合并的方式,我們可以獲得一種較為穩健的繪制順序;
  • 當前的實現雖然冗長重復,但可以先確保邏輯清晰和正確;
  • 后續可以通過函數模板或策略模式優化代碼結構,避免重復邏輯;
  • 這種排序方式避免了Z-buffer的復雜性和控制困難,適合需要自定義繪制順序的半2D場景渲染邏輯。
    在這里插入圖片描述

考慮如何區分Y軸和Z軸精靈

我們現在正在完善整個精靈排序系統的合并階段。到目前為止,我們已經實現了兩個分別基于Y軸(Y精靈)和Z軸(Z精靈)的合并排序邏輯。接下來的工作,是將這兩種類型的精靈排序結果合并為一個統一的渲染列表。下面是我們詳細的操作思路和處理流程:


1. 異質數據的預處理:分離Y精靈和Z精靈

這次的排序不再是對一個同質(相同類型)精靈集合進行排序,而是對一個包含Y精靈和Z精靈的混合集合進行操作。因此,我們第一步要做的就是將它們分離

具體思路是:

  • 創建一個臨時緩沖區(Temp Buffer),大小等于當前所有精靈的緩沖區;
  • 將所有的Z精靈移動到這個臨時緩沖區中;
  • 再將所有的Y精靈復制回主緩沖區中;
  • 最終形成兩個獨立的子集:一個包含所有Z精靈(在temp中),一個包含所有Y精靈(在原緩沖區中);
  • 這種方式利用現有的緩沖區空間來分離數據,避免額外內存分配。

這種處理雖然在效率上不是最優的,但目前的目標是先驗證整個算法鏈是否可行。未來如有性能瓶頸,可以直接在輸出時就將精靈寫入不同的緩沖區,從根源上避免在渲染階段再拆分。


2. 獨立排序

完成分離后,我們對這兩個子集分別使用前面實現的兩個排序函數:

  • Z精靈使用基于zmax的排序邏輯(較大者優先繪制);
  • Y精靈使用基于ymin的排序邏輯(較小者優先繪制);

由于每個子集內部都是同一類型的精靈,它們的排序邏輯是一致的,排序過程是清晰且高效的。


3. 合并兩個已排序的緩沖區

當Y精靈和Z精靈分別排好序之后,我們進行最后一步:合并這兩個排序好的列表

此時使用的是“誰在前面”的判斷邏輯(is_in_front_of):

  • 比較兩個當前候選精靈的位置;
  • 判斷哪個應當先繪制;
  • 將其插入到最終的渲染順序中;
  • 依次推進,直到兩個列表都被完全處理;

該階段相當于一個雙指針遍歷合并排序(Merge Sort)中兩個有序數組的標準合并步驟。


總結流程:

  1. 分離精靈

    • 使用一塊足夠大的臨時緩沖區;
    • 將Z精靈移動到Temp,Y精靈保留在原緩沖區;
  2. 各自排序

    • Z精靈使用zmax降序合并排序;
    • Y精靈使用ymin升序合并排序;
  3. 最終合并

    • 使用統一的“前后”判斷準則(is_in_front_of);
    • 將兩個排序結果合并為一個最終渲染列表;

這種設計雖然初期在實現時多了一步數據拆分的開銷,但其邏輯清晰,結構穩定,并為后續性能優化和算法替代提供了明確的邊界(比如可以替換排序算法、改為GPU排序、使用并行策略等)。后期如果驗證此邏輯正確且高效,可以再做底層結構調整,從一開始就將不同類型的精靈分開處理,進一步提升渲染階段的效率。
在這里插入圖片描述

game_sort.cpp:讓MergeSort()區分Y軸和Z軸精靈

我們要遍歷整個緩沖區中的所有元素。首先確定當前正在處理的精靈(sprite)在緩沖區中的索引位置,也就是我們當前所在的“first sprite index”。

然后,對于每一個元素,我們可以判斷它是否是一個特定類型的精靈,比如是否是一個Z的精靈(IsZSprite)或 Z 向的精靈(Z sprite)。如果是某種特定類型,比如東向精靈,我們希望將其移動到臨時目錄(temp directory)中進行處理。

在實現上,我們可以使用一個類似 temp[zCount++] = me; 的語句,把當前的精靈對象放入臨時數組中。這里 zCount 是一個用于記錄已處理 Z 類型精靈數量的計數器,每次添加一個就自增一次。整個過程是一個草圖式的設計思路,目的是先大致規劃出整個數據篩選與處理邏輯的結構。

核心步驟包括:

  • 遍歷整個緩沖區
  • 獲取當前元素的索引
  • 判斷是否屬于某種類型(如 Z sprite)
  • 將符合條件的元素移動到臨時結構中
  • 更新對應的計數器(如 zCount++)

整體意圖是為了從緩沖區中篩選出特定類型的精靈,并為后續操作(如移動、排序或重組)做準備。
在這里插入圖片描述

黑板講解:分離并壓縮數組

我們開始將元素從原始緩沖區移動到另一個臨時緩沖區時,首先需要理解緩沖區中包含了各種不同類型的元素,比如 Z 精靈(Z sprite)、Y 精靈(Y sprite)等。假設我們希望把所有的 Z 精靈提取出來并放入一個新的空緩沖區中。

具體操作中,每當我們遍歷到一個 Z 精靈時,將它復制到臨時緩沖區中,這個步驟本身并不復雜。但是,關鍵在于:一旦將某個 Z 精靈移出原緩沖區,該位置就形成了一個“空洞”(hole),原來的數據結構中就出現了一個空位。

為了處理這個空位,我們需要在繼續遍歷的過程中執行壓縮操作,即把后面的非 Z 元素(比如 Y 精靈)向前搬移,填補這些空洞。比如,找到一個 Y 精靈后,應將其拷貝到前一個空位所在的位置,保持原緩沖區的連續性。這種做法的目的是對原緩沖區進行就地壓縮,使得未被移除的元素能夠連續存放,避免中間出現無效的空隙。

因此,我們需要兩個關鍵步驟配合完成:

  1. 從原緩沖區中篩選出所有 Z 精靈,并依次復制到臨時緩沖區中;
  2. 同時在原緩沖區內,對剩余非 Z 元素進行壓縮搬移操作,以消除由于移除 Z 精靈產生的空洞。

這樣既能完成目標元素的提取,也能保證原緩沖區的數據結構維持緊湊。整個過程是為了實現數據結構的邏輯整理和優化,確保后續處理效率更高。

game_sort.cpp:繼續實現MergeSort()分離精靈功能

我們在處理緩沖區中的精靈數據時,需要引入幾個關鍵變量來輔助邏輯的實現:一個是 Y 精靈的計數器(yCount),一個是 Z 精靈的計數器(zCount),以及當前處理的索引(index)。

具體做法如下:

我們會有一個指向當前處理元素的指針,比如 sortSpriteBound,它等于原始緩沖區的起始地址加上當前索引 index,代表我們正在查看的那個元素。

接下來,我們檢查這個元素的類型:

  • 如果是 Z 精靈,就把它復制到臨時緩沖區中對應 zCount 所指的位置,并將 zCount 自增。這樣做的結果是原緩沖區當前位置形成了一個“空洞”。
  • 如果不是 Z 精靈(即是 Y 精靈),我們就把它壓縮移動到緩沖區最前面尚未使用的空間,也就是當前的 yCount 所指的位置,并將 yCount 自增。

需要注意的是,在最開始我們還沒遇到任何 Z 精靈時,Y 精靈可能會被復制到它原本所在的位置,這種情況下會出現“自拷貝”,但這并不會影響結果,因為復制到自己身上是無害的。我們可以選擇優化這部分邏輯,比如在位置相同的時候跳過拷貝,但當前階段的重點不是性能優化,而是先確保邏輯正確性。優化的事情可以在確認這個方法是可行、值得使用之后再進行。

執行完這一遍遍歷之后,我們會得到兩個結果:

  • 原始緩沖區只剩下所有 Y 精靈,順序被壓縮整理過;
  • 臨時緩沖區中存放著所有 Z 精靈。

接下來,我們可以將 Z 精靈從臨時緩沖區復制回原始緩沖區的末尾,或者反過來,把 Y 精靈復制到 Z 精靈之后。這樣做的目的是將兩個類別的精靈組合回一個連續的數組中,同時保持順序和結構的清晰。這種方法雖然不是最高效的實現方式,但在當前階段我們更關注邏輯正確性而不是性能。

另外提到一個補充點:理想情況下我們應該提前準備好足夠的臨時緩沖區空間(temp memory),避免運行中頻繁申請內存。如果是在更嚴謹的代碼結構下,臨時緩沖區應該作為參數傳入,而不是硬編碼處理,但在當前這個草圖階段,為了簡化思路,我們可以先內嵌操作,以驗證整體流程是否可行。
在這里插入圖片描述

黑板講解:兩個數組的大小

我們在前面的步驟中,將緩沖區中的元素分離成了 Y 精靈和 Z 精靈兩類,并分別放入不同的位置:Y 精靈保留在原緩沖區的前部分,Z 精靈則被移動到了一個臨時緩沖區中。

現在我們意識到一個非常重要的事實:原緩沖區和臨時緩沖區一開始的大小是相同的,因此分離出來的元素數量本質上決定了剩余空間的大小,也就是說:

  • 原緩沖區中,Y 精靈占據了前面一部分空間,后面的未使用部分正好等于我們提取出去的 Z 精靈數量。
  • 同樣地,Z 精靈所在的緩沖區,只被填滿了部分,剩下的空間正好等于我們保留下來的 Y 精靈數量。

也就是說,這兩個緩沖區中未被占用的部分,正好可以用作對方的臨時空間。這是一個非常關鍵的推理,因為它意味著我們不再需要額外的內存來進行歸并排序的中間緩沖區處理。

因此,我們現在可以非常巧妙地利用這些已經“空出”的空間來執行歸并排序的第一階段。具體來說,我們可以:

  • 使用 Y 精靈所在緩沖區的“尾部空白”作為 Z 精靈排序的臨時空間;
  • 使用 Z 精靈所在緩沖區的“尾部空白”作為 Y 精靈排序的臨時空間;

通過精確計算指針位置和傳入合適的參數,我們可以在不再需要額外內存分配的前提下完成整個排序流程的合并階段。這種方法本質上是一種內存復用策略:利用數據結構自身經過預處理后產生的空白空間,實現無額外內存開銷的高效排序。

這個設計顯著提升了空間利用率,同時降低了系統復雜度,也為后續進一步的優化打下了良好的基礎。

game_sort.cpp:讓MergeSort()調用MergeSortY()和MergeSortZ()

我們接下來可以調用已有的歸并排序(merge sort)函數來分別對 Y 精靈和 Z 精靈進行排序。這里使用的是之前已經實現好的標準歸并排序算法。

我們現在已經知道:

  • yCount 是 Y 精靈的數量;
  • zCount 是 Z 精靈的數量;
  • 原始緩沖區 first 中前段是 Y 精靈;
  • 臨時緩沖區 temp 中前段是 Z 精靈。

為了排序:

  • 對 Y 精靈排序時,我們以 first 為起點,傳入的臨時緩沖區是 temp + zCount,也就是跳過前面的 Z 精靈,用其后部作為臨時空間;
  • 對 Z 精靈排序時,我們以 temp 為起點,臨時緩沖區使用 first + yCount,也就是跳過前面的 Y 精靈,用其尾部作為臨時空間。

這兩個排序過程完成后,Y 精靈和 Z 精靈分別在自己的緩沖區中是有序的,且都已經就地排好。

需要注意的是,我們目前的實現中,歸并排序函數在排序完成后會執行一個數據復制回原數組的步驟,即將臨時緩沖區中的數據拷貝回原緩沖區。這一部分邏輯現在其實已經不太必要了,因為我們可以直接在原始數組中操作。但由于該歸并排序函數是遞歸的,并且可能多次使用臨時緩沖區中的內容,因此當前階段我們還是保留這個復制操作,避免影響其正確性。不過,在最終優化中,這段復制邏輯是完全可以去除的,從而減少不必要的內存操作。

在完成對 Y 精靈和 Z 精靈的各自排序后,我們分別有兩個已排序的子區段:

  • first 指向排序后的 Y 精靈;
  • temp 指向排序后的 Z 精靈。

接下來我們需要做的是:將這兩個已排序的子區段合并成一個完整的排序結果。也就是實現歸并排序的最后一步。

然而這一步的合并存在一個挑戰:標準歸并的策略通常是假設有一個足夠大的空間可以將兩個已排序數組按順序合并寫入。而在當前這個結構中,我們并不清楚是否有足夠的臨時空間可以支持這種合并操作,也無法像前面那樣“巧妙”地利用已知空白區進行內存復用。

因此我們可能無法像前面那樣精細復用已有緩沖區的空白部分來完成這一合并操作,這一階段可能就需要一個真正獨立的合并緩沖區,或采用一種額外的內存支持。這也提示我們,在后續實現中需要評估是否保留這種合并方式,或者重新設計這一階段的內存策略以保持整體的空間效率。
在這里插入圖片描述

在這里插入圖片描述

黑板講解:排序數據的復制

在這一部分,我們意識到一個非常巧妙的細節:盡管我們以為排序后的結果需要“拷貝回原緩沖區”,但其實并不需要。

原因是,在歸并排序過程中,我們實際上是進行了一個“復制式排序”操作,也就是說,排序結果本質上已經被保留在原始緩沖區或另一個緩沖區中。換句話說,即便我們傳入的是 firsttemp,排序函數本身并沒有銷毀源數據,而是將排序結果復制到了目標位置,同時源緩沖區中的數據依然完整保留。

這意味著我們在排序完成后,實際上手頭有兩個副本:

  • 一個是在目標緩沖區中的排序結果;
  • 另一個是在臨時緩沖區中保留下來的排序副本。

因此,我們原本以為在歸并排序后需要將結果再“拷貝回來”以保證順序一致,其實完全沒有這個必要。因為排序結果早就已經以正確的形式存在了,不管是要讀取排序后的 Y 精靈還是 Z 精靈,都可以直接使用已經排序好的緩沖區內容。

這個認識帶來了一個直接的優化機會:我們可以省略掉歸并排序函數最后的“結果拷貝”步驟,既減少了一次數據傳輸,又提升了效率。

這同時也提醒我們,在整個排序系統的設計中,理解數據在緩沖區中的真實分布狀態,以及每一步操作對原始數據和目標數據的影響,是非常重要的。掌握了這些,就可以做出更高效、更精簡的實現邏輯。

game_sort.cpp:考慮讓MergeSortZ()復制到臨時緩沖區

我們在這里思考的是一種邊界情況:當歸并排序過程中實際上沒有“實際工作”要做時(即數據已經是有序的,或只包含一個元素),我們該如何處理拷貝行為。

首先明確目標:最終所有的數據都應該集中在 first 緩沖區中。因此,在排序 Z 精靈的時候,我們不能完全依賴排序函數中的“就地結果”,因為在某些情況下(例如數據本身已經有序),排序函數可能不會實際執行任何拷貝動作,數據就停留在了 temp 緩沖區中。這就造成了一個潛在問題:我們最終需要的排序結果可能還留在了 temp 中,而不是 first 中。

為了解決這個問題,我們需要做兩件事之一:

  1. 強制拷貝結果:即使排序過程中沒有真正交換或移動元素,也要確保我們將排序結果復制到目標緩沖區(first)。這是為了保證最終的數據位置符合整體流程的設計要求。

    • 比如,在 Z 精靈排序時,如果排序函數的實現會將最終結果保留在 temp,那我們必須手動將其復制回 first
    • 同理,在 Y 精靈排序中也一樣,確保結果最終都統一保存在 first
  2. 修改排序邏輯:也可以考慮修改歸并排序函數,在其設計中支持顯式指定“結果應該落在哪個緩沖區中”。這樣在排序完成后,排序函數能自動判斷當前操作是否需要執行真正的拷貝,或只是指針切換。這種方法更優雅也更高效,但也更復雜。

此外,還涉及一個小例子來說明這個問題:

  • 假設我們排序的是兩個元素,分別是 first[0]first[1]
  • 如果它們本來就已經排好序了,那么排序函數可能根本不會將數據寫入臨時緩沖區;
  • 但在我們的系統中,我們預期排序結果應該最終位于 first 中。

因此,為了避免這些邊界問題帶來的混亂,我們當前階段選擇保守做法,即 無論是否真正發生排序操作,都執行一次結果拷貝,以確保數據位置正確可靠。這雖然可能多花了一點時間成本,但可以換來更清晰的邏輯和更容易維護的代碼結構。

在未來優化時,我們可以再考慮是否對這些無效拷貝進行剪枝優化。
在這里插入圖片描述

game_sort.cpp:當有1或2個Z軸精靈待排序時,讓MergeSort()復制到臨時緩沖區

我們現在討論的是對 Y 精靈和 Z 精靈分別進行歸并排序的具體策略,以及如何處理緩沖區的數據拷貝問題,確保最終數據正確且效率較高。

首先,針對排序的邊界情況,如果 Z 精靈的數量小于等于 2(比如 1 或 2 個),我們必須強制執行一次數據拷貝操作,將結果從臨時緩沖區復制回主緩沖區,因為在這種情況下歸并排序不會自動完成拷貝。這是為了保證數據的最終一致性和正確性。

排序的流程是:

  • 對 Y 精靈執行歸并排序,數量是 yCount,數據存在主緩沖區 first 中,臨時緩沖區使用 temp + zCount(即跳過 Z 精靈部分的空間);
  • 對 Z 精靈執行歸并排序,數量是 zCount,數據存在臨時緩沖區 temp 中,臨時緩沖區使用 first + yCount(跳過 Y 精靈部分的空間)。

這里我們用條件判斷來區分 Y 精靈和 Z 精靈的排序依據:

  • 判斷一個元素是 Y 精靈還是 Z 精靈,通過比較它的排序關鍵字(比如 wMinwMax)是否相等;
  • 如果不相等,則認為是 Z 精靈,按 Z 的排序規則進行排序;
  • 如果相等,則認為是 Y 精靈,按 Y 的排序規則進行排序。

針對小規模 Z 精靈(數量小于等于 2)的特殊情況,明確在排序后要把 temp 中的數據復制回 first,這樣保證了主緩沖區包含所有最終的已排序數據。

接下來,我們整理了緩沖區的使用方案:

  • 主緩沖區 first 包含排序后的 Y 精靈;
  • 臨時緩沖區 temp 包含排序后的 Z 精靈;
  • 臨時緩沖區剩余部分和主緩沖區剩余部分互為對方臨時使用的空間。

關于緩沖區的切換,優化思路是:

  • 原先可能會先從主緩沖區讀取數據,再寫入臨時緩沖區,最終又要把數據復制回主緩沖區,產生多余的拷貝;
  • 優化后可以直接以臨時緩沖區作為數據讀取源,直接寫入主緩沖區,從而避免最后的額外復制步驟。

具體實現上就是把歸并排序的輸入緩沖區改成臨時緩沖區 temp,而輸出緩沖區改成主緩沖區 first,利用好兩者剩余的未使用空間作為輔助緩存。

這樣一來,排序的流程更簡潔,避免了不必要的數據搬運,保證了內存利用效率和性能。

總結來看,我們通過判斷數據規模,合理安排歸并排序的輸入輸出緩沖區,并配合必要的拷貝,確保無論大小數據都能正確有序地存放到預期的緩沖區,且整體內存操作更加高效、簡潔。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

game_sort.cpp:清理編譯錯誤

在這里插入圖片描述

在這里插入圖片描述

遞歸調用棧溢出嗎

在這里插入圖片描述

在這里插入圖片描述

修改一下

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

段錯誤

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

運行游戲,觀察排序結果是否接近預期

我們現在的進展雖然比之前好多了,明顯有了改進,但整體來看距離理想狀態仍然還有不少差距,工作還遠未完成。雖然基礎框架已經搭建起來,排序和合并邏輯也更加清晰,但仍存在需要優化和完善的地方,需要繼續努力才能達到預期的效果和效率。

運行游戲,觸發SortEntries()中的斷言錯誤

我們打算更仔細地檢查當前的排序和合并邏輯,尤其是比較操作部分。雖然有一點不確定,比如某些條件下的判斷標準是否完全正確,但暫時先不急于斷定有無錯誤。因為如果排序或比較邏輯本身沒問題,那么排序整體才有意義。我們認為現在下結論還為時過早,重點是先細致地逐步梳理和驗證每一步的實現,確保基礎邏輯沒有問題,再去排查潛在的錯誤。
在這里插入圖片描述

在這里插入圖片描述

調試器:進入MergeSort(),檢查YCount和ZCount

打算深入檢查排序的效果。首先,從整體流程看,我們開始時會檢測緩沖區里有多少Z精靈和Y精靈,并分別對它們進行排序。觀察排序完成后的結果,可以看到Z精靈和Y精靈的數量符合預期。具體來說,Z精靈數量遠多于Y精靈,因為每個地板瓷磚都是Z精靈,而Y精靈通常是樹、英雄等占用的對象。這種分布符合邏輯,說明分類和計數過程是合理的。接下來,我們應該更細致地檢查排序后的具體內容,確認排序操作是否按預期正確執行。

game_sort.cpp:給MergeSort()添加驗證測試

我們決定從索引0開始,遍歷到Y精靈的數量范圍內,逐一檢查每個精靈,斷言它們確實屬于我們預期的類型。這樣做的目的是驗證分類和排序是否準確無誤,確保每個位置上的精靈類型符合之前的邏輯判斷,避免潛在的錯誤或數據混淆。這個步驟是為了對之前的處理做一個嚴格的確認,保證數據的完整性和正確性。
在這里插入圖片描述

game_sort.cpp:引入IsZSprite()函數

我們在代碼中定義了一個判斷精靈類型的函數(比如isZSprite),并且考慮使用之前傳遞過的參數,比如sortSpriteBound(排序精靈邊界)來幫助判斷。通過寫這個函數,我們可以更方便地根據傳入的邊界或條件來確定當前處理的是哪種類型的精靈,從而使分類和排序過程更加明確和結構化。這樣做能夠提升代碼的可讀性和可維護性,同時確保判斷邏輯的一致性。
在這里插入圖片描述

game_sort.cpp:繼續完善MergeSort()的測試

我們根據是否是“IsZSprite”(假設是某種類型標識)來分類精靈。無論哪種情況,Y緩沖區里存放的是Y類型的精靈,Z緩沖區里存放的是Z類型的精靈。這樣,我們明確區分了兩種類型的精靈,確保它們分別歸類到對應的緩沖區中,方便后續的排序和處理。
在這里插入圖片描述

調試器:進入MergeSort(),確認通過測試

這樣做能夠給我們一定的保障,驗證所有精靈確實被正確地分配到了兩個緩沖區中,分類和排序過程沒有出錯。這看起來是有效的,接下來還可以進一步增加其他驗證或處理步驟,確保整體流程的正確性和穩定性。

game_sort.cpp:引入VerifyBuffer()函數驗證緩沖區內精靈類型,并讓MergeSort()調用它

我們可以寫一個驗證函數,輸入一個緩沖區,然后判斷緩沖區里所有元素是否都是Z精靈或者Y精靈。傳入true表示驗證全是Z精靈,傳入false表示驗證全是Y精靈。這樣,我們可以用它來驗證各個緩沖區,比如用它來驗證Y精靈緩沖區和Z精靈臨時緩沖區。合并排序后,也可以用這個方法來驗證排序結果是否正確。比如排序后,我們檢查half 0緩沖區里的全部都是Z精靈,數量是Z計數;half 1緩沖區里的全部都是Y精靈,數量是Y計數。這樣就可以確保排序和分類過程的準確性。
在這里插入圖片描述

沒觸發斷言

在這里插入圖片描述

調試器:再次進入MergeSort(),確認數據都正確

現在我們至少可以驗證兩個主要緩沖區,確保它們輸出的內容沒有經過錯誤的魔法式亂序,所有元素都在正確的位置上。驗證就是通過大量斷言來完成的,確保分類和排序過程中沒有出錯。整個過程其實很簡單,就是遍歷檢查所有數據是否符合預期,保證了數據的正確性和排序的準確性。

考慮做更好的合并操作

在驗證完成后,確認排序的內容是正確且有序的,我們接下來需要把這些部分合并起來。不過現在的合并過程還有些問題,特別是在處理這些平面分層的Z軸和Y軸排序時,當前的合并方式可能不夠理想。因為我們是按Z軸層和Y軸層逐步向上處理的,決定下一步該選擇哪個層次時,簡單判斷兩個元素是否重疊并不一定夠用。

所以我們覺得需要設計一個更完善的合并方法,才能正確地將這些分層排序結果合并。具體該怎么做還需要進一步思考和調整。目前還不清楚別人是怎么處理這部分的,可能需要參考其他人的方案或者再深入研究。總之,合并部分是當前的關鍵難點,需要更多時間和精力來完善。

黑板講解:排序的具體操作

現在遇到的情況是,從側面觀察場景,攝像機往下看,我們實際上是在沿著兩個方向移動不同的“平面”。每次到達一個新的平面時,我們必須決定這個新的平面是不是應該下一個繪制,或者是之前的平面應該先繪制。這個決策看起來很難做出。

心里有個疑問,如果能準確地做出這個繪制順序的決策,那為什么不能用一個單一的排序標準來完成呢?這讓整個方案顯得有些怪異,不太確定目前的方法到底能不能真正奏效,畢竟很難判斷哪一個平面該先繪制。

總之,這部分的排序和繪制順序問題還很復雜,暫時沒有明確的解決方案,還需要繼續思考和嘗試。

game_sort.cpp:查看IsInFrontOf()函數的實現

我們現在快速回顧一下當前的邏輯:

目前處理的是 Z 精靈和 Y 精靈之間的排序問題。在實際測試中,“兩個都是 Z 精靈”的情況現在理論上不會發生了,至少在設計上不應該再出現這種情況。雖然在驗證函數(如 is_in_front_of)中可能仍然會調用這種情況,但在實際排序邏輯里不會遇到兩個 Z 精靈的比較。

在比較時,對于“兩個都是 Z 精靈”的分支邏輯,當前邏輯會繞過它。我們現在會進入接下來的兩個判斷分支,也就是判斷 A 包含 B 或 B 包含 A 的部分。

因為現在我們不再有兩個 Z 精靈之間的比較,那么在判斷是否要以 Z 為主排序時,之前的邏輯是:

  • 如果兩個都是 Z 精靈,或者它們之間有 Y 軸重疊(即 Z 精靈和另一個精靈的 Y 范圍有交集),那么我們以 Z 值排序;
  • 否則就以 Y 值排序。

這意味著:即使一個是 Z 精靈,另一個是 Y 精靈,只要兩者在 Y 軸上存在重疊,就仍然按 Z 值排序,以保證視覺層次正確;如果沒有交集,就按 Y 值排序。

接下來邏輯上我們比較的是:

  • 如果按 Z 排序,就比較 zmax
  • 否則按 ymin

這一點與之前的排序方式保持一致,也就是說:

  • 如果 Z 值有重疊或者是純 Z 精靈比較,則使用 zmax 決定前后;
  • 否則,使用 ymin 來判斷。

整體來看,這套排序系統試圖通過判斷幾何包圍盒在 Y 和 Z 平面上的關系來決定繪制順序,核心是保持視覺正確性。邏輯逐步剝離了復雜情況,比如兩個都是 Z 精靈的分支,從而簡化了判斷流程。下一步仍需要繼續驗證這些邏輯在邊界條件下是否能夠穩定運行。

# game_sort.cpp:讓VerifyBuffer()驗證精靈排序是否正確

現在我們對 verify_buffer 函數進行改進,目的是不僅僅驗證緩沖區中的元素是否是 Z 精靈或 Y 精靈,還要驗證這些元素在排序上是否是正確的順序。我們添加了一個新的邏輯:遍歷整個緩沖區,確保其中每個元素都“在前”于它前面的那個元素,也就是說,緩沖區中的元素已經正確地按繪制順序進行了排序。

具體實現方式是:

  • 遍歷緩沖區,從第一個元素開始(跳過索引 0);
  • 對于當前元素和前一個元素,調用 is_in_front_of 或類似函數;
  • 確保當前元素處于正確的前后順序(即當前元素要在前一個元素的后面);
  • 如果不滿足條件,則通過斷言拋出錯誤。

這樣做的好處是,我們不僅可以驗證緩沖區是否裝入了正確類型的精靈(Z 或 Y),還可以驗證排序是否生效,從而捕捉潛在的排序邏輯錯誤。

這種檢查屬于雙重驗證機制:

  1. 類型驗證:確保 Z 精靈和 Y 精靈沒有被混入錯誤的緩沖區;
  2. 順序驗證:確保排序邏輯(如根據 zmaxymin)在結果上是有效的。

這讓我們對排序流程的準確性更有信心,也使得調試更加高效。

在這里插入圖片描述

在這里插入圖片描述

調試器:運行游戲時觸發VerifyBuffer()斷言

在進入合并操作的核心邏輯之前,我們發現自己之前出現了一些混亂。面對這種情況,我們的處理方式是:由于整個過程中有大量數據在流動,如果一味依賴調試器逐步查看數據,會變得非常低效。因此,我們選擇了一種更直接的策略 —— 編寫專門的測試代碼,對關鍵狀態進行自動驗證。

我們采用的思路如下:

  • 不是試圖在調試器中手動檢查大量變量,而是通過添加斷言或驗證函數來自動驗證數據是否符合預期;
  • 特別是在排序合并前,我們增加了驗證函數 verify_buffer,用于檢查精靈是否被正確分類(Z 精靈或 Y 精靈);
  • 同時,我們還在驗證函數中添加了順序檢查,確保排序操作之后的數據確實是按預期順序排列的;
  • 這些驗證手段幫助我們快速發現數據處理中的邏輯錯誤,而不需要反復手動調試。

這種方法的優勢在于能在邏輯混亂或者狀態復雜的階段,及時攔截并定位潛在問題,為后續合并處理的“魔法部分”提供了一個干凈、可信賴的數據基礎。通過自動化驗證手段,我們可以更有信心地進行后續復雜邏輯的實現與測試。

關于“測試驅動開發”的簡短討論

我們之所以選擇在這一階段添加更復雜的測試邏輯,是基于一個明確的權衡判斷:通過寫測試代碼來驗證當前的排序和合并邏輯,可能比手動排查 bug 更高效。這并不嚴格屬于“測試驅動開發(TDD)”,但背后的核心思想是類似的,即在開發過程中判斷寫測試是否能節省時間。

在這種思考模式下,我們始終在做一個理性取舍:

  • 每當遇到需要調試的代碼時,我們會思考:如果此處寫測試,是節省時間還是浪費時間?
  • 大多數情況下,尤其在游戲開發中,寫測試未必劃算,測試代碼本身的維護成本也不容忽視;
  • 但也有例外,比如當前這種排序和合并邏輯復雜、數據交互繁多的情形,寫測試反而是更省時省力的方式;
  • 這也是為什么我們在這個階段選擇了編寫自動化斷言和驗證函數的原因,期望能更快發現并定位 bug;

當然,目前的測試代碼是否能準確發揮作用,還要進一步確認。有可能我們并沒有發現真正的 bug,而只是測試本身寫錯了。這也是為什么我們認為測試驅動開發雖然有幫助,但始終伴隨著一定成本的原因——測試邏輯本身也必須正確,才能發揮其價值。

總結來說,在開發過程中,我們持續動態評估寫測試是否值得,尤其在邏輯密集或調試成本高的環節,測試往往能帶來切實的幫助。此刻,我們正是基于這樣的考量,決定通過測試來加速問題定位與驗證流程。

調試器:進入VerifyBuffer(),檢查失敗時精靈的位置

我們決定深入檢查循環第一次執行時的具體情況,以確保整體邏輯大致正常。從測試結果來看,雖然有部分測試通過,但仍存在失敗情況,說明測試并非完全無效,而是有一定參考意義的。

在某次失敗的測試中,我們發現被比較的兩個精靈具有相同的 Z 值。也就是說,這兩個對象在深度排序上沒有明確的先后順序。正是這種情況導致斷言失敗,引發我們進一步的思考:當兩個對象具有相同的 Z 值時,是否仍然應該執行進一步的排序邏輯來保證穩定性。

于是,我們開始重新評估當前的排序策略。最初的做法是在某些條件下才使用特定的排序邏輯,比如僅在兩個對象都為 Z 精靈或存在遮擋關系時,才進一步判斷誰在前。但從這次測試可以看出,這種分支邏輯在 Z 值相等的情況下可能不足以處理所有邊界情況。

因此,我們開始傾向于調整邏輯,將原本只在部分條件成立時才使用的比較方法,改為在所有情況下都統一使用相同的排序函數。這種做法能確保排序在所有輸入下都保持一致性和確定性,避免排序不穩定帶來的錯誤或視覺問題。

這也意味著,在深度值一致的前提下,我們將使用一個更精確或更嚴格的比較函數,以明確決定繪制順序。這種處理方式不僅增強了系統魯棒性,也為后續調試減少不確定因素。整體來看,這次測試失敗雖然暴露了問題,但也幫助我們理清了邏輯盲點,推進了排序機制的完善。
在這里插入圖片描述

game_sort.cpp:將MergeSortY()轉變為MergeSort(),以便同時使用IsInFrontOf()進行判斷

我們開始反思當前的處理方式,意識到或許原先的策略才是更合理的選擇。我們之前嘗試在某些情況下簡化判斷邏輯,僅在特定條件成立時才使用 is_in_front_of 來決定繪制順序,但測試中暴露出的錯誤表明這種做法可能不夠嚴謹。

因此我們考慮恢復原來的方式,即無論在何種情況下,都統一調用 is_in_front_of 來決定排序順序。這樣可以在 Z 值相等時,進一步依據 Y 值或其他因素進行細致排序,從而避免出現排序不穩定的情況。例如兩個 Z 精靈 Z 值相同但位置略有不同時,這種精細判斷顯得尤為重要。

我們意識到,通過統一使用 is_in_front_of 來比較兩個對象的優先級,可以帶來更一致的行為,也更符合繪制的邏輯需求。這一方式不僅適用于 Y 精靈之間的排序,也適用于 Z 精靈之間的排序。Z 值一致時,通過 Y 值來補充判斷,可以保證視覺上的正確遮擋關系,避免混亂。

接著我們還注意到命名上的一些模糊問題。雖然目前的排序函數被稱為 merge_sort,但它實際上承擔了更多職責,不僅是執行標準的合并排序,還融合了復雜的繪制優先級邏輯。為此我們開始思考是否需要為這個過程更換更準確的函數名,以更清晰地表達其真實作用。畢竟,這不僅僅是一個通用排序過程,更像是一個繪制排序邏輯驅動的過程。

總的來說,我們決定恢復統一的排序判斷邏輯,即始終使用 is_in_front_of 來判斷對象的前后關系。這樣做不僅提高了系統的穩定性和正確性,也簡化了處理邏輯,避免因不同分支判斷帶來的隱藏 bug。同時,我們也開始審視函數命名的清晰度,希望通過明確職責來提升代碼的可讀性和可維護性。
在這里插入圖片描述

game_sort.cpp:將MergeSort()重命名為SeparatedSort(),并完全移除MergeSortZ()

我們決定對當前的排序邏輯進行更明確的區分和命名。將目前這個按照繪制前后關系進行判斷的排序過程命名為 merge_sort,因為它真正執行了合并排序的核心操作。而之前的那個初步分類排序過程則被稱為 separated_sort,它的主要功能是將不同類型的精靈(如 Z 精靈與 Y 精靈)分離到各自的緩沖區中,而不是進行真正的排序邏輯處理。

接下來,我們決定簡化并統一排序邏輯。我們去除了之前在某些特定情況下繞過 is_in_front_of 的判斷邏輯,恢復為在所有排序過程中都統一使用 is_in_front_of 來判斷前后繪制關系。這意味著無論是在處理 Z 精靈還是 Y 精靈的排序中,始終通過該函數來決定兩個元素的相對順序。

這樣做有幾個明顯的好處:

  1. 邏輯統一性:不再需要判斷當前是否是 Z 精靈還是 Y 精靈,只需一律用 is_in_front_of 來判斷,簡化了代碼結構。

  2. 提高穩定性:過去的邏輯在精靈屬性接近或重疊時可能出現排序不一致的問題,統一使用 is_in_front_of 能避免這種排序模糊性。

  3. 更貼近實際需求:繪制順序本質上是一個空間中的可視優先級關系,而 is_in_front_of 正是用于表達這一空間邏輯的函數,用它做排序依據更符合系統設計目的。

  4. 減少Bug風險:不再依賴多個路徑或分支處理排序,有利于排查錯誤與調試。

最后我們將所有使用排序的地方都更新為調用統一的 merge_sort 方法,確保在任何時候,排序都是依照同樣的規則進行。這一步標志著我們徹底拋棄了之前試圖通過條件判斷來優化排序性能的做法,而是回歸到一個更穩定、可維護、邏輯清晰的排序策略。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

調試器:運行游戲,捕捉測試認為排序錯誤的精靈

我們現在使用統一的排序方式,在排序過程中保持了 Y 和 Z 精靈的正確邏輯,即無論是哪種類型的精靈,都通過 is_in_front_of 來判斷前后關系。然而,我們在某個排序斷言中觸發了失敗。

根據調試信息的觀察:當前排序中出現了一個距離我們更近(Z 值更小)的精靈被排在后面(也就是繪制得更晚),這表面上看是符合預期的。但問題在于:雖然該精靈在 Z 上靠近我們,但它的 Y 值卻比另一個精靈低,且兩者中只有一個是 Z 精靈。

這就造成了矛盾和困惑:為什么 is_in_front_of 沒有判定靠近我們、Y 值也更低的那個精靈應該更早繪制?

我們懷疑問題出在 is_in_front_of 對混合精靈類型的處理上。當兩個精靈中只有一個是 Z 精靈時,排序邏輯會依賴于特定條件,比如是以 Z 為主、還是 Y 為主。這種情況下的處理可能不夠嚴謹,或邏輯沒有覆蓋所有的實際交錯情況。

因此,我們有以下幾點思考:

  1. Z 精靈 vs Y 精靈混合比較邏輯需明確
    當前 is_in_front_of 的實現可能只在兩者同為 Z 精靈或 Y 精靈時表現良好,而對混合場景未進行完善的判斷邏輯處理。

  2. 是否需要強制同類型精靈優先比較?
    例如:若一方為 Z 精靈而另一方為 Y 精靈,是否應該先依據精靈類型決定排序優先級,再比較坐標。

  3. Y 值在某些場景下優先級是否應高于 Z?
    因為 Y 軸往往表示屏幕“上下”,而 Z 表示“深度”,但渲染順序取決于美術意圖,比如俯視或側視角度。

  4. 需要補充測試用例與日志
    為了進一步確認 is_in_front_of 的判斷是否合理,可能需要打印出相關精靈的詳細坐標、類型及其排序結果,排查可能出現的邏輯誤判。

綜上,我們意識到混合精靈排序是個邊界復雜的情況,當前的邏輯在該情況下還存在不清晰或潛在錯誤,需要進一步明確規則與實現邏輯,確保判斷穩定一致。后續應在 is_in_front_of 內部增加更精確的精靈類型和坐標綜合比較,以避免此類斷言失敗。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

問答環節

表示嘗試用不同方法,利用屏幕上投影的精靈和它們的最小/最大Y值(上部)作為深度排序的關鍵

我們嘗試采用一種不同的方法,即利用投影到屏幕上的精靈圖像進行排序,并使用這些精靈在屏幕上的最小和最大 Y 值作為深度排序的關鍵值。這種方法的核心思想是通過分析精靈在最終投影畫面中的覆蓋區域,來確定它們在視覺上的前后關系,而不是依賴于三維空間中的 Y 或 Z 值直接排序。

目前,這一方法仍在推進中,尚未完全完成或驗證其效果,正在開發階段,尚未得出最終結論或實用成果。不過這種方法本身具有一定潛力,特別是在復雜重疊和傾斜視角場景下,能夠更貼近真實渲染順序的判斷邏輯。

我們會持續關注這一方向的進展,目標是更準確地處理精靈之間的深度遮擋問題,確保在任何角度和布局下都能保持正確的繪制順序。

正在進行中

我們想了解一下Quarter Tron對我們當前實現方法的看法,特別是想知道他是否認為這種實現是合理的,或者這是否和他之前嘗試的方法相似。由于沒人主動提問,我們自己也在思考是否應該主動去問他的意見,以便確認目前的思路和實現是否在正確的方向上,或者是否存在值得借鑒的經驗和改進空間。

問什么是全棧開發者?

全棧開發者,實際上就是以前人們稱之為程序員的那種角色。全棧開發者不僅僅會寫某一種語言,比如僅僅會寫JavaScript,還要對計算機的底層原理有一定的了解。他們不僅會寫前端界面,也能夠理解甚至修改底層數據庫等技術。舉例來說,一個只會發SQL查詢語句,看到結果卻不懂底層數據庫是如何運作的人,并不能算是真正的全棧開發者。因為他們不知道數據是如何“魔法般”地被處理和返回的。

所謂全棧開發者,其實是能夠完成從前端到后端再到底層系統整合的“端到端”編程工作的人。他們可以被指派去完成各種編程任務,而不是只會寫網頁前端或者只會數據庫查詢。如果一個人只懂寫網頁但不懂后端,或者只懂數據庫卻不會寫前端代碼,那么他們都不算是全棧開發者。

現在軟件開發領域工作量龐大,分工也越來越細,有很多人雖然做著開發相關的工作,但并不具備全棧開發者的全面技能和視野。全棧開發者則是那些既懂前端技術,也懂后端架構,還理解系統運作原理,能夠處理各種編程挑戰的綜合型開發人員。

質疑排序會失敗,因為當Y軸精靈重疊時,較低的Z值有時需要畫在較高Z值之上

發現排序失敗的原因在于,有時Z值較低的元素需要繪制在Z值較高的元素之上,尤其當Y軸方向發生重疊時。這種情況導致簡單的基于Z值的排序無法正確反映實際的繪制順序。對此,有人提供了一個很好的示意圖來說明這種情況,圖示清晰地表現了為何僅憑Z值排序不足以保證正確的顯示順序,特別是在Y軸上發生覆蓋時。考慮到這一點,需要在排序算法中同時考慮Z軸和Y軸的關系,以解決繪制順序的問題。

黑板講解:PoohShoes的示意圖

從側面觀察,有三個元素A、B、C,從攝像機視角來看,A在最前面,C居中,B在最遠處,但B的Z值比C還低。這導致單純用Z值排序的方法無法正確排序,因為看起來順序應是B最遠、C中間、A最前,但Z值卻不符合這個關系。這個問題實際上回到了之前遇到的循環依賴問題,說明仍然需要使用圖結構的方法來處理這種復雜的覆蓋關系。雖然我們之前打算用圖的方式解決這個問題,感覺還是必須這樣做。雖然如果能避免使用圖結構會更好,但目前看來難度較大,所以還是得用圖的方法去處理。

問之前提到的“插入排序”和自動生成二叉樹表示的想法最后怎么樣了,假設對所做內容有大致理解

我們曾考慮過用插入排序,并且嘗試自動生成一個二叉樹來表示這些精靈。這個想法的核心是通過插入排序逐步構建一棵二叉樹,以便更有效地管理和排序精靈。不過,目前我們還沒有完全實現這個方案,主要是因為我們還不確定它是否可行,或者是否能解決現有排序中的復雜問題。如果這個方案有效,我們可以嘗試在接下來的工作中實現它,作為對當前方法的補充或替代。現在我們仍在評估當前算法的可行性,確保沒有遺漏什么關鍵點,才能決定是否要轉向這個新的思路。

黑板講解:圖排序

我們設想用插入排序構建一個二叉樹來排序精靈,具體做法是:比如有精靈B,B作為樹的根節點;接著遇到精靈A,判斷A在B后面,所以A放在B的左側;然后遇到精靈C,C在B前面,所以放在右側;再遇到另一個精靈D,先判斷D在B的哪一側,再判斷D相對于C的位置,最終插入到合適的位置。這個過程通過不斷比較和判斷來決定精靈在樹中的位置。

然而,問題在于這種方法可能會退化成和之前遇到的排序問題一樣的復雜情況。因為如果兩個精靈之間關系不明確,或者它們的位置無法明確分割,就無法通過簡單的樹結構來解決。這導致插入排序的二叉樹方法并不能避免排序中的循環依賴或沖突問題。

因此,我們懷疑真正有效的方案只能是基于圖結構的排序方法,即構建依賴關系圖,通過圖的拓撲排序等方法來解決精靈排序問題。總的來說,這種插入排序+二叉樹的方式因為無法準確處理復雜的空間關系,難以取代圖論方法。

問如何將精靈從世界坐標轉換到屏幕坐標

我們把世界坐標轉換成屏幕坐標,基本上就是用一個標準或者非常基礎的正交投影來完成的。這個過程本身比較簡單,并沒有做太復雜的操作,就是把三維空間中的點直接映射到二維屏幕上。這樣處理后,屏幕上的坐標就能用來進行后續的排序和繪制工作。整體來說,這個轉換步驟并不復雜,屬于基礎的圖形學操作。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/81913.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/81913.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/81913.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

InetAddress 類詳解

InetAddress 類詳解 一、核心作用 封裝 IP 地址&#xff1a;同時支持 IPv4 和 IPv6 地址域名解析&#xff1a;將域名轉換為 IP 地址&#xff08;DNS 查詢&#xff09;地址驗證&#xff1a;檢查網絡地址的有效性無構造方法&#xff1a;通過靜態工廠方法獲取實例 二、核心方法 …

spring cloud alibaba-Geteway詳解

spring cloud alibaba-Gateway詳解 Gateway介紹 在 Spring Cloud Alibaba 生態系統中&#xff0c;Gateway 是一個非常重要的組件&#xff0c;用于構建微服務架構中的網關服務。它基于 Spring Cloud Gateway 進行擴展和優化&#xff0c;提供了更強大的功能和更好的性能。 Gat…

iOS 直播技術及優化

iOS直播技術的實現和優化涉及多個技術環節&#xff0c;需結合協議選擇、編解碼方案、播放器技術及性能調優等多方面。 一、核心技術實現 協議選擇與傳輸優化 HLS&#xff08;HTTP Live Streaming&#xff09;&#xff1a;蘋果官方推薦&#xff0c;基于HTTP分片傳輸&#xff0c…

目標檢測135個前沿算法模型匯總(附源碼)!

目標檢測是計算機視覺核心方向之一&#xff0c;也是發論文的熱門領域&#xff01; 近來不僅YOLO算法迎來了新突破&#xff0c;迭代出YOLOv12&#xff01;Mamba、大模型等新技術的發展&#xff0c;也給該領域注入了全新的力量&#xff0c;取得了諸多顯著成果。比如性能飆升82.3…

期刊采編系統安裝升級錯誤

我們以ojs系統為例&#xff1a; PHP Fatal error: Uncaught Error: Call to a member function getId() on null in /esci/data/html/classes/install/Upgrade.inc.php:1019 Stacktrace: #0 /esci/data/html/lib/pkp/classes/install/Installer.inc.php(415): Upgrade->con…

淺談無服務器WebSocket的優勢

實際上&#xff0c;一個實用的解決方案是將構建業務關鍵型實時平臺的復雜性卸載到專門的云服務中。 完全托管的無服務器 WebSocket 解決方案為事件驅動的消息傳遞提供了基礎結構;它使底層基礎設施成為一種商品。客戶端使用提供程序服務發送/接收低延遲消息&#xff0c;并專注于…

Python數據可視化高級實戰之二——熱力圖繪制探究

目錄 一、熱力圖的作用 二、熱力圖反映的信息類型 三、熱力圖的典型應用場景 1. 地球信息系統 (GIS) 2. 城市交通分析 3. 市場分析 4. 用戶行為分析 5. 網絡流量分析 6. 傳染病傳播分析 7. 社交媒體輿情分析 四、Python 繪制熱力圖的關鍵技術要點 1. 數據預處理 2. 顏色選擇與漸…

配電網運行狀態綜合評估方法研究

1評估指標體系的構建 [1]冷華,童瑩,李欣然,等.配電網運行狀態綜合評估方法研究[J].電力系統保護與控制,2017,45(01):53-59. 1.1評估范圍 圖1為配電系統組成示意圖&#xff0c;其中A、B、C分別表示高、中、低壓配電系統。高壓配變(也稱主變)將35kV或110kV的電壓降到10kV&#…

Docker安裝MinIO對象存儲中間件

MinIO 是一個高性能、分布式的對象存儲系統&#xff0c;兼容 Amazon S3 云存儲服務協議&#xff0c;廣泛應用于企業存儲、大數據、機器學習和容器化應用等領域。以下是詳細介紹&#xff1a; 核心特點 兼容 S3 API &#xff1a;全面兼容 Amazon S3 API&#xff0c;這意味著使用…

HTML回顧

html全稱:HyperText Markup Language(超文本標記語言) 注重標簽語義,而不是默認效果 規則 塊級元素包括: marquee、div等 行內元素包括: span、input等 規則1:塊級元素中能寫:行內元素、塊級元素(幾乎什么都能寫) 規則2:行級元素中能寫:行內元素,但不能寫:塊…

JAVA Spring MVC+Mybatis Spring MVC的工作流程*,多表連查

目錄 注解總結 將傳送到客戶端的數據轉成json數據 **描述一下Spring MVC的工作流程** 1。屬性賦值 BeanUtils.copyProperties(addUserDTO,user); 添加依賴&#xff1a; spring web、mybatis framework、mysql driver Controller和ResponseBody優化 直接改成RestControl…

H2數據庫中一條insert語句到生成java對象到數據寫入磁盤的完整步驟

H2 數據庫將 SQL 語句轉換為磁盤存儲的全過程可以分為以下 8 個關鍵步驟&#xff0c;我們以 INSERT INTO users (id, name) VALUES (1, Alice) 為例詳細說明&#xff1a; 1. SQL 解析與語法樹生成 詞法分析&#xff1a;拆分語句為 INSERT、INTO、users 等 Token語法分析&#…

重磅升級!Google Play商店改版上線

5 月 21 日消息&#xff0c;Android Headline 今天&#xff08;5 月 21 日&#xff09;發布博文&#xff0c;報道稱在 2025 年 I/O 開發者大會上&#xff0c;谷歌宣布更新 Google Play 應用商店&#xff0c;在優化用戶體驗的同時&#xff0c;提升開發者收益。 本次更新中&…

Docker面試題(1)

什么是Docker 一個容器化平臺 形式是容器 將你的應用程序及所有依賴項打包在一起 確保應用程序在任何環境中無縫運行 什么是Docker鏡像 Docker鏡像是Docker容器的源代碼 用于創建容器 使用build命令創建鏡像 什么是 Docker容器 包括應用程序及所有的依賴項 作為操作系統的獨立進…

Ulisses Braga-Neto《模式識別和機器學習基礎》

模式識別和機器學習基礎 [專著] Fundamentals of pattern recognition and machine learning / (美)烏利塞斯布拉加&#xff0d;內托(Ulisses Braga-Neto)著 ; 潘巍[等]譯 推薦這本書&#xff0c;作者有自己的見解&#xff0c;而且提供代碼。問題是難度高&#xff0c;對于初學…

RabbitMQ的簡介

三個概念 生產者&#xff1a;生產消息的服務消息代理&#xff1a;消息中間件&#xff0c;如RabbitMQ消費者&#xff1a;獲取使用消息的服務 消息隊列到達消費者的兩種形式 隊列&#xff08;queue&#xff09;:點對點消息通信&#xff08;point-to-point&#xff09; 消息進入隊…

自動切換剪貼板路徑中反斜杠為正斜杠

有時候需要將我們常見的win全路徑中反斜杠為正斜杠&#xff0c;每次用記事本&#xff0c;編輯替換非常麻煩&#xff0c;于是寫了這個工具&#xff0c;能自動修改剪貼板中的數據&#xff0c;只需要運行一下即可。 實現效果&#xff0c;將類似于下面的路徑&#xff1a; C:\User…

【時時三省】Python 語言----文件

目錄 1,文件打開 2, 文件關閉 3, 文件寫入 4, 文件讀出 5, 文件定位 6, 文件重命名 7, 復制文件 山不在高,有仙則名。水不在深,有龍則靈。 ----CSDN 時時三省 1,文件打開 file = open(file, mode, buffering, encoding, errors, newline, closefd, opener) 2, 文…

React 個人筆記 Hooks編程

作用 配合函數式編程&#xff0c;保證在不產生類的時候完成一個整體的組件 常用組件 useStateuseContextuseReduceruseEffectuseMemouseCallback 前三個值為自變量 后三者為因變量 前三者相當于其他編程函數的變量聲明&#xff0c;而后三者相當于對變量進行了(if now ! pr…

logits是啥、傅里葉變換

什么是logtis&#xff1f; 在深度學習的上下文中&#xff0c;logits 就是一個向量&#xff0c;下一步通常被投給 softmax/sigmoid 的向量。。 softmax的輸出是分類任務的概率&#xff0c;其輸入是logits層。 logits層通常產生-infinity到 infinity的值&#xff0c;而softmax層…