深度檢測與動態透明度控制 - 基于Babylon.js的遮擋檢測實現解析

首先貼出實現代碼:

OcclusionFader.ts

import { AbstractEngine, Material, type Behavior, type Mesh, type PBRMetallicRoughnessMaterial, type Scene } from "@babylonjs/core";
import { OcclusionTester } from "../../OcclusionTester";export class OcclusionFader  implements Behavior<Mesh>{name: string = "OcclusionFader";private _mesh:Mesh | null = null;private _scene:Scene | null = null;private _engine:AbstractEngine | null = null;private _meshes:Mesh[] = [];private _mat:PBRMetallicRoughnessMaterial | null = null;private _visibility:number = 0.1;private _occlusionTester : OcclusionTester | null = null;constructor(visibility:number = 0.1){this._visibility = visibility;}init(): void {}private _attached = false;attach(target: Mesh): void {if(this._attached)return;this._attached = true;this._mesh = target;this._scene = target.getScene();this._engine = this._scene.getEngine();this._mat = this._mesh.material?.clone(this._mesh.material.name + "_clone") as PBRMetallicRoughnessMaterial;this._mesh.material = this._mat;this._occlusionTester = new OcclusionTester(this._scene as Scene);this._occlusionTester.setDivisor(8);this._occlusionTester.updateMesh(this._meshes, this._mesh);this._occlusionTester.onFinishCheckOcclusion.add(this._setIsOccluded.bind(this));this._occlusionTester.startCheckOcclusion();this._scene.onBeforeRenderObservable.add(this._updateVisibility.bind(this));}detach(): void {this._attached = false;this._mesh = null;}public addMesh(mesh:Mesh):void{this._meshes.push(mesh);if(this._occlusionTester)this._occlusionTester.updateMesh(this._meshes, this._mesh as Mesh);}private _isOccluded:boolean = false;private _setIsOccluded(isOccluded:boolean):void{this._isOccluded = isOccluded;}private _vUse:number = 1;private _updateVisibility():void{if(!this._mat){console.log("mat is null!");return;}if(!this._occlusionTester){console.log("occlusionTester is null!");return;}this._mat.transparencyMode = Material.MATERIAL_ALPHABLEND;if(this._isOccluded){if(this._vUse > this._visibility){this._vUse -= this._engine!.getDeltaTime() * 0.005;}else{this._vUse = this._visibility;}}else{if(this._vUse < 1){this._vUse += this._engine!.getDeltaTime() * 0.005;}else{this._mat.transparencyMode = Material.MATERIAL_ALPHATEST;this._vUse = 1;}}this._mesh!.material!.alpha = this._vUse;}public dispose(): void {this._attached = false;this._mesh = null;this._occlusionTester?.dispose();}
}

OcclusionTester.ts

import { AbstractEngine, Color4, Engine, Mesh, Observable, RenderTargetTexture, Scene, ShaderMaterial, UniversalCamera } from "@babylonjs/core";export class OcclusionTester {private _engine: AbstractEngine;private _mainScene: Scene;private _tempScene: Scene; // 臨時場景(離屏渲染)private _tempCam: UniversalCamera;private _w: number = 8;private _h: number = 8;private _mat = this._createDepthMaterial(); private _depthTexA:RenderTargetTexture | null = null;private _depthTexB:RenderTargetTexture | null = null;private _divisor:number = 1;private options = {generateDepthBuffer: true,      // 啟用深度緩沖generateStencilBuffer: false,   // 禁用模板緩沖type: Engine.TEXTURETYPE_FLOAT  // 浮點紋理}constructor(mainScene: Scene) {this._mainScene = mainScene;this._engine = mainScene.getEngine();// 創建臨時場景和相機this._tempScene = new Scene(this._engine);this._tempCam = mainScene.activeCamera!.clone("tempCamera") as UniversalCamera;this._mainScene.removeCamera(this._tempCam);this._tempScene.addCamera(this._tempCam);this._tempScene.activeCamera = this._tempCam;this._tempScene.clearColor  = new Color4(0, 0, 0, 0);const size = this.resize();this._depthTexA = this.createDepthTex("depthTexA", size);this._depthTexB = this.createDepthTex("depthTexB", size);this._engine.onResizeObservable.add(()=>{const size = this.resize();if(this._depthTexA)this._depthTexA.resize(size);if(this._depthTexB)this._depthTexB.resize(size);});}public setDivisor(divisor:number):void{this._divisor = divisor < 1 ? 1 : divisor;}public getDivisor():number{return this._divisor;	}private createDepthTex(name:string, size:{width: number, height: number}):RenderTargetTexture{const depthTex = new RenderTargetTexture(name, size, this._tempScene, this.options);depthTex.activeCamera = this._tempCam;this._tempScene.customRenderTargets.push(depthTex);return depthTex;}private resize = ():{width: number, height: number} => {this._w = Math.floor(this._engine.getRenderWidth() / this._divisor);this._h = Math.floor(this._engine.getRenderHeight() / this._divisor);return {width: this._w, height: this._h};};private _meshesCloned:Mesh[] = [];private _meshOccCloned:Mesh[] = [];public updateMesh(meshes: Mesh[], meshOcc: Mesh): void {if(!this._depthTexA)return;this._meshesCloned.forEach((mesh)=>{mesh.dispose();});this._meshesCloned.length = 0;meshes.forEach((mesh)=>{const meshClone = this._cloneMeshToTempScene(mesh);this._meshesCloned.push(meshClone);});this._depthTexA.renderList = this._meshesCloned;if(!this._depthTexB)return;this._meshOccCloned.forEach((mesh)=>{mesh.dispose();});this._meshOccCloned.length = 0;const meshOccClone = this._cloneMeshToTempScene(meshOcc);this._meshOccCloned.push(meshOccClone);this._depthTexB.renderList = this._meshOccCloned;}private _cloneMeshToTempScene(mesh:Mesh):Mesh{const meshClone = mesh.clone(mesh.name + "_Cloned");this._mainScene.removeMesh(meshClone);const occ = meshClone.getBehaviorByName("OcclusionFader");if(occ) meshClone.removeBehavior(occ);meshClone.material = this._mat;this._tempScene.addMesh(meshClone);return meshClone;};private checkEnabled:boolean = true;public startCheckOcclusion():void{this.checkEnabled = true;this.checkOcclusion();}public stopCheckOcclusion():void{this.checkEnabled = false;}private isOccluded:boolean = false;public getIsOccluded():boolean{return this.isOccluded;}public onFinishCheckOcclusion:Observable<boolean> = new Observable<boolean>();private async checkOcclusion(): Promise<void> {if(!this.checkEnabled)return;this.syncCam();// 在臨時場景中執行離屏渲染await new Promise<void>(resolve => {this._tempScene.executeWhenReady(() => {this._tempScene.render();resolve();});});// 讀取深度數據const depthBufA = await this._depthTexA!.readPixels(0,      // faceIndex (立方體貼圖用,默認0)0,      // level (mipmap級別,默認0)null,   // buffer (不預分配緩沖區)true,   // flushRenderer (強制刷新渲染器)false,  // noDataConversion (允許數據轉換)0,      // x (起始X坐標)0,      // y (起始Y坐標)this._w,// width (讀取寬度)this._h // height (讀取高度)) as Float32Array; // 關鍵:聲明為Float32Arrayconst depthBufB = await this._depthTexB!.readPixels(0,      // faceIndex (立方體貼圖用,默認0)0,      // level (mipmap級別,默認0)null,   // buffer (不預分配緩沖區)true,   // flushRenderer (強制刷新渲染器)false,  // noDataConversion (允許數據轉換)0,      // x (起始X坐標)0,      // y (起始Y坐標)this._w,// width (讀取寬度)this._h // height (讀取高度)) as Float32Array; // 關鍵:聲明為Float32Array// 檢查遮擋let isOccluded = false;for (let i = 0; i < depthBufA.length; i += 4) {if (depthBufA[i] > 0 && depthBufB[i] > 0){if(depthBufB[i] < depthBufA[i]) {isOccluded = true;break;}}}this.isOccluded = isOccluded;this.onFinishCheckOcclusion.notifyObservers(isOccluded);// 使用setTimeout來延遲下一次檢查,而不是直接遞歸setTimeout(() => this.checkOcclusion(), 0);}private syncCam() {const mainCam = this._mainScene.activeCamera as UniversalCamera;this._tempCam.position.copyFrom(mainCam.position);this._tempCam.rotation.copyFrom(mainCam.rotation);}// 創建深度寫入材質private _createDepthMaterial(): ShaderMaterial {const vertexShader = `precision highp float;attribute vec3 position;uniform mat4 worldViewProjection;varying float vDepth;void main() {vec4 pos = worldViewProjection * vec4(position, 1.0);gl_Position = pos;vDepth = pos.z / pos.w; // 透視除法后的歸一化深度}`;const fragmentShader = `precision highp float;varying float vDepth;void main() {gl_FragColor = vec4(vDepth, vDepth, vDepth, 1.0);}`;return new ShaderMaterial("depthMaterial",this._tempScene,{vertexSource: vertexShader,fragmentSource: fragmentShader},{attributes: ["position"],uniforms: ["worldViewProjection"]});}public dispose() {this._tempScene.dispose();this._tempScene.customRenderTargets.forEach(rt => rt.dispose());this._tempScene.customRenderTargets = [];this._tempScene.meshes.forEach(mesh => mesh.dispose());}
}

一、核心思路解析

本方案通過結合離屏渲染與深度檢測技術,實現了一個動態的物體遮擋透明度控制系統。主要分為兩大模塊:

  1. OcclusionTester:負責執行遮擋檢測的核心邏輯

  2. OcclusionFader:基于檢測結果控制物體透明度的行為組件


二、關鍵技術實現

1. 雙場景渲染機制
  • 主場景:承載實際可見的3D物體

  • 臨時場景:專門用于離屏深度渲染

  • 優勢:避免對主場景渲染管線造成干擾

this._tempScene = new Scene(this._engine);
2. 深度信息采集
  • 使用RenderTargetTexture生成兩張深度圖:

    • depthTexA:被檢測物體組的深度

    • depthTexB:目標物體的深度

// 創建深度紋理
createDepthTex(name: string, size: {width: number, height: number}){return new RenderTargetTexture(name, size, this._tempScene, {generateDepthBuffer: true,type: Engine.TEXTURETYPE_FLOAT});
}
3. 深度比較算法
for (let i = 0; i < depthBufA.length; i += 4) {if (depthBufA[i] > 0 && depthBufB[i] > 0){if(depthBufB[i] < depthBufA[i]) {isOccluded = true;break;}}
}
4. 透明度漸變控制
// 平滑過渡效果
this._vUse += this._engine!.getDeltaTime() * 0.005;
this._mesh!.material!.alpha = this._vUse;

三、實現步驟詳解

步驟1:場景初始化
  • 克隆主場景相機到臨時場景

  • 設置純黑色背景消除干擾

步驟2:物體克隆
  • 克隆待檢測物體到臨時場景

  • 替換為專用深度材質

private _cloneMeshToTempScene(mesh: Mesh){const clone = mesh.clone();clone.material = this._mat; // 使用深度材質return clone;
}
步驟3:異步深度檢測
  • 使用requestAnimationFrame避免阻塞主線程

  • 通過readPixels讀取深度緩沖

const depthBuf = await texture.readPixels() as Float32Array;
步驟4:結果反饋
  • 通過Observable通知透明度控制器

  • 實現檢測與渲染的解耦


四、性能優化策略

  1. 分辨率控制:通過divisor參數降低檢測精度

    setDivisor(8); // 使用1/8分辨率檢測
  2. 異步檢測機制:使用setTimeout保持事件循環暢通

  3. 對象復用:緩存克隆物體避免重復創建

  4. 按需渲染:僅在需要時啟動檢測循環


五、應用場景示例

  1. AR應用中重要物體的防遮擋

  2. 3D編輯器中的選中物體高亮

  3. 游戲中的動態場景元素管理

  4. 可視化大屏的重點信息保護


六、潛在優化方向

  1. WebGL2特性利用:改用深度紋理格式

    layout(depth) out float gl_FragDepth;
  2. GPU加速計算:改用Compute Shader處理深度比較

  3. 空間分割優化:結合八叉樹空間劃分

  4. LOD策略:動態調整檢測精度


七、總結

本方案通過創新的雙場景架構,在保證主場景渲染性能的同時,實現了精確的實時遮擋檢測。深度信息的對比算法與透明度控制的結合,展現了WebGL在復雜交互場景中的應用潛力。開發者可根據具體需求調整檢測精度和響應速度,在視覺效果與性能消耗之間找到最佳平衡點。

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

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

相關文章

openssl 使用生成key pem

好的&#xff0c;以下是完整的步驟&#xff0c;幫助你在 Windows 系統中使用 OpenSSL 生成私鑰&#xff08;key&#xff09;和 PEM 文件。假設你的 openssl.cnf 配置文件位于桌面。 步驟 1&#xff1a;打開命令提示符 按 Win R 鍵&#xff0c;打開“運行”對話框。輸入 cmd&…

音視頻之視頻壓縮及數字視頻基礎概念

系列文章&#xff1a; 1、音視頻之視頻壓縮技術及數字視頻綜述 一、視頻壓縮編碼技術綜述&#xff1a; 1、信息化與視頻通信&#xff1a; 什么是信息&#xff1a; 眾所周知&#xff0c;人類社會的三大支柱是物質、能量和信息。具體而言&#xff0c;農業現代化的支柱是物質&…

傳統數據表設計與Prompt驅動設計的范式對比:以NBA投籃數據表為例

引言&#xff1a;數據表設計方法的演進 在數據庫設計領域&#xff0c;傳統的數據表設計方法與新興的Prompt驅動設計方法代表了兩種截然不同的思維方式。本文將以NBA賽季投籃數據表(shots)的設計為例&#xff0c;深入探討這兩種方法的差異、優劣及適用場景。隨著AI技術在數據領…

XCTF-web-mfw

發現了git 使用GitHack下載一下源文件&#xff0c;找到了php源代碼 <?phpif (isset($_GET[page])) {$page $_GET[page]; } else {$page "home"; }$file "templates/" . $page . ".php";// I heard .. is dangerous! assert("strpos…

Prompt Tuning與自然語言微調對比解析

Prompt Tuning 與輸入提示詞自然語言微調的區別和聯系 一、核心定義與區別 維度Prompt Tuning(提示微調)輸入提示詞自然語言微調本質優化連續向量空間中的提示嵌入(不可直接閱讀)優化離散自然語言文本(人類可理解)操作對象模型輸入嵌入層的連續向量(如WordEmbedding)自…

LVS的DR模式部署

目錄 一、引言&#xff1a;高并發場景下的流量調度方案 二、LVS-DR 集群核心原理與架構設計 &#xff08;一&#xff09;工作原理與數據流向 數據包流向步驟3&#xff1a; &#xff08;二&#xff09;模式特性與53網絡要求 三、實戰配置&#xff1a;從9環境搭建到參數調整…

8種常見數據結構及其特點簡介

一、8種常見數據結構 1. 數組&#xff08;Array&#xff09; 簡介&#xff1a;數組是有序元素的序列&#xff0c;連續內存塊存儲相同類型元素&#xff0c;通過下標直接訪問。數組會為存儲的元素都分配一個下標&#xff08;索引&#xff09;&#xff0c;此下標是一個自增連續的…

通過mailto:實現web/html郵件模板喚起新建郵件并填寫內容

一、背景 在實現網站、html郵件模板過程中&#xff0c;難免會遇到需要通過郵箱向服務提供方發起技術支持等需求&#xff0c;因此&#xff0c;我們需要通過一個功能&#xff0c;能新建郵件并提供模板&#xff0c;提高溝通效率 二、mailto協議配置說明 參數描述mailto:nameema…

好用但不常用的Git配置

參考文章 文章目錄 tag標簽分支新倉庫默認分支推送 代碼合并沖突處理默認diff算法 tag標簽 默認是以字母順序排序&#xff0c;這會導致一些問題&#xff0c;比如0.5.101排在0.5.1000之后。為了解決這個問題&#xff0c;我們可以把默認排序改為數值排序 git config --global t…

第六十八篇 從“超市收銀系統崩潰”看JVM性能監控與故障定位實戰

目錄 引言&#xff1a;當技術問題遇上生活場景一、JVM的“超市貨架管理哲學”二、收銀員工具箱&#xff1a;JVM監控三板斧三、典型故障診斷實錄四、防患于未然的運維智慧五、結語&#xff1a;從故障救火到體系化防控 引言&#xff1a;當技術問題遇上生活場景 想象一個周末的傍…

tauri2項目打開某個文件夾,類似于mac系統中的 open ./

在 Tauri 2 項目中打開文件夾 在 Tauri 2 項目中&#xff0c;你可以使用以下幾種方法來打開文件夾&#xff0c;類似于 macOS 中的 open ./ 命令功能&#xff1a; 方法一&#xff1a;使用 shell 命令 use tauri::Manager;#[tauri::command] async fn open_folder(path: Strin…

編譯pg_duckdb步驟

1. 要求cmake的版本要高于3.17&#xff0c;可以通過下載最新的cmake的程序&#xff0c;然后設置.bash_profile的PATH環境變量&#xff0c;將最新的cmake的bin目錄放到PATH環境變量的最前面 2. g的版本要支持c17標準&#xff0c;否則會報 error ‘invoke_result in namespace ‘…

GO 語言中變量的聲明

Go 語言變量名由字母、數字、下劃線組成&#xff0c;其中首個字符不能為數字。Go 語言中關鍵字和保留字都不能用作變量名。Go 語言中的變量需要聲明后才能使用&#xff0c;同一作用域內不支持重復聲明。 并且 Go 語言的變量聲明后必須使用。 1. var 聲明變量 在 Go 語言中&…

windows和mac安裝虛擬機-詳細教程

簡介 虛擬機&#xff1a;Virtual Machine&#xff0c;虛擬化技術的一種&#xff0c;通過軟件模擬的、具有完整硬件功能的、運行在一個完全隔離的環境中的計算機。 在學習linux系統的時候&#xff0c;需要安裝虛擬機&#xff0c;在虛擬機上來運行操作系統&#xff0c;因為我使…

XCTF-web-Cat

嘗試輸入127.0.0.1 嘗試127.0.0.1;ls 試了很多&#xff0c;都錯誤&#xff0c;嘗試在url里直接輸入&#xff0c;最后發現輸入%8f報錯 發現了Django和DEBUG 根據Django的目錄&#xff0c;我們使用進行文件傳遞 嘗試?url/opt/api/database.sqlite3&#xff0c;找到了flag

C#、C++、Java、Python 選擇哪個好

選擇哪種語言取決于具體需求&#xff1a;若關注性能和底層控制選C、若開發企業級應用選Java、若偏好快速開發和豐富生態選Python、若構建Windows生態應用選C#。 以Python為例&#xff0c;它因語法簡潔、開發效率高、應用廣泛而在AI、數據分析、Web開發等領域大放異彩。根據TIOB…

CEH Practical 實戰考試真題與答案

什么是 CEH Practical&#xff1f; CEH Practical 是 EC-Council 推出的 Certified Ethical Hacker&#xff08;CEH&#xff09;認證項目中的一項高級動手實踐考試。它不同于傳統的理論考試&#xff0c;側重于在真實環境中檢驗考生的實操能力。 CEH Practical 主要亮點 &…

自媒體運營新利器:賬號矩陣+指紋瀏覽器,解鎖流量密碼

你是否因多賬號關聯被平臺封禁&#xff1f;或在多設備間切換賬號效率低下&#xff1f;賬號矩陣與指紋瀏覽器的結合&#xff0c;正是解決這些難題的利器&#xff01; 一、核心優勢&#xff1a;安全、高效、精準、協同 1**. 保障賬號安全** 指紋瀏覽器模擬設備指紋與兔子住宅…

將 AI 解答轉換為 Word 文檔

相關說明 DeepSeek 風靡全球的2025年&#xff0c;估計好多人都已經試過了&#xff0c;對于理科老師而言&#xff0c;有一個使用痛點&#xff0c;就是如何將 AI 輸出的 mathjax 格式的符號轉化為我們經常使用的 mathtype 格式的&#xff0c;以下舉例說明。 溫馨提示&#xff1…

Tailwind CSS 實戰,基于 Kooboo 構建 AI 對話框頁面(三):實現暗黑模式主題切換

基于前兩篇的內容&#xff0c;為頁面添加主題切換功能&#xff0c;實現網站頁面的暗黑模式&#xff1a; Tailwind css實戰&#xff0c;基于Kooboo構建AI對話框頁面&#xff08;一&#xff09;-CSDN博客 Tailwind css實戰&#xff0c;基于Kooboo構建AI對話框頁面&#xff08;…