threejs模型后期处理发光模型变得太暗

threejs

模型增加发光效果的时候,场景变暗了,需要恢复一下

参考记录

修正postprocessing导致的颜色空间问题

解决思路

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>

完整实例代码下载

相关文件下载地址
此资源需支付 ¥1 后下载
喜欢
threejs模型后期处理发光模型变得太暗