1. Encapsulate the water ripple effect yourself
src/main/main01.js
import * as THREE from "three"; import {<!-- --> OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import gsap from "gsap"; import * as dat from "dat.gui"; import vertexShader from "../shaders/water/vertex.glsl"; import fragmentShader from "../shaders/water/fragment.glsl"; // Goal: Set the cloud smoke effect //Create gui object const gui = new dat.GUI(); // console.log(THREE); //Initialize scene const scene = new THREE.Scene(); //Create a perspective camera const camera = new THREE.PerspectiveCamera( 90, window.innerHeight / window.innerHeight, 0.1, 1000 ); //Set camera position // object3d has position, and the attribute is a 3-dimensional vector camera.position.set(0, 0, 2); //Update camera camera.aspect = window.innerWidth / window.innerHeight; //Update the camera's projection matrix camera.updateProjectionMatrix(); scene.add(camera); //Add auxiliary axis to help us view the 3D coordinate axis const axesHelper = new THREE.AxesHelper(5); scene.add(axesHelper); const params = {<!-- --> uWaresFrequency: 14, uScale: 0.03, uXzScale: 1.5, uNoiseFrequency: 10, uNoiseScale: 1.5, uLowColor: "#ff0000", uHighColor: "#ffff00", uXspeed: 1, uZspeed: 1, uNoiseSpeed: 1, uOpacity: 1, }; const shaderMaterial = new THREE.ShaderMaterial({<!-- --> vertexShader: vertexShader, fragmentShader: fragmentShader, side: THREE.DoubleSide, uniforms: {<!-- --> uWaresFrequency: {<!-- --> value: params.uWaresFrequency, }, uScale: {<!-- --> value: params.uScale, }, uNoiseFrequency: {<!-- --> value: params.uNoiseFrequency, }, uNoiseScale: {<!-- --> value: params.uNoiseScale, }, uXzScale: {<!-- --> value: params.uXzScale, }, uTime: {<!-- --> value: params.uTime, }, uLowColor: {<!-- --> value: new THREE.Color(params.uLowColor), }, uHighColor: {<!-- --> value: new THREE.Color(params.uHighColor), }, uXspeed: {<!-- --> value: params.uXspeed, }, uZspeed: {<!-- --> value: params.uZspeed, }, uNoiseSpeed: {<!-- --> value: params.uNoiseSpeed, }, uOpacity: {<!-- --> value: params.uOpacity, }, }, transparent: true, }); gui .add(params, "uWaresFrequency") .min(1) .max(100) .step(0.1) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uWaresFrequency.value = value; }); gui .add(params, "uScale") .min(0) .max(0.2) .step(0.001) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uScale.value = value; }); gui .add(params, "uNoiseFrequency") .min(1) .max(100) .step(0.1) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uNoiseFrequency.value = value; }); gui .add(params, "uNoiseScale") .min(0) .max(5) .step(0.001) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uNoiseScale.value = value; }); gui .add(params, "uXzScale") .min(0) .max(5) .step(0.1) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uXzScale.value = value; }); gui.addColor(params, "uLowColor").onFinishChange((value) => {<!-- --> shaderMaterial.uniforms.uLowColor.value = new THREE.Color(value); }); gui.addColor(params, "uHighColor").onFinishChange((value) => {<!-- --> shaderMaterial.uniforms.uHighColor.value = new THREE.Color(value); }); gui .add(params, "uXspeed") .min(0) .max(5) .step(0.001) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uXspeed.value = value; }); gui .add(params, "uZspeed") .min(0) .max(5) .step(0.001) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uZspeed.value = value; }); gui .add(params, "uNoiseSpeed") .min(0) .max(5) .step(0.001) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uNoiseSpeed.value = value; }); gui .add(params, "uOpacity") .min(0) .max(1) .step(0.01) .onChange((value) => {<!-- --> shaderMaterial.uniforms.uOpacity.value = value; }); const plane = new THREE.Mesh( new THREE.PlaneBufferGeometry(1, 1, 1024, 1024), shaderMaterial ); plane.rotation.x = -Math.PI / 2; scene.add(plane); //Initialize renderer const renderer = new THREE.WebGLRenderer({<!-- --> alpha: true }); //Set the rendering size renderer.setSize(window.innerWidth, window.innerHeight); // Monitor screen size changes and set the rendering size window.addEventListener("resize", () => {<!-- --> // console.log("resize"); //Update camera camera.aspect = window.innerWidth / window.innerHeight; //Update the camera's projection matrix camera.updateProjectionMatrix(); //Update renderer renderer.setSize(window.innerWidth, window.innerHeight); //Set the pixel ratio of the renderer renderer.setPixelRatio(window.devicePixelRatio); }); //Add renderer to body document.body.appendChild(renderer.domElement); //Initialize controller const controls = new OrbitControls(camera, renderer.domElement); //Set controller damping controls.enableDamping = true; const clock = new THREE.Clock(); function animate(t) {<!-- --> const elapsedTime = clock.getElapsedTime(); shaderMaterial.uniforms.uTime.value = elapsedTime; requestAnimationFrame(animate); // Use the renderer to render the camera to see the content of this scene rendered. renderer.render(scene, camera); } animate();
src/shaders/water/fragment.glsl
precision lowp float; uniform vec3 uHighColor; uniform vec3 uLowColor; varying float vElevation; uniform float uOpacity; void main(){<!-- --> float a = (vElevation + 1.0)/2.0; vec3 color = mix(uLowColor,uHighColor,a); gl_FragColor = vec4(color,uOpacity); }
src/shaders/water/vertex.glsl
precision lowp float; uniform float uWaresFrequency; uniform float uScale; uniform float uNoiseFrequency; uniform float uNoiseScale; uniform float uXzScale; uniform float uTime; uniform float uXspeed; uniform float uZspeed; uniform float uNoiseSpeed; //The calculated height is passed to the fragment shader varying float vElevation; // random function float random (vec2 st) {<!-- --> return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123); } // rotation function vec2 rotate(vec2 uv, float rotation, vec2 mid) {<!-- --> return vec2( cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x, cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y ); } // noise function float noise (in vec2 _st) {<!-- --> vec2 i = floor(_st); vec2 f = fract(_st); // Four corners in 2D of a tile float a = random(i); float b = random(i + vec2(1.0, 0.0)); float c = random(i + vec2(0.0, 1.0)); float d = random(i + vec2(1.0, 1.0)); vec2 u = f * f * (3.0 - 2.0 * f); return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y; } // Classic Perlin 2D Noise // by Stefan Gustavson // vec4 permute(vec4 x) {<!-- --> return mod(((x*34.0) + 1.0)*x, 289.0); } vec2 fade(vec2 t) {<!-- --> return t*t*t*(t*(t*6.0-15.0) + 10.0); } float cnoise(vec2 P) {<!-- --> vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation vec4 ix = Pi.xzxz; vec4 iy = Pi.yyww; vec4 fx = Pf.xzxz; vec4 fy = Pf.yyww; vec4 i = permute(permute(ix) + iy); vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024... vec4 gy = abs(gx) - 0.5; vec4 tx = floor(gx + 0.5); gx = gx - tx; vec2 g00 = vec2(gx.x,gy.x); vec2 g10 = vec2(gx.y,gy.y); vec2 g01 = vec2(gx.z,gy.z); vec2 g11 = vec2(gx.w,gy.w); vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)); g00 *= norm.x; g01 *= norm.y; g10 *= norm.z; g11 *= norm.w; float n00 = dot(g00, vec2(fx.x, fy.x)); float n10 = dot(g10, vec2(fx.y, fy.y)); float n01 = dot(g01, vec2(fx.z, fy.z)); float n11 = dot(g11, vec2(fx.w, fy.w)); vec2 fade_xy = fade(Pf.xy); vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); float n_xy = mix(n_x.x, n_x.y, fade_xy.y); return 2.3 * n_xy; } void main(){<!-- --> vec4 modelPosition = modelMatrix * vec4(position,1.0); float elevation = sin(modelPosition.x*uWaresFrequency + uTime*uXspeed)*sin(modelPosition.z*uWaresFrequency*uXzScale + uTime*uZspeed); elevation + = -abs(cnoise(vec2(modelPosition.xz*uNoiseFrequency + uTime*uNoiseSpeed))) *uNoiseScale; vElevation = elevation; elevation *= uScale; modelPosition.y + = elevation; gl_Position = projectionMatrix * viewMatrix *modelPosition; }
2. Use the official water method
src/main/main.js
import * as THREE from "three"; import {<!-- --> OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import gsap from "gsap"; import * as dat from "dat.gui"; import {<!-- --> RGBELoader } from "three/examples/jsm/loaders/RGBELoader"; import {<!-- --> GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; //Import water import {<!-- --> Water } from "three/examples/jsm/objects/Water2"; // Goal: get to know shaders //Create gui object const gui = new dat.GUI(); // console.log(THREE); //Initialize scene const scene = new THREE.Scene(); //Create a perspective camera const camera = new THREE.PerspectiveCamera( 90, window.innerHeight / window.innerHeight, 0.1, 1000 ); //Set camera position // object3d has position, and the attribute is a 3-dimensional vector camera.position.set(5, 5, 5); //Update camera camera.aspect = window.innerWidth / window.innerHeight; //Update the camera's projection matrix camera.updateProjectionMatrix(); scene.add(camera); //Add auxiliary axis to help us view the 3D coordinate axis const axesHelper = new THREE.AxesHelper(5); scene.add(axesHelper); // const water = new Water(new THREE.PlaneBufferGeometry(1, 1, 1024, 1024), {<!-- --> // color: "#ffffff", // scale: 1, // flowDirection: new THREE.Vector2(1, 1), // textureHeight: 1024, // textureWidth: 1024, // }); // water.rotation.x = -Math.PI / 2; // scene.add(water); //Load scene background const rgbeLoader = new RGBELoader(); rgbeLoader.loadAsync("./assets/050.hdr").then((texture) => {<!-- --> texture.mapping = THREE.EquirectangularReflectionMapping; scene.background = texture; scene.environment = texture; }); // load bathtub const gltfLoader = new GLTFLoader(); gltfLoader.load("./assets/model/yugang.glb", (gltf) => {<!-- --> console.log(gltf); const yugang = gltf.scene.children[0]; yugang.material.side = THREE.DoubleSide; const waterGeometry = gltf.scene.children[1].geometry; const water = new Water(waterGeometry, {<!-- --> color: "#ffffff", scale: 1, flowDirection: new THREE.Vector2(1, 1), textureHeight: 1024, textureWidth: 1024, }); scene.add(water); scene.add(yugang); }); const light = new THREE.AmbientLight(0xffffff); // soft white light light.intensity = 10; scene.add(light); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); scene.add(directionalLight); //Initialize renderer const renderer = new THREE.WebGLRenderer({<!-- --> alpha: true, antialias: true }); renderer.outputEncoding = THREE.sRGBEncoding; renderer.toneMapping = THREE.ACESFilmicToneMapping; //Set the rendering size renderer.setSize(window.innerWidth, window.innerHeight); // Monitor screen size changes and set the rendering size window.addEventListener("resize", () => {<!-- --> // console.log("resize"); //Update camera camera.aspect = window.innerWidth / window.innerHeight; //Update the camera's projection matrix camera.updateProjectionMatrix(); //Update renderer renderer.setSize(window.innerWidth, window.innerHeight); //Set the pixel ratio of the renderer renderer.setPixelRatio(window.devicePixelRatio); }); //Add renderer to body document.body.appendChild(renderer.domElement); //Initialize controller const controls = new OrbitControls(camera, renderer.domElement); //Set controller damping controls.enableDamping = true; const clock = new THREE.Clock(); function animate(t) {<!-- --> const elapsedTime = clock.getElapsedTime(); requestAnimationFrame(animate); // Use the renderer to render the camera to see the content of this scene rendered. renderer.render(scene, camera); } animate();