当前位置: 首页 > article >正文

深度检测与动态透明度控制 - 基于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在复杂交互场景中的应用潜力。开发者可根据具体需求调整检测精度和响应速度,在视觉效果与性能消耗之间找到最佳平衡点。

相关文章:

深度检测与动态透明度控制 - 基于Babylon.js的遮挡检测实现解析

首先贴出实现代码&#xff1a; OcclusionFader.ts import { AbstractEngine, Material, type Behavior, type Mesh, type PBRMetallicRoughnessMaterial, type Scene } from "babylonjs/core"; import { OcclusionTester } from "../../OcclusionTester"…...

Linux下使用socat将TCP服务转为虚拟串口设备

Linux下使用socat将TCP服务转为虚拟串口设备 socat是一个强大的网络工具&#xff0c;可以将TCP连接转换为虚拟串口设备&#xff0c;这在嵌入式开发、工业控制等领域非常有用。下面详细介绍如何实现这一功能。 基本原理 socat可以通过创建伪终端(PTY)来模拟串口设备&#xff…...

docker push 报错 denied: requested access to the resource is denied

问题&#xff1a;当 docker logout -> docker login 用户登录&#xff0c;但仍然无法 docker push $ docker push <username>/nginx-custom:v1 The push refers to repository [docker.io/jagger/nginx-custom] 340e6d3ea0c7: Preparing 941dd9dd8ee4: Preparing f6…...

epub→pdf | which 在线转换??好用!!

1、PDF派&#xff08;free&quick) pdf转word_pdf转换成excel_pdf转换成ppt _纬来PDF转换器 评价&#xff1a;目前使用免费&#xff0c;转化的时候有进度条提示&#xff0c;总的来说比较快&#xff0c;50mb的文件在40秒内可以转换完成&#xff0c;推荐 2、pdfconvert(free…...

PBX、IP PBX、FXO 、FXS 、VOIP、SIP 的概念解析以及关系

PBX&#xff08;Private Branch Exchange&#xff09; 概念 &#xff1a;PBX 是专用交换机&#xff0c;是一种在企业或组织内部使用的电话交换系统。它允许内部用户之间以及内部用户与外部公共电话网络&#xff08;PSTN&#xff09;之间进行通信。例如&#xff0c;在一个大型企…...

MySQL数据高效集成到金蝶云星空的技术分享

MySQL数据集成到金蝶云星空的技术案例分享&#xff1a;SR新建调拨单内部供应商-深圳天一 在企业信息化系统中&#xff0c;数据的高效流动和准确对接是实现业务流程自动化的关键。本文将聚焦于一个具体的系统对接集成案例——将MySQL中的数据集成到金蝶云星空&#xff0c;以支持…...

git 命令之-git cherry-pick

今天得到一个通知&#xff0c;这个业务版本里面部分已经开发但还没测试的内容要新开一个分支提交&#xff0c;但是我已经有几个提交上去了&#xff0c;难道只能一个一个文件复制到新的分支吗&#xff1f;我不&#xff0c;我找到了这个git命令&#xff0c;可以解决我的困惑&…...

如何在STM32CubeMX下为STM32工程配置调试打印功能

为STM32工程配置调试打印功能 一、配置调试用的打印串口 #include <stdio.h> //标准输入输出库//1.在STM32CubeMX中打开并配置好某串口设备&#xff1b; //2.在main.c文件中添加如下代码行对输入输出重定向&#xff1b; //3.在文件开头包含stdio.h头文件。 #pragma im…...

Linux系统 - 基本概念

介绍一些Linux系统的基本概念 1 操作系统的核心—内核 “操作系统”通常包含两种不同含义。 1&#xff0e;指完整的软件包&#xff0c;这包括用来管理计算机资源的核心层软件&#xff0c;以及附带的所有标准软件工具&#xff0c;诸如命令行解释器、图形用户界面、文件操作工具…...

kerberos在无痕浏览器 获取用户信息失败 如何判断是否无痕浏览器

kerberos在无痕浏览器 获取用户信息失败 如何判断是否无痕浏览器 js 代码 其他地方用直接导入js getCurrentUserId 这是自己后端获取 域账号地址 我是成功返回200 //true普通浏览器 fasle 无痕浏览器 export const checkBrowserMode async () > {try {const response a…...

在h5端实现录音发送功能(兼容内嵌微信小程序) recorder-core

本文将通过一个实际的 Vue3 组件示例&#xff0c;带你一步步实现“按住录音&#xff0c;松开发送&#xff0c;上滑取消”的语音录制功能。 我们将使用强大且小巧的开源库 recorder-core&#xff0c;支持 MP3、WAV、AAC 等编码格式&#xff0c;兼容性较好。 &#x1f527; 项目…...

PDF电子发票数据提取至Excel

声明&#xff1a;本软件是吾爱大佬th4c3y原创&#xff0c;本人只是搬运工&#xff01; 发票识别更新记录 【2025-3-14】更新 v2.0 在字段设置中新增自定义字段&#xff08;仅在 PDF 正则式接口下生效&#xff09;&#xff0c;支持自定义正则表达式或固定字符。 自定义字段会…...

【身份证识别表格】把大量手机拍摄的身份证信息转换成EXCEL表格的数据,拍的身份证照片转成excel表格保存,基于WPF和腾讯OCR的实现方案

基于WPF和腾讯OCR的身份证照片转Excel方案 应用场景 ​​企业人事管理​​&#xff1a;新员工入职时批量录入数百份身份证信息&#xff0c;传统手动录入易出错且耗时。通过OCR自动提取姓名、身份证号等字段&#xff0c;生成结构化Excel表格&#xff0c;效率提升10倍以上。 ​​…...

FPGA高速接口 mipi lvds cameralink hdml 千兆网 sdi

mipi: https://blog.csdn.net/SDJ_success/article/details/146541776 cameralink CameraLink协议 CameraLink协议是一种专门针对机器视觉应用领域的串行通信协议&#xff0c;它使用低压差分信号(LVDS)进行数据的传输和通信。CameraLink标准是在ChannelLink标准的基础上多加了…...

Linux路径解析指南:逻辑路径 vs 实际路径详解

在 Linux 系统中&#xff0c;逻辑路径&#xff08;Logical Path&#xff09;和 实际路径&#xff08;Physical Path&#xff09;是两个不同的概念&#xff0c;主要区别在于它们如何解析文件或目录的位置。以下是详细解释&#xff1a; 目录 1. 逻辑路径&#xff08;Logical Path…...

Azure 公有云基础架构与核心服务:从基础到实践指南

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 Azure 的基础架构由多个核心组件构成&#xff0c;理解这些概念是掌握其技术框架的第一步&#xff1a; 地理区域&#xff08;Geographic R…...

【运维_日常报错解决方案_docker系列】一、docker系统不起来

今天忽然想起来哎&#xff0c;还有一台”尘封“着的服务器&#xff0c;好久没用了&#xff0c;就随便打开登了登&#xff0c;然后想看一下服务器上面还有正在跑着的容器服务吗&#xff0c;然后使用docker ps 发现报错了。 然后重启也是下面这个状态。 查看docker状态&#xf…...

C# 数组与字符串:全面解析与应用实践

在C#编程语言中&#xff0c;数组和字符串是两种最基础也是最重要的数据类型。无论是简单的控制台应用程序&#xff0c;还是复杂的企业级系统&#xff0c;数组和字符串都扮演着不可或缺的角色。本文将全面深入地探讨C#中数组和字符串的特性、使用方法、性能考量以及实际应用场景…...

前端vue中使用signalr

一、引入SignalR库 使用NPM引入SignalR库 npm install @microsoft/signalrJs文件中引入 import * as signalR from @microsoft/signalr;二、初始化连接 这一步需要指定SignalR Hub的URL。 const connection = new signalR.HubConnectionBuilder().withUrl("https://y…...

Stable Diffusion底模对应的VAE推荐

以下是主流Stable Diffusion底模对应的VAE推荐表格&#xff1a; 底模版本推荐VAE类型说明SD1.5SD1.5专用VAE通常使用vae-ft-mse-840000-ema-pruned.safetensorsSD2.0SD1.5兼容VAE或SD2专用VAE部分SD2模型需配套512-ema-only.vae.ptSD3内置VAESD3系列模型通常自带集成VAE无需额…...

centos7.5安装kubernetes1.25.0

centos7.5安装kubernetes centos7.5kubernetes1&#xff09;准备阶段准备2台虚拟机配置静态IP修改主机名桥接设置配置阿里云的repo源配置k8s切国际源配置时间同步安装基础软件包 2)安装containerd服务安装配置开启启动 3)安装k8s4)安装kubersphere下载helm安装包解压将helm配置…...

‌AT2659S射频前端芯片技术解析:L1频段低噪声高增益GNSS信号放大

以下是关于‌AT2659S L1频段卫星导航射频前端芯片‌的客观描述&#xff0c;严格基于用户提供的原始信息&#xff0c;采用分享式表述&#xff0c;保持参数和核心内容不变&#xff1a; AT2659S芯片概述‌ AT2659S是一款基于SiGe工艺的射频前端芯片&#xff0c;专为L1频段&#…...

ROS2学习(15)------ROS2 TF2 机器人坐标系管理器

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 ROS版本&#xff1a;2 在 ROS 2 中&#xff0c;TF2&#xff08;Transform Library, v2&#xff09; 是一个非常核心的工具库&#xff0c;用于管理多个坐标系之间的 变换关系&#xff08;tran…...

每日c/c++题 备战蓝桥杯(洛谷P3382 三分法求极值详解)

洛谷P3382 三分法求极值详解 题目描述 P3382 三分法 要求在给定区间内寻找一个多项式函数的最大值点。题目保证函数在区间内先严格递增后严格递减&#xff08;单峰函数&#xff09;&#xff0c;适合使用三分法求解。 算法原理 三分法核心思想 对于单峰函数&#xff0c;在区…...

Vue+css实现扫描动画效果(使用@keyframes scan)

实现效果 扫描效果 参考链接 MDN Web Docs: CSS Animations 关键代码 示例代码 <div class"scanner-container"><div class"scanner-line"></div><div class"scanner-icon">&#x1f4f7;</div><p>Scan m…...

Windows 配置 ssh 秘钥登录 Ubuntu

在 Windows 上推送 SSH 公钥到远程服务器&#xff08;类似于 Linux 上的 ssh-copy-id&#xff09;可以通过以下几种方法实现&#xff1a; ** 手动复制公钥内容** 查看本地公钥内容&#xff1a;type $env:USERPROFILE\.ssh\id_rsa.pub登录远程服务器&#xff0c;将公钥内容粘贴…...

Conda:环境移植及更新1--使用conda-pack

更多内容&#xff1a;XiaoJ的知识星球 目录 一、使用conda-pack1.安装 conda-pack2.移植整个 Anaconda 环境3.移植单个虚拟环境4.验证是否生效 在相同Linux设备上移植Miniconda3&#xff08;Anaconda3同理&#xff09;常用方法有。 使用conda-pack&#xff1a;使用conda-pack工…...

github好玩的工具

以下是 GitHub 上一些有趣且实用的开源工具推荐,涵盖 AI 应用、效率提升、趣味开发等方向,结合最新趋势和项目热度整理: 一、AI 与深度伪造工具 Deep-Live-Cam 仅需一张图片即可在视频直播中实时替换人脸,适用于内容创作和虚拟角色开发,支持多平台硬件运行(如 NVIDIA CUD…...

PHP学习笔记(九)

箭头函数 箭头函数是 PHP 7.4的新语法。是一种更简洁的匿名函数的写法&#xff0c;它们都是closure类的实现。 箭头函数的基本语法为fn&#xff08;argument_list&#xff09; > expr 箭头函数支持与匿名函数相同的功能&#xff0c;只是其父作用域的变量总是自动的。 当表…...

共现矩阵的SVD降维与低维词向量计算详解

共现矩阵的SVD降维与低维词向量计算详解 1. 原始共现矩阵构建 根据用户提供的共现对&#xff1a; 句子1: (I, like), (like, apples)句子2: (I, like), (like, bananas) 词汇表&#xff1a;[I, like, apples, bananas] 窗口大小2&#xff08;假设共现对直接作为矩阵的非零元…...