React Three Fiber中Shader动画不渲染及自定义材质报错问题
问题与解决方案:旗帜动画不更新及自定义材质报错
问题1:uTime随时间更新但旗帜动画未生效
问题描述
编写了接收uFrequency和uTime参数的顶点着色器实现旗帜动画,逻辑正确,但uTime随时间更新时平面没有动画效果。
原因分析
- 时间更新机制不合理:用
setInterval+useState每1秒更新一次时间,而useFrame是每帧执行,导致时间更新不及时;且useState更新异步,useFrame中获取的时间值不是最新的。 - 缺少片元着色器:原代码仅传入顶点着色器,没有片元着色器,物体无法正确渲染出颜色,也是动画看似失效的潜在原因。
解决代码修改
替换时间更新逻辑,改用THREE.Clock实时计算时间,并补充基础片元着色器:
import React, { useRef } from 'react' import portalVertexShader from "../shaders/vertex.glsl" import * as THREE from 'three'; import { useFrame } from 'react-three-fiber'; import { Plane } from '@react-three/drei'; export default function Flag() { const materialRef = useRef(); const clock = useRef(new THREE.Clock()); useFrame(() => { // 实时获取已流逝的时间(秒) const elapsedTime = clock.current.getElapsedTime(); materialRef.current.uniforms.uTime.value = elapsedTime; }); return ( <Plane args={[1, 1, 32, 32]} rotation={[1.5, 0, 0]}> <shaderMaterial ref={materialRef} side={THREE.DoubleSide} uniforms={{ uTime: { value: 0 }, uFrequency: { value: new THREE.Vector2(10, 20) } }} vertexShader={portalVertexShader} // 补充基础片元着色器让物体可见 fragmentShader={` void main() { gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0); } `} /> </Plane> ) }
关键说明
- 移除定时器和
useState,用THREE.Clock在useFrame中实时计算时间,确保每帧都能获取最新的uTime值。 - 片元着色器负责像素颜色输出,缺失会导致物体无法正常显示,必须补充。
问题2:自定义shaderMaterial报错TypeError: Class constructor material cannot be invoked without 'new'
问题描述
尝试通过shaderMaterial创建自定义材质组件并使用时,触发上述错误。
原因分析
- 未正确从
@react-three/drei导入shaderMaterial和extend函数。 - 使用自定义材质组件时,未遵循React组件首字母大写的命名规范,导致被当作原生HTML标签处理。
解决代码修改
确保正确导入并规范使用自定义材质:
// 正确导入所需工具 import { shaderMaterial, extend } from '@react-three/drei'; import portalVertexShader from "../shaders/vertex.glsl" import portalFragmentShader from "../shaders/fragment.glsl" import * as THREE from 'three'; // 创建自定义材质 const PortalMaterial = shaderMaterial( { uTime: 0, uColorStart: new THREE.Color('#ffffff'), uColorEnd: new THREE.Color('#000000') }, portalVertexShader, portalFragmentShader ); // 扩展Three.js材质库 extend({ PortalMaterial }); // 组件中使用自定义材质 export default function Flag() { const materialRef = useRef(); const clock = useRef(new THREE.Clock()); useFrame(() => { const elapsedTime = clock.current.getElapsedTime(); // 自定义材质可直接通过属性赋值uniforms materialRef.current.uTime = elapsedTime; }); return ( <Plane args={[1, 1, 32, 32]} rotation={[1.5, 0, 0]}> <PortalMaterial ref={materialRef} side={THREE.DoubleSide} /> </Plane> ) }
关键说明
- 必须从
@react-three/drei导入shaderMaterial和extend,确保依赖正确。 - 自定义材质组件名必须首字母大写,符合React组件命名规则。
- 自定义材质的uniforms可直接通过组件实例属性赋值,无需手动访问
uniforms对象。
内容的提问来源于stack exchange,提问作者Shirish Shakya
相关产品推荐
相关产品推荐

