開始
然而,我們仍然有一些工作要做,渲染部分并沒有完全完成。雖然現在已經能夠運行游戲,而且幀率已經可以接受,但仍然有一些東西需要進一步完善。正在使用調試構建編譯版本,雖然調試版本的性能不如優化版本,但仍然可以流暢運行游戲。
接下來,需要做一些調整和改進。渲染器中有一些還沒有完成的部分,可能會對整體表現產生影響,需要注意。為了做好準備,寫了一些待辦事項,主要是關于渲染器的一些優化,比如確保所有的瓦片都能夠正確地對齊緩存行。雖然目前的實現已經足夠好,但仍然有一些細節上的優化空間,尤其是在緩存管理方面,確保能夠最大化性能。
這些改進并不需要立刻完成,因為目前的系統已經相對穩定,可以繼續處理這些問題而不會影響到其他部分的代碼。因此,接下來的一段時間可以用來進行這些優化,進一步提升渲染器的效率和性能。
總的來說,當前進展順利,渲染器的速度令人滿意,但仍需進行一些細節上的優化和調整,確保最終的效果更好。
黑板:對齊到緩存行
在開發過程中,討論到了一個緩存相關的問題,特別是如何確保瓦片(tile)在內存中的對齊,以提高渲染性能。當前我們將屏幕分成瓦片并將其分配給不同的線程進行處理,目的是使得每個線程可以獨立渲染不同的瓦片,從而加速渲染過程。例如,核心0負責一個瓦片,核心1負責另一個瓦片,依此類推。
然而,即使我們已經保證每個瓦片的內存位置是對齊的,避免了線程間的直接競爭,仍然存在一個潛在的問題,即“緩存線競爭”(Cache Line Contention)。所謂緩存線,就是處理器緩存的最小單位。如果兩個相鄰的瓦片跨越了同一個緩存線,那么當一個核心修改了緩存線的數據,另一個核心的緩存中的相同數據將被標記為無效,從而需要重新加載數據。這種數據的“爭奪”可能導致性能下降,因為核心1必須重新從核心0的緩存中讀取數據。
為了防止這種情況發生,理想的做法是確保瓦片的邊界剛好落在緩存線的邊界上,這樣每個核心就能獨立處理自己的瓦片而不會互相干擾。如果瓦片的邊界沒有對齊緩存線,可能會導致緩存行在核心之間來回傳遞,增加內存流量和緩存無效的次數,最終影響性能。
因此,為了避免可能的性能瓶頸,需要進行優化,確保瓦片的內存布局與緩存線對齊。這可能會帶來顯著的性能提升,尤其是在多核心處理時。雖然目前還沒有出現明顯的性能問題,但這個問題仍然需要關注和測試,以確保不會影響最終的渲染效率。
渲染器待辦事項
在開發過程中,討論了幾個優化渲染性能的方向,特別是如何利用超線程和調整瓦片大小來提升渲染效率。
首先,考慮到超線程(Hyper-Threading)技術,目前系統并沒有充分利用超線程來提升并行性。雖然系統支持超線程,但并未專門針對其進行優化。超線程允許每個核心同時執行兩個線程,理想情況下,可以將任務分配給這些線程,以便更高效地利用CPU資源。不過,當前的問題在于如何同步這些線程的工作,使得超線程能有效地交替工作,確保每個線程處理不同的任務,而不會產生沖突或數據共享問題。由于目前沒有明顯的同步機制,暫時還沒有實現對超線程的優化。未來可以通過研究和與他人討論,探索如何充分利用超線程,以提高渲染效率,或者如果這個思路不行,也可以放棄這個方案。
接下來,討論了瓦片(Tile)大小對性能的影響。瓦片的大小決定了在渲染過程中每個瓦片能占據多少緩存。如果瓦片太大,可能會導致大量數據無法全部加載到緩存中,從而降低效率。而如果瓦片太小,雖然每個瓦片能更好地利用緩存,但每個瓦片的處理開銷也會增加,尤其是在設置階段,需要處理更多的塊、裁剪等工作。因此,瓦片大小的選擇是一個需要權衡的點,既要保證性能,又要考慮到每個瓦片的處理成本。未來需要進行測試,找出一個合適的瓦片大小,以優化渲染過程。
另外,提到需要對渲染流程的內存帶寬進行評估,以了解當前的內存使用效率。需要通過實際測試來計算內存帶寬,以判斷是否存在瓶頸,以及是否可以通過調整內存訪問方式來提高性能。
最后,討論了對指令選擇的重新測試。現在系統已經支持多線程渲染,因此需要重新評估現有指令的選擇,確保在多線程環境下,指令執行能最大限度地提高效率。通過分析和測試,進一步優化指令集,以提升整體性能。
盡管有這些優化方向,但目前還不打算馬上實施這些改進,而是希望在稍后進行更深入的性能測試和調整,找出最合適的優化方案。
今天的工作:解決剩余的問題
接下來,討論了渲染系統的清理工作。在渲染功能已經可以運行并且性能良好的基礎上,目標是進一步完善渲染的功能,并確保渲染系統在最終優化之前具有完整的特性。主要目的是避免在不完全理解系統的情況下對渲染進行過早的優化。
首先,需要整理和修正坐標系統。雖然之前進行了一些優化,并且渲染系統的運行速度已經非常快,但仍然需要進行一個坐標系統的整理,以確保各部分的功能和結構合理。之后,還需要繼續完成一些關于光照的工作,進一步優化渲染性能,最終可以將渲染部分標記為完成。
除了光照,另一個需要考慮的內容是粒子系統。粒子系統在渲染中通常具有不同于其他元素的特點,因此需要專門考慮如何將其與渲染系統集成。粒子系統的開發可能會在光照優化前或后進行,具體順序尚不確定,但粒子系統也是渲染優化的一部分。
完成上述工作后,計劃進行一次最終的組織整理,然后可以將渲染部分視為基本完成,為游戲的開發和發布打下基礎。渲染系統的優化和完成將意味著進入下一階段的游戲開發,準備逐步推進其他方面的工作。
此外,還需要考慮游戲中的音頻和動畫系統的整合,并將這些工作安排在接下來的開發計劃中。音頻和動畫相關的內容將逐步融入到游戲中,并且這部分將進入更高層次的游戲功能開發。
總結來說,當前的工作重點是完成低層引擎部分的開發,并盡快完成渲染系統的最終整理和優化。這些低層工作完成后,將為接下來的游戲開發提供更堅實的基礎,使得開發團隊能夠順利向游戲的其他方面過渡,并最終推動游戲的發布。
查看坐標系統
在渲染系統的開發過程中,渲染實體的處理和相機系統的管理顯得有些混亂。最初,我們將渲染任務簡單地推送到一個列表中,但這樣做顯得有些草率,并且沒有認真考慮這些任務應該如何有序地工作。具體來說,渲染實體的處理方式讓人感覺有些繁瑣,包含了許多不必要的操作,這些操作可能并非渲染過程中必需的。
相機的處理也是如此,感覺我們在操作相機時過于隨意,沒有遵循一個標準的三維渲染管線做法,這讓系統的行為顯得不夠清晰和可控。因此,計劃對這些部分進行重新審視和優化,目的是使其更加符合標準的三維渲染管線結構,這樣能夠更好地理解和控制渲染過程中的每一個環節,確保系統的穩定性和性能。
重新整理和優化渲染實體的處理和相機系統的管理將幫助系統更加高效和穩定,也能為后續的開發和調試打下更好的基礎。
重新審視基礎變換
目前渲染過程中存在一些問題,尤其是在三維變換的處理上。雖然渲染大部分內容是在二維空間中進行的,但實際上,很多操作仍然依賴于三維變換。我們目前面臨的一個問題是沒有有效的機制來避免不必要的三維操作,尤其是在處理地面區塊時。這導致我們無法確保在正確的像素空間進行操作,進而影響了地面區塊的無縫拼接。
為了更好地解決這個問題,計劃將渲染的變換過程進行改進。目的是確保可以在兩種模式下靈活處理變換:一種是標準的正交變換(如最初的像素空間變換),另一種是透視變換。為了實現這一點,打算不再延遲變換的執行,而是將其直接在需要的位置進行。這一改變需要調整當前的實體模擬方式,避免在實體模擬后才進行渲染處理。
具體來說,當前的流程是在實體模擬時執行其對應的操作,然后等待模擬結束后再渲染圖形。但這種做法存在問題,因為直到模擬完成后才能知道實體的位置,導致渲染的時機被延后。因此,為了改進這一流程,打算在實體移動時直接進行相應的渲染變換,從而避免不必要的延遲和復雜性,使整個渲染過程更加高效和流暢。
通過這些改進,希望能使渲染系統更清晰、更具可控性,同時避免冗余的三維變換和延遲渲染,從而提高整體的渲染性能和靈活性。
拆分更新/渲染
渲染敵人的過程可以通過兩種方式實現。為了簡單起見,當前選擇了最直接的方式,但將來可能會做一些調整。目前的做法是復制現有的switch語句,并將其一部分用來進行渲染,另一部分用于實體模擬。這樣,渲染和實體模擬分開處理,渲染部分負責繪制圖像,實體模擬部分負責計算實體的行為和狀態。
計劃在未來可能會將這兩部分合并處理,尤其是在移動方式上有所改動時。也可能會重新命名這些操作函數,使得它們更符合實際的工作內容,比如將“模擬實體”的部分重命名為“自由物理模擬實體工作”之類,以更清晰地表達每個函數的作用。
目前,為了保持簡單,決定先按現有的方式將代碼拆分,渲染部分和模擬部分各自獨立執行。這一過程包括移除一些不必要的代碼,比如一些不需要調用的發送操作。刪除這些后,剩下的代碼只涉及到需要渲染或模擬的實體,減少了不必要的復雜性。
通過這種方式,渲染和實體處理將更清晰,實體將被渲染到它們實際的位置,而不再是之前那種依賴模擬后再進行渲染的方式。這使得渲染和實體處理更加直接和高效,能更精確地控制每個實體的狀態和位置。
按需執行基礎變換
為了避免延遲執行的復雜性,現在決定將轉換操作直接在線上進行處理,而不是事后再進行調整。具體來說,首先通過修改渲染組的默認基礎設置,使得它可以直接被使用和覆蓋。這意味著,所有渲染和變換的設置將直接嵌入到渲染組中,而不需要單獨的延遲步驟。
首先,要提取出渲染所需的基礎數據,比如屏幕坐標、攝像機的距離和焦距等,所有這些都將被直接放入渲染組中進行處理。這一過程中,攝像機相關的設置(例如渲染相機)將移除,統一由渲染組來處理。這種調整將使得渲染和變換操作更加直接和清晰。
此外,調整后,渲染過程中的每個操作(如推送位圖或矩形)都會先進行基礎變換,再檢查變換后的物體是否在視圖范圍內。如果物體超出視圖,它將不會被推送到渲染隊列中,這樣避免了不必要的渲染計算。
對于坐標系統,也同樣調整為在操作時實時計算,而不再是后續再進行補充。這樣可以減少不必要的計算和數據存儲,提升渲染效率和清晰度。
總之,通過將這些處理步驟提前并簡化為實時操作,避免了延遲計算和數據存儲,提高了系統的整體效率。這些改動讓渲染和變換過程更加直接,減少了復雜度,同時提升了渲染的實時性和精度。
去除EntityBasis
在處理渲染實體時,決定簡化和優化數據結構,去掉了一些冗余的字段。首先,原來存儲的“scale”(縮放)字段實際上沒有被使用,因此決定移除它,只保留了“p”以及與大小相關的必要信息。對于渲染位圖,原本存儲的“size”和“scale”字段不再需要,因為它們對當前渲染流程沒有實際作用。只有位置和大小信息會被保留。
在考慮是否需要存儲更多的信息時,發現僅當涉及到照明計算時,才有必要存儲“像素到米”的轉換比例,因為這一信息會影響到照明計算。對于不涉及照明的對象(例如渲染實體位圖),則不需要存儲這個轉換比例。這樣,系統可以根據實際需求決定是否存儲這些額外的轉換數據。
為了處理照明問題,當渲染實體需要參與照明計算時,會特別存儲“像素到米”的轉換比例,而其他對象則不需要。對于坐標系統,可能也需要存儲這一比例,以便正確計算光照效果。
在進一步分析和整理時,去除了冗余的字段和不必要的轉換步驟。例如,原本為矩形繪制設置的“pixels to meters”字段,現僅在需要照明的情況下保留。其他不涉及照明的情況下,這一字段被默認設置為1,表示沒有進行任何轉換。
最終,通過將不必要的數據移除和簡化存儲結構,渲染代碼變得更加高效,減少了內存占用并簡化了計算過程,特別是在不涉及照明的情況下,進一步提高了渲染效率。
初始化變換
在這段處理中,主要是對渲染變換(transform)的初始化進行了一些調整。之前的默認變換(DefaultBasis)已經移除,但我們現在需要確保渲染變換對象正確初始化,并且包含必要的字段,例如縮放(Scale)和偏移(OffsetP)。這些字段將成為渲染變換的核心部分。
初始化渲染變換:
在新的變換結構中,首先需要將一些必要的參數加入進來,包括:
- 縮放(Scale)
- 偏移(OffsetP)
- 焦距(FocalLength)
- 目標上方的距離(DistanceAboveTarget)
- 顯示器的半寬度(MonitorHalfDimInMeters)
- 像素到米的轉換(MetersToPixels)
這些數據在渲染過程中是必須的,用于計算和顯示正確的渲染效果。
問題解決:
- 渲染變換的初始化:將變換結構初始化為無效狀態(或者合理的初始值),確保它在后續處理中能夠正確工作。
- 移除不再需要的字段:由于默認變換已被移除,某些冗余的代碼也被去除。例如,渲染組直接包含變換對象,不再需要通過間接方式處理。
關于顯示器設置:
- 顯示器的半寬度(MonitorHalfDimInMeters)在變換結構中仍然需要處理,因為它直接影響到渲染時的視角設置,因此沒有被移除。
- **全球透明度(GlobalAlpha)**仍然是需要保留的,這與渲染中的淡入淡出效果有關。
總結:
經過調整,所有渲染變換相關的內容都被集中到變換結構里。所有必要的參數都被初始化并正確設置,同時去除了一些不再需要的字段。為了確保計算正確,變換初始化時需要根據顯示器尺寸、焦距、目標距離等信息來設置。這些更改使得渲染系統更加簡潔高效,并且避免了冗余的計算和存儲。
翻譯調用
在這段代碼中,主要的目的是對渲染變換(transform)進行重新組織和優化,確保渲染的操作在內存中高效執行,并且簡化了對一些參數的處理。整體思路是通過將變換相關的數據集成在渲染變換結構中,避免了不必要的中間步驟和冗余的數據存儲。
變換結構的調整:
-
屏幕中心(ScreenCenter)加入到變換中:
之前的代碼中,屏幕中心信息是單獨管理的,而現在它作為變換的一部分,直接包含在變換結構中。這樣可以簡化處理過程,直接從變換中獲取所需的信息。 -
P值和偏移(OffsetP):
在渲染過程中,實體位置的計算依賴于P值。原先的EntityBasis
主要是用來獲取P值,而現在直接通過傳遞渲染變換中的P值來簡化操作。P值包括了三維空間中的位置,而偏移則是通過變換中的OffsetP來處理的,因此原來的EntityBasis
可以被移除。 -
調整P值:
為了確保位置正確,P值需要根據變換中的偏移進行調整。每次計算P值時,都需要考慮變換的偏移,這樣可以實現位置的正確調整。例如,在計算渲染位置時,P值要加上偏移量,確保顯示的正確性。 -
去除不必要的字段:
之前的代碼中存在一些不再需要的字段,比如顯示器的半寬度(MonitorHalfDimInMeters)已經不再需要,因此被刪除。我們現在只保留必要的字段,如縮放比例、偏移量和焦距等。 -
像素與米的轉換(PixelsToMeters):
PixelsToMeters
仍然是一個需要保留的參數,因為它在渲染和光照計算中使用。如果渲染項沒有涉及光照,它可以不被使用,但是對于涉及光照的物體來說,PixelsToMeters
是必不可少的。 -
渲染流程簡化:
通過將所有變換計算直接嵌入到渲染過程中,避免了等待或者延遲的計算。這樣每次渲染時,都會直接計算出最終的P值,避免了后續重新計算帶來的開銷。渲染過程中的每一步(如矩形繪制、位圖推送等)都會按照這種新機制進行。 -
大小(Size)和縮放(Scale)處理:
之前可能對每個渲染項都處理了縮放(Scale)和大小(Size)分離的操作,現在直接將縮放參數內嵌到大小值中,確保在渲染時可以直接使用正確的縮放后大小,而不需要單獨存儲和計算縮放。 -
調整后的渲染邏輯:
渲染操作中,首先檢查渲染項是否有效,如果有效,再根據變換后的P值計算實際的顯示位置。然后通過新的渲染方式,直接應用變換并計算出正確的P值,避免了過多的冗余存儲和計算。
總結:
整體優化過程中,重點在于簡化渲染變換的計算流程,移除不再需要的中間步驟,避免冗余的存儲和計算。變換數據直接嵌入渲染流程中,使得每個渲染項都能在執行時直接計算出正確的P值,并且通過合適的偏移和縮放進行調整。這樣,渲染系統的性能得到了提升,代碼結構也變得更加清晰和高效。
完成代碼修改
在這段代碼的修改過程中,核心目標是簡化和優化變換和渲染基礎結構的管理。主要的操作包括調整如何傳遞和使用變換數據,去除不再需要的冗余代碼,并確保渲染組(RenderGroup)能夠正確地處理變換。
主要修改內容總結:
-
變換的優化:
- 現在不再需要使用“調試攝像機”(DebugCamera)或者其他不必要的調試工具。原本需要多次計算的變換,現在通過直接傳遞和使用渲染組的變換來完成。所有的變換計算被簡化為直接應用當前的變換,并根據目標位置和偏移量調整。
- 其中涉及到的變換(如目標距離等)不再單獨處理,而是通過調整變換數據結構來完成。這樣做的目的是減少冗余計算,提高代碼效率。
-
渲染基礎(RenderBasis)移除:
- 原本的
RenderBasis
處理邏輯被簡化,不再需要額外的渲染基礎類。現在只需要將相關的大小(size)直接傳遞給渲染組,避免了不必要的數據結構操作。 - 渲染組的變換可以直接從變換數據中提取出來,不再依賴原本的渲染基礎類。去除了之前的
RenderBasis
相關的代碼,清理了冗余部分。
- 原本的
-
移除不必要的初始化操作:
- 渲染組中不再需要重復初始化渲染基礎數據(如位置、大小等)。原本的代碼中需要將數據推送到渲染隊列,但現在直接傳遞所需的變換數據即可,不再涉及不必要的初始化和處理步驟。
- 在原本需要設置渲染基礎數據的位置,現在只需要設置渲染組的變換參數,并通過調整位置和偏移量來正確定位渲染物體。
-
清理冗余字段和操作:
- 刪除了一些不再需要的字段和處理邏輯。例如,渲染基礎位置的設置和偏移量的調整,現在由渲染組的變換和目標位置直接管理,而不再需要額外的計算和設置。
- 通過去除這些冗余代碼,簡化了渲染和變換流程,使代碼更簡潔易懂。
-
錯誤處理和調試:
- 在進行代碼修改時,遇到了一個關于渲染組變換(RenderGroup.Transform)的錯誤。錯誤提示指出,渲染組的變換必須是一個類實例,而不是一個指針。經過檢查,發現需要正確設置渲染組中的變換類型和指針,解決了這個問題。
-
未來計劃和優化:
- 當前的修改已經基本完成,下一步是繼續優化攝像機的設置和渲染組的使用,確保變換數據和渲染數據的高效結合。
- 盡管現在的代碼已經簡化,未來可能還需要調整渲染組的設置方式,確保其能夠更靈活地適應不同的渲染需求。
總結:
整體上,修改的核心是優化和簡化渲染和變換的流程,通過直接使用變換數據來減少不必要的中間步驟,去除冗余的渲染基礎結構,提升了代碼的可維護性和執行效率。變換和渲染基礎的調整使得每次渲染操作都更加直接和高效。同時,解決了相關的調試錯誤,確保了渲染組和變換的正確連接。
什么都沒有黑屏調試一下
PushBitmap 沒調用
測試更改
在調試中使渲染變換生效
考慮到調試過程中的變換操作,想要嘗試一種新的方法:在調試模式下,變換可能會以不同的方式工作,主要是希望能夠直接對變換進行修改,并且在調試過程中,變換的修改可以是后置的、事后才會應用的。具體來說,考慮通過設置“DistanceAboveTarget”來調整調試相機的位置,并且在某些情況下,可以在調試過程中讓這個距離數值更高,或與常規操作下的距離不同。
一種可能的實現方式是,在調試相機模式下,調整“DistanceAboveTarget”值,可能會使得相機距離目標更遠或更近,從而影響相機視角,或者甚至完全改變其視距。這種改變可以在調試過程中進行,而不影響正常模式下的表現。例如,可能通過代碼控制將相機距離的變化與調試狀態掛鉤,只有在調試狀態下,這些變動才會生效,從而避免干擾到正式發布的功能。
不過,對于這種方法是否真的有效,還沒有完全確定。考慮到需要在調試中調試視角并獲得精準的數據,或許這種調整可以幫助開發過程,但在實際操作時可能還需要進一步考慮其復雜性和可行性。
一切第一次工作是因為你在大聲思考。
有時候,雖然我們在系統中加入了很多功能,但并不是每個功能都會在第一次運行時就能完美工作。通常,意料之外的地方可能會出現問題,而一些自己預料到會出問題的地方,反而有時卻能順利運行。這種情況挺奇怪的。可能是因為當我們覺得某個功能很復雜時,我們會更加專注和小心,避免出錯。而那些覺得比較簡單或者沒那么重視的地方,反而可能會出現意外的錯誤。
黃色部分代表加載的位圖嗎?
黃色部分表示的是加載的位圖,但黃色本身只是為了方便調試,展示了目前哪些部分沒有正常渲染出來。它主要是用來標記地面區域的填充情況。原本這些黃色標記只是調試用的,幫助看到哪些地方沒有被正確填充。現在可以考慮把這些標記重新打開,甚至在明天的工作中,可能會把它們永久保留,因為不再需要為調試目的而關閉它們。
目前,剩下的核心工作就是處理一些與深度排序、照明和如何處理對象的深度(z軸偏移)有關的問題。這部分內容是引擎中的大問題,尤其是在處理這些與深度和排序有關的功能時,還需要解決如何與光照系統協調工作的問題。具體來說,處理這些問題是當前待辦事項中唯一的“大問題”。
另外,關于2D處理,當前出現了一個問題,就是在處理深度偏移時,把所有的偏移量都當作一樣對待了,但顯然不應該這樣做。比如,現在可以看到角色的頭部在上下浮動時,它也會左右移動,這種現象本來是不想發生的,但時不時會有些猶豫,不確定是否應該這么處理。總的來說,關于這個問題,需要進一步探討,明天可以繼續看看該如何調整。
有些現階段的占位符是為了未來的樓梯設計而放置的,目前還不確定最終會如何處理這些占位符。理想的效果是讓這些對象能穩定地堆疊在一起,保持正確的層級關系,雖然目前還不確定如何做到這一點,但這確實是一個棘手的挑戰。
然而,最讓人興奮的是縮放功能。對縮放效果的喜愛讓大家決心一定要找到解決方案,確保游戲中的物體在走樓梯時會根據玩家的高度變化進行縮放,視覺效果非常酷,大家覺得這個功能必須要有。所以,盡管處理起來有些難度,但一定要想辦法解決,并且與美術資源協調好。
黑板:緩存失效
在處理緩存時,首先需要了解 CPU 中的緩存是如何工作的。每個核心(Core)都有自己的緩存,這些緩存用于存儲數據,以提高訪問速度。緩存比主內存要小得多,但它們的作用是減少 CPU 訪問主內存的時間。
-
數據加載
假設我們有一個位置 A 的數據存儲在內存中。當 CPU 核心(比如核心 0)需要加載這個數據時,它會先檢查自己的緩存。如果緩存中沒有這個數據,它就會向主內存請求數據。當主內存將數據提供給 CPU 后,數據會被加載到核心的緩存中,并可能標記為 “共享” 或 “獨占”(具體取決于緩存的一些標記)。 -
多個核心共享數據
如果另一個核心(比如核心 1)也需要訪問位置 A 的數據,它會先查詢自己的緩存。通常它會通過 “嗅探” 操作(snooping)檢查其他核心是否已經有了這個數據。如果核心 0 已經緩存了這個數據,核心 1 會從核心 0 的緩存中讀取數據,而不是再次去主內存請求。這時,兩個核心都共享這塊數據。 -
數據修改與緩存失效
問題出現在當某個核心(例如核心 0)需要修改這塊數據時。如果核心 0 正在修改數據,它需要先通知其他核心它將獨占這塊數據。為了確保數據一致性,核心 0 會將這塊數據的緩存標記為 “獨占”(exclusive),同時通知其他核心標記該數據為無效(invalid)。這時,其他核心不能再繼續使用這塊數據,必須重新從內存或核心 0 的緩存中獲取最新的數據。 -
寫入操作與緩存一致性
當核心 0 完成寫入操作后,它將數據標記為 “修改”(modified),而其他核心必須清空它們的緩存中原來的數據,以確保所有核心都能獲取到修改后的數據。這是緩存一致性協議的一部分,確保所有核心的緩存數據是同步的。 -
緩存失效的過程
所謂 “無效化” 緩存,就是在數據被修改后,某個核心的緩存中的數據不再是最新的,必須標記為無效。這是因為其他核心可能在背后修改了數據,從而導致該緩存行的數據已經不再正確。每當發生寫操作時,緩存行會被標記為 “無效”,并且其他需要該數據的核心必須重新獲取。
總結來說,緩存失效是在多核 CPU 中保持數據一致性的一個關鍵步驟。它確保了當多個核心操作相同的數據時,能夠通過有效的緩存協議保證數據的準確性和一致性。
能否總結一下今晚做了什么?有些地方有點難以跟上。
今天做的主要工作其實比較簡單,涉及到一些渲染流程的改動。之前的做法是,我們有一個渲染緩沖區(render buffer),然后會推送一個叫做“實體基準”(entity basis)的變換。接著,使用這些變換來處理位圖記錄(bitmap records),這些記錄定義了要繪制的位圖以及相關的基準數據。
在渲染時,我們會結合這些基準數據和位圖數據,來計算出位圖的實際位置和屏幕上顯示的矩形區域。也就是說,變換和位圖數據是分開處理的,變換數據僅在渲染時才會結合起來。
不過,我們對這個過程做了一些改動,目的是提高效率。改動后的做法是,在渲染組(render group)中直接存儲當前的變換矩陣,而不再推送基準數據。然后,在渲染緩沖區(push buffer)中,我們不再推送基準數據,而是直接推送位圖。當位圖被推送時,我們會立即在推送時進行變換計算,而不是等到渲染時再計算。這樣,我們直接存儲變換后的結果,而不是存儲所有需要重構數據的中間信息。
這樣做的主要目的是提高效率,避免延遲執行變換,而是在數據推送時就完成變換操作。這就是今天的主要工作,簡單來說,就是將變換從延遲執行改為推送時立即執行。
你能解釋一下什么叫做串行處理嗎?
當說某個操作是按串行(serial)進行時,意味著這些操作必須按順序執行,一個接著一個,沒有重疊。也就是說,每個操作都得等前一個操作完成后才能開始,不能并行執行。比如,假設有四個步驟:A、B、C 和 D,按串行執行時,它們會依次執行:
- 第一步:執行 A
- 第二步:執行 B(A 完成后才能開始)
- 第三步:執行 C(B 完成后才能開始)
- 第四步:執行 D(C 完成后才能開始)
每個步驟只能在前一步完成后才能開始,換句話說,所有的操作都得“排隊”,因此這些操作在時間上是連續發生的。
而并行(parallel)執行則是完全不同的。并行意味著這些操作可以同時進行,不需要等待其他操作完成。舉個例子,假設我們仍然有 A、B、C 和 D 四個步驟,在并行執行的情況下,所有的步驟都可以同時開始:
- 在同一時刻,A、B、C 和 D 都開始執行,盡管它們可能花費不同的時間,但它們不需要等待其他步驟完成就可以一起開始。
串行執行的時間是所有步驟所需時間的總和。也就是說,若每個步驟需要的時間分別是 tA、tB、tC 和 tD,那么總時間就是 tA + tB + tC + tD。
而并行執行的時間則是所有步驟中花費時間最長的那個。也就是說,假設 A 需要 tA 時間,B 需要 tB 時間,C 需要 tC 時間,D 需要 tD 時間,那么并行執行的總時間是 max(tA, tB, tC, tD),即取最慢的步驟的時間作為整個過程的執行時間。
總結來說,串行需要所有操作按順序逐個執行,整體耗時較長。而并行則能讓操作同時進行,整體耗時則取決于執行時間最長的那個步驟。
在你移除縮放后,我錯過了一部分內容。難道你不需要它來做Z軸上的縮放嗎?
在這個過程里,之前使用的縮放(scale)功能被移除的原因是因為不再需要在渲染過程中存儲它。原先的做法是為了根據 Z 軸來進行縮放,但現在的做法是通過直接計算縮放值來替代存儲縮放因子。
具體來說,渲染轉換(render transform)中設置了縮放值,但這個值并不再被存儲到渲染緩沖區中。每次進行推送(push)時,都會在推送時計算出當前的縮放值,并且直接存儲計算后的尺寸,而不需要存儲縮放因子本身。
例如,計算縮放時,會在推送時直接處理這個縮放,而不在渲染數據中保留縮放因子。計算完成后,所有相關的內容,包括位置(p),都會被正確地縮放,并且只存儲這些最終的值,而不再存儲原始的縮放因子。這樣做的目的是提高效率,避免不必要的存儲,因為在推送后,縮放因子已經沒有必要再被使用。
在位圖之間的階梯通過是什么情況?是基于Z軸的簡單縮放嗎?
在這段過程中,涉及到的是如何在渲染和縮放時處理 Z 軸的偏移問題。最初,p
值被用于計算偏移,這個值主要影響 X 和 Y 軸的偏移。然而,Z 軸的處理需要進行調整,p
值不應該影響 Z 軸的偏移。
具體來說,目標是消除 Z 軸的偏移(即,不再使用 p
的值來直接調整 Z 軸)。在進行轉換時,首先需要應用 p
的變換來影響位置,然后再考慮縮放。在這一過程中,縮放是基于 p
值和 Z 軸的偏移量進行計算的。這個偏移量可能會影響 Z 軸的最終位置,但需要在渲染計算后才加上。
目前遇到的問題是,雖然已經調整了 Z 軸的偏移,但仍然發現 Z 值可能沒有完全“歸零”或清除,所以在實際操作中還是可能有一些偏移。暫時的解決方法是將 Z 軸的偏移設置為零,并計劃在未來繼續調整和優化如何正確地處理這個問題。
簡而言之,目標是修正 Z 軸偏移的處理方式,確保縮放和變換僅影響 X 和 Y 軸,并且將 Z 軸的偏移在渲染時進行正確的計算和調整。
這個的FPS是多少?
目前,運行的幀率是每秒 60 幀(FPS),并且是固定的。如果將編譯器切換到優化模式,性能會更好,達到更高的效率。
不過,地面塊的重建(ground chunk rebuilding)目前還沒有在單獨的線程中處理,雖然理論上可以將其獨立出來,這樣可以避免一些性能問題。當前,仍然會在某些情況下出現輕微的卡頓,比如移動到新位置時會看到一些小的“卡頓”現象。這個問題需要解決,雖然整體來說,性能表現還算不錯,進展比較順利。
為了創建階梯的3D效果,是否需要創建一種渲染技術來繪制階梯的墻壁,從底樓開始,到頂樓結束?
為了實現樓梯的三維效果,目的是從底層開始繪制墻面,直到頂層,形成一個很酷的視差效果。這需要設計一個渲染技術來處理樓梯墻面的繪制,確保在不同的高度上呈現出良好的效果。具體要怎么做,還沒有完全確定,可能會在之后進行調整和改進,計劃會根據實際情況進行優化和實現。
什么時候會考慮咨詢其他程序員?什么情況才到達這個門檻?
在考慮是否向其他程序員請教時,通常是在自己遇到某個自己不擅長的領域時,特別是當我知道某個程序員在某個領域非常專業時。例如,涉及到多線程編程時,如果自己對這一部分不夠精通,可能會向一些擅長這個領域的程序員請教,了解他們的思路和方法,獲取一些有價值的見解。通常不太會單純為了討論編程問題而去咨詢別人,因為大部分情況下自己能處理這些問題。但如果遇到自己不熟悉的領域,或者對某些技術有疑問時,會主動請教經驗豐富的人。這不僅是為了當下的項目,也是為了以后能積累更多的經驗和知識。
如果你想對場景的不同部分應用不同的變換或光照,會發生什么?
如果需要對場景中不同部分應用不同的變換,其實系統已經為此做好了準備。當前,在處理每個實體時,已經有不同的變換應用到了每個實體上。所以,如果需要對場景的不同部分進行不同的變換,這個操作是非常簡單的。實際上,現在系統已經在每個實體上應用了變換,其中每個實體的變換都可以根據其位置來設置。如果需要為每個實體設置不同的焦距或其他任何參數,也是可以輕松做到的。總的來說,系統已經支持了這種靈活的變換應用,可以根據需求進行不同的設置和調整。
你預期使用OpenGL/D3D進行Blitting會有多少性能提升?
使用OpenGL進行處理時,主要的性能提升來自于硬件方面的優化。OpenGL提供了更多的硬件資源來處理內存復制,這些硬件資源本身就為這種操作進行了優化,因此在處理紋理時,OpenGL應該能比當前的方式更快速。如果指的是將數據展示到屏幕上的過程,那么通過OpenGL進行渲染可能會更加流暢,因為沒有經過一些復雜的路徑。不過,對于具體提升的幅度,目前還不太確定,可能提升的程度并不會特別大。
渲染器中是否有類似于OpenGL/D3D中對模型空間/攝像機空間/裁剪空間的翻譯的概念?
在OpenGL中,模型空間(local space)、世界空間(model space)、相機空間(camera space)和裁剪空間(clip space)之間的轉換是非常關鍵的,但OpenGL本身并不直接處理或關心這些轉換。它只關心裁剪空間,并且允許開發者自己在著色器中進行所有必要的變換。具體來說,OpenGL并不直接計算從局部空間、模型空間到相機空間的轉換,而是將這些變換組合起來,在著色器中計算最終的裁剪空間坐標。
在這種方式下,OpenGL讓開發者完全控制所有的變換過程,而無需顯式地處理每一個空間轉換。相反,其他圖形API或渲染系統(比如DirectX)可能更關注這些轉換步驟,特別是如何將對象從局部空間轉換到裁剪空間,進行相應的計算和處理。
總之,OpenGL并不關心局部空間、模型空間或相機空間等中間步驟,而是通過在著色器中完成所有必要的計算,最終將對象轉換為裁剪空間。
我找到一個你講解四元數雙重覆蓋的視頻,但沒有解釋四元數。你會考慮在不久的將來做一個解釋嗎?
關于四元數雙重覆蓋(quaternion double cover)的問題,目前并不打算提供這方面的講解,因為已經很久沒有深入研究四元數了,尤其是自從最初接觸時,四元數在計算機圖形學中還比較新穎。雖然四元數的數學背景本身并不新,早在哈密頓時代就已經有相關理論,但隨著時間的推移,四元數的應用和理論也有了很多發展,尤其是現在的雙四元數(dual quaternion)等技術,已經超出了以前的研究范圍。
四元數的應用如今涉及到許多復雜的幾何代數內容,這些內容并不熟悉,也沒有進行過深入的學習,因此無法為大家提供一個完整的入門講解。四元數的學習不僅僅是理解其數學背景,還需要涉及一些現代幾何代數的概念,而這些內容并沒有深刻了解。因此,可能需要找到更合適的專家來進行詳細的講解。
總的來說,四元數雖然是一個重要的數學工具,特別在計算機圖形學中用于旋轉等操作,但如今的技術發展已經將其應用范圍拓展,理解和使用現代的雙四元數等技術,也要求對更廣泛的幾何代數有所了解。
1. 什么是四元素(Quaternion)?
四元素是一種數學對象,用于表示三維空間中的旋轉。它由威廉·哈密頓(William Hamilton)在1843年提出,形式為:
q = w + x i + y j + z k q = w + xi + yj + zk q=w+xi+yj+zk
其中:
- w , x , y , z w, x, y, z w,x,y,z 是實數;
- i , j , k i, j, k i,j,k 是虛數單位,滿足 i 2 = j 2 = k 2 = i j k = ? 1 i^2 = j^2 = k^2 = ijk = -1 i2=j2=k2=ijk=?1。
四元素可以看作一個標量( w w w)加上一個三維向量( x i + y j + z k xi + yj + zk xi+yj+zk)。在計算機圖形學、機器人學和航空航天中,四元素常用來表示旋轉,因為它比旋轉矩陣更緊湊,且避免了“萬向鎖”(gimbal lock)問題。
例子:
假設一個四元素 q = 0.707 + 0.707 i + 0 j + 0 k q = 0.707 + 0.707i + 0j + 0k q=0.707+0.707i+0j+0k,它表示繞 x x x 軸旋轉 90 度的旋轉。
- w = cos ? ( θ / 2 ) w = \cos(\theta/2) w=cos(θ/2), θ \theta θ 是旋轉角度,這里 θ = 9 0 ° \theta = 90^\circ θ=90°,所以 w = cos ? ( 4 5 ° ) = 0.707 w = \cos(45^\circ) = 0.707 w=cos(45°)=0.707;
- 向量部分 ( x , y , z ) = ( 1 , 0 , 0 ) (x, y, z) = (1, 0, 0) (x,y,z)=(1,0,0) 表示旋轉軸,模長標準化后乘以 sin ? ( θ / 2 ) = sin ? ( 4 5 ° ) = 0.707 \sin(\theta/2) = \sin(45^\circ) = 0.707 sin(θ/2)=sin(45°)=0.707。
2. 什么是四元素的雙覆蓋(Double Cover)?
四元素的“雙覆蓋”是指:對于同一個三維旋轉,存在兩個不同的四元素可以表示它。具體來說,如果 q q q 表示某個旋轉,那么 ? q -q ?q(即每個分量取負)表示的也是同一個旋轉。這是因為四元素通過單位四元素的乘法對應到旋轉群 S O ( 3 ) SO(3) SO(3)(三維旋轉群),而這種對應是 2:1 的映射。
數學上:
- 四元素屬于 S U ( 2 ) SU(2) SU(2) 群(單位四元素的集合),而 S U ( 2 ) SU(2) SU(2) 到 S O ( 3 ) SO(3) SO(3) 的映射是一個雙覆蓋。
- 對于旋轉角度 θ \theta θ 和軸 v ? \vec{v} v,可以用 q = cos ? ( θ / 2 ) + sin ? ( θ / 2 ) v ? q = \cos(\theta/2) + \sin(\theta/2)\vec{v} q=cos(θ/2)+sin(θ/2)v 表示,但 q q q 和 ? q -q ?q 對應同一個旋轉。
例子:
- 旋轉 180 度繞 z z z 軸:
- q 1 = cos ? ( 9 0 ° ) + sin ? ( 9 0 ° ) k = 0 + 1 k q_1 = \cos(90^\circ) + \sin(90^\circ)k = 0 + 1k q1?=cos(90°)+sin(90°)k=0+1k;
- q 2 = cos ? ( 27 0 ° ) + sin ? ( 27 0 ° ) k = 0 ? 1 k = ? q 1 q_2 = \cos(270^\circ) + \sin(270^\circ)k = 0 - 1k = -q_1 q2?=cos(270°)+sin(270°)k=0?1k=?q1?。
- q 1 q_1 q1? 和 q 2 q_2 q2? 都表示繞 z z z 軸旋轉 180 度。
3. 優勢是什么?
四元素的優勢:
- 緊湊性:只需 4 個數字表示旋轉,而旋轉矩陣需要 9 個。
- 無萬向鎖:避免了歐拉角在某些角度下的奇異性問題。
- 平滑插值:四元素可以用球面線性插值(SLERP)平滑過渡兩個旋轉,適合動畫和運動規劃。
- 計算效率:四元素的乘法和逆運算比矩陣更高效。
雙覆蓋的優勢(或特性):
- 數學完備性:雙覆蓋反映了旋轉群的拓撲性質( S O ( 3 ) SO(3) SO(3) 不是單連通的),在理論物理(如量子力學中的自旋)中有重要應用。
- 連續性:在路徑規劃中,雙覆蓋允許區分“旋轉一圈”和“回到原位”,例如區分 360 度和 0 度的路徑,這在某些應用(如機器人手臂控制)中很有用。
總結:
四元素是一個表示旋轉的強大工具,雙覆蓋是它的一個數學特性,雖然在實際應用中我們通常只關心單一表示,但在理論和某些復雜場景下,雙覆蓋提供了額外的靈活性和精確性。比如在游戲引擎(如Unity或Unreal)中,四元素被廣泛使用來確保旋轉的平滑和穩定。
import numpy as np
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg# 設置支持中文的字體并放大
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.size'] = 14# 四元素類
class Quaternion:def __init__(self, w, x, y, z):self.w = wself.x = xself.y = yself.z = zdef __str__(self):return f"{self.w:.2f} + {self.x:.2f}i + {self.y:.2f}j + {self.z:.2f}k"def multiply(self, other):w1, x1, y1, z1 = self.w, self.x, self.y, self.zw2, x2, y2, z2 = other.w, other.x, other.y, other.zw = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2return Quaternion(w, x, y, z)def conjugate(self):return Quaternion(self.w, -self.x, -self.y, -self.z)def rotate_vector(self, vector):p = Quaternion(0, vector[0], vector[1], vector[2])q_conj = self.conjugate()result = self.multiply(p).multiply(q_conj)return np.array([result.x, result.y, result.z])# 創建四元素
def create_quaternion(axis, angle_deg):angle_rad = np.radians(angle_deg)w = np.cos(angle_rad / 2)s = np.sin(angle_rad / 2)norm = np.sqrt(sum(a**2 for a in axis))if norm == 0:return Quaternion(1, 0, 0, 0)x, y, z = [a / norm * s for a in axis]return Quaternion(w, x, y, z)# 可視化工具類
class QuaternionVisualizer:def __init__(self, root):self.root = rootself.root.title("四元素可視化工具")self.root.geometry("900x700")# 設置全局字體self.font_large = ('SimHei', 14)# 初始向量self.initial_vector = np.array([1, 0, 0])self.rotated_vector = self.initial_vector.copy()self.current_q = Quaternion(1, 0, 0, 0)# 主框架self.frame = ttk.Frame(root, padding="20")self.frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))# 設置行列權重self.root.grid_rowconfigure(0, weight=1)self.root.grid_columnconfigure(0, weight=1)self.frame.grid_rowconfigure(0, weight=1)self.frame.grid_columnconfigure(2, weight=1)# 輸入區域(左側)self.input_frame = ttk.Frame(self.frame)self.input_frame.grid(row=0, column=0, sticky=(tk.W, tk.N, tk.S), padx=10)ttk.Label(self.input_frame, text="旋轉軸 (x y z):", font=self.font_large).grid(row=0, column=0, sticky=tk.W, pady=10)self.axis_entry = ttk.Entry(self.input_frame, width=15, font=self.font_large)self.axis_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=10)self.axis_entry.insert(0, "0 0 1")ttk.Label(self.input_frame, text="旋轉角度 (度):", font=self.font_large).grid(row=1, column=0, sticky=tk.W, pady=10)self.angle_entry = ttk.Entry(self.input_frame, width=15, font=self.font_large)self.angle_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=10)self.angle_entry.insert(0, "90")ttk.Button(self.input_frame, text="應用旋轉", command=self.apply_rotation, style='Large.TButton').grid(row=2, column=0, columnspan=2, pady=10)ttk.Button(self.input_frame, text="重置", command=self.reset, style='Large.TButton').grid(row=3, column=0, columnspan=2, pady=10)# 四元素信息(中部)self.info_frame = ttk.Frame(self.frame)self.info_frame.grid(row=3, column=0, sticky=(tk.W, tk.N, tk.S), padx=10)self.q_label = ttk.Label(self.info_frame, text="當前四元素: 1.00 + 0.00i + 0.00j + 0.00k", font=self.font_large)self.q_label.grid(row=4, column=0, pady=10)self.double_cover_label = ttk.Label(self.info_frame, text="雙覆蓋版本: -1.00 + 0.00i + 0.00j + 0.00k", font=self.font_large)self.double_cover_label.grid(row=5, column=0, pady=10)self.explain_label = ttk.Label(self.info_frame, text="解釋: 未旋轉狀態", font=self.font_large, wraplength=400)self.explain_label.grid(row=6, column=0, pady=10)# Matplotlib 嵌入(右側)self.fig = plt.Figure(figsize=(7, 7))self.ax = self.fig.add_subplot(111, projection='3d')self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame)self.canvas.get_tk_widget().grid(row=0, column=2, sticky=(tk.W, tk.E, tk.N, tk.S))# 綁定窗口大小變化事件self.root.bind("<Configure>", self.on_resize)# 自定義按鈕樣式style = ttk.Style()style.configure('Large.TButton', font=self.font_large)# 初次繪制self.update_plot()self.last_width = self.root.winfo_width()self.last_height = self.root.winfo_height()def update_plot(self):self.ax.clear()self.ax.quiver(0, 0, 0, self.initial_vector[0], self.initial_vector[1], self.initial_vector[2], color='r', label='初始向量', linewidth=2)self.ax.quiver(0, 0, 0, self.rotated_vector[0], self.rotated_vector[1], self.rotated_vector[2], color='b', label='旋轉后向量', linewidth=2)self.ax.set_xlim([-1.5, 1.5])self.ax.set_ylim([-1.5, 1.5])self.ax.set_zlim([-1.5, 1.5])self.ax.set_xlabel('X軸')self.ax.set_ylabel('Y軸')self.ax.set_zlabel('Z軸')self.ax.legend()self.canvas.draw()def on_resize(self, event):# 避免初始調整和重復繪制if event.widget == self.root:new_width = event.widthnew_height = event.heightif new_width != self.last_width or new_height != self.last_height:# 只調整圖形大小,不重繪內容dpi = self.fig.dpiself.fig.set_size_inches((new_width * 0.5) / dpi, (new_height * 0.8) / dpi)self.canvas.draw() # 只更新畫布,不重新生成視圖self.last_width = new_widthself.last_height = new_heightdef apply_rotation(self):try:axis = [float(x) for x in self.axis_entry.get().split()]if len(axis) != 3:raise ValueError("軸必須是3個數字")angle = float(self.angle_entry.get())except ValueError as e:tk.messagebox.showerror("輸入錯誤", f"無效輸入: {e}")returnq = create_quaternion(axis, angle)self.current_q = q.multiply(self.current_q)self.rotated_vector = self.current_q.rotate_vector(self.initial_vector)self.q_label.config(text=f"當前四元素: {self.current_q}")self.double_cover_label.config(text=f"雙覆蓋版本: {Quaternion(-self.current_q.w, -self.current_q.x, -self.current_q.y, -self.current_q.z)}")norm_axis = np.array(axis) / np.sqrt(sum(a**2 for a in axis))explain_text = (f"解釋:\n"f"1. 四元素 = w + xi + yj + zk\n"f" - w = cos(θ/2) = {self.current_q.w:.2f} (標量部分,表示旋轉角度)\n"f" - (x, y, z) = ({self.current_q.x:.2f}, {self.current_q.y:.2f}, {self.current_q.z:.2f}) (向量部分,表示軸)\n"f"2. 當前旋轉軸: [{norm_axis[0]:.2f}, {norm_axis[1]:.2f}, {norm_axis[2]:.2f}]\n"f"3. 雙覆蓋: q 和 -q 表示相同旋轉")self.explain_label.config(text=explain_text)self.update_plot()def reset(self):self.current_q = Quaternion(1, 0, 0, 0)self.rotated_vector = self.initial_vector.copy()self.axis_entry.delete(0, tk.END)self.axis_entry.insert(0, "0 0 1")self.angle_entry.delete(0, tk.END)self.angle_entry.insert(0, "90")self.q_label.config(text="當前四元素: 1.00 + 0.00i + 0.00j + 0.00k")self.double_cover_label.config(text="雙覆蓋版本: -1.00 + 0.00i + 0.00j + 0.00k")self.explain_label.config(text="解釋: 未旋轉狀態")self.update_plot()# 啟動工具
if __name__ == "__main__":root = tk.Tk()app = QuaternionVisualizer(root)root.mainloop()