You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用水焦散GIF作为Three.js聚光灯图案仅显示首帧,求解决

问题:Three.js SpotLight的GIF纹理仅显示第一帧无法动画

我尝试将水焦散GIF作为Three.js SpotLight的gobo(图案片)使用,但目前该GIF仅显示第一帧。参考纹理动画方案修改代码后仍未实现动态效果,相关代码如下:

function Sprite({ textureSrc, IconPosition, IconSize }) {
  const spriteTexture = useLoader(THREE.TextureLoader, textureSrc)
  const [animator] = useState(() => new PlainAnimator(spriteTexture, 4, 4, 10, 10))
  useFrame(() => animator.animate())
  return (
      <spotLight position={[0, 10, 0]} intensity={1} color="#fff" map={spriteTexture} />
  )
}

<Suspense fallback={null}>
  <Sprite IconPosition={[2, 1, 0]} IconSize={[3, 3, 0]} textureSrc="./3s0F.gif" />
</Suspense>

对应的水焦散GIF效果:
水焦散GIF效果


解决方案

问题根源

THREE.TextureLoader加载GIF时只会提取第一帧,原生不支持GIF的帧动画。你用的PlainAnimator是针对**精灵图集(Sprite Sheet)**的动画逻辑,和单文件GIF的帧动画不匹配,所以无法生效。

实现步骤

需要借助第三方库解析GIF的所有帧数据,然后在渲染循环中切换SpotLight的纹理:

  1. 先安装解析GIF的依赖:
npm install gifuct-js
  1. 替换组件代码为以下实现:
import { useLoader, useFrame, useRef } from '@react-three/fiber'
import * as THREE from 'three'
import { parseGIF, decompressFrames } from 'gifuct-js'

function AnimatedGoboLight({ textureSrc, position }) {
  // 加载并解析GIF文件
  const gifBuffer = useLoader(THREE.FileLoader, textureSrc, loader => {
    loader.setResponseType('arraybuffer')
  })
  const gifInfo = parseGIF(gifBuffer)
  const gifFrames = decompressFrames(gifInfo, true)

  // 将每帧GIF转为Three.js Canvas纹理
  const frameTextures = gifFrames.map(frame => {
    const canvas = document.createElement('canvas')
    canvas.width = gifInfo.lsd.width
    canvas.height = gifInfo.lsd.height
    const ctx = canvas.getContext('2d')

    // 绘制当前帧(处理透明通道)
    const imgData = ctx.createImageData(canvas.width, canvas.height)
    imgData.data.set(frame.patch)
    ctx.putImageData(imgData, 0, 0)

    const texture = new THREE.CanvasTexture(canvas)
    texture.needsUpdate = true
    return texture
  })

  const lightRef = useRef(null)
  let elapsedTime = 0
  const frameDelay = gifFrames[0].delay * 10 // 转换为毫秒(GIF延迟单位是1/100秒)

  useFrame((_, delta) => {
    if (!lightRef.current) return
    
    // 根据时间切换帧
    elapsedTime += delta * 1000
    if (elapsedTime >= frameDelay) {
      elapsedTime %= frameDelay
      const currentFrameIndex = Math.floor((elapsedTime / frameDelay) * gifFrames.length)
      lightRef.current.map = frameTextures[currentFrameIndex]
    }
  })

  return (
    <spotLight
      ref={lightRef}
      position={position}
      intensity={1}
      color="#fff"
      map={frameTextures[0]}
      angle={Math.PI / 4}
      penumbra={0.5}
    />
  )
}

// 使用组件
<Suspense fallback={null}>
  <AnimatedGoboLight position={[0, 10, 0]} textureSrc="./3s0F.gif" />
</Suspense>

关键说明

  • 用gifuct-js解析出GIF的所有帧数据和每帧延迟时间
  • 将每一帧转为CanvasTexture,确保保留GIF的透明通道
  • 在useFrame中根据时间戳计算当前应该显示的帧,切换灯光的map属性实现动画

内容的提问来源于stack exchange,提问作者Ashbury

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 20:25:40