全部文章

Realsee 以 WebGPU 全面重寫空間渲染器:追求極致 3D 體驗

Realsee 以 WebGPU 全面重寫空間渲染器,把對極致 3D 體驗的追求落到完整渲染鏈路:從全景與模型、高清圖塊和材質,到空間插件、輔助視圖與圖像輸出。本文拆解這次重寫的架構選擇與工程處理。
Panorama views and a textured 3D model overview

Realsee 以 WebGPU 全面重寫了空間渲染器,目標是追求極致 3D 體驗。重寫覆蓋從全景與模型顯示,到高清圖塊、材質、插件和圖像輸出的完整鏈路。

用戶轉動視角、放大細節、在拍攝位置間移動,或切換帶紋理的模型查看佈局時,渲染器都要承接這些操作。標註、視頻、測量與導覽,也要與空間中的具體位置保持對應。[1]

高清資源要跟隨當前視野,影像與模型要保持一致的材質與顏色處理。主播放器、空間插件、輔助畫布和截圖,也要共享同一套資源與輸出規則。

WebGPU 與 WebGL、WebGL2 的區別

WebGL、WebGL2 和 WebGPU 都能使用 GPU 繪製三維畫面。WebGL2 在 WebGL 的基礎上擴展圖形能力,將實例化繪製、多渲染目標等納入核心,並增加統一變量緩衝、三維紋理和 Transform Feedback。Transform Feedback 可以把頂點著色器的輸出保存到緩衝區,供後續處理使用。[2] [3]

WebGPU 面向 Metal、Vulkan、Direct3D 12 等現代圖形接口,重新組織管線、資源綁定和命令提交,同時提供通用 GPU 計算。[4]

對比 WebGL WebGL2 WebGPU
API 設計基礎 OpenGL ES 2.0 OpenGL ES 3.0 面向現代 GPU 接口
核心能力 圖形繪製 更完整的圖形繪製與數據處理 圖形繪製與通用 GPU 計算
GPU 數據計算 利用著色器與紋理組織計算 還可用 Transform Feedback 保存頂點結果 計算管線、可讀寫存儲緩衝和工作組
應用側任務組織 修改上下文狀態,發出繪製調用 延續該模型,增加批量傳參等能力 明確管線與資源綁定,編碼命令後提交隊列
著色器語言 GLSL ES 1.00 支持 GLSL ES 3.00,並保留 1.00 能力 WGSL

繪製狀態可以更明確地組織和複用。 WebGPU 把著色器、頂點佈局、深度和混合狀態等配置組織為管線對象。[5] 繪製時選擇管線並綁定資源,多個繪製任務可以複用固定配置;命令緩衝則讓瀏覽器和驅動基於一批任務安排執行。Chrome 官方將降低相同畫面所需的 JavaScript 工作量列為 WebGPU 的主要收益。[6]

圖形與計算擁有各自的程序模型。 計算管線、存儲緩衝和間接調度可以承載並行數據處理,為引擎擴展算法提供基礎。[5] 日常全景與紋理模型瀏覽主要通過圖形管線完成:CPU 組織視野相關資源,GPU 負責圖像採樣、材質計算和場景繪製。

擴展亮度可以接入畫布輸出。 對帶 HDR 數據的全景,恢復出的高亮值需要沿渲染過程保留下來。WebGPU 的半浮點畫布格式配合 extended 輸出模式,可把這些數值用於支持 HDR 的顯示環境。[7] 這讓全景影像的亮度信息能夠一直參與到最終呈現。

全景與模型怎樣共同成像

全景照片提供表面顏色,模型提供空間位置與形狀。模型材質根據表面位置,以及全景的拍攝位置和方向計算採樣,再組合全景顏色與模型自身的材質輸出。場景還保留全景背景,用來覆蓋模型表面之外的視野。

帶過渡的點位切換需要同步處理相機和圖像。前後兩個點位的全景資源參與材質更新,相機的位置、朝向和視場角隨所選移動效果變化,兩組全景顏色按過渡規則組合。進入模型視圖時,再調整全景與模型材質的呈現權重,讓用戶從照片細節轉向觀察整體結構。遷移材質時,這些採樣關係和過渡行為需要一起保留。

高清圖塊按當前視野加載

全景的清晰度需求隨視場角、畫布尺寸和像素比變化。我們據此選擇圖塊級別,再由 CPU 檢查圖塊是否與當前視錐相交。請求按較低級別優先、同級靠近畫面中心優先的順序安排,並限制同時進行的請求數量。用戶放大窗邊或牆面的細節時,資源加載便優先補充當前視野需要的區域。

基礎全景先提供完整內容,新圖塊加載後逐漸顯現。基礎圖與已加載的高清圖塊被繪製成對應當前視角的細節紋理,再交給主場景材質按投影位置採樣。視野細節紋理與最終場景畫面承擔不同任務:前者為全景材質補充清晰度,後者包含場景繪製結果,供輸出和後處理使用。

基礎全景與CPU選擇的高清圖塊生成視野細節紋理,再與模型和空間媒體共同參與場景繪製

視野細節紋理用於全景材質採樣,場景畫面用於輸出和可選後處理。

全線遷移涉及哪些挑戰

Realsee 把主 VR、空間插件、戶型圖、導覽、小地圖、商品與模型預覽,以及截圖和封面輸出,都納入這輪渲染體系遷移。主場景之外,一些組件擁有獨立的相機和畫布;另一些組件依賴已有材質、顏色和截圖約定。遷移需要讓這些入口共同使用新的渲染能力。

材質遷移要保留既有視覺行為

Three.js 的 WebGPU 渲染器使用節點材質,原有自定義 GLSL 材質需要改寫為 Three.js 著色語言(TSL)。TSL 用節點組合描述著色過程,並可根據後端生成 WGSL 或 GLSL。[8]

我們遷移了全景視頻、導覽線、小模型背景和粒子等材質,同時保留它們的投影、遮罩、漸隱和深度關係。測量線還有一個容易漏掉的狀態:用戶剛開始操作,第一段線尚未形成。這裡需要先準備合法的幾何輸入佈局,把可見實例數保持為零,等端點齊備後再繪製。

跨包依賴也要一起調整。主引擎和空間插件的 Three.js 依賴由宿主提供;插件打包時外置這項依賴,宿主統一版本與解析入口。獨立模型預覽再註冊主引擎使用的模型材質映射,讓加載出的模型進入對應的節點材質體系。

顏色和透明度要沿著合成過程核對

同一段視頻換到新渲染器後,可能出現偏亮。我們定位到的一類原因是:視頻像素已經採用 sRGB 編碼,卻被當作線性顏色,再做了一次輸出編碼。處理辦法是明確聲明視頻紋理的顏色空間,讓輸入解碼與最終輸出轉換各司其職。Three.js 的視頻紋理文檔也明確了這一輸入要求。[9]

透明媒體、導覽線和已有標註還要與背景合成。透明媒體分別設置顏色與 alpha 的混合方式,線條保留已經繪製的背景;已有標註則在合成邊界處理工作空間與顯示編碼,讓保存的顏色沿用原有含義。這裡需要同時看輸入顏色、線性工作空間與顯示編碼;單獨調整亮度會掩蓋轉換環節的錯誤。[10]

初始化、退出和設備重建要形成完整生命週期

WebGPU 渲染器的初始化是異步過程。[8] 模型預覽和獨立指南針會等待渲染器準備完成,再建立依賴資源或開始繪製。用戶快速切換預覽時,模型加載結果還要確認自己屬於當前請求;已經過期的結果直接釋放,當前畫面繼續響應最新操作。

關閉商品預覽時,先取消請求與動畫、移除監聽;待初始化結束後釋放實例並移除畫布。GPU 設備丟失則進入另一條處理路徑:主視圖先保存瀏覽狀態,釋放舊會話,再建立新會話,按資源加載順序恢復視角和樓層。採用共享獨立渲染器封裝的插件畫布,會重建渲染器,恢復尺寸、像素比和透明背景,待新實例就緒後替換畫布。主視圖與這些插件畫布分別負責各自的資源重建。

截圖和封面也要適配新的像素輸出

這條截圖路徑先把所需區域繪製到 RGBA8 目標,再異步將像素從 GPU 讀回 CPU。使用 WebGPU 的 copyTextureToBuffer 複製多行這類像素時,行步長需要是 256 字節的整數倍。例如,寬 100 像素的 RGBA8 圖像,每行有 400 字節顏色數據,複製佈局可以採用 512 字節的行步長;交給普通圖像處理之前,要取出每行的 400 字節有效像素。[5]

讀回層先檢查數據長度:已經緊密排列就直接複製,帶有行末空隙時逐行取出有效像素。宿主再按所用 SDK 的實際後端行為統一輸出行向。裁剪區域仍沿用底部原點座標,輸出像素行按從上到下排列後交給圖像畫布。裁剪和封面共用這層適配,臨時隱藏的輔助元素在截圖結束後恢復。

讓沒有變化的畫面繼續使用

畫面複用與按需更新適用於 WebGL2 和 WebGPU 兩種後端,由渲染引擎決定更新規則。相機停在一個位置時,場景可能已經靜止,某個動畫效果卻還在運行。如果每次更新效果都重新處理整個場景,就會重複執行本來沒有變化的工作。

在 Realsee 的渲染實現中,我們把場景合成與後處理分開。場景合成產生一幅中間畫面,後處理在這幅畫面上繼續添加效果。只要場景內容沒有發生相關變化,這個中間結果就可以留給後續繪製使用。

這個中間結果有明確的失效條件:

發生的變化 渲染器需要做什麼
相機或場景內容變化 重新合成場景;舊畫面已經不再對應當前視圖
圖像資源到達、視頻有新幀 將變化傳給場景更新,避免繼續顯示舊內容
後處理仍啟用,且啟用數量或依賴的輸入種類變化 重新準備對應的合成結果
關閉最後一個後處理 應用請求一次更新,輸出不含該效果的畫面
視口尺寸變化 重建輸出資源,再按新尺寸合成
場景不變,但動畫類後處理繼續運行 複用場景畫面,繼續計算後處理效果

切換效果時,調用方需要請求一幀更新;關閉最後一個後處理時,這一幀把不含該效果的畫面輸出到屏幕。隨後,未啟用後處理且場景沒有更新時,渲染器可以跳過畫面輸出。仍啟用後處理時,處理步驟繼續讀取保留的場景畫面,新工作照常編碼並提交給 GPU。

保存中間畫面會佔用顯存,格式選擇也會影響讀寫量。以保存顏色輸出的紋理,也就是顏色附件為例:尺寸為 1920 × 1080,單層、單採樣,不生成多級紋理(mipmap)。RGBA8 由四個 8 位通道組成,RGBA16F 則是四個 16 位浮點通道;按 texel(紋理像素)計算存儲量:[5]

顏色格式 每個 texel 宽 × 高 × 字节数 存儲量
RGBA8 4 字節 1920 × 1080 × 4 約 7.9 MiB
RGBA16F 8 字節 1920 × 1080 × 8 約 15.8 MiB

這裡 1 MiB = 1,048,576 字節,深度、緩衝副本、其他附件和分配對齊另行計入。寬和高都變為原來的兩倍,同格式的顏色存儲就會變為原來的四倍。這些數值描述顏色 texel 的存儲空間;一幀的讀寫成本還取決於渲染過程怎樣訪問這些紋理。選擇輸出精度時,需要同時考慮容量和訪問方式。Three.js 的文檔也將畫質與內存、帶寬作為共同取捨。[11]

圖塊的資源複用另有一層管理。近期使用、仍在緩存中的圖塊可以繼續提供紋理,超出緩存範圍後再釋放資源;需要新的紋理對象時,複用池按尺寸和顏色空間匹配。這樣既能保留已加載的細節,也能讓顏色圖與數據紋理沿用各自正確的解釋方式。

讓視頻驅動畫面更新

按需渲染需要知道場景何時改變。相機靜止時,全景裡播放的視頻仍會產生新內容,所以媒體組件也要負責通知渲染器。

收到“開始播放”的事件時,未必已經有可供繪製的視頻幀。我們的組件會檢查當前幀是否可用,以及視頻是否屬於正在觀看的全景,再讓它進入場景。播放期間,組件持續請求場景重繪,並在紋理尚未更新時補充更新請求;即使相機沒有移動,媒體畫面也能繼續推進。

暫停時,組件撤銷播放期間的繪製調度;銷燬時,釋放視頻紋理、幾何和材質。對遲到的播放、暫停或卸載事件,還要結合組件當前狀態處理,避免上一段播放過程干擾新畫面。

兼容 WebGPU 與 WebGL 2

WebGPU 的可用性取決於瀏覽器、操作系統和 GPU。瀏覽器除了提供相應接口,還需要提供滿足應用要求的可用 GPU 設備。[6] [4]

Three.js 的 WebGPU 渲染器支持 WebGPU 和 WebGL 2 兩種後端。[11] Realsee 保留了對應的渲染路徑,並在初始化後確認實際後端。全景、模型和空間組件使用與後端匹配的材質與資源,截圖等輸出環節也按實際後端適配。

應用需要讓視點移動、模式切換和媒體更新分別接入對應後端。相同的內容可以有不同的繪製方法,具體效果也應與所選路徑的能力匹配。

從 npm 體驗 WebGPU 版本

開發者可以使用 npm 上的 Five WebGPU 開始構建空間瀏覽。核心 SDK 通過 alpha 通道發佈 WebGPU 版本;本文對應 1.0.0-alpha.11,與 three@0.183.2 配套。[12]

npm install --save-exact three@0.183.2 @realsee/five-webgpu@1.0.0-alpha.11

需要標籤、測量、導覽等空間插件時,可追加 Dnalogel 的 WebGPU 版本,本文使用 4.0.0-webgpu.6。插件與核心 SDK 使用同一份 Three.js。[13]

npm install --save-exact @realsee/dnalogel@4.0.0-webgpu.6

可以先在 Realsee Discover 探索餐廳、住宅、博物館和零售等類型的公開空間案例。[14]

開發者可以訪問 Realsee 開發者平臺,瞭解空間瀏覽能力並構建自己的應用。

參考資料