Difference in water between plain three js and react three js

Hello, since the past 2 days I have been trying to replicate water that I have done in plain three js. Below is the code

        import { Water } from "three/addons/objects/Water.js";
        import { Sky } from "three/addons/objects/Sky.js";
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        let container;
        let camera, scene, renderer;
        let water, sun;
        let mixer;
        let model;
        init();
        function init() {
            container = document.getElementById('container');
            renderer = new THREE.WebGLRenderer();
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setAnimationLoop(animate);
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 0.3; // Increase exposure to brighten the scene
            container.appendChild(renderer.domElement);
            scene = new THREE.Scene();
            camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 20000);
            camera.position.set(34.197718304489214, 42.92727023247369, 98.40921054440358);
            camera.rotation.set(0.23163731194605403, 0.5339245571653, -0.11946685651052753);
            sun = new THREE.Vector3();
            const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
            const loader = new THREE.TextureLoader();
            const waterNormals = loader.load('https://threejs.org/examples/textures/waternormals.jpg');
            waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
            water = new Water(
                waterGeometry,
                {
                    textureWidth: 1024,
                    textureHeight: 1024,
                    waterNormals: waterNormals,
                    sunDirection: new THREE.Vector3(),
                    sunColor: 0x2432a0, 
                    waterColor: 0x070b2d, 
                    distortionScale: 8.5,
                    size: 10000 
                }
            );

            water.rotation.x = -Math.PI / 2;
            scene.add(water);

            const skyGeometry = new THREE.SphereGeometry(5000, 32, 32);
            const skyMaterial = new THREE.ShaderMaterial({
                
                uniforms: {
                    topColor: { value: new THREE.Color(0x00011b) },
                    bottomColor: { value: new THREE.Color(0x07249b) }, 
                    offset: { value: 1100 },
                    exponent: { value: 0.6 }
                },
                vertexShader: `
                    varying vec3 vWorldPosition;
                    void main() {
                        vec4 worldPosition = modelViewMatrix * vec4(position, 1.0);
                        vWorldPosition = worldPosition.xyz;
                        gl_Position = projectionMatrix * worldPosition;
                    }
                `,
                fragmentShader: `
                    uniform vec3 topColor;
                    uniform vec3 bottomColor;
                    uniform float offset;
                    uniform float exponent;
                    varying vec3 vWorldPosition;
                    void main() {
                        float h = normalize(vWorldPosition + offset).y;
                        gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
                    }
                `,
                side: THREE.BackSide
            });

            const sky = new THREE.Mesh(skyGeometry, skyMaterial);
            scene.add(sky);

            // Add stars to the sky with twinkling effect using custom shader material
            const starGeometry = new THREE.BufferGeometry();
            const starVertices = [];
            const starPhases = [];
            const starSizes = [];
            for (let i = 0; i < 5000; i++) {
                const x = THREE.MathUtils.randFloatSpread(2000);
                const y = THREE.MathUtils.randFloat(100, 2000); // Ensure stars are above the horizon
                const z = THREE.MathUtils.randFloatSpread(2000);

                // Ensure stars are far from the camera
                if (Math.sqrt(x * x + y * y + z * z) > 500) {
                    starVertices.push(x, y, z);
                    starPhases.push(Math.random() * 2.0 * Math.PI); // Random phase for each star
                    starSizes.push(Math.random() * 5 + 2); // Random size between 2 and 7
                }
            }
            starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
            starGeometry.setAttribute('phase', new THREE.Float32BufferAttribute(starPhases, 1));
            starGeometry.setAttribute('size', new THREE.Float32BufferAttribute(starSizes, 1));

            const starMaterial = new THREE.ShaderMaterial({
                uniforms: {
                    time: { value: 1.0 }
                },
                vertexShader: `
                    attribute float phase;
                    attribute float size;
                    varying float vPhase;
                    void main() {
                        vPhase = phase;
                        vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
                        gl_PointSize = size; // Use the size attribute for point size
                        gl_Position = projectionMatrix * mvPosition;
                    }
                `,
                fragmentShader: `
                    uniform float time;
                    varying float vPhase;
                    void main() {
                        float alpha = 0.8 + 0.5 * sin(time + vPhase);
                        vec2 coord = gl_PointCoord - vec2(0.5);
                        float distance = length(coord);
                        if (distance > 0.5) {
                            discard; // Discard fragments outside the circular area
                        }
                        float glow = smoothstep(0.1, 0.3, distance); // Adjust smoothstep for smaller white core
                        vec3 color = mix(vec3(1.0, 1.0, 1.0), vec3(0.0, 0.0, 1.0), glow);
                        gl_FragColor = vec4(color, alpha * (1.0 - distance * 0.5)); // Adjust alpha for more intensity
                    }
                `,
                transparent: true
            });

            const stars = new THREE.Points(starGeometry, starMaterial);
            scene.add(stars);
            

    // Add directional light
    const directionalLight = new THREE.DirectionalLight(0xffffff, 5);
    directionalLight.position.set(34.197718304489214, 42.92727023247369, 20 ).normalize();
    const targetObject = new THREE.Object3D();
targetObject.position.set(34.197718304489214, 42.92727023247369, 20); 
scene.add(targetObject);
directionalLight.target = targetObject;
    directionalLight.castShadow = false;
    scene.add(directionalLight);

            window.addEventListener('resize', onWindowResize);
        }
        function loadModel() {
            const loader = new GLTFLoader();
loader.load(
    './Moon JellyfishT.glb',
    function (gltf) {
        console.log('Model loaded successfully');
        model = gltf.scene;
        
        // Position in front of camera
        model.position.set(34.197718304489214, 42.92727023247369, 20);
        model.rotation.set(10,0,-3);
        
        // Make model larger
        model.scale.set(6, 6, 6);
        
        // Traverse the model and adjust materials
        model.traverse((child) => {
            if (child.isMesh) {
                child.material.emissive = new THREE.Color(0xffffff);
                child.material.emissiveIntensity = 2;
            }
        });

        scene.add(model);

        // Log animations
        console.log('Available animations:', gltf.animations.length);

        // Setup animation mixer
        mixer = new THREE.AnimationMixer(model);
        if (gltf.animations.length > 0) {
            const action = mixer.clipAction(gltf.animations[0]);
            action.play();
        }
    },
    // Loading progress
    function (xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
    },
    // Error handler
    function (error) {
        console.error('Error loading model:', error);
    }
);
}
loadModel();

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            render();
        }

        function render() {
            const time = performance.now() * 0.001;
            water.material.uniforms['time'].value += 0.2 / 60.0; // Default speed
           
            if (mixer) {
        mixer.update(0.016); // Update animation with approximate delta time
    }
        
            scene.children.forEach(child => {
                if (child.material && child.material.uniforms && child.material.uniforms.time) {
                    child.material.uniforms.time.value = time;
                }
            });

            renderer.render(scene, camera);
        }

        document.addEventListener('DOMContentLoaded', () => {
            const firstSvg = document.querySelector('.svg-elem-1');
            const secondSvg = document.querySelector('.svg-elem-2');
            const dotSvg = document.querySelector('.hidden.circle');
            const loadingScreen = document.querySelector('#loadingScreen');

            if (firstSvg && secondSvg && dotSvg && loadingScreen) {
                firstSvg.classList.remove('hidden');
                firstSvg.classList.add('animate');

                setTimeout(() => {
                    dotSvg.classList.remove('hidden');
                    dotSvg.classList.add('fade-in');
                }, 1); // 4000

                firstSvg.addEventListener('animationend', function() {
                    setTimeout(() => {
                        secondSvg.classList.add('animate');
                        document.querySelector('.hidden.spacing').classList.remove('hidden');
                        setTimeout(() => {
                            loadingScreen.classList.add('fade-out');
                        }, 1);    //2500  
                    }, 1); //800
                });
            }
        });

I know that’s the worst kinda code you’ve ever seen but I am a beginner at three js, below is how it looked.

So I thought to move on to react three js which is much more user friendly and was trying to replicate the same scene that I did in plain three js, below is my code

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 });

export function CustomWater() {
  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(1000, 1000), []);
  const config = useMemo(
    () => ({
      textureWidth: 512, // Reduced resolution
      textureHeight: 512,
      waterNormals,
      sunDirection: new THREE.Vector3(0, 1, 0),
      sunColor: new THREE.Color(0x111111).convertSRGBToLinear(), // Darker sun
      waterColor: new THREE.Color(0x001e0f).convertSRGBToLinear(), // Deep blue
      distortionScale: 0.5, // Reduced distortion
      size: 2,
      alpha: 0.7, // More transparency
      fog: true
    }),
    [waterNormals]
  );

  useFrame((state, delta) => {
    if (ref.current?.material) {
      const material = ref.current.material;
      material.uniforms.time.value += delta * 0.5; // Slower movement
      material.uniforms.size.value = config.size;
      material.uniforms.distortionScale.value = config.distortionScale;
      material.transparent = true;
      material.opacity = config.alpha;
    }
  });

  return (
    <water
      ref={ref}
      args={[geom, config]}
      rotation-x={-Math.PI / 2}
      position={[0, -1, 0]}
      transparent
    />
  );
}``` for the customwater and then actual app.jsx 
```import { Canvas } from "@react-three/fiber"
import { OrbitControls, Sphere } from "@react-three/drei"
import { CustomStars } from "./components/CustomStars"
import {CustomWater} from './components/CustomWater';
import { Jellyfish } from "./components/Jellyfish"
import { Suspense } from "react"
import { Gradient, LayerMaterial } from "lamina";
import * as THREE from "three";

function App() {
  return (
    <div id="canvas-container" style={{height: "100vh", width: "100vw"}}>
      <Canvas camera={{ position: [3.36, 2.80, 21.02], rotation: [0.26, 0.16, -0.04] }} >
      
      /* Remove orbit controls to get back to the position you left*/
      <OrbitControls 
          makeDefault
          enableDamping
          dampingFactor={0.05}
          target={[0, 0, 0]}
        />
        
        <CustomStars />
        <Sphere scale={[900, 900, 900]} rotation-y={-90}>
          <LayerMaterial 
            lighting="physical" 
            transmission={1} 
            side={THREE.BackSide}
            envMapIntensity={0}
    roughness={1}
    metalness={0}
          >
            <Gradient colorA={"#07249b"} colorB={"#09104d"} axes="y" start={-0.3} end={0.5}/>
          </LayerMaterial>
        </Sphere>
        <Suspense fallback={null}>
          <Jellyfish scale={0.5} position={[0, 0, 0]} />
        </Suspense>
        <CustomWater />
      </Canvas>
    </div>
  )
}

export default App

And below is how it looked

I know that’s a lot of read, if you did all that Thank you so much! So I don’t get why there is such a difference between both (I know does not look like a big difference but the water in react is more white even after ignoring the stars’ reflection). The reflectiveness of water is just too much. I have tried changing water settings and color management settings but still no luck. Please help! I’ve been stuck here for the past two days and still can’t get it to work.

1 Like

Looks like it could be a color mapping issue, but it’s just a guess. Either it’s applied on everything, or perhaps the environment texture if you’re reflecting it.

Thanks for the reply, I will look into that. Do you mean tonemapping as color mapping just so I am not getting it wrong?

For anyone who are having problems with colors between both here: reactjs - Color differences between threejs + vanilla js and react-three-fiber + create-react-app - Stack Overflow

This should fix the color issues. Good Luck!

1 Like