深度检测与动态透明度控制 - 基于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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;…