解決 Nintendo 64 上的加法混合問題

對於許多復古遊戲愛好者來說,第五代遊戲主機在處理視覺效果——特別是爆炸、電漿光束和魔法咒語——的方式上,一直存在著明顯的差異。雖然原版 PlayStation (PSX) 通常具有鮮豔且發光的視覺效果,但 Nintendo 64 (N64) 卻經常缺乏那種「跳脫感」。

這種視覺上的差異並非源於原始算力的不足,而是兩個系統在處理加法混合(additive blending)方式上的根本差異。在最近的一項技術探索中,PhobosLab 的 Dominic Szablewski 解析了為什麼 N64 的硬體使得傳統的加法混合幾乎變得不可能,以及如何利用現代的 homebrew 工具來繞過這些限制。

PSX 的方法:簡單且有效

PlayStation 的 GPU 支援四種不同的混合模式,用以決定如何將精靈(sprites)和幾何圖形混合到現有的幀緩衝區(frame buffer)中。其中對視覺效果最關鍵的是 src + dst 模式。

在此模式下,來源(精靈)的顏色值會直接加到目標(幀緩衝區)上。如果結果值超過了最大可能值(例如 8 位元系統中的 255),GPU 會自動將該值「鉗制」(clamp)在 255。這確保了在場景中增加光線只會讓畫面變得更亮,從而創造出火焰和能量效果所必需的發光感。

N64 的問題:具備靈活性但缺乏鉗制功能

N64 的 Reality Display Processor (RDP) 具有高度靈活的「顏色組合器」(Color Combiner),其概念與 OpenGL 的 glBlendFunc() 類似。使用 Libdragon 等工具,開發者可以使用 RDPQ_BLENDER 宏來執行如 (P * A) + (Q * B) 的運算。

雖然在程式碼中設定加法混合非常簡單——基本上就是將來源 RGB 值加到記憶體中的 RGB 值——但 RDP 有一個關鍵缺陷:它不會對結果進行鉗制。

當顏色值超過最大限制時,它不會停留在最大值;而是會發生「回繞」(wrap around)。例如,如果將藍色值 226 加到幀緩衝區值 170 上,結果會是 396。N64 不會將其鉗制在 255,而是會讓該值回繞,結果變成 140。這會造成令人不悅的視覺偽影(artifacts),使得爆炸最亮的部分突然變暗或發生意想不到的顏色偏移。

工程上的解決方案

為了在不產生回繞偽影的情況下實現真正的加法混合,Szablewski 提出了利用 N64 獨特架構的多步驟流程:

1. 使用 32 位元緩衝區增加餘裕空間

雖然大多數 N64 遊戲使用 16 位元幀緩衝區以節省記憶體頻寬,但 RDP 可以繪製到 32 位元的 RGBA 8888 緩衝區中。透過將所有內容以原始強度的一小部分進行繪製,開發者可以創造出「餘裕空間」(headroom),防止多個加法精靈的總和達到回繞閾值(255)。

與其在離線狀態下預處理資產,不如透過濫用霧氣(fog)的 alpha 值,即時地以 1/8 強度繪製所有顏色:

rdpq_set_fog_color(RGBA32(0, 0, 0, 256/8));
rdpq_mode_blender(RDPQ_BLENDER(( IN_RGB, FOG_ALPHA, MEMORY_RGB, ONE )));

2. 轉換瓶頸

繪製到 32 位元緩衝區只是成功的一半;最終輸出必須轉換回 16 位元緩衝區才能進行顯示。在 CPU 上執行此操作非常緩慢,對於 320x240 解析度,每幀大約需要 70ms——這對於即時遊戲來說實在太慢了。

3. 利用 RSP

這就是 N64 的向量共處理器 Reality Signal Processor (RSP) 發揮作用的地方。透過編寫自定義的微碼(microcode),轉換和鉗制過程可以從 CPU 卸載到 RSP。利用 RSP 的 128 位元向量指令,系統可以同時處理八個像素點。

在社群的優化幫助下(特別是透過 RSPL 語言,它將類 C 語言程式碼編譯成 MIPS assembly),轉換時間從 70ms 減少到了每幀僅僅 3.1ms

權衡與技術洞察

這種技術並非「萬靈丹」。大多數開發者避免使用 32 位元緩衝區是有原因的:N64 的記憶體吞吐量非常低。繪製到 graph 32 位元緩衝區需要從 RDRAM 移動兩倍的量的數據,這會顯量著地影響效能。

然而,其結果是視覺效果更佳的加法混合,能夠模仿 PSX 的行為,而不會產生回繞偽影。社群指出,這個問題與音訊混音非常相似;如果沒有鉗制(飽和算術),信號溢出會導致嚴重的數位剪裁(clipping)或回繞雜訊。

流程總結

  1. 初始化一個 16 位元幀緩衝區用於最終顯示。

  2. 渲染場景到一個次要的 32 位元緩衝區,並以 1/8 強度進行,以防止溢出。

  3. 處理 32 位元緩衝區,使用 RSP 來鉗制值並將其轉換回 16 位元 RGBA 5551 格式。

  4. 呈現最終的 16 位元緩衝區到螢幕上。

對於有興趣實作這個方案的開發者,GitHub 上有一個示範專案:phoboslab/n64_addblend

Sources