4

I want to create 3D scene with water like in this example three.js shader ocean or water but I have to create this in react-three-fiber library. I already searched the internet to find some good working example for this case but without result.

Can I ask for help to figure it out how to implement above examples into react-three-fiber approach?

here is what I have so far for metioned Water component:

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 ;

DonkeyLuck0
  • 103
  • 1
  • 10
  • three jsm has a water shader https://codesandbox.io/s/water-shader-1b40u but it's broken in srgb so colors are terrible – hpalu May 21 '21 at 20:42
  • 1
    ps. i didn't read it right, you already were trying to use the same thing. so, you can't just dump anything into jsx like that. jsx is for components (uppercase) and native elements (mesh, material, ...). if you have a third party three class and you want to use it in jsx you must use the extend function, like in the box i posted. – hpalu May 21 '21 at 22:45
  • did you figure out how to do this? I'm having an issue where it is telling me that Water is not a valid jsx element. @hpalu I looked at another example which uses `extend({ Water });` but still haven't figured out what that is actually doing. I asked a similar question here: https://stackoverflow.com/questions/70642162/using-extend-from-react-three-fiber – timhc22 Jan 10 '22 at 18:03
  • https://codesandbox.io/s/react-three-fiber-orbit-controls-without-drei-7c11y I found this example which explains `extend` pretty well (and why `` becomes ``, but it is still not working :( – timhc22 Jan 10 '22 at 18:44
  • @timhc22 I posted an answer maybe this will help. – DonkeyLuck0 Jan 12 '22 at 01:42
  • 1
    @timhc22 About Why is changing to the I think that it is related with Classes (pls someone correct me if I'm wrong). Water is Class (uppercase 1st letter) and you cannot use it as JSX elemnt but when you use extend it makes an instnace of object and can be used as standard JSX element here you can find this component https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/renderer.ts . The same thing happen when you write custom shader example: https://codesandbox.io/s/shadermaterials-1g4qq?file=/src/App.js:721-738 – DonkeyLuck0 Jan 12 '22 at 01:43
  • Hey thanks for the response, I managed to figure out what I needed, I was using typescript, and I needed to basically do this to get it working: https://stackoverflow.com/questions/65459024/shaders-with-typescript-and-react-three-fiber (with `water: ReactThreeFiber.Object3DNode`) – timhc22 Jan 12 '22 at 14:46

2 Answers2

2

Solved thanks to the hpalu.

Below code to the component which can be imported and used in canvas component.

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;
DonkeyLuck0
  • 103
  • 1
  • 10
1

Thanks both! This helped a great deal...

Still had quite some struggles getting this to work with other versions of what have ya, so adding the changes I made in case they could help others:

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

to prevent object is possibly 'null' error.

Declared a namespace interface, as the lower case 'water' really doesn't work otherwise:

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

... and finally... if your up-vector is the Z-axis, as opposed to the default Y-axis, you get funky results, so beware the rotation of the object.

prkrekel
  • 11
  • 1