模型增加发光效果的时候,场景变暗了,需要恢复一下
参考记录
解决思路
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
const effectComposer = new EffectComposer(renderer)
const gammaCorrectionShader = new ShaderPass( GammaCorrectionShader );
effectComposer.addPass( gammaCorrectionShader );
实例代码
实例代码
<script>
import { onMounted, ref, onUnmounted } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
import { SSAARenderPass } from 'three/examples/jsm/postprocessing/SSAARenderPass.js';
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import gsap from 'gsap';
import poopleGlb from './model/factoryTwo.glb';
export default {
name: 'map3D',
setup() {
const map3DMain = ref(null);
let renderer, scene, camera, orbitcontrols;
let thismodel;
let composer;
let ssaaRenderPass, outlinePass, gammaCorrectionPass;
let glowAnimation = null;
let targetMesh = null;
let originalMaterials = [];
// ✅ 呼吸灯效果管理(简化版)
let breathAnimations = new Map();
let isBreathing = false;
// ✅ 定义所有函数(确保作用域正确)
let handleResize, animate, init, initPostProcessing, loadModel;
let applyOutlineBreathEffect, enhanceTargetMaterialBreath, addHighlightObject;
let startYellowBreathAnimation, updateOutlineBreathStrength;
let removeHighlightObject, stopBreathAnimation, restoreOriginalState;
onMounted(async () => {
init();
animate();
function init() {
const width = map3DMain.value.offsetWidth;
const height = map3DMain.value.offsetHeight;
// 初始化渲染器
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.setClearColor(0x000000, 0);
renderer.toneMapping = THREE.ReinhardToneMapping;
renderer.toneMappingExposure = 1.0;
map3DMain.value.appendChild(renderer.domElement);
// 初始化场景
scene = new THREE.Scene();
// 初始化相机
camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
camera.position.set(3.31, 1.65, -270);
camera.lookAt(scene.position);
// 初始化控制器
orbitcontrols = new OrbitControls(camera, renderer.domElement);
orbitcontrols.enableDamping = true;
orbitcontrols.dampingFactor = 0.1;
orbitcontrols.enablePan = false;
orbitcontrols.enableZoom = true;
orbitcontrols.minDistance = 50;
orbitcontrols.maxDistance = 500;
// ✅ 增强光源亮度来解决变暗问题
const pointLight = new THREE.PointLight(0xffaa33, 2.5, 500);
pointLight.position.set(0, 80, 0);
scene.add(pointLight);
const ambientLight = new THREE.AmbientLight(0xffffff, 1.2);
scene.add(ambientLight);
// 添加补充光源
const fillLight = new THREE.DirectionalLight(0xffffff, 0.8);
fillLight.position.set(-50, 30, 50);
scene.add(fillLight);
const rimLight = new THREE.DirectionalLight(0xffdd88, 0.6);
rimLight.position.set(50, 40, -50);
scene.add(rimLight);
// 初始化后处理
initPostProcessing(width, height);
// 加载模型
loadModel();
// 窗口尺寸调整 - 现在handleResize在同一作用域内
window.addEventListener('resize', handleResize);
}
function initPostProcessing(width, height) {
// 创建渲染通道
const renderPass = new RenderPass(scene, camera);
// ✅ 添加SSAARenderPass
ssaaRenderPass = new SSAARenderPass(scene, camera);
ssaaRenderPass.unbiased = false;
ssaaRenderPass.sampleLevel = 2;
// ✅ 创建OutlinePass - 呼吸灯配置
const params = {
edgeStrength: 3.0, // 降低基础强度,为呼吸留空间
edgeGlow: 0.5, // 降低基础发光
edgeThickness: 1.5, // 降低基础厚度
pulsePeriod: 0, // ✅ 禁用内置脉冲,我们用自定义呼吸
visibleEdgeColor: '#ffff00', // ✅ 纯黄色
hiddenEdgeColor: '#ffaa00' // ✅ 橙黄色
};
outlinePass = new OutlinePass(new THREE.Vector2(width, height), scene, camera);
outlinePass.edgeStrength = params.edgeStrength;
outlinePass.edgeGlow = params.edgeGlow;
outlinePass.edgeThickness = params.edgeThickness;
outlinePass.pulsePeriod = params.pulsePeriod;
outlinePass.visibleEdgeColor.set(params.visibleEdgeColor);
outlinePass.hiddenEdgeColor.set(params.hiddenEdgeColor);
// ✅ 创建Gamma校正通道
gammaCorrectionPass = new ShaderPass(GammaCorrectionShader);
// ✅ 安全设置Gamma值
if (gammaCorrectionPass.uniforms) {
console.log('🔍 GammaCorrectionShader uniforms:', Object.keys(gammaCorrectionPass.uniforms));
if (gammaCorrectionPass.uniforms['gammaValue'] !== undefined) {
gammaCorrectionPass.uniforms['gammaValue'].value = 2.0;
} else {
console.log('✅ GammaCorrectionShader 使用默认参数');
}
} else {
console.log('ℹ️ GammaCorrectionShader 没有uniforms对象');
}
// ✅ 创建效果合成器
composer = new EffectComposer(renderer);
composer.addPass(renderPass);
composer.addPass(ssaaRenderPass);
composer.addPass(outlinePass);
if (gammaCorrectionPass) {
composer.addPass(gammaCorrectionPass);
console.log('✅ Gamma校正已添加到后处理链');
}
console.log('✅ 后处理链配置完成: RenderPass → SSAARenderPass → OutlinePass' +
(gammaCorrectionPass ? ' → GammaCorrection' : ''));
}
function loadModel() {
// 配置 DRACOLoader
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
dracoLoader.setDecoderConfig({ type: 'js' });
// 配置 GLTFLoader
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.load(
poopleGlb,
function (gltf) {
console.log('✅ 模型加载成功', gltf);
thismodel = gltf.scene;
thismodel.scale.set(0.35, 0.35, 0.35);
// 等待下一帧再修改材质
requestAnimationFrame(() => {
applyOutlineBreathEffect();
});
scene.add(thismodel);
},
function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% 已加载');
},
function (error) {
console.error('❌ 模型加载失败:', error);
}
);
}
/**
* ✅ 核心:应用OutlinePass黄色呼吸灯效果(简化版)
*/
function applyOutlineBreathEffect() {
if (!thismodel) return;
let foundTarget = false;
thismodel.traverse((child) => {
if (child.isMesh && child.name.toLowerCase().includes('frame_117')) {
console.log('✅ 找到 frame_117, 应用 OutlinePass 黄色呼吸灯效果');
foundTarget = true;
targetMesh = child;
try {
// ✅ 增强目标网格的基础材质亮度(轻微)
enhanceTargetMaterialBreath(child);
// 添加高亮对象
addHighlightObject(child, 'frame_117');
// 启动黄色呼吸动画
startYellowBreathAnimation(child, 'frame_117');
console.log('✅ OutlinePass 黄色呼吸灯效果已启动');
} catch (error) {
console.error('❌ 设置OutlinePass效果失败:', error);
}
}
});
if (!foundTarget) {
console.warn('⚠️ 未找到包含 frame_117 的网格');
console.log('📋 可用网格名称:');
thismodel.traverse((child) => {
if (child.isMesh) {
console.log(` - ${child.name}`);
}
});
}
}
/**
* ✅ 增强目标网格材质亮度(呼吸灯专用,轻微增强)
*/
function enhanceTargetMaterialBreath(mesh) {
const materials = Array.isArray(mesh.material)
? mesh.material
: [mesh.material];
materials.forEach((mat, index) => {
// 保存原始属性
if (!mat.userData.originalColor) {
mat.userData.originalColor = mat.color ? mat.color.clone() : new THREE.Color(0xffffff);
mat.userData.originalEmissive = mat.emissive ? mat.emissive.clone() : new THREE.Color(0x000000);
mat.userData.originalEmissiveIntensity = mat.emissiveIntensity ?? 0;
}
// 轻微增强基础颜色亮度(只增加10%,不要过亮)
if (mat.color) {
mat.color.multiplyScalar(1.1);
}
// 添加极轻微的自发光(几乎不可见,只是防止太暗)
if (mat.emissive) {
mat.emissive.setHex(0x110800); // 非常暗的暖色调
mat.emissiveIntensity = 0.05;
}
mat.needsUpdate = true;
console.log(`✅ 材质 ${index} 亮度已轻微增强(呼吸灯模式)`);
});
}
/**
* ✅ 添加高亮对象到OutlinePass
*/
function addHighlightObject(mesh, deviceName) {
if (!outlinePass || !mesh) return;
const highlightSelection = [];
highlightSelection.push(mesh);
outlinePass.selectedObjects = highlightSelection;
if (!window.highlightObjects) {
window.highlightObjects = new Map();
}
window.highlightObjects.set(deviceName, {
mesh: mesh,
uuid: mesh.uuid
});
console.log(`✅ 已添加高亮对象: ${deviceName}`);
}
/**
* ✅ 启动黄色呼吸动画(纯呼吸,无爆闪)
*/
function startYellowBreathAnimation(mesh, deviceName) {
if (!outlinePass) return;
const key = deviceName;
// 如果已有动画在运行,先停止
if (breathAnimations.has(key)) {
breathAnimations.get(key).kill();
}
// ✅ 呼吸动画配置(简单自然)
const breathConfig = {
minStrength: 2.0, // 最小强度
maxStrength: 5.0, // 最大强度
inhaleDuration: 2.0, // 吸气时间(变强)
exhaleDuration: 2.0, // 呼气时间(变弱)
pauseDuration: 0.5 // 停顿时间
};
// 创建呼吸时间线(无限循环)
const breathTimeline = gsap.timeline({ repeat: -1 });
// 自然的呼吸循环:弱→强→停顿→强→弱→停顿
breathTimeline
// 从最弱开始
.set({}, {}, 0)
// 吸气阶段(逐渐变强)
.to({}, {
duration: breathConfig.inhaleDuration,
ease: "power2.inOut",
onUpdate: () => {
const progress = breathTimeline.progress();
const strengthMultiplier = breathConfig.minStrength +
(breathConfig.maxStrength - breathConfig.minStrength) *
_easeInOutQuad(progress / breathConfig.inhaleDuration);
updateOutlineBreathStrength(strengthMultiplier, breathConfig);
}
})
// 停顿(保持最强)
.to({}, {
duration: breathConfig.pauseDuration,
ease: "none"
})
// 呼气阶段(逐渐变弱)
.to({}, {
duration: breathConfig.exhaleDuration,
ease: "power2.inOut",
onUpdate: () => {
const timelineDuration = breathConfig.inhaleDuration + breathConfig.pauseDuration + breathConfig.exhaleDuration + breathConfig.pauseDuration;
const localProgress = breathTimeline.progress() * timelineDuration -
(breathConfig.inhaleDuration + breathConfig.pauseDuration);
const normalizedProgress = localProgress / breathConfig.exhaleDuration;
const strengthMultiplier = breathConfig.maxStrength -
(breathConfig.maxStrength - breathConfig.minStrength) *
_easeInOutQuad(normalizedProgress);
updateOutlineBreathStrength(strengthMultiplier, breathConfig);
}
})
// 停顿(保持最弱)
.to({}, {
duration: breathConfig.pauseDuration,
ease: "none"
});
// 存储动画
breathAnimations.set(key, breathTimeline);
// 设置初始状态
updateOutlineBreathStrength(breathConfig.minStrength, breathConfig);
console.log(`🌊 黄色呼吸动画已启动: ${deviceName} (${breathConfig.inhaleDuration}s吸气, ${breathConfig.exhaleDuration}s呼气)`);
isBreathing = true;
}
/**
* ✅ 更新OutlinePass呼吸强度
*/
function updateOutlineBreathStrength(multiplier, config) {
if (!outlinePass) return;
// 动态更新OutlinePass参数(呼吸效果)
outlinePass.edgeStrength = 2.0 * multiplier; // 基础强度2.0
outlinePass.edgeGlow = 0.3 * multiplier; // 基础发光0.3
outlinePass.edgeThickness = 1.0 * (0.8 + multiplier * 0.4); // 厚度变化
// 保持黄色
outlinePass.visibleEdgeColor.set('#ffff00');
outlinePass.hiddenEdgeColor.set('#ffaa00');
}
/**
* ✅ 缓动函数(二次方缓入缓出)
*/
function _easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
/**
* ✅ 移除高亮对象
*/
function removeHighlightObject(deviceName) {
if (window.highlightObjects && window.highlightObjects.has(deviceName)) {
window.highlightObjects.delete(deviceName);
console.log(`⏹️ 已移除高亮对象: ${deviceName}`);
}
}
/**
* ✅ 停止呼吸动画
*/
function stopBreathAnimation(deviceName) {
if (breathAnimations.has(deviceName)) {
breathAnimations.get(deviceName).kill();
breathAnimations.delete(deviceName);
console.log(`⏹️ 已停止呼吸动画: ${deviceName}`);
isBreathing = false;
}
}
/**
* ✅ 恢复原始状态
*/
function restoreOriginalState() {
// 停止所有动画
breathAnimations.forEach((animation, key) => {
animation.kill();
});
breathAnimations.clear();
// 清空高亮对象
if (window.highlightObjects) {
window.highlightObjects.clear();
}
// 重置OutlinePass
if (outlinePass) {
outlinePass.selectedObjects = [];
outlinePass.edgeStrength = 3.0;
outlinePass.edgeGlow = 0.5;
outlinePass.edgeThickness = 1.5;
outlinePass.pulsePeriod = 0;
outlinePass.visibleEdgeColor.set('#ffff00');
outlinePass.hiddenEdgeColor.set('#ffaa00');
}
// 重置Gamma校正
if (gammaCorrectionPass && gammaCorrectionPass.uniforms &&
gammaCorrectionPass.uniforms['gammaValue'] !== undefined) {
gammaCorrectionPass.uniforms['gammaValue'].value = 2.0;
}
isBreathing = false;
console.log('✅ 已恢复原始状态');
}
/**
* ✅ 动画循环
*/
function animate() {
requestAnimationFrame(animate);
if (orbitcontrols) orbitcontrols.update();
camera.updateMatrixWorld();
composer.render();
}
/**
* ✅ 窗口尺寸调整处理函数 - 现在在同一作用域内
*/
handleResize = function() {
const width = map3DMain.value.offsetWidth;
const height = map3DMain.value.offsetHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
if (composer) {
composer.setSize(width, height);
if (outlinePass) {
outlinePass.setSize(width, height);
}
}
};
});
// ✅ 组件销毁时清理资源
onUnmounted(() => {
// 停止所有动画
if (window.BreathGlowController) {
window.BreathGlowController.restoreOriginalState();
}
// 移除事件监听器 - 现在handleResize在同一作用域内可访问
window.removeEventListener('resize', handleResize);
if (glowAnimation) {
glowAnimation.kill();
glowAnimation = null;
}
if (renderer) {
renderer.dispose();
}
console.log('✅ 资源已清理');
});
// ✅ 暴露控制方法
window.BreathGlowController = {
addHighlightObject,
removeHighlightObject,
startYellowBreathAnimation,
stopBreathAnimation,
restoreOriginalState,
isBreathing: () => isBreathing
};
return {
map3DMain
};
}
};
</script>
