THREEJS 片元着色器实现更自然的呼吸灯效果



javascript 复制代码
<template>
  <div ref="container" class="three-container"></div>
</template>

<script setup>
import {ref, onMounted, onBeforeUnmount} from 'vue'
import * as THREE from 'three'
import {OrbitControls} from 'three/addons/controls/OrbitControls.js'
import {GUI} from 'three/addons/libs/lil-gui.module.min.js'


// 着色器代码保持原样
const vertexShader = `
varying vec2 vUv;
void main(){
    vUv = vec2(uv.x,uv.y);
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
}
`

const fragmentShader = `
varying vec2 vUv;
uniform float vTime;
uniform float vPow;
uniform vec3 vColor;

void main(){
    float vy = 1.0 - vUv.y;
    // 添加呼吸波动计算
    float breath = ( sin(vTime * 2.0) * 0.5 + sin(vTime * 3.0) * 0.3) * 0.5 + 0.5; // 生成0-1的波动值
    float alpha = pow(vy, vPow) * breath; // 叠加呼吸效果

    // 添加颜色脉冲效果
    vec3 pulseColor = vColor * (0.8 + breath * 0.2);

    gl_FragColor = vec4(pulseColor, alpha);

    // 在颜色计算后添加边缘光晕
    float edgeGlow = smoothstep(0.7, 1.0, vy);
    gl_FragColor.rgb += edgeGlow * breath * 0.5;
}
`

const container = ref(null)
let scene, renderer, camera, orbit, gui
const uniforms = {
  vTime: {value: 0.01},
  vPow: {value: 2.0},
  vColor: {value: new THREE.Color("#ff0000")}
}

// 初始化场景
const initScene = () => {
  scene = new THREE.Scene()
  renderer = new THREE.WebGLRenderer({
    alpha: true,
    antialias: true
  })
  renderer.setSize(window.innerWidth, window.innerHeight)
  container.value.appendChild(renderer.domElement)

  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 2000)
  camera.add(new THREE.PointLight())
  camera.position.set(10, 10, 10)
  scene.add(camera)

  orbit = new OrbitControls(camera, renderer.domElement)
  orbit.enableDamping = true
  scene.add(new THREE.GridHelper(10, 10))
}

// 创建网格
const createMesh = () => {
  const planeHeight = 5
  const geometry = new THREE.PlaneGeometry(10, planeHeight)
  const material = new THREE.ShaderMaterial({
    uniforms,
    vertexShader,
    fragmentShader,
    transparent: true,
    side: THREE.DoubleSide
  })

  const createWall = (position, rotation) => {
    const mesh = new THREE.Mesh(geometry, material)
    mesh.position.set(...position)
    if (rotation) mesh.rotation.y = rotation
    return mesh
  }

  scene.add(createWall([5, planeHeight / 2, 0], Math.PI / 2))
  scene.add(createWall([-5, planeHeight / 2, 0], Math.PI / 2))
  scene.add(createWall([0, planeHeight / 2, -5]))
  scene.add(createWall([0, planeHeight / 2, 5]))
}

// 初始化GUI
const initGUI = () => {
  gui = new GUI()
  gui.add(uniforms.vPow, 'value', 0, 10).name('光栅栏高度')

  const colors = {vColor: "#ff0000"}
  gui.addColor(colors, 'vColor').name('光栅栏颜色').onChange(v => {
    uniforms.vColor.value = new THREE.Color(v)
  })
}

// 动画循环
const animate = () => {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
  orbit.update()
  uniforms.vTime.value += 0.01
}

onMounted(() => {
  initScene()
  createMesh()
  initGUI()
  animate()
  window.addEventListener('resize', onWindowResize)
})

onBeforeUnmount(() => {
  // 清理资源
  gui.destroy()
  scene.traverse(child => {
    if (child.material) child.material.dispose()
    if (child.geometry) child.geometry.dispose()
  })
  renderer.dispose()
  window.removeEventListener('resize', onWindowResize)
})

// 响应窗口变化
const onWindowResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
}
</script>

<style scoped>
.three-container {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  margin: 0;
  padding: 0;
}
</style>
相关推荐
GISer_Jing8 分钟前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪1 小时前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲5 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter7 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友7 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry7 小时前
Jetpack Compose 中的状态
前端
dae bal8 小时前
关于RSA和AES加密
前端·vue.js