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

移植ShaderToy着色器到React-Vite项目遇编译错误求排查

问题排查与解决思路

核心错误修正

1. 修复iChannel0的赋值错误

你错误地将片段着色器字符串直接赋值给了sampler2D类型的iChannel0 uniform,这完全不符合Three.js对纹理uniform的要求。ShaderToy中的Buffer A是离屏渲染目标,需要先将Buffer_A_Frag渲染到一个纹理上,再把这个纹理传给主着色器的iChannel0。

在React Three Fiber中,可借助@react-three/drei的RenderTarget组件实现离屏渲染:

# 先安装依赖
npm install @react-three/drei

修改代码创建Buffer渲染目标:

import { RenderTarget } from '@react-three/drei';

// 在OceanShaderMesh组件内部
const bufferTarget = useRenderTarget({ width: size.width, height: size.height });

// 渲染Buffer A到目标纹理
<RenderTarget target={bufferTarget}>
  <mesh>
    <planeGeometry args={[2, 2]} />
    <shaderMaterial
      uniforms={{
        iTime: { value: clock.getElapsedTime() },
        iResolution: { value: new THREE.Vector3(size.width, size.height, 1) }
      }}
      fragmentShader={Buffer_A_Frag}
      vertexShader={vertexShader}
    />
  </mesh>
</RenderTarget>

随后将主着色器的iChannel0值改为bufferTarget.texture。

2. 修正useLoader的使用位置

useLoader是React Hook,不能在useMemo内部调用,必须在组件顶层执行:

const OceanShaderMesh = () => {
  // 顶层调用useLoader
  const buffer1Tex = useLoader(THREE.TextureLoader, "buffer1.png");
  const noiseTex = useLoader(THREE.TextureLoader, "noise.png");
  // ...其他代码
}

3. 修正uniforms的依赖与类型

  • ShaderToy中iResolution是vec3类型,你误用了THREE.Vector2,改为THREE.Vector3(size.width, size.height, 1)
  • useMemo的依赖数组需包含size、buffer1Tex、noiseTex,否则uniforms无法正确更新:
const uniforms = useMemo(() => ({
  iTime: { value: 0 },
  iResolution: { value: new THREE.Vector3(size.width, size.height, 1) },
  iChannel0: { value: bufferTarget.texture },
  iChannel1: { value: buffer1Tex },
  iChannel2: { value: noiseTex },
}), [size, bufferTarget.texture, buffer1Tex, noiseTex]);

BufferShader概念澄清

ShaderToy的Buffer通道本质是离屏渲染缓冲区:

  • 每个Buffer(如Buffer A)是独立着色器,先渲染到一个纹理上
  • 主着色器可将该纹理作为iChannel0等采样器输入,实现多阶段渲染
  • 在React Three Fiber中,对应实现是用RenderTarget创建离屏纹理,先渲染Buffer着色器到该纹理,再将纹理传入主着色器。

其他优化点

  • 移除gl.setSize(size.width, size.height),React Three Fiber会自动处理画布尺寸
  • 正交相机的zoom设置为1即可,平面已适配屏幕尺寸
  • 着色器中100次循环可能性能过低,可适当减少到30-50次,或用#define常量控制

完整修正后的核心代码片段

import { Fragment, useRef, useMemo } from "react";
import * as THREE from "three";
import { Canvas, useFrame, useLoader, useThree, useRenderTarget } from "@react-three/fiber";
import { RenderTarget } from "@react-three/drei";

// ... vertexShader、Buffer_A_Frag、fragmentShader 保持不变

const OceanShaderMesh = () => {
  const materialRef = useRef(null);
  const { size, gl } = useThree();
  const buffer1Tex = useLoader(THREE.TextureLoader, "buffer1.png");
  const noiseTex = useLoader(THREE.TextureLoader, "noise.png");
  const bufferTarget = useRenderTarget({ width: size.width, height: size.height, dpr: 1 });

  gl.setPixelRatio(1);

  useFrame(({ clock }) => {
    if (materialRef.current) {
      materialRef.current.uniforms.iTime.value = clock.getElapsedTime();
      materialRef.current.uniforms.iResolution.value.set(size.width, size.height, 1);
    }
  });

  const uniforms = useMemo(() => ({
    iTime: { value: 0 },
    iResolution: { value: new THREE.Vector3(size.width, size.height, 1) },
    iChannel0: { value: bufferTarget.texture },
    iChannel1: { value: buffer1Tex },
    iChannel2: { value: noiseTex },
  }), [size, bufferTarget.texture, buffer1Tex, noiseTex]);

  return (
    <>
      {/* 离屏渲染Buffer A */}
      <RenderTarget target={bufferTarget}>
        <mesh>
          <planeGeometry args={[2, 2]} />
          <shaderMaterial
            uniforms={{
              iTime: { value: 0 },
              iResolution: { value: new THREE.Vector3(size.width, size.height, 1) }
            }}
            vertexShader={vertexShader}
            fragmentShader={Buffer_A_Frag}
          />
        </mesh>
      </RenderTarget>
      {/* 主渲染mesh */}
      <mesh scale={[size.width / size.height, 1, 1]}>
        <planeGeometry args={[2, 2]} />
        <shaderMaterial
          ref={materialRef}
          uniforms={uniforms}
          vertexShader={vertexShader}
          fragmentShader={fragmentShader}
        />
      </mesh>
    </>
  );
};

// ... Background组件保持不变

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 01:27:33