【问题标题】:How to create water in react-three-fiber如何在反应三纤维中产生水
【发布时间】:2026-02-21 23:25:02
【问题描述】:

我想用水创建 3D 场景,就像这个例子中的 three.js shader oceanwater 但我必须在 react-three-fiber 库中创建它。我已经在互联网上搜索了这个案例的一些很好的工作示例,但没有结果。

我可以寻求帮助以弄清楚如何将上述示例实施到 react-three-fiber 方法中吗?

这是我目前所提到的水成分:

import React from "react";
import waterNormal from "./waternormals.jpg";
//import { useLoader } from "react-three-fiber";
import * as THREE from "three";

import { Water } from "three/examples/jsm/objects/Water.js";

const WaterObject = () => {
  //const mapNormalWater = useLoader(TextureLoader, waterNormal);

  return (
    <mesh>
      <planeBufferGeometry attach="geometry" args={[100, 100]} />
      <Water
        options={{
          textureWidth: 512,
          textureHeight: 512,
          waterNormals: new THREE.TextureLoader().load(
            waterNormal,
            function (texture) {
              texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
            }
          ),
          sunDirection: new THREE.Vector3(),
          sunColor: 0xffffff,
          waterColor: 0x001e0f,
          distortionScale: 3.7,
          // fog: scene.fog !== undefined
        }}
      ></Water>
    </mesh>
  );
};

export default WaterObject ;

【问题讨论】:

  • 三个 jsm 有一个水着色器 codesandbox.io/s/water-shader-1b40u 但它在 srgb 中坏了所以颜色很糟糕
  • ps。我没看错,你已经在尝试使用同样的东西了。所以,你不能像那样把任何东西都转储到 jsx 中。 jsx 用于组件(大写)和原生元素(网格、材质……)。如果你有第三方三类并且你想在 jsx 中使用它,你必须使用扩展函数,就像我发布的框中一样。
  • 你知道怎么做吗?我遇到了一个问题,它告诉我 Water 不是有效的 jsx 元素。 @hpalu 我查看了另一个使用extend({ Water }); 的示例,但仍然没有弄清楚它实际上在做什么。我在这里问了一个类似的问题:*.com/questions/70642162/…
  • codesandbox.io/s/… 我发现这个例子很好地解释了extend (以及为什么&lt;Water&gt; 变成&lt;water&gt;,但它仍然无法正常工作:(
  • @timhc22 关于为什么 更改为 我认为它与类有关(如果我错了,请有人纠正我)。 Water 是 Class(第一个大写字母),您不能将它用作 JSX 元素,但是当您使用 extend 时,它会创建一个对象实例,并且可以在此处用作标准 JSX 元素,您可以在此处找到此组件 github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/…。当您编写自定义着色器示例时也会发生同样的事情:codesandbox.io/s/shadermaterials-1g4qq?file=/src/App.js:721-738

标签: javascript reactjs three.js shader react-three-fiber


【解决方案1】:

感谢hpalu解决。

下面的代码是可以在画布组件中导入和使用的组件。

import React, { useRef, useMemo } from "react";
import { extend, useThree, useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";

import { Water } from "three/examples/jsm/objects/Water.js";

extend({ Water });

function Ocean() {
  const ref = useRef();
  const gl = useThree((state) => state.gl);
  const waterNormals = useLoader(
    THREE.TextureLoader, "https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/waternormals.jpg"
  );


  waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
  const geom = useMemo(() => new THREE.PlaneGeometry(30000, 30000), []);
  const config = useMemo(
    () => ({
      textureWidth: 512,
      textureHeight: 512,
      waterNormals,
      sunDirection: new THREE.Vector3(),
      sunColor: 0xeb8934,
      waterColor: 0x0064b5,
      distortionScale: 40,
      fog: false,
      format: gl.encoding,
    }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [waterNormals]
  );
  useFrame(
    (state, delta) => (ref.current.material.uniforms.time.value += delta)
  );
  return (
    <water
      ref={ref}
      args={[geom, config]}
      rotation-x={-Math.PI / 2}
      position={[0, 0, 0]}
    />
  );
}

export default Ocean;

【讨论】:

    【解决方案2】:

    谢谢两位!这帮助很大...

    仍然很难让它与其他版本的有 ya 一起工作,所以添加我所做的更改以防他们可以帮助其他人:

    useFrame((state, delta) => {
        const material = ref?.current?.material as THREE.ShaderMaterial;
        material.uniforms.time.value += delta;
    })
    

    防止对象可能是'null'错误。

    声明了一个命名空间接口,否则小写的“水”真的不起作用:

    extend({ Water });
    declare global {
    namespace JSX {
      interface IntrinsicElements {
        water: Object3DNode<Water, typeof Water>
      }
    }
    }
    

    ...最后...如果您的向上向量是 Z 轴,而不是默认的 Y 轴,您会得到很奇怪的结果,所以要注意对象的旋转。

    【讨论】: