使用水焦散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效果:
解决方案
问题根源
THREE.TextureLoader加载GIF时只会提取第一帧,原生不支持GIF的帧动画。你用的PlainAnimator是针对**精灵图集(Sprite Sheet)**的动画逻辑,和单文件GIF的帧动画不匹配,所以无法生效。
实现步骤
需要借助第三方库解析GIF的所有帧数据,然后在渲染循环中切换SpotLight的纹理:
- 先安装解析GIF的依赖:
npm install gifuct-js
- 替换组件代码为以下实现:
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
相关产品推荐
相关产品推荐

