大学网站建设与管理职责承德做网站优化
2026/2/6 20:15:41 网站建设 项目流程
大学网站建设与管理职责,承德做网站优化,建设一个旅游平台网站需要多少资金,网站建设和微站建设的区别在 3D 编辑器开发中#xff0c;镜面反射是一个既常见又充满挑战的功能。最近我实现了 MirrorReflectionBehaviorEditor#xff0c;一个基于 Babylon.js 的镜面反射行为编辑器。本文将深入剖析其核心实现#xff0c;重点讲解 MirrorTexture 的创建过程 和 Transform 改变的检…在 3D 编辑器开发中镜面反射是一个既常见又充满挑战的功能。最近我实现了MirrorReflectionBehaviorEditor一个基于 Babylon.js 的镜面反射行为编辑器。本文将深入剖析其核心实现重点讲解MirrorTexture 的创建过程和Transform 改变的检测机制并分享开发中的关键思考。一、功能概述MirrorReflectionBehaviorEditor的主要功能是为任意网格Mesh添加实时镜面反射效果。它会自动检测网格平整度确保只有平坦的表面才能生成正确的镜面反射动态创建反射纹理使用 Babylon.js 的MirrorTexture实时渲染反射场景自适应变换更新当物体移动、旋转或缩放时自动更新反射平面预览模式切换支持编辑器中实时预览开关不影响原始材质核心亮点整个系统完全响应式所有参数修改都通过命令模式Command Pattern实现可撤销/重做。二、MirrorTexture 创建过程详解这是整个行为的核心。创建过程在_initializeMirrorTexture()方法中完成涉及多个关键步骤1. 资源清理与准备// 清理旧的镜面纹理 if (this._mirrorTexture) { this._mirrorTexture.dispose(); this._mirrorTexture null; }每次重新初始化时必须先释放旧纹理避免内存泄漏。这是 Babylon.js 开发的最佳实践。2. 纹理实例化this._mirrorTexture new MirrorTexture( mirrorTexture_${this._mesh.name}, this._textureSize, this.scene!, false // 不生成 mipmap性能考虑 );关键参数说明name: 唯一标识便于调试size: 纹理分辨率支持 128/256/512/1024/2048 动态切换scene: 目标场景generateMipMaps: 关闭 mipmap 可显著提升性能对镜面反射效果影响极小3. 反射平面计算this._updateReflectionPlane();这一步调用专门的方法计算数学意义上的反射平面后续详细讲解。4. 动态模糊设置this._setBlurKernel(this._blurKernel);根据纹理尺寸动态调整模糊核大小确保不同分辨率下模糊效果一致const kernel blurKernel * this._textureSize / 1024; this._mirrorTexture.blurKernelX kernel; this._mirrorTexture.blurKernelY kernel;5. 渲染列表管理const allMeshes this.scene.meshes.filter(m m ! this._mesh m.isEnabled()); this._mirrorTexture.renderList allMeshes; // 动态监听场景网格变化 this._sceneAddNewMeshObserver this.scene.onNewMeshAddedObservable.add((m: AbstractMesh) { if(this._mirrorTexture this._mirrorTexture.renderList this._mirrorTexture.renderList.indexOf(m) -1){ this._mirrorTexture.renderList?.push(m); } });这是性能优化的关键只渲染启用中的网格且动态维护渲染列表避免每次帧更新时重复计算。6. 原始材质备份this._applyMirrorTexture();不直接替换材质而是只修改environmentTexture属性保留材质的其他所有设置。这种非破坏性修改是编辑器架构的核心理念。三、Transform 改变检测机制当物体在场景中移动、旋转或缩放时反射平面必须实时更新。但每帧都重新计算会造成性能浪费因此需要精确的变更检测。1. 变更检测的核心逻辑在_updatePreview()方法中实现protected _updatePreview(): void { super._updatePreview(); if (this._isValid this.enabled this._mesh) { // 检测材质是否被更换 if (this._mesh.material this._lastAppliedMaterial this._lastAppliedMaterial ! this._mesh.material) { this._applyMirrorTexture(); } // 检测变换是否改变 if (this._hasTransformChanged()) { this._updateReflectionPlane(); } } }2. 矩阵比较算法_hasTransformChanged()是检测的核心它比较世界矩阵的变换private _hasTransformChanged(): boolean { if (!this._mesh) return false; const currentWorldMatrix this._mesh.getWorldMatrix(); if (!this._lastWorldMatrix) { this._lastWorldMatrix new Float32Array(currentWorldMatrix.asArray()); return false; } const current currentWorldMatrix.asArray(); const last this._lastWorldMatrix; // 只比较前15个元素排除齐次坐标 for (let i 0; i 15; i) { if (Math.abs(current[i] - last[i]) 0.0001) { this._lastWorldMatrix.set(current); return true; } } return false; }关键设计点惰性初始化首次检测时保存矩阵不触发更新精确到小数点后4位过滤浮点误差避免微抖动引起的误判只比较关键元素索引 0-14 涵盖旋转、缩放、位移索引 15齐次坐标恒为 1无需比较立即更新缓存检测到变化后立即用set()方法更新 Float32Array避免创建新数组3. 反射平面更新当检测到变换改变时调用_updateReflectionPlane()private _updateReflectionPlane(): void { if (!this._mesh || !this._overallNormal || !this._mirrorTexture) { return; } const boundingInfo this._mesh.getBoundingBox(); const center boundingInfo.boundingBox.center; const worldMatrix this._mesh.getWorldMatrix(); const worldCenter Vector3.TransformCoordinates(center, worldMatrix); const worldNormal Vector3.TransformNormal(this._overallNormal, worldMatrix); worldNormal.normalize(); // Babylon.js 平面方程ax by cz d 0 // 注意MirrorTexture 需要反向法线 const mirrorNormal worldNormal.scale(-1); const d -Vector3.Dot(mirrorNormal, worldCenter); this._reflectionPlane new Plane(mirrorNormal.x, mirrorNormal.y, mirrorNormal.z, d); this._mirrorTexture.mirrorPlane this._reflectionPlane; }数学原理TransformCoordinates: 将局部坐标系中心点转换到世界坐标系TransformNormal: 将法向量转换到世界坐标系不受平移影响法线反向Babylon.js 的MirrorTexture要求法线指向被反射的空间因此需要取反平面方程通过点法式(normal, d)构造平面确保反射几何正确四、其他关键设计1. 平整度检测在_checkFlatnessAndInitialize()中通过计算所有顶点法线与整体法线的最大夹角确保表面足够平坦。这是避免反射扭曲的保障机制。2. 预览模式与非破坏性编辑通过备份environmentTexture实现预览开关时不销毁资源private _restoreOriginalTexture(): void { if (this._mesh this._mesh.material instanceof PBRMetallicRoughnessMaterial) { this._mesh.material.environmentTexture this._originalEnvironmentTexture; } }3. 命令模式集成所有参数修改通过CmdProperty包装确保编辑器可撤销public cmdSetFlatnessThreshold(threshold: number): void { threshold Scalar.Clamp(threshold, this.minFlatnessThreshold, this.maxFlatnessThreshold); if(Math.abs(threshold - this._flatnessThreshold) 0.00001) return; const cmd new CmdPropertynumber( this._setFlatnessThreshold.bind(this), threshold, this._flatnessThreshold ); cmdEmitter.emit(execute, cmd); }五、完整代码以下是三个相关文件的完整代码无任何省略BaseBehaviorEditor.tsimport { Observable, Observer, Scene, TransformNode, type Behavior, type Nullable } from babylonjs/core; import { cmdEmitter } from ../../../utils/EventBus; import CmdProperty from ../../../Command/CmdProperty; export default class BaseBehaviorEditor implements BehaviorTransformNode { private _uuid:string; public get uuid(): string{return this._uuid;} constructor() { this._uuid crypto.randomUUID(); } /** * 设置行为的 UUID仅用于反序列化 * ⚠️ 警告此方法仅应在从 JSON 反序列化时调用以保持引用关系的一致性 * 在正常创建行为时UUID 由构造函数自动生成 * param uuid 要设置的 UUID */ protected _setUUID(uuid: string): void { this._uuid uuid; } private _scene:Scene | null null; public get scene(): Scene | null { return this._scene; } // private _target: TransformNode | null null; public get target(): TransformNode | null { return this._target; } // private _enabled: boolean true; public get enabled(): boolean { return this._enabled; } // private _isPreview: boolean false; public get isPreview(): boolean { return this._isPreview; } // 记录原来启用时的预览状态用于在禁用后重新启用时恢复预览状态 private _isPreviewOnEnabled: boolean false; // get name(): string { return DriveBehaviorEditor; } public readonly cnName: string 驱动行为; init(): void {} private _beforeRenderObserver:NullableObserverScene null; attach(target: TransformNode): void { this._target target; this._scene this._target.getScene(); this._isPreviewOnEnabled this._isPreview; if(this.scene){ this._beforeRenderObserver this.scene.onBeforeRenderObservable.add(this._updatePreview.bind(this)); } } public readonly onDetachObservable new Observablevoid(); detach(): void { // 先移除渲染观察者此时 scene 还存在 if(this._beforeRenderObserver){ this.scene?.onBeforeRenderObservable.remove(this._beforeRenderObserver); this._beforeRenderObserver null; } // 通知观察者分离事件此时场景和目标还未置空 this.onDetachObservable.notifyObservers(); // 清空可观察对象 this.onSetPreviewObservable.clear(); this.onDetachObservable.clear(); // 最后置空引用 this._target null; this._scene null; } protected _updatePreview(): void {} public readonly onSetEnabledObservable new Observableboolean(); public setEnabled(enabled: boolean): void { if (this._enabled enabled)return; this._enabled enabled; if(this._enabled){ if(this._isPreviewOnEnabled){ this.setPreview(true); } } else{ this._isPreviewOnEnabled this._isPreview; if(this._isPreview){ this.setPreview(false); } } if(enabled){ if(this.scene){ this._beforeRenderObserver this.scene.onBeforeRenderObservable.add(this._updatePreview.bind(this)); } } else{ if(this._beforeRenderObserver){ this.scene?.onBeforeRenderObservable.remove(this._beforeRenderObserver); this._beforeRenderObserver null; } } this.onSetEnabledObservable.notifyObservers(enabled); } // public cmdSetPreview(isPreview: boolean): void { if(!this._enabled)return; if(this._isPreview isPreview)return; const cmd new CmdPropertyboolean( this.setPreview.bind(this), isPreview, this._isPreview); cmdEmitter.emit(execute, cmd); } public readonly onSetPreviewObservable new Observableboolean(); public setPreview(isPreview: boolean): void { if(this._isPreview isPreview)return; this._isPreview isPreview; this.onSetPreviewObservable.notifyObservers(isPreview); } public getCopy(): BaseBehaviorEditor { return new BaseBehaviorEditor(); } public getJson(): string { return ; } public setByJson(json:string){ console.log(json); } }GeneralBehaviorEditor.tsimport BaseBehaviorEditor from ../BaseBehaviorEditor; import type { GeneralType } from ./NodeGeneralBehavManagerEditor; export default class GeneralBehaviorEditor extends BaseBehaviorEditor { public readonly generalType: GeneralType Base; }MirrorReflectionBehaviorEditor.tsimport { TransformNode, Mesh, MirrorTexture, Plane, Vector3, Material, PBRMetallicRoughnessMaterial, Observable, BaseTexture, type Nullable, AbstractMesh, Observer, Scalar } from babylonjs/core; import CmdProperty from ../../../../Command/CmdProperty; import { cmdEmitter } from ../../../../utils/EventBus; import { DTO_MirrorReflectionEditor } from ../../../../../../Shared/TScripts/DTO/DTO_EditorSystem; import { DTO_MirrorReflection } from ../../../../../../Shared/TScripts/DTO/DTO_RuntimeSystem; import GeneralBehaviorEditor from ./GeneralBehaviorEditor; import type { GeneralType } from ./NodeGeneralBehavManagerEditor; // 镜面反射行为使用 MirrorTexture 实现 export default class MirrorReflectionBehaviorEditor extends GeneralBehaviorEditor { // 必须实现行为的唯一标识名称用于注册表 public static readonly behaviorName MirrorReflectionBehaviorEditor; public readonly generalType: GeneralType EnvTex; // 必须实现创建行为实例的工厂方法 public static createInstance(): MirrorReflectionBehaviorEditor { return new MirrorReflectionBehaviorEditor(); } // 重写 name 属性 get name(): string { return MirrorReflectionBehaviorEditor; } // 重写 cnName 属性 public readonly cnName: string 镜面反射; private _mesh:Mesh | null null; public get mesh(): Mesh | null { return this._mesh; } // 平整度阈值角度范围0.05 - 5 public readonly minFlatnessThreshold: number 0.05; public readonly maxFlatnessThreshold: number 5.0; private _flatnessThreshold: number 1.0; public get flatnessThreshold(): number { return this._flatnessThreshold; } // 反射纹理尺寸范围256 - 2048 private _textureSize: MirrorTexSize 512; public get textureSize(): MirrorTexSize { return this._textureSize; } public readonly minBlurKernel: number 0; public readonly maxBlurKernel: number 64; private _blurKernel: number 0; public get blurKernel(): number { return this._blurKernel; } // 行为是否有效平整度检查是否通过 private _isValid: boolean true; public get isValid(): boolean { return this._isValid; } // 镜面反射纹理 private _mirrorTexture: MirrorTexture | null null; // 计算得出的反射平面这时数学意义上的抽象平面并不是实际的网格平面 private _reflectionPlane: Plane | null null; // 整体反射朝向与反射平面垂直 private _overallNormal: Vector3 | null null; // 原始环境纹理备份保存材质原来的 environmentTexture private _originalEnvironmentTexture: NullableBaseTexture null; // 记录上次应用时的材质用于检测材质是否被更换 private _lastAppliedMaterial: NullableMaterial null; constructor() { super(); this.onSetPreviewObservable.add((isPreview: boolean) { if (isPreview) { this._applyMirrorTexture(); } else { this._restoreOriginalTexture(); } }); } // 重写 attach 方法添加自定义逻辑 attach(target: TransformNode): void { super.attach(target); if (!(target instanceof Mesh)) { console.warn(MirrorReflectionBehavior can only be attached to Mesh, but got: ${target.constructor.name}); this._isValid false; return; } this._mesh target as Mesh; this.setPreview(true); // 检查平整度并初始化镜面反射 this._checkFlatnessAndInitialize(); } // 重写 detach 方法添加清理逻辑 detach(): void { this._cleanup(); super.detach(); } // 保存上一次的世界矩阵用于检测变换是否改变 private _lastWorldMatrix: Float32Array | null null; // 重写 _updatePreview 方法在每帧检测材质是否被更换和变换是否改变 protected _updatePreview(): void { super._updatePreview(); // 如果行为有效且启用 if (this._isValid this.enabled this._mesh) { // 检测材质是否被更换 if (this._mesh.material this._lastAppliedMaterial this._lastAppliedMaterial ! this._mesh.material) { this._applyMirrorTexture(); } // 检测变换是否改变如果改变则更新反射平面 if (this._hasTransformChanged()) { this._updateReflectionPlane(); } } } // 检测mesh的世界变换是否改变 private _hasTransformChanged(): boolean { if (!this._mesh) return false; const currentWorldMatrix this._mesh.getWorldMatrix(); // 如果是第一次检测保存当前矩阵 if (!this._lastWorldMatrix) { this._lastWorldMatrix new Float32Array(currentWorldMatrix.asArray()); return false; } // 比较矩阵是否改变比较旋转/缩放和位置信息 const current currentWorldMatrix.asArray(); const last this._lastWorldMatrix; // 比较旋转/缩放部分索引0-11和位置部分索引12-14 // 索引15是齐次坐标通常为1不需要比较 for (let i 0; i 15; i) { if (Math.abs(current[i] - last[i]) 0.0001) { // 变换已改变更新保存的矩阵 this._lastWorldMatrix.set(current); return true; } } return false; } public cmdSetFlatnessThreshold(threshold: number): void { // 限制范围 threshold Scalar.Clamp(threshold, this.minFlatnessThreshold, this.maxFlatnessThreshold); if(Math.abs(threshold - this._flatnessThreshold) 0.00001)return; const cmd new CmdPropertynumber( this._setFlatnessThreshold.bind(this), threshold, this._flatnessThreshold ); cmdEmitter.emit(execute, cmd); } public readonly onSetFlatnessThresholdObservable new Observablenumber(); // 设置平整度阈值 private _setFlatnessThreshold(threshold: number): void { // 限制范围 threshold Scalar.Clamp(threshold, this.minFlatnessThreshold, this.maxFlatnessThreshold); const oldThreshold this._flatnessThreshold; const needRecheck (this._isValid threshold oldThreshold) || // 当前有效且阈值变小 (!this._isValid threshold oldThreshold); // 当前失效且阈值变大 this._flatnessThreshold threshold; if (needRecheck this.target) { this._checkFlatnessAndInitialize(); } this.onSetFlatnessThresholdObservable.notifyObservers(threshold); } public cmdSetTexSize(size: MirrorTexSize): void { if(size this._textureSize)return; const cmd new CmdPropertyMirrorTexSize( this._setTexSize.bind(this), size, this._textureSize ); cmdEmitter.emit(execute, cmd); } public readonly onSetTexSizeObservable new ObservableMirrorTexSize(); // 设置反射纹理尺寸 private _setTexSize(size: MirrorTexSize): void { this._textureSize size; // 如果已经初始化需要重新创建纹理 if (this._mirrorTexture this._isValid) { this._initializeMirrorTexture(); } this.onSetTexSizeObservable.notifyObservers(size); } public cmdSetBlurKernel(blurKernel: number): void { blurKernel Scalar.Clamp(blurKernel, this.minBlurKernel, this.maxBlurKernel); if(Math.abs(blurKernel - this._blurKernel) 0.00001)return; const cmd new CmdPropertynumber( this._setBlurKernel.bind(this), blurKernel, this._blurKernel ); cmdEmitter.emit(execute, cmd); } public readonly onSetBlurKernelObservable new Observablenumber(); private _setBlurKernel(blurKernel: number): void { this._blurKernel Scalar.Clamp(blurKernel, this.minBlurKernel, this.maxBlurKernel); if (this._mirrorTexture) { const kernel blurKernel * this._textureSize / 1024; this._mirrorTexture.blurKernelX kernel; this._mirrorTexture.blurKernelY kernel; } this.onSetBlurKernelObservable.notifyObservers(blurKernel); } // 检查平整度并初始化镜面反射 private _checkFlatnessAndInitialize(): void { if (!this._mesh) { this._isValid false; return; } // 获取网格的位置和法线数据 const positions this._mesh.getVerticesData(position); const normals this._mesh.getVerticesData(normal); if (!positions || !normals || positions.length 0 || normals.length 0) { console.warn(Mesh does not have position or normal data); this._isValid false; return; } // 计算整体法线方向平均所有法线 const normalSum new Vector3(0, 0, 0); for (let i 0; i normals.length; i 3) { normalSum.x normals[i]; normalSum.y normals[i 1]; normalSum.z normals[i 2]; } this._overallNormal normalSum.normalize(); // 检查所有法线与整体法线的夹角 const thresholdRadians this._flatnessThreshold * Math.PI / 180; let maxDeviation 0; for (let i 0; i normals.length; i 3) { const normal new Vector3(normals[i], normals[i 1], normals[i 2]); normal.normalize(); // 计算与整体法线的夹角 const dotProduct Vector3.Dot(normal, this._overallNormal); const angle Math.acos(Math.max(-1, Math.min(1, dotProduct))); maxDeviation Math.max(maxDeviation, angle); // 如果超过阈值标记为无效 if (angle thresholdRadians) { this._isValid false; console.warn(Mesh is not flat enough. Max deviation: ${(maxDeviation * 180 / Math.PI).toFixed(3)}°, Threshold: ${this._flatnessThreshold}°); this._cleanup(); return; } } // 平整度检查通过 this._isValid true; // 初始化镜面纹理会自动计算反射平面 this._initializeMirrorTexture(); } // 更新反射平面当mesh的位置或朝向改变时调用 private _updateReflectionPlane(): void { if (!this._mesh || !this._overallNormal || !this._mirrorTexture) { return; } // 计算反射平面使用网格中心点和整体法线 const boundingInfo this._mesh.getBoundingInfo(); const center boundingInfo.boundingBox.center; // 将法线和中心点转换到世界坐标 const worldMatrix this._mesh.getWorldMatrix(); const worldCenter Vector3.TransformCoordinates(center, worldMatrix); const worldNormal Vector3.TransformNormal(this._overallNormal, worldMatrix); worldNormal.normalize(); // 创建反射平面Babylon.js 平面方程ax by cz d 0 // 注意MirrorTexture 需要反向法线指向被反射空间的方向 // 所以需要对法线取反 const mirrorNormal worldNormal.scale(-1); const d -Vector3.Dot(mirrorNormal, worldCenter); this._reflectionPlane new Plane(mirrorNormal.x, mirrorNormal.y, mirrorNormal.z, d); // 更新镜面纹理的反射平面 this._mirrorTexture.mirrorPlane this._reflectionPlane; } private _sceneAddNewMeshObserver: ObserverAbstractMesh | null null; private _sceneRemoveMeshObserver: ObserverAbstractMesh | null null; // 初始化镜面反射纹理 private _initializeMirrorTexture(): void { if (!this.scene || !this._mesh) { return; } // 清理旧的镜面纹理 if (this._mirrorTexture) { this._mirrorTexture.dispose(); this._mirrorTexture null; } // 创建镜面纹理 this._mirrorTexture new MirrorTexture( mirrorTexture_${this._mesh.name}, this._textureSize, this.scene, false // 不生成 mipmap性能考虑 ); // 计算并设置反射平面 this._updateReflectionPlane(); // 设置模糊 this._setBlurKernel(this._blurKernel); // 设置渲染列表反射场景中的所有网格 const allMeshes this.scene.meshes.filter(m m ! this._mesh m.isEnabled()); this._mirrorTexture.renderList allMeshes; this._sceneAddNewMeshObserver this.scene.onNewMeshAddedObservable.add((m: AbstractMesh) { if(this._mirrorTexture this._mirrorTexture.renderList this._mirrorTexture.renderList.indexOf(m) -1){ this._mirrorTexture.renderList?.push(m); } }); this._sceneRemoveMeshObserver this.scene.onMeshRemovedObservable.add((m: AbstractMesh) { if(this._mirrorTexture this._mirrorTexture.renderList this._mirrorTexture.renderList.indexOf(m) ! -1){ this._mirrorTexture.renderList?.splice(this._mirrorTexture.renderList.indexOf(m), 1); } }); // 备份原始材质并应用镜面纹理 this._applyMirrorTexture(); } // 将镜面纹理应用到材质 private _applyMirrorTexture(): void { if (!this._mesh || !this._mirrorTexture) { return; } // 如果没有材质创建一个 PBRMetallicRoughnessMaterial if (!this._mesh.material) { this._mesh.material new PBRMetallicRoughnessMaterial(mirrorMaterial_${this._mesh.name}, this.scene!); } const material this._mesh.material; // 检测材质是否被更换与上次应用时的材质不同 if (this._lastAppliedMaterial this._lastAppliedMaterial ! material) { // 材质已更换重置原始纹理备份 this._originalEnvironmentTexture null; } // 备份原始的 environmentTexture如果还没有备份 if (this._originalEnvironmentTexture null) { if (material instanceof PBRMetallicRoughnessMaterial) { this._originalEnvironmentTexture material.environmentTexture; } } // 只替换 environmentTexture不改变材质的其他属性 if (material instanceof PBRMetallicRoughnessMaterial) { material.environmentTexture this._mirrorTexture; } // 记录本次应用的材质 this._lastAppliedMaterial material; } // 恢复原始纹理用于 Preview 切换不销毁资源 private _restoreOriginalTexture(): void { if (this._mesh this._mesh.material instanceof PBRMetallicRoughnessMaterial) { this._mesh.material.environmentTexture this._originalEnvironmentTexture; } } // 清理资源用于 detach完全销毁 private _cleanup(): void { // 先恢复原始纹理 this._restoreOriginalTexture(); // 移除场景观察者 if(this._sceneAddNewMeshObserver){ if(this.scene){ this.scene.onNewMeshAddedObservable.remove(this._sceneAddNewMeshObserver); } this._sceneAddNewMeshObserver null; } if(this._sceneRemoveMeshObserver){ if(this.scene){ this.scene.onMeshRemovedObservable.remove(this._sceneRemoveMeshObserver); } this._sceneRemoveMeshObserver null; } // 清空备份引用 this._originalEnvironmentTexture null; this._lastAppliedMaterial null; // 释放镜面纹理 if (this._mirrorTexture) { this._mirrorTexture.dispose(); this._mirrorTexture null; } // 清空其他引用 this._reflectionPlane null; this._overallNormal null; this._lastWorldMatrix null; } // 重写 setEnabled 方法 public setEnabled(enabled: boolean): void { super.setEnabled(enabled); if (enabled this._isValid) { this._applyMirrorTexture(); } else if (!enabled) { this._restoreOriginalTexture(); } } // 重写复制方法 public getCopy(): MirrorReflectionBehaviorEditor { const copy new MirrorReflectionBehaviorEditor(); copy._flatnessThreshold this._flatnessThreshold; copy._textureSize this._textureSize; return copy; } public getDataRuntime(): DTO_MirrorReflection { return new DTO_MirrorReflection( this.uuid, this._flatnessThreshold, this._textureSize, this._blurKernel ); } public getDataEditor(): DTO_MirrorReflectionEditor { return new DTO_MirrorReflectionEditor( this.getDataRuntime(), this.isPreview ); } public setByDataEditor(info: DTO_MirrorReflectionEditor): void { this._flatnessThreshold info.mirrorReflection.flatnessThreshold; this._textureSize info.mirrorReflection.textureSize; this._blurKernel info.mirrorReflection.blurKernel; this.setPreview(info.isPreview); } // 重写序列化方法 public getJson(): string { const data this.getDataEditor(); return JSON.stringify(data); } // 重写反序列化方法 public setByJson(json: string): void { try { const data JSON.parse(json) as DTO_MirrorReflectionEditor; this.setByDataEditor(data); } catch (error) { console.error(Failed to parse JSON:, error); } } } export type MirrorTexSize 128 | 256 | 512 | 1024 | 2048;六、开发心得与踩坑总结性能优先反射纹理是性能杀手。关闭 mipmap、动态渲染列表、变换检测优化都是必要的性能保障。数学严谨性平面方程、法线变换、矩阵比较任何数学细节的错误都会导致反射画面撕裂或错位。特别是法线需要 normalize 且方向要正确。编辑器友好非破坏性修改是核心原则。不能让用户应用反射后丢失原有材质配置备份机制必不可少。鲁棒性从平整度检测、材质类型判断到观察者管理每个环节都要考虑异常情况避免崩溃。浮点精度0.0001的误差阈值是经验值太小会导致误判太大则会漏掉微小但重要的变换。这套系统已在我们的 3D 编辑器中稳定运行支持设计师实时调整镜面效果极大地提升了场景表现力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询