戰(zhàn)(02):把 3D 展廳視角映射為可試聽(tīng)的聲源快照)
圖鑒升級(jí)為 3D 展廳后旋轉(zhuǎn)模型只是第一步。假設(shè)一個(gè)知識(shí)熱點(diǎn)位于模型右側(cè)模型轉(zhuǎn)過(guò)半圈后熱點(diǎn)應(yīng)出現(xiàn)在左側(cè)與它關(guān)聯(lián)的聲音也需要隨之換到左側(cè)。若每次播放都使用同一個(gè)固定音頻坐標(biāo)畫面與聲音就會(huì)脫節(jié)。本實(shí)驗(yàn)把模型的 Y 軸旋轉(zhuǎn)、觀察距離與空間聲源連接起來(lái)導(dǎo)入帶高斯數(shù)據(jù)的模型調(diào)整角度點(diǎn)擊按鈕生成當(dāng)前位置的四秒方位測(cè)試音。它是離散快照試聽(tīng)適合逐項(xiàng)核對(duì)坐標(biāo)滑動(dòng)過(guò)程中不會(huì)連續(xù)更新正在播放的聲源。先把四套概念分清同一個(gè)熱點(diǎn)會(huì)經(jīng)歷局部坐標(biāo)、場(chǎng)景坐標(biāo)、聽(tīng)者相對(duì)坐標(biāo)和音頻輸入坐標(biāo)。模型局部坐標(biāo)描述熱點(diǎn)附著在模型哪里場(chǎng)景坐標(biāo)反映模型旋轉(zhuǎn)后的實(shí)際位置聽(tīng)者相對(duì)坐標(biāo)需要減去觀察者的位置音頻輸入則必須符合音頻節(jié)點(diǎn)的軸向與范圍。Demo 將一個(gè)測(cè)試熱點(diǎn)固定在模型局部(1,0,0)。它是坐標(biāo)標(biāo)定點(diǎn)并不自動(dòng)識(shí)別模型中的某個(gè)展品也沒(méi)有把任意模型的一米都解釋為真實(shí)世界的一米。正式展廳應(yīng)由內(nèi)容生產(chǎn)流程提供熱點(diǎn)坐標(biāo)和單位比例。對(duì)象本實(shí)驗(yàn)約定模型圍繞 Y 軸旋轉(zhuǎn)原點(diǎn)位置不變相機(jī)位于(0,0,distance)保持朝向場(chǎng)景原點(diǎn)支持繞Z軸滾轉(zhuǎn)180°測(cè)試熱點(diǎn)局部位置(1,0,0)音頻坐標(biāo)右 X、上 Y、前 Z三個(gè)分量限制在 [-5,5]這些約定讓首個(gè)聯(lián)動(dòng)實(shí)驗(yàn)可以使用簡(jiǎn)單變換。未來(lái)如果加入相機(jī)平移、俯仰或任意朝向必須使用完整視圖變換不能只改一個(gè)角度參數(shù)。模型旋轉(zhuǎn)與聲源計(jì)算使用同一個(gè)狀態(tài)3D 頁(yè)面保存水平旋轉(zhuǎn)角、觀察距離與相機(jī)滾轉(zhuǎn)角。渲染控制器把角度寫入 GSNode 的旋轉(zhuǎn)四元數(shù)同時(shí)把距離寫入相機(jī) Z 坐標(biāo)。試聽(tīng)按鈕直接讀取這三個(gè)頁(yè)面狀態(tài)避免再維護(hù)一份可能滯后的“音頻角度”。對(duì)局部點(diǎn)(x,y,z)繞 Y 軸旋轉(zhuǎn)后constworldXMath.cos(yaw)*xMath.sin(yaw)*z;constworldZ-Math.sin(yaw)*xMath.cos(yaw)*z;相機(jī)未滾轉(zhuǎn)時(shí)音頻前向分量是distance - worldZ高度保持為 y。啟用畫面翻轉(zhuǎn)后先用相機(jī)滾轉(zhuǎn)的逆變換計(jì)算聽(tīng)者坐標(biāo)listenerX cos(roll) * worldX sin(roll) * ylistenerY -sin(roll) * worldX cos(roll) * y。相機(jī)滾轉(zhuǎn)π時(shí)聽(tīng)者的X、Y方向同時(shí)反號(hào)前向分量保持不變。這個(gè)符號(hào)來(lái)自場(chǎng)景與音頻軸向的對(duì)應(yīng)關(guān)系不是靠試聽(tīng)后隨意加負(fù)號(hào)。相機(jī)滾轉(zhuǎn)為0時(shí)局部點(diǎn)(1,0,0)在零度時(shí)位于右側(cè)在180度時(shí)位于左側(cè)。這兩個(gè)輸入能直觀檢查符號(hào)90度則用于觀察深度變化。啟用畫面翻轉(zhuǎn)后左右方向隨聽(tīng)者坐標(biāo)一同翻轉(zhuǎn)。超過(guò)五米時(shí)為什么不能分別截?cái)?XYZ場(chǎng)景允許較大的觀察距離而音頻位置接口有自己的范圍。直接把每個(gè)分量分別限制到 [-5,5] 看起來(lái)能消除報(bào)錯(cuò)卻可能改變聲源方向。例如相對(duì)位置(2,1,30)分別截?cái)鄷?huì)變?yōu)?2,1,5)水平方向比例從2/30變成2/5聽(tīng)覺(jué)上被拉向側(cè)面。本實(shí)驗(yàn)對(duì)三個(gè)分量使用相同縮放系數(shù)constforwarddistance-worldZ;constlistenerXMath.cos(cameraRoll)*worldXMath.sin(cameraRoll)*y;constlistenerY-Math.sin(cameraRoll)*worldXMath.cos(cameraRoll)*y;constscaleMath.min(1,5/Math.max(Math.abs(listenerX),Math.abs(listenerY),Math.abs(forward),0.001));return{x:listenerX*scale,y:listenerY*scale,z:forward*scale,scale:scale};這樣(2,1,30)被映射到約(0.333,0.167,5)各分量比例保持不變。代價(jià)是遠(yuǎn)距離被壓縮不能再把映射后的米數(shù)當(dāng)作真實(shí)展廳距離。實(shí)際產(chǎn)品應(yīng)根據(jù)導(dǎo)覽場(chǎng)景選擇方向優(yōu)先、展廳分區(qū)、統(tǒng)一世界單位或限制活動(dòng)范圍。一次點(diǎn)擊保存一個(gè)完整快照點(diǎn)擊試聽(tīng)時(shí)先計(jì)算聲源位置再調(diào)用普通空間渲染節(jié)點(diǎn)的固定擺位模式。后續(xù)即使繼續(xù)移動(dòng)滑塊本次四秒聲音仍使用點(diǎn)擊時(shí)的位置。按鈕文字使用“試聽(tīng)聲源快照”讓用戶知道需要重新點(diǎn)擊才能比較新位置。constpointmapExhibitToAudio(1,0,0,this.yaw*Math.PI/180,this.distance,this.cameraRoll);constresultawaitthis.audioService.play(0,point.x,point.y,point.z);頁(yè)面回顯映射后的 XYZ 與渲染耗時(shí)。這里的耗時(shí)只覆蓋調(diào)用到 Native 返回 PCM不包括用戶聽(tīng)到聲音的全部端到端延遲。若要測(cè)量交互延遲還需把播放器創(chuàng)建、啟動(dòng)與輸出路由等階段納入獨(dú)立測(cè)量。與空間音頻基礎(chǔ)篇相比這一篇增加了坐標(biāo)關(guān)聯(lián)與同步契約。底層管線沿用同一服務(wù)避免兩個(gè)獨(dú)立音頻模塊對(duì)錯(cuò)誤碼、停止行為和輸出格式作出不同解釋。模型加載與聲音不能互相拖住生命周期導(dǎo)入模型經(jīng)系統(tǒng)選擇器獲取文件再?gòu)?fù)制到應(yīng)用沙箱。GLB或PLY的基本文件頭檢查只能說(shuō)明部分結(jié)構(gòu)不能證明里面有高斯數(shù)據(jù)真正加載由 GSPlugin 完成不合適的模型會(huì)進(jìn)入加載失敗路徑。模型成功加載后才允許試聽(tīng)。離開(kāi)頁(yè)面時(shí)同步使場(chǎng)景加載結(jié)果失效并停止音頻服務(wù)。Native 渲染若稍后返回其代次已經(jīng)過(guò)期不會(huì)重新創(chuàng)建播放器。3D 場(chǎng)景同樣有代次檢查防止返回后的異步模型結(jié)果繼續(xù)持有資源??煺章曇羰褂贸绦蛏傻膾哳l信號(hào)適合檢查方向。它還不是講解內(nèi)容系統(tǒng)展品選擇、講解文件、字幕、音量和播放進(jìn)度都需要在后續(xù)內(nèi)容會(huì)話中建立明確關(guān)聯(lián)。三個(gè)角度構(gòu)成一組可手算的坐標(biāo)用例固定局部點(diǎn) (1,0,0)、相機(jī)距離4、相機(jī)滾轉(zhuǎn)0按前面的公式得到下表。這些是公式預(yù)期值不是設(shè)備輸出記錄浮點(diǎn)測(cè)試應(yīng)使用適當(dāng)容差。yawworldXworldZ音頻前向 distance-worldZ0°10490°0-15180°-104在這三個(gè)點(diǎn)上縮放系數(shù)均為 1便于單獨(dú)檢查旋轉(zhuǎn)符號(hào)再增加遠(yuǎn)距離樣本檢查等比例縮放避免一次用例同時(shí)引入多個(gè)變量。相機(jī)位置或朝向約定改變后應(yīng)重新推導(dǎo)這張表而不是沿用舊預(yù)期值修補(bǔ)新公式。讓實(shí)驗(yàn)結(jié)果能被另一個(gè)人復(fù)查坐標(biāo)測(cè)試可先獨(dú)立于設(shè)備完成零度熱點(diǎn)應(yīng)在右側(cè)180 度應(yīng)在左側(cè)較大距離的映射應(yīng)保持分量比例NaN 或無(wú)窮數(shù)應(yīng)被拒絕。這些測(cè)試發(fā)現(xiàn)的是變換和輸入問(wèn)題不評(píng)價(jià)系統(tǒng)音效質(zhì)量。設(shè)備驗(yàn)收再使用同一模型、同一熱點(diǎn)與同一耳機(jī)。保存零度和 180 度兩個(gè)畫面同時(shí)記錄對(duì)應(yīng)音頻參數(shù)與實(shí)際聽(tīng)感。截圖可以呈現(xiàn)場(chǎng)景狀態(tài)無(wú)法單獨(dú)證明聲音具有方位感因此還需要試聽(tīng)記錄或音頻采集證據(jù)。后續(xù)若升級(jí)為連續(xù)導(dǎo)覽建議另設(shè)實(shí)時(shí)音頻會(huì)話在播放中更新聲源參數(shù)對(duì)位置采樣節(jié)流并平滑處理協(xié)調(diào)相機(jī)、模型和音頻線程的時(shí)序。不能把每次手勢(shì)變化都當(dāng)作新的四秒離線渲染任務(wù)那會(huì)產(chǎn)生任務(wù)積壓和不連貫播放。參考HarmonyOS 7 空間與 AI 征文方向、SpatialRender 官方接口。試聽(tīng)捕獲的是哪一次視角試聽(tīng)取當(dāng)前視角生成聲源快照完成后把 PCM 交給播放器。選擇模型和離頁(yè)都會(huì)改變控制器生命周期頁(yè)面只接受仍屬于本次操作的結(jié)果。這一入口播放離散片段連續(xù)聲場(chǎng)更新需要另建持續(xù)音頻鏈。exportinterfaceAudioPoint{x:number;y:number;z:number;scale:number;}/** 模型繞Y軸旋轉(zhuǎn)后將坐標(biāo)變換到相機(jī)滾轉(zhuǎn)后的聽(tīng)者坐標(biāo)系聽(tīng)者前方使用正Z。 */exportfunctionmapExhibitToAudio(x:number,y:number,z:number,yaw:number,distance:number,cameraRoll:number0):AudioPoint{if(![x,y,z,yaw,distance,cameraRoll].every((value:number)Number.isFinite(value))){thrownewError(場(chǎng)景坐標(biāo)必須為有限數(shù)值);}constworldXMath.cos(yaw)*xMath.sin(yaw)*z;constworldZ-Math.sin(yaw)*xMath.cos(yaw)*z;constforwarddistance-worldZ;constlistenerXMath.cos(cameraRoll)*worldXMath.sin(cameraRoll)*y;constlistenerY-Math.sin(cameraRoll)*worldXMath.cos(cameraRoll)*y;// Preserve bearing by applying one uniform scale, not three independent clamps.constscaleMath.min(1,5/Math.max(Math.abs(listenerX),Math.abs(listenerY),Math.abs(forward),0.001));return{x:listenerX*scale,y:listenerY*scale,z:forward*scale,scale:scale};}import{common}fromkit.AbilityKit;import{picker}fromkit.CoreFileKit;import{Scene}fromkit.ArkGraphics3D;import{SpatialSceneController}from../services/SpatialSceneController;import{ModelRepository}from../services/ModelRepository;import{SpatialAudioService}from../services/SpatialAudioService;import{mapExhibitToAudio}from../services/TourCoordinates;import{LabTheme}from../common/LabTheme;import{describeError}from../common/LabError;EntryComponentstruct TourPage{Statescene:Scene|undefinedundefined;Statestatus:string選擇帶有 3DGS 數(shù)據(jù)的 GLB 或 PLY。普通網(wǎng)格或點(diǎn)云不等于高斯模型。;Stateyaw:number0;Statedistance:number4;StatecameraRoll:number0;Statebusy:booleanfalse;privateactive:booleantrue;privateaudioService:SpatialAudioServicenewSpatialAudioService();StateaudioStatus:string試聽(tīng)模型局部點(diǎn) (1, 0, 0) 的方位測(cè)試音;StateaudioBusy:booleanfalse;privatecontroller:SpatialSceneControllernewSpatialSceneController();aboutToAppear():void{this.activetrue;}aboutToDisappear():void{this.activefalse;this.audioService.stop().catch((){});this.sceneundefined;this.controller.dispose();}privateasyncselectModel():Promisevoid{if(this.busy){return;}this.busytrue;try{constcontextthis.getUIContext().getHostContext()ascommon.UIAbilityContext;constviewPickernewpicker.DocumentViewPicker(context);constoptionsnewpicker.DocumentSelectOptions();options.maxSelectNumber1;constselectedawaitviewPicker.select(options);if(!this.active||selected.length0){return;}awaitthis.audioService.stop();this.audioStatus試聽(tīng)模型局部點(diǎn) (1, 0, 0) 的方位測(cè)試音;this.sceneundefined;this.controller.dispose();consturiawaitModelRepository.importModel(context,selected[0]);if(!this.active){return;}conststartDate.now();constsceneawaitthis.controller.load(uri);if(this.activescene){this.scenescene;this.yaw0;this.distance4;this.cameraRoll0;this.status3DGS 模型加載完成 ·${Date.now()-start}ms;}}catch(error){if(this.active){this.statusdescribeError(errorasError);}}finally{this.busyfalse;}}privateasynclisten():Promisevoid{if(this.audioBusy){return;}this.audioBusytrue;try{constpointmapExhibitToAudio(1,0,0,this.yaw*Math.PI/180,this.distance,this.cameraRoll);this.audioStatus聲源快照 (${point.x.toFixed(2)},${point.y.toFixed(2)},${point.z.toFixed(2)})m;constresultawaitthis.audioService.play(0,point.x,point.y,point.z);if(this.activeresult){this.audioStatus· 渲染${result.renderMs}ms;}}catch(error){if(this.active){this.audioStatusdescribeError(errorasError);}}finally{this.audioBusyfalse;}}build(){Column({space:12}){Text(有聲展廳 · 視角與聲源).fontSize(24).fontWeight(FontWeight.Bold).fontColor(LabTheme.ink)Button(選擇 3DGS 模型).enabled(!this.busy).onClick((){this.selectModel();})Text(this.status).fontSize(14).fontColor(LabTheme.muted)if(this.scene){Component3D({scene:this.scene}).width(100%).layoutWeight(1)}else{Text(等待模型).width(100%).layoutWeight(1).textAlign(TextAlign.Center)}Text(this.audioStatus).fontSize(13)Row({space:8}){Button(試聽(tīng)聲源快照).fontSize(12).enabled(!!this.scene!this.audioBusy!this.busy).onClick((){this.listen();})Button(this.cameraRoll0?畫面翻轉(zhuǎn)關(guān):畫面翻轉(zhuǎn)開(kāi)).fontSize(12).enabled(!!this.scene!this.busy!this.audioBusy).onClick((){this.cameraRollthis.cameraRoll0?Math.PI:0;this.controller.setView(this.yaw*Math.PI/180,this.distance,0,this.cameraRoll);})}Text(水平旋轉(zhuǎn)${this.yaw}°).fontSize(14)Slider({value:this.yaw,min:-180,max:180,step:1}).onChange((value:number){this.yawvalue;this.controller.setView(value*Math.PI/180,this.distance,0,this.cameraRoll);})Text(觀察距離).fontSize(14)Slider({value:this.distance,min:0.2,max:30,step:0.1}).onChange((value:number){this.distancevalue;this.controller.setView(this.yaw*Math.PI/180,value,0,this.cameraRoll);})Button(復(fù)位).onClick((){this.yaw0;this.distance4;this.cameraRoll0;this.controller.setView(0,4);})}.padding(20).height(100%).width(100%).backgroundColor(LabTheme.background)}}操作或邊界應(yīng)檢查的結(jié)果選擇新模型清理舊場(chǎng)景和試聽(tīng)試聽(tīng)開(kāi)始后轉(zhuǎn)動(dòng)視角已提交快照不被隱式篡改退出頁(yè)面停止音頻并釋放場(chǎng)景文件選擇器返回有效新文件后頁(yè)面先等待音頻服務(wù)停止再解除舊場(chǎng)景并導(dǎo)入新模型。取消選擇保持當(dāng)前模型和試聽(tīng)狀態(tài)渲染階段或播放器創(chuàng)建階段的遲到音頻結(jié)果由服務(wù)代次攔截并釋放。換模型資源合同、音頻服務(wù)測(cè)試和構(gòu)建結(jié)果見(jiàn)第12篇換模型清理舊試聽(tīng)。order場(chǎng)景的真機(jī)聯(lián)動(dòng)結(jié)果2026年9月24日HBN-AL80/API26加載Apartment order版本距離保持4、相機(jī)滾轉(zhuǎn)π。水平角137°時(shí)頁(yè)面回讀聲源(0.73,0.00,4.68)m渲染耗時(shí)197ms調(diào)到0°后回讀(-1.00,-0.00,4.00)m渲染耗時(shí)159ms。這兩組回讀與相機(jī)滾轉(zhuǎn)后的坐標(biāo)公式一致。素材SA3D_RD_XP47Stéphane AgulloCC-BY-4.0由Apartment SOG轉(zhuǎn)換為PLY并重排字段。0°重復(fù)運(yùn)行時(shí)系統(tǒng)音頻日志記錄Native節(jié)點(diǎn)實(shí)際接收(-1,0,4)隨后播放器Start SUCCESS、Stop SUCCESS和Release end。日志同時(shí)存在系統(tǒng)調(diào)度、權(quán)限與清理提示保留在實(shí)測(cè)記錄中。這組實(shí)驗(yàn)記錄坐標(biāo)傳遞與播放生命周期對(duì)應(yīng)耳機(jī)的方位主觀試聽(tīng)單獨(dú)安排頁(yè)面仍使用離散快照音頻。