From 55e808e7f602d40edafda5ccd29755951bb18d90 Mon Sep 17 00:00:00 2001 From: bandinopla <71508858+bandinopla@users.noreply.github.com> Date: Mon, 30 Jun 2025 22:30:57 +0000 Subject: [PATCH 1/4] Add FluidSim class for fluid simulation --- examples/files.json | 1 + examples/jsm/objects/FluidSimulator.js | 442 ++++++++++++++++++ .../jsm/shaders/FluidSimulationShaders.js | 409 ++++++++++++++++ .../screenshots/webgl_materials_fluidsim.jpg | Bin 0 -> 10478 bytes examples/webgl_materials_fluidsim.html | 156 +++++++ 5 files changed, 1008 insertions(+) create mode 100644 examples/jsm/objects/FluidSimulator.js create mode 100644 examples/jsm/shaders/FluidSimulationShaders.js create mode 100644 examples/screenshots/webgl_materials_fluidsim.jpg create mode 100644 examples/webgl_materials_fluidsim.html diff --git a/examples/files.json b/examples/files.json index 3a51bc11df9da8..64fe864b0ec195 100644 --- a/examples/files.json +++ b/examples/files.json @@ -141,6 +141,7 @@ "webgl_materials_envmaps_exr", "webgl_materials_envmaps_groundprojected", "webgl_materials_envmaps_hdr", + "webgl_materials_fluidsim", "webgl_materials_matcap", "webgl_materials_normalmap", "webgl_materials_normalmap_object_space", diff --git a/examples/jsm/objects/FluidSimulator.js b/examples/jsm/objects/FluidSimulator.js new file mode 100644 index 00000000000000..2ccc2f87714368 --- /dev/null +++ b/examples/jsm/objects/FluidSimulator.js @@ -0,0 +1,442 @@ +import { DataTexture, FloatType, Mesh, Object3D, PlaneGeometry, Raycaster, RGBAFormat, ShaderMaterial, Vector2, Vector3, WebGLRenderer, WebGLRenderTarget } from 'three'; +import { FullScreenQuad } from "../Addons.js"; +import { AdvectVelocityShader, ClearShader, CurlShader, DivergenceShader, GradientSubtractShader, PressureShader, SplatShader, VorticityShader } from '../shaders/FluidSimulationShaders.js'; + +function mix(...configs) { + return configs.reduce((acc, curr) => ({ + ...acc, + ...curr, + uniforms: { ...acc.uniforms, ...curr.uniforms }, + }), {}); +} + +export class FluidSimulator extends Mesh { + + get splatForce() { + return this.splat.uniforms.splatForce.value; + } + set splatForce(v) { + this.splat.uniforms.splatForce.value = v; + } + + get splatThickness() { return this.splat.uniforms.thickness.value } + set splatThickness(v) { this.splat.uniforms.thickness.value = v } + get vorticityInfluence() { return this.curl.uniforms.vorticityInfluence.value } + set vorticityInfluence(v) { this.curl.uniforms.vorticityInfluence.value = v } + + get swirlIntensity() { return this.vorticity.uniforms.curl.value } + set swirlIntensity(v) { this.vorticity.uniforms.curl.value = v } + + get pressure() { return this.clearShader.uniforms.value.value } + set pressure(v) { this.clearShader.uniforms.value.value = v } + + + /** + * The texture representin the liquid. To be used as displacementMap + */ + get elevationTexture() { + return this.dyeRT.texture; + } + + /** + * + * @param {WebGLRenderer} renderer + * @param {Number} width + * @param {Number} height + * @param {Number} resolution + * @param {Number} maxTrackedObjects + */ + constructor(renderer, width, height, resolution = 100, maxTrackedObjects = 1) { + + const aspect = height / width; + const planeGeo = new PlaneGeometry(1, aspect, resolution, Math.round(resolution * aspect)); + planeGeo.rotateX(-Math.PI / 2); + + super(planeGeo); + + this.velocityDissipation = 0.283; + this.densityDissipation = 0.138; + this.pressureIterations = 39; + + /** + * @private + */ + this.t = 0; + + /** + * @private + */ + this.tmp = new Vector3(); + + /** + * @private + */ + this.tmp2 = new Vector3(); + + /** + * @private + */ + this.currentRT = new WebGLRenderTarget(width, height, { type: FloatType }); + + /** + * @private + */ + this.nextRT = new WebGLRenderTarget(width, height, { type: FloatType }); + + /** + * @private + */ + this.dyeRT = new WebGLRenderTarget(width, height, { type: FloatType }); + + /** + * @private + */ + this.nextDyeRT = new WebGLRenderTarget(width, height, { type: FloatType }); + + // 4 components per object: current.x, current.y, prev.x, prev.y + /** + * @private + */ + this.objectDataArray = new Float32Array(maxTrackedObjects * 4); + + // 3. Create the DataTexture + /** + * @private + */ + this.objectDataTexture = new DataTexture( + this.objectDataArray, + maxTrackedObjects, // width + 1, // height + RGBAFormat, + FloatType + ); + + /** + * @type { {target?:Object3D, index:number}[] } + * @private + */ + this.tracking = new Array(maxTrackedObjects).fill(0).map((_, index) => ({ target: undefined, index })); + + /** + * @private + */ + this.quad = new FullScreenQuad(); + + /** + * @private + */ + this.raycaster = new Raycaster(); + + /** + * @private + */ + this.renderer = renderer; + + const texel = { + uniforms: { + texelSize: { + value: new Vector2(1 / width, 1 / height) + } + } + }; + + const gl = renderer.getContext(); + + /** + * @private + */ + this.supportLinearFiltering = !!gl.getExtension('OES_texture_half_float_linear'); + + // ----- shaders used to simulate the liquid ----- + + /** + * @private + */ + this.splat = new ShaderMaterial(mix(SplatShader, texel)); //new SplatShader( texel, objectCount, aspect ); + + + /** + * @private + */ + this.curl = new ShaderMaterial(mix(CurlShader, texel)); + + /** + * @private + */ + this.vorticity = new ShaderMaterial(mix(VorticityShader, texel)); + + /** + * @private + */ + this.divergenceShader = new ShaderMaterial(mix(DivergenceShader, texel)); + + /** + * @private + */ + this.clearShader = new ShaderMaterial(mix(ClearShader, texel)); + + /** + * @private + */ + this.pressureShader = new ShaderMaterial(mix(PressureShader, texel)); + + /** + * @private + */ + this.gradientShader = new ShaderMaterial(mix(GradientSubtractShader, texel )); + + /** + * @private + */ + this.advectionShader = new ShaderMaterial(mix(AdvectVelocityShader, texel, { uniforms: { dyeTexelSize: texel.uniforms.texelSize } }, { defines: { MANUAL_FILTERING: this.supportLinearFiltering } } )); + } + + /** + * + * @param {Object3D} obj + */ + track( object ) + { + const freeSlot = this.tracking.find( slot=>!slot.target ); + if( !freeSlot ) + { + throw new Error(`No room for tracking, all slots taken!`); + } + + // hacer un raycast desde la posision del objeto hacia abajo + // averiguar el UV donde nos pega + // setear ese valor como nuestra posision + + freeSlot.target = object; + } + + /** + * @param {Object3D} object + */ + untrack( object ) + { + this.tracking.forEach( t=> { + + if( t.target==object ) + { + t.target = undefined; + } + + }); + } + + /** + * Update the positions... we use the UVs as the positions. We cast a ray from the objects to the surface simulating the liquid + * and calculate the UV that is below the object. + * @private + * @param {Object3D} mesh + */ + updatePositions( mesh ) { + // update objects positions.... + this.tracking.forEach( obj => { + + const i = obj.index; + + if( !obj.target ) { + + this.objectDataArray[i * 4 + 0] = 0; + this.objectDataArray[i * 4 + 1] = 0; + this.objectDataArray[i * 4 + 2] = 0; + this.objectDataArray[i * 4 + 3] = 0; + return; + }; + + + this.tmp.set(0,1,0); //<--- assuming the origin ob the objects is at the bottom of the models. + const wpos = obj.target.localToWorld( this.tmp ); + + this.tmp2.copy( wpos ); + + const rpos = mesh.worldToLocal( this.tmp2 ); + rpos.y = 0; // this will put the position at the surface of the mesh + + mesh.localToWorld( rpos ); // this way we point at the surface of the mesh. + + + this.raycaster.set( wpos, rpos.sub(wpos).normalize() ); + + const hit = this.raycaster.intersectObject( mesh, true); + + if( hit.length ) + { + const uv = hit[0].uv; // <--- UV under the object + + if( uv ) + { + // old positions... + this.objectDataArray[i * 4 + 2] = this.objectDataArray[i * 4 + 0]; + this.objectDataArray[i * 4 + 3] = this.objectDataArray[i * 4 + 1]; + + // new positions... + this.objectDataArray[i * 4 + 0] = uv.x; + this.objectDataArray[i * 4 + 1] = uv.y; + } + + } + + }); + + this.objectDataTexture.needsUpdate = true; + } + + /** + * Renders the material into the next render texture and then swaps them so the new currentRT is the one that was generated by the material. + * @private + * @param {ShaderMaterial} material + */ + blit( material ) + { + this.renderer.setRenderTarget( this.nextRT ); + this.quad.material = material; + this.quad.render(this.renderer); + + //swap + [this.currentRT, this.nextRT] = [this.nextRT, this.currentRT]; + } + + /** + * @private + * @param {ShaderMaterial} material + */ + blitDye( material ) { + this.renderer.setRenderTarget( this.nextDyeRT ); + this.quad.material = material; + this.quad.render(this.renderer); + + //swap + [this.dyeRT, this.nextDyeRT] = [this.nextDyeRT, this.dyeRT]; + } + + /** + * @private + * @param {number} delta + */ + update( delta ) + { + this.t += delta; + + this.updatePositions( this ); + + // 1. add new velocities based on objects movement + this.splat.uniforms.objectData.value = this.objectDataTexture; + this.splat.uniforms.uTarget.value = this.currentRT.texture; + this.splat.uniforms.splatVelocity.value = true; + + this.blit( this.splat ); + + // add colors + this.splat.uniforms.objectData.value = this.objectDataTexture; + this.splat.uniforms.uTarget.value = this.dyeRT.texture; + this.splat.uniforms.splatVelocity.value = false; + + this.blitDye( this.splat ); + + // 2. vorticity : will be put into the alpha channel... + this.curl.uniforms.uVelocity.value = this.currentRT.texture; + this.blit( this.curl ); + + // 3. apply vorticity forces + this.vorticity.uniforms.uVelocityAndCurl.value = this.currentRT.texture; + this.vorticity.uniforms.dt.value = delta; + this.blit( this.vorticity ); + + // 4. divergence + this.divergenceShader.uniforms.uVelocity.value = this.currentRT.texture; + this.blit( this.divergenceShader ); + + // 5. clear pressure + this.clearShader.uniforms.uTexture.value = this.currentRT.texture; + this.blit( this.clearShader ); + + // 6. calculates and updates pressure + + for (let i = 0; i < this.pressureIterations; i++) { + this.pressureShader.uniforms.uPressureWithDivergence.value = this.currentRT.texture; + this.blit( this.pressureShader ); + } + + // 7. Gradient + this.gradientShader.uniforms.uPressureWithVelocity.value = this.currentRT.texture; + this.blit( this.gradientShader ); + + // 8. Advect velocity + this.advectionShader.uniforms.dt.value = delta; + + this.advectionShader.uniforms.uVelocity.value = this.currentRT.texture; + this.advectionShader.uniforms.uSource.value = this.currentRT.texture; + this.advectionShader.uniforms.sourceIsVelocity.value = true; + this.advectionShader.uniforms.dissipation.value = this.velocityDissipation; //VELOCITY_DISSIPATION + this.blit( this.advectionShader ); + + // 8. Advect dye / color + this.advectionShader.uniforms.uVelocity.value = this.currentRT.texture; + this.advectionShader.uniforms.uSource.value = this.dyeRT.texture; + this.advectionShader.uniforms.sourceIsVelocity.value = false; + this.advectionShader.uniforms.dissipation.value = this.densityDissipation; //DENSITY_DISSIPATION + this.blitDye( this.advectionShader ); + + + + this.renderer.setRenderTarget(null); + //this.map = this.dyeRT.texture; + //this.displacementMap = this.dyeRT.texture; + } + + fixMaterial( material ) + { + material.onBeforeCompile = shader => { + // Pass UV and world position to fragment shader + shader.vertexShader = shader.vertexShader + .replace( + '#include ', + `#include + varying vec2 vUv; + varying vec3 vWorldPos;` + ) + .replace( + '#include ', + `#include + vUv = uv;` + ) + .replace( + '#include ', + `#include + vWorldPos = position; // (modelMatrix * vec4(position, 1.0)).xyz;` + ); + + // Displace in fragment and recompute normals from that + shader.fragmentShader = shader.fragmentShader + .replace( + '#include ', + `#include + uniform sampler2D displacementMap; + uniform float displacementScale; + uniform mat3 normalMatrix; + varying vec2 vUv; + varying vec3 vWorldPos;` + ) + .replace( + '#include ', + ` + float d = texture2D(displacementMap, vUv).r- 0.5; + vec3 displacedWorld = vWorldPos + vec3(0.0, d * displacementScale, 0.0); + + vec3 dx = dFdx(displacedWorld); + vec3 dy = dFdy(displacedWorld); + vec3 displacedNormal = normalize(cross(dx, dy)); + + vec3 normalView = normalize(normalMatrix * displacedNormal); + vec3 normal = normalView; + vec3 nonPerturbedNormal = normalView; + ` + ); + } + } +} + + \ No newline at end of file diff --git a/examples/jsm/shaders/FluidSimulationShaders.js b/examples/jsm/shaders/FluidSimulationShaders.js new file mode 100644 index 00000000000000..10a557be17c491 --- /dev/null +++ b/examples/jsm/shaders/FluidSimulationShaders.js @@ -0,0 +1,409 @@ +import {Color} from "three"; + +// Based on (c) 2017 Pavel Dobryakov : WebGL shader code (https://github.com/PavelDoGreat/WebGL-Fluid-Simulation/tree/master) + +/** + * R - Pressure + * G - X dir + * B - Y dir + * A - wildcard, used to pass values from shader to shader. Not persisted. + */ + +const vertexShader = ` + varying vec2 vUv; + varying vec2 vL; + varying vec2 vR; + varying vec2 vT; + varying vec2 vB; + uniform vec2 texelSize; + + void main() { + vUv = uv; + + vL = uv - vec2(texelSize.x, 0.0); + vR = uv + vec2(texelSize.x, 0.0); + vT = uv + vec2(0.0, texelSize.y); + vB = uv - vec2(0.0, texelSize.y); + + gl_Position = vec4(position, 1.0); + } + `; + +/** + * Introduces either velocity or color into target. Depending on `splatVelocity` flag. + */ +export const SplatShader = { + uniforms: { + uTarget: { value: null }, + splatVelocity: { value:false }, + color: { value: new Color(0xffffff) }, + texelSize: { value: null }, + objectData: { value: null }, // Contains current and previous object positions + count: { value: 1 }, + thickness: { value: 0.035223 }, // in UV units + aspectRatio: { value:1 } // in UV units + , splatForce: { value: -196 } + }, + + vertexShader, + fragmentShader:` + precision mediump float; + precision mediump sampler2D; + + varying highp vec2 vUv; + uniform sampler2D uTarget; + uniform sampler2D objectData; + uniform int count; + uniform float thickness; //TODO: this shold be individual per object to allow diferent types of bodies affecting the liquid + uniform float aspectRatio; + uniform highp vec2 texelSize; + uniform bool splatVelocity; + uniform vec3 color; + uniform float splatForce; + + void main () { + + vec4 pixel = texture2D(uTarget, vUv); + + // Add External Forces (from objects) + // IMPROVEMENT: This loop is much more efficient as it reads from a texture. + for (int i = 0; i < count; i++) { + // Read object data from the texture. + // texelFetch is used for direct, un-interpolated pixel reads. + vec4 data = texelFetch(objectData, ivec2(i, 0), 0); + vec2 curr = data.xy; // Current position in .xy + vec2 prev = data.zw; // Previous position in .zw + + vec2 diff = curr - prev; + if (length(diff) == 0.0) continue; // Skip if the object hasn't moved + + vec2 toFrag = vUv - prev; + float t = clamp(dot(toFrag, diff) / dot(diff, diff), 0.0, 1.0); + vec2 proj = prev + t * diff; + + vec2 aspect = vec2(aspectRatio, 1.0); + + // Calculate distance in a way that respects the screen's aspect ratio + float d = distance(vUv * aspect, proj * aspect); + + if (d < thickness) { + // IMPROVEMENT: Correct influence logic. + // Influence is strongest when distance 'd' is 0. + float influence = smoothstep(thickness, 0.0, d); + + if( splatVelocity ) + { + + vec2 vel = normalize( ( diff )/texelSize ) * -splatForce; + + + //vel = mix( pixel.gb, vel, influence ); + + pixel.g = vel.x; + pixel.b = vel.y; + } + else + { + pixel = mix( pixel, vec4( color, 1.0 ), influence ); + } + + } + } + + gl_FragColor = pixel; + } + ` +}; + +/** + * sets vorticity inthe alpha channel of uVelocity image + */ +export const CurlShader = { + uniforms: { + uVelocity: { value: null }, + texelSize: { value: null }, + vorticityInfluence: { value:1 } + }, + vertexShader, + fragmentShader:` + precision mediump float; + precision mediump sampler2D; + + varying highp vec2 vUv; + varying highp vec2 vL; + varying highp vec2 vR; + varying highp vec2 vT; + varying highp vec2 vB; + uniform sampler2D uVelocity; + uniform float vorticityInfluence; + + void main () { + float L = texture2D(uVelocity, vL).b; + float R = texture2D(uVelocity, vR).b; + float T = texture2D(uVelocity, vT).g; + float B = texture2D(uVelocity, vB).g; + float vorticity = R - L - T + B; + + vec4 pixel = texture2D(uVelocity, vUv); + + pixel.a = vorticityInfluence * vorticity; // set in the 4th component... + + gl_FragColor = pixel; + } + ` + }; + +/** + * updates the velocity image + */ +export const VorticityShader = { + uniforms: { + uVelocityAndCurl: { value: null }, + texelSize: { value: null }, + curl: { value: 1 }, + dt: { value: 0 }, + }, + vertexShader, + fragmentShader:` + precision highp float; + precision highp sampler2D; + + varying vec2 vUv; + varying vec2 vL; + varying vec2 vR; + varying vec2 vT; + varying vec2 vB; + uniform sampler2D uVelocityAndCurl; + uniform float curl; + uniform float dt; + + void main () { + float L = texture2D(uVelocityAndCurl, vL).a; + float R = texture2D(uVelocityAndCurl, vR).a; + float T = texture2D(uVelocityAndCurl, vT).a; + float B = texture2D(uVelocityAndCurl, vB).a; + float C = texture2D(uVelocityAndCurl, vUv).a; + + vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L)); + force /= length(force) + 0.0001; + force *= curl * C; + force.y *= -1.0; + + vec4 pixel = texture2D(uVelocityAndCurl, vUv); + + vec2 velocity = pixel.gb; + velocity += force * dt; + velocity = min(max(velocity, -1000.0), 1000.0); + + gl_FragColor = vec4( pixel.r, velocity, 0.0 ); + } + ` + }; + +/** + * Adds divergence in the alpha channel of the velocity image + */ +export const DivergenceShader = { + uniforms: { + uVelocity: { value: null }, + texelSize: { value: null }, + }, + vertexShader, + fragmentShader:` + precision mediump float; + precision mediump sampler2D; + + varying highp vec2 vUv; + varying highp vec2 vL; + varying highp vec2 vR; + varying highp vec2 vT; + varying highp vec2 vB; + uniform sampler2D uVelocity; + + void main () { + float L = texture2D(uVelocity, vL).g; + float R = texture2D(uVelocity, vR).g; + float T = texture2D(uVelocity, vT).b; + float B = texture2D(uVelocity, vB).b; + + vec4 pixel = texture2D(uVelocity, vUv); + + vec2 C = pixel.gb; + if (vL.x < 0.0) { L = -C.x; } + if (vR.x > 1.0) { R = -C.x; } + if (vT.y > 1.0) { T = -C.y; } + if (vB.y < 0.0) { B = -C.y; } + + float div = 0.5 * (R - L + T - B); + + gl_FragColor = vec4( pixel.r, C, div ); + } + ` + }; + +/** + * Multiplies the pressure by `value` uniform + */ +export const ClearShader = { + uniforms: { + uTexture: { value: null }, + value: { value: 0.317 }, //PRESSURE + texelSize: { value: null }, + }, + vertexShader, + fragmentShader:` + precision mediump float; + precision mediump sampler2D; + + varying highp vec2 vUv; + uniform sampler2D uTexture; + uniform float value; + + void main () { + vec4 pixel = texture2D(uTexture, vUv); + + pixel.r *= value; + + gl_FragColor = pixel ; + } + ` + }; + +/** + * updates the pressure of the image + */ +export const PressureShader = { + uniforms: { + uPressureWithDivergence: { value: null }, + texelSize: { value: null }, + }, + vertexShader, + fragmentShader:` + precision mediump float; + precision mediump sampler2D; + + varying highp vec2 vUv; + varying highp vec2 vL; + varying highp vec2 vR; + varying highp vec2 vT; + varying highp vec2 vB; + uniform sampler2D uPressureWithDivergence; + + void main () { + float L = texture2D(uPressureWithDivergence, vL).x; + float R = texture2D(uPressureWithDivergence, vR).x; + float T = texture2D(uPressureWithDivergence, vT).x; + float B = texture2D(uPressureWithDivergence, vB).x; + float C = texture2D(uPressureWithDivergence, vUv).x; + + vec4 pixel = texture2D(uPressureWithDivergence, vUv); + float divergence = pixel.a; + float pressure = (L + R + B + T - divergence) * 0.25; + + pixel.x = pressure; + + gl_FragColor = pixel; + } + ` + }; + + +export const GradientSubtractShader = { + uniforms: { + uPressureWithVelocity: { value: null }, + texelSize: { value: null }, + }, + vertexShader, + fragmentShader:` + precision mediump float; + precision mediump sampler2D; + + varying highp vec2 vUv; + varying highp vec2 vL; + varying highp vec2 vR; + varying highp vec2 vT; + varying highp vec2 vB; + uniform sampler2D uPressureWithVelocity; + + void main () { + float L = texture2D(uPressureWithVelocity, vL).x; + float R = texture2D(uPressureWithVelocity, vR).x; + float T = texture2D(uPressureWithVelocity, vT).x; + float B = texture2D(uPressureWithVelocity, vB).x; + + vec4 pixel = texture2D(uPressureWithVelocity, vUv); + + vec2 velocity = pixel.gb; + velocity.xy -= vec2(R - L, T - B); + + gl_FragColor = vec4( pixel.r, velocity, 0.0 ); + } + ` + }; + + +export const AdvectVelocityShader = { + uniforms: { + uVelocity: { value: null }, + uSource: { value: null }, + sourceIsVelocity: { value: null }, + texelSize: { value: null }, + dt: { value: 0 }, + dyeTexelSize: { value: null }, + dissipation: { value: 0.2 }, + }, + defines: { + MANUAL_FILTERING: false + }, + vertexShader, + fragmentShader:` + precision highp float; + precision highp sampler2D; + + varying vec2 vUv; + uniform sampler2D uVelocity; + uniform sampler2D uSource; + uniform vec2 texelSize; + uniform vec2 dyeTexelSize; + uniform float dt; + uniform float dissipation; + uniform bool sourceIsVelocity; + + vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) { + vec2 st = uv / tsize - 0.5; + + vec2 iuv = floor(st); + vec2 fuv = fract(st); + + vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize); + vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize); + vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize); + vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize); + + return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y); + } + + void main () { + + #ifdef MANUAL_FILTERING + vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).gb * texelSize; + vec4 result = bilerp(uSource, coord, dyeTexelSize); + #else + vec2 coord = vUv - dt * texture2D(uVelocity, vUv).gb * texelSize; + vec4 result = texture2D(uSource, coord); + #endif + float decay = 1.0 + dissipation * dt; + result /= decay; + + if( sourceIsVelocity ) + { + vec4 data = texture2D(uVelocity, vUv); + gl_FragColor = vec4( data.r, result.g, result.b, data.a); + } + else + { + gl_FragColor = result; + } + } + ` + }; \ No newline at end of file diff --git a/examples/screenshots/webgl_materials_fluidsim.jpg b/examples/screenshots/webgl_materials_fluidsim.jpg new file mode 100644 index 0000000000000000000000000000000000000000..72a33a87f68f8fe07c20ba352c47e04b2149f1dc GIT binary patch literal 10478 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 literal 0 HcmV?d00001 diff --git a/examples/webgl_materials_fluidsim.html b/examples/webgl_materials_fluidsim.html new file mode 100644 index 00000000000000..be5192b864b976 --- /dev/null +++ b/examples/webgl_materials_fluidsim.html @@ -0,0 +1,156 @@ + + + + three.js webgl - materials + + + + + + +
+
three.js - Fluid Simulation
+ + + + + + + From 004b768bb2b6224390d878007ea9aa6def297c80 Mon Sep 17 00:00:00 2001 From: bandinopla <71508858+bandinopla@users.noreply.github.com> Date: Mon, 30 Jun 2025 22:58:48 +0000 Subject: [PATCH 2/4] lint fixes --- examples/jsm/objects/FluidSimulator.js | 8 ++++++-- examples/webgl_materials_fluidsim.html | 10 +++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/jsm/objects/FluidSimulator.js b/examples/jsm/objects/FluidSimulator.js index 2ccc2f87714368..062c35cf60d072 100644 --- a/examples/jsm/objects/FluidSimulator.js +++ b/examples/jsm/objects/FluidSimulator.js @@ -1,7 +1,11 @@ -import { DataTexture, FloatType, Mesh, Object3D, PlaneGeometry, Raycaster, RGBAFormat, ShaderMaterial, Vector2, Vector3, WebGLRenderer, WebGLRenderTarget } from 'three'; +import { DataTexture, FloatType, Mesh, PlaneGeometry, Raycaster, RGBAFormat, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget } from 'three'; import { FullScreenQuad } from "../Addons.js"; import { AdvectVelocityShader, ClearShader, CurlShader, DivergenceShader, GradientSubtractShader, PressureShader, SplatShader, VorticityShader } from '../shaders/FluidSimulationShaders.js'; +/** @typedef {import('three').Object3D} Object3D */ +/** @typedef {import('three').WebGLRenderer} WebGLRenderer */ + + function mix(...configs) { return configs.reduce((acc, curr) => ({ ...acc, @@ -193,7 +197,7 @@ export class FluidSimulator extends Mesh { /** * - * @param {Object3D} obj + * @param {Object3D} object */ track( object ) { diff --git a/examples/webgl_materials_fluidsim.html b/examples/webgl_materials_fluidsim.html index be5192b864b976..273a18305fa948 100644 --- a/examples/webgl_materials_fluidsim.html +++ b/examples/webgl_materials_fluidsim.html @@ -34,7 +34,7 @@ let container, stats; - let camera, scene, renderer, effect; + let camera, scene, renderer; let time = 0; const clock = new THREE.Clock(); @@ -43,7 +43,7 @@ camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.01, 10 ); camera.position.set( 1, 1, 1 ); - camera.lookAt(0,0,0) + camera.lookAt(0,0,0); // @@ -72,16 +72,12 @@ stats = new Stats(); container.appendChild( stats.dom ); - const controls = new OrbitControls( camera, renderer.domElement ); + new OrbitControls( camera, renderer.domElement ); window.addEventListener( 'resize', onWindowResize ); //---------------------------------------- DEMO SCENE SETUP ------------------------------------------------ - const size = 1024 ; - const sizey = size/2; - const objectCount = 1; - const fluid = new FluidSimulator( renderer, 1024, 512 ); scene.add( fluid ); From d6dd916b9ee056a65f8cf4d2592aa936f32062e7 Mon Sep 17 00:00:00 2001 From: bandinopla <71508858+bandinopla@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:08:20 +0000 Subject: [PATCH 3/4] update screenshot (generated with make-screenshot) --- .../screenshots/webgl_materials_fluidsim.jpg | Bin 10478 -> 16850 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/screenshots/webgl_materials_fluidsim.jpg b/examples/screenshots/webgl_materials_fluidsim.jpg index 72a33a87f68f8fe07c20ba352c47e04b2149f1dc..dbbd7bf89e8db142c229bec3c675ad0cc8cfaae9 100644 GIT binary patch literal 16850 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 literal 10478 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 From 3b67e76102d1155a30ffce31179debc53fc35962 Mon Sep 17 00:00:00 2001 From: bandinopla Date: Tue, 1 Jul 2025 09:08:33 +0000 Subject: [PATCH 4/4] link fixes and improved example --- examples/jsm/objects/FluidSimulator.js | 670 ++++++++++-------- .../jsm/shaders/FluidSimulationShaders.js | 152 ++-- .../screenshots/webgl_materials_fluidsim.jpg | Bin 16850 -> 51081 bytes examples/webgl_materials_fluidsim.html | 96 +-- 4 files changed, 496 insertions(+), 422 deletions(-) diff --git a/examples/jsm/objects/FluidSimulator.js b/examples/jsm/objects/FluidSimulator.js index 062c35cf60d072..909e70e207a9ff 100644 --- a/examples/jsm/objects/FluidSimulator.js +++ b/examples/jsm/objects/FluidSimulator.js @@ -1,432 +1,490 @@ import { DataTexture, FloatType, Mesh, PlaneGeometry, Raycaster, RGBAFormat, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget } from 'three'; -import { FullScreenQuad } from "../Addons.js"; +import { FullScreenQuad } from '../Addons.js'; import { AdvectVelocityShader, ClearShader, CurlShader, DivergenceShader, GradientSubtractShader, PressureShader, SplatShader, VorticityShader } from '../shaders/FluidSimulationShaders.js'; /** @typedef {import('three').Object3D} Object3D */ -/** @typedef {import('three').WebGLRenderer} WebGLRenderer */ +/** @typedef {import('three').WebGLRenderer} WebGLRenderer */ -function mix(...configs) { - return configs.reduce((acc, curr) => ({ - ...acc, - ...curr, - uniforms: { ...acc.uniforms, ...curr.uniforms }, - }), {}); +function mix( ...configs ) { + + return configs.reduce( ( acc, curr ) => ( { + ...acc, + ...curr, + uniforms: { ...acc.uniforms, ...curr.uniforms }, + } ), {} ); + } export class FluidSimulator extends Mesh { - get splatForce() { - return this.splat.uniforms.splatForce.value; - } - set splatForce(v) { - this.splat.uniforms.splatForce.value = v; - } + get splatForce() { + + return this.splat.uniforms.splatForce.value; + + } + set splatForce( v ) { + + this.splat.uniforms.splatForce.value = v; + + } + + get splatThickness() { + + return this.splat.uniforms.thickness.value; + + } + set splatThickness( v ) { + + this.splat.uniforms.thickness.value = v; + + } + get vorticityInfluence() { + + return this.curl.uniforms.vorticityInfluence.value; - get splatThickness() { return this.splat.uniforms.thickness.value } - set splatThickness(v) { this.splat.uniforms.thickness.value = v } - get vorticityInfluence() { return this.curl.uniforms.vorticityInfluence.value } - set vorticityInfluence(v) { this.curl.uniforms.vorticityInfluence.value = v } + } + set vorticityInfluence( v ) { - get swirlIntensity() { return this.vorticity.uniforms.curl.value } - set swirlIntensity(v) { this.vorticity.uniforms.curl.value = v } + this.curl.uniforms.vorticityInfluence.value = v; - get pressure() { return this.clearShader.uniforms.value.value } - set pressure(v) { this.clearShader.uniforms.value.value = v } + } + get swirlIntensity() { - /** + return this.vorticity.uniforms.curl.value; + + } + set swirlIntensity( v ) { + + this.vorticity.uniforms.curl.value = v; + + } + + get pressure() { + + return this.clearShader.uniforms.value.value; + + } + set pressure( v ) { + + this.clearShader.uniforms.value.value = v; + + } + + + /** * The texture representin the liquid. To be used as displacementMap */ - get elevationTexture() { - return this.dyeRT.texture; - } - - /** - * - * @param {WebGLRenderer} renderer - * @param {Number} width - * @param {Number} height - * @param {Number} resolution - * @param {Number} maxTrackedObjects + get elevationTexture() { + + return this.dyeRT.texture; + + } + + /** + * + * @param {WebGLRenderer} renderer + * @param {number} width + * @param {number} height + * @param {number} resolution + * @param {number} maxTrackedObjects */ - constructor(renderer, width, height, resolution = 100, maxTrackedObjects = 1) { + constructor( renderer, width, height, resolution = 100, maxTrackedObjects = 1 ) { + + const aspect = height / width; + const planeGeo = new PlaneGeometry( 1, aspect, resolution, Math.round( resolution * aspect ) ); + planeGeo.rotateX( - Math.PI / 2 ); - const aspect = height / width; - const planeGeo = new PlaneGeometry(1, aspect, resolution, Math.round(resolution * aspect)); - planeGeo.rotateX(-Math.PI / 2); + super( planeGeo ); - super(planeGeo); + this.velocityDissipation = 0.283; + this.densityDissipation = 0.138; + this.pressureIterations = 39; - this.velocityDissipation = 0.283; - this.densityDissipation = 0.138; - this.pressureIterations = 39; - - /** + /** * @private */ - this.t = 0; + this.t = 0; - /** + /** * @private */ - this.tmp = new Vector3(); + this.tmp = new Vector3(); - /** + /** * @private */ - this.tmp2 = new Vector3(); + this.tmp2 = new Vector3(); - /** + /** * @private */ - this.currentRT = new WebGLRenderTarget(width, height, { type: FloatType }); - - /** + this.currentRT = new WebGLRenderTarget( width, height, { type: FloatType } ); + + /** * @private */ - this.nextRT = new WebGLRenderTarget(width, height, { type: FloatType }); + this.nextRT = new WebGLRenderTarget( width, height, { type: FloatType } ); - /** + /** * @private */ - this.dyeRT = new WebGLRenderTarget(width, height, { type: FloatType }); - - /** + this.dyeRT = new WebGLRenderTarget( width, height, { type: FloatType } ); + + /** * @private */ - this.nextDyeRT = new WebGLRenderTarget(width, height, { type: FloatType }); + this.nextDyeRT = new WebGLRenderTarget( width, height, { type: FloatType } ); - // 4 components per object: current.x, current.y, prev.x, prev.y - /** + // 4 components per object: current.x, current.y, prev.x, prev.y + /** * @private */ - this.objectDataArray = new Float32Array(maxTrackedObjects * 4); + this.objectDataArray = new Float32Array( maxTrackedObjects * 4 ); - // 3. Create the DataTexture - /** + // 3. Create the DataTexture + /** * @private */ - this.objectDataTexture = new DataTexture( - this.objectDataArray, - maxTrackedObjects, // width - 1, // height - RGBAFormat, - FloatType - ); - - /** - * @type { {target?:Object3D, index:number}[] } + this.objectDataTexture = new DataTexture( + this.objectDataArray, + maxTrackedObjects, // width + 1, // height + RGBAFormat, + FloatType + ); + + /** + * @type { {target?:Object3D, index:number}[] } * @private */ - this.tracking = new Array(maxTrackedObjects).fill(0).map((_, index) => ({ target: undefined, index })); - - /** + this.tracking = new Array( maxTrackedObjects ).fill( 0 ).map( ( _, index ) => ( { target: undefined, index } ) ); + + /** * @private */ - this.quad = new FullScreenQuad(); - - /** + this.quad = new FullScreenQuad(); + + /** * @private */ - this.raycaster = new Raycaster(); - - /** + this.raycaster = new Raycaster(); + + /** * @private */ - this.renderer = renderer; + this.renderer = renderer; - const texel = { - uniforms: { - texelSize: { - value: new Vector2(1 / width, 1 / height) - } - } - }; + const texel = { + uniforms: { + texelSize: { + value: new Vector2( 1 / width, 1 / height ) + } + } + }; - const gl = renderer.getContext(); + const gl = renderer.getContext(); - /** + /** * @private */ - this.supportLinearFiltering = !!gl.getExtension('OES_texture_half_float_linear'); + this.supportLinearFiltering = !! gl.getExtension( 'OES_texture_half_float_linear' ); - // ----- shaders used to simulate the liquid ----- + // ----- shaders used to simulate the liquid ----- - /** + /** * @private */ - this.splat = new ShaderMaterial(mix(SplatShader, texel)); //new SplatShader( texel, objectCount, aspect ); - + this.splat = new ShaderMaterial( mix( SplatShader, texel ) ); //new SplatShader( texel, objectCount, aspect ); + - /** + /** * @private */ - this.curl = new ShaderMaterial(mix(CurlShader, texel)); + this.curl = new ShaderMaterial( mix( CurlShader, texel ) ); - /** + /** * @private */ - this.vorticity = new ShaderMaterial(mix(VorticityShader, texel)); + this.vorticity = new ShaderMaterial( mix( VorticityShader, texel ) ); - /** + /** * @private */ - this.divergenceShader = new ShaderMaterial(mix(DivergenceShader, texel)); + this.divergenceShader = new ShaderMaterial( mix( DivergenceShader, texel ) ); - /** + /** * @private */ - this.clearShader = new ShaderMaterial(mix(ClearShader, texel)); + this.clearShader = new ShaderMaterial( mix( ClearShader, texel ) ); - /** + /** * @private */ - this.pressureShader = new ShaderMaterial(mix(PressureShader, texel)); + this.pressureShader = new ShaderMaterial( mix( PressureShader, texel ) ); - /** + /** * @private */ - this.gradientShader = new ShaderMaterial(mix(GradientSubtractShader, texel )); + this.gradientShader = new ShaderMaterial( mix( GradientSubtractShader, texel ) ); - /** + /** * @private */ - this.advectionShader = new ShaderMaterial(mix(AdvectVelocityShader, texel, { uniforms: { dyeTexelSize: texel.uniforms.texelSize } }, { defines: { MANUAL_FILTERING: this.supportLinearFiltering } } )); - } + this.advectionShader = new ShaderMaterial( mix( AdvectVelocityShader, texel, { uniforms: { dyeTexelSize: texel.uniforms.texelSize } }, { defines: { MANUAL_FILTERING: this.supportLinearFiltering } } ) ); - /** - * - * @param {Object3D} object + } + + /** + * + * @param {Object3D} object */ - track( object ) - { - const freeSlot = this.tracking.find( slot=>!slot.target ); - if( !freeSlot ) - { - throw new Error(`No room for tracking, all slots taken!`); - } - - // hacer un raycast desde la posision del objeto hacia abajo - // averiguar el UV donde nos pega - // setear ese valor como nuestra posision - - freeSlot.target = object; - } - - /** - * @param {Object3D} object + track( object ) { + + const freeSlot = this.tracking.find( slot=>! slot.target ); + if ( ! freeSlot ) { + + throw new Error( 'No room for tracking, all slots taken!' ); + + } + + // hacer un raycast desde la posision del objeto hacia abajo + // averiguar el UV donde nos pega + // setear ese valor como nuestra posision + + freeSlot.target = object; + + } + + /** + * @param {Object3D} object */ - untrack( object ) - { - this.tracking.forEach( t=> { + untrack( object ) { - if( t.target==object ) - { - t.target = undefined; - } + this.tracking.forEach( t=> { - }); - } + if ( t.target == object ) { - /** + t.target = undefined; + + } + + } ); + + } + + /** * Update the positions... we use the UVs as the positions. We cast a ray from the objects to the surface simulating the liquid * and calculate the UV that is below the object. * @private - * @param {Object3D} mesh + * @param {Object3D} mesh */ - updatePositions( mesh ) { - // update objects positions.... - this.tracking.forEach( obj => { + updatePositions( mesh ) { + + // update objects positions.... + this.tracking.forEach( obj => { - const i = obj.index; + const i = obj.index; - if( !obj.target ) { + if ( ! obj.target ) { - this.objectDataArray[i * 4 + 0] = 0; - this.objectDataArray[i * 4 + 1] = 0; - this.objectDataArray[i * 4 + 2] = 0; - this.objectDataArray[i * 4 + 3] = 0; - return; - }; + this.objectDataArray[ i * 4 + 0 ] = 0; + this.objectDataArray[ i * 4 + 1 ] = 0; + this.objectDataArray[ i * 4 + 2 ] = 0; + this.objectDataArray[ i * 4 + 3 ] = 0; + return; - - this.tmp.set(0,1,0); //<--- assuming the origin ob the objects is at the bottom of the models. - const wpos = obj.target.localToWorld( this.tmp ); + } - this.tmp2.copy( wpos ); - const rpos = mesh.worldToLocal( this.tmp2 ); - rpos.y = 0; // this will put the position at the surface of the mesh - mesh.localToWorld( rpos ); // this way we point at the surface of the mesh. - - this.raycaster.set( wpos, rpos.sub(wpos).normalize() ); + this.tmp.set( 0, 1, 0 ); //<--- assuming the origin ob the objects is at the bottom of the models. + const wpos = obj.target.localToWorld( this.tmp ); - const hit = this.raycaster.intersectObject( mesh, true); + this.tmp2.copy( wpos ); - if( hit.length ) - { - const uv = hit[0].uv; // <--- UV under the object - - if( uv ) - { - // old positions... - this.objectDataArray[i * 4 + 2] = this.objectDataArray[i * 4 + 0]; - this.objectDataArray[i * 4 + 3] = this.objectDataArray[i * 4 + 1]; + const rpos = mesh.worldToLocal( this.tmp2 ); + rpos.y = 0; // this will put the position at the surface of the mesh - // new positions... - this.objectDataArray[i * 4 + 0] = uv.x; - this.objectDataArray[i * 4 + 1] = uv.y; - } - - } + mesh.localToWorld( rpos ); // this way we point at the surface of the mesh. - }); - this.objectDataTexture.needsUpdate = true; - } + this.raycaster.set( wpos, rpos.sub( wpos ).normalize() ); - /** + const hit = this.raycaster.intersectObject( mesh, true ); + + if ( hit.length ) { + + const uv = hit[ 0 ].uv; // <--- UV under the object + + if ( uv ) { + + // old positions... + this.objectDataArray[ i * 4 + 2 ] = this.objectDataArray[ i * 4 + 0 ]; + this.objectDataArray[ i * 4 + 3 ] = this.objectDataArray[ i * 4 + 1 ]; + + // new positions... + this.objectDataArray[ i * 4 + 0 ] = uv.x; + this.objectDataArray[ i * 4 + 1 ] = uv.y; + + } + + } + + } ); + + this.objectDataTexture.needsUpdate = true; + + } + + /** * Renders the material into the next render texture and then swaps them so the new currentRT is the one that was generated by the material. * @private - * @param {ShaderMaterial} material + * @param {ShaderMaterial} material */ - blit( material ) - { - this.renderer.setRenderTarget( this.nextRT ); - this.quad.material = material; - this.quad.render(this.renderer); + blit( material ) { - //swap - [this.currentRT, this.nextRT] = [this.nextRT, this.currentRT]; - } + this.renderer.setRenderTarget( this.nextRT ); + this.quad.material = material; + this.quad.render( this.renderer ); - /** + //swap + [ this.currentRT, this.nextRT ] = [ this.nextRT, this.currentRT ]; + + } + + /** * @private - * @param {ShaderMaterial} material + * @param {ShaderMaterial} material */ - blitDye( material ) { - this.renderer.setRenderTarget( this.nextDyeRT ); - this.quad.material = material; - this.quad.render(this.renderer); + blitDye( material ) { + + this.renderer.setRenderTarget( this.nextDyeRT ); + this.quad.material = material; + this.quad.render( this.renderer ); - //swap - [this.dyeRT, this.nextDyeRT] = [this.nextDyeRT, this.dyeRT]; - } + //swap + [ this.dyeRT, this.nextDyeRT ] = [ this.nextDyeRT, this.dyeRT ]; - /** + } + + /** * @private - * @param {number} delta + * @param {number} delta */ - update( delta ) - { - this.t += delta; - - this.updatePositions( this ); - - // 1. add new velocities based on objects movement - this.splat.uniforms.objectData.value = this.objectDataTexture; - this.splat.uniforms.uTarget.value = this.currentRT.texture; - this.splat.uniforms.splatVelocity.value = true; - - this.blit( this.splat ); - - // add colors - this.splat.uniforms.objectData.value = this.objectDataTexture; - this.splat.uniforms.uTarget.value = this.dyeRT.texture; - this.splat.uniforms.splatVelocity.value = false; - - this.blitDye( this.splat ); - - // 2. vorticity : will be put into the alpha channel... - this.curl.uniforms.uVelocity.value = this.currentRT.texture; - this.blit( this.curl ); - - // 3. apply vorticity forces - this.vorticity.uniforms.uVelocityAndCurl.value = this.currentRT.texture; - this.vorticity.uniforms.dt.value = delta; - this.blit( this.vorticity ); - - // 4. divergence - this.divergenceShader.uniforms.uVelocity.value = this.currentRT.texture; - this.blit( this.divergenceShader ); - - // 5. clear pressure - this.clearShader.uniforms.uTexture.value = this.currentRT.texture; - this.blit( this.clearShader ); - - // 6. calculates and updates pressure - - for (let i = 0; i < this.pressureIterations; i++) { - this.pressureShader.uniforms.uPressureWithDivergence.value = this.currentRT.texture; - this.blit( this.pressureShader ); - } - - // 7. Gradient - this.gradientShader.uniforms.uPressureWithVelocity.value = this.currentRT.texture; - this.blit( this.gradientShader ); - - // 8. Advect velocity - this.advectionShader.uniforms.dt.value = delta; - - this.advectionShader.uniforms.uVelocity.value = this.currentRT.texture; - this.advectionShader.uniforms.uSource.value = this.currentRT.texture; - this.advectionShader.uniforms.sourceIsVelocity.value = true; - this.advectionShader.uniforms.dissipation.value = this.velocityDissipation; //VELOCITY_DISSIPATION - this.blit( this.advectionShader ); - - // 8. Advect dye / color - this.advectionShader.uniforms.uVelocity.value = this.currentRT.texture; - this.advectionShader.uniforms.uSource.value = this.dyeRT.texture; - this.advectionShader.uniforms.sourceIsVelocity.value = false; - this.advectionShader.uniforms.dissipation.value = this.densityDissipation; //DENSITY_DISSIPATION - this.blitDye( this.advectionShader ); - - - - this.renderer.setRenderTarget(null); - //this.map = this.dyeRT.texture; - //this.displacementMap = this.dyeRT.texture; - } - - fixMaterial( material ) - { - material.onBeforeCompile = shader => { - // Pass UV and world position to fragment shader - shader.vertexShader = shader.vertexShader - .replace( - '#include ', - `#include + update( delta ) { + + this.t += delta; + + this.updatePositions( this ); + + // 1. add new velocities based on objects movement + this.splat.uniforms.objectData.value = this.objectDataTexture; + this.splat.uniforms.uTarget.value = this.currentRT.texture; + this.splat.uniforms.splatVelocity.value = true; + + this.blit( this.splat ); + + // add colors + this.splat.uniforms.objectData.value = this.objectDataTexture; + this.splat.uniforms.uTarget.value = this.dyeRT.texture; + this.splat.uniforms.splatVelocity.value = false; + + this.blitDye( this.splat ); + + // 2. vorticity : will be put into the alpha channel... + this.curl.uniforms.uVelocity.value = this.currentRT.texture; + this.blit( this.curl ); + + // 3. apply vorticity forces + this.vorticity.uniforms.uVelocityAndCurl.value = this.currentRT.texture; + this.vorticity.uniforms.dt.value = delta; + this.blit( this.vorticity ); + + // 4. divergence + this.divergenceShader.uniforms.uVelocity.value = this.currentRT.texture; + this.blit( this.divergenceShader ); + + // 5. clear pressure + this.clearShader.uniforms.uTexture.value = this.currentRT.texture; + this.blit( this.clearShader ); + + // 6. calculates and updates pressure + + for ( let i = 0; i < this.pressureIterations; i ++ ) { + + this.pressureShader.uniforms.uPressureWithDivergence.value = this.currentRT.texture; + this.blit( this.pressureShader ); + + } + + // 7. Gradient + this.gradientShader.uniforms.uPressureWithVelocity.value = this.currentRT.texture; + this.blit( this.gradientShader ); + + // 8. Advect velocity + this.advectionShader.uniforms.dt.value = delta; + + this.advectionShader.uniforms.uVelocity.value = this.currentRT.texture; + this.advectionShader.uniforms.uSource.value = this.currentRT.texture; + this.advectionShader.uniforms.sourceIsVelocity.value = true; + this.advectionShader.uniforms.dissipation.value = this.velocityDissipation; //VELOCITY_DISSIPATION + this.blit( this.advectionShader ); + + // 8. Advect dye / color + this.advectionShader.uniforms.uVelocity.value = this.currentRT.texture; + this.advectionShader.uniforms.uSource.value = this.dyeRT.texture; + this.advectionShader.uniforms.sourceIsVelocity.value = false; + this.advectionShader.uniforms.dissipation.value = this.densityDissipation; //DENSITY_DISSIPATION + this.blitDye( this.advectionShader ); + + + + this.renderer.setRenderTarget( null ); + //this.map = this.dyeRT.texture; + //this.displacementMap = this.dyeRT.texture; + + } + + fixMaterial( material ) { + + material.onBeforeCompile = shader => { + + // Pass UV and world position to fragment shader + shader.vertexShader = shader.vertexShader + .replace( + '#include ', + `#include varying vec2 vUv; varying vec3 vWorldPos;` - ) - .replace( - '#include ', - `#include + ) + .replace( + '#include ', + `#include vUv = uv;` - ) - .replace( - '#include ', - `#include + ) + .replace( + '#include ', + `#include vWorldPos = position; // (modelMatrix * vec4(position, 1.0)).xyz;` - ); + ); - // Displace in fragment and recompute normals from that - shader.fragmentShader = shader.fragmentShader - .replace( - '#include ', - `#include + // Displace in fragment and recompute normals from that + shader.fragmentShader = shader.fragmentShader + .replace( + '#include ', + `#include uniform sampler2D displacementMap; uniform float displacementScale; uniform mat3 normalMatrix; varying vec2 vUv; varying vec3 vWorldPos;` - ) - .replace( - '#include ', - ` + ) + .replace( + '#include ', + ` float d = texture2D(displacementMap, vUv).r- 0.5; vec3 displacedWorld = vWorldPos + vec3(0.0, d * displacementScale, 0.0); @@ -438,9 +496,11 @@ export class FluidSimulator extends Mesh { vec3 normal = normalView; vec3 nonPerturbedNormal = normalView; ` - ); - } - } + ); + + }; + + } + } - \ No newline at end of file diff --git a/examples/jsm/shaders/FluidSimulationShaders.js b/examples/jsm/shaders/FluidSimulationShaders.js index 10a557be17c491..7e54889ccc47af 100644 --- a/examples/jsm/shaders/FluidSimulationShaders.js +++ b/examples/jsm/shaders/FluidSimulationShaders.js @@ -1,4 +1,4 @@ -import {Color} from "three"; +import { Color } from 'three'; // Based on (c) 2017 Pavel Dobryakov : WebGL shader code (https://github.com/PavelDoGreat/WebGL-Fluid-Simulation/tree/master) @@ -33,20 +33,20 @@ const vertexShader = ` * Introduces either velocity or color into target. Depending on `splatVelocity` flag. */ export const SplatShader = { - uniforms: { - uTarget: { value: null }, - splatVelocity: { value:false }, - color: { value: new Color(0xffffff) }, - texelSize: { value: null }, - objectData: { value: null }, // Contains current and previous object positions - count: { value: 1 }, - thickness: { value: 0.035223 }, // in UV units - aspectRatio: { value:1 } // in UV units - , splatForce: { value: -196 } - }, - - vertexShader, - fragmentShader:` + uniforms: { + uTarget: { value: null }, + splatVelocity: { value: false }, + color: { value: new Color( 0xffffff ) }, + texelSize: { value: null }, + objectData: { value: null }, // Contains current and previous object positions + count: { value: 1 }, + thickness: { value: 0.035223 }, // in UV units + aspectRatio: { value: 1 }, // in UV units + splatForce: { value: - 196 } + }, + + vertexShader, + fragmentShader: ` precision mediump float; precision mediump sampler2D; @@ -119,13 +119,13 @@ export const SplatShader = { * sets vorticity inthe alpha channel of uVelocity image */ export const CurlShader = { - uniforms: { - uVelocity: { value: null }, - texelSize: { value: null }, - vorticityInfluence: { value:1 } - }, - vertexShader, - fragmentShader:` + uniforms: { + uVelocity: { value: null }, + texelSize: { value: null }, + vorticityInfluence: { value: 1 } + }, + vertexShader, + fragmentShader: ` precision mediump float; precision mediump sampler2D; @@ -151,20 +151,20 @@ export const CurlShader = { gl_FragColor = pixel; } ` - }; +}; /** * updates the velocity image */ export const VorticityShader = { - uniforms: { - uVelocityAndCurl: { value: null }, - texelSize: { value: null }, - curl: { value: 1 }, - dt: { value: 0 }, - }, - vertexShader, - fragmentShader:` + uniforms: { + uVelocityAndCurl: { value: null }, + texelSize: { value: null }, + curl: { value: 1 }, + dt: { value: 0 }, + }, + vertexShader, + fragmentShader: ` precision highp float; precision highp sampler2D; @@ -198,18 +198,18 @@ export const VorticityShader = { gl_FragColor = vec4( pixel.r, velocity, 0.0 ); } ` - }; +}; /** * Adds divergence in the alpha channel of the velocity image */ export const DivergenceShader = { - uniforms: { - uVelocity: { value: null }, - texelSize: { value: null }, - }, - vertexShader, - fragmentShader:` + uniforms: { + uVelocity: { value: null }, + texelSize: { value: null }, + }, + vertexShader, + fragmentShader: ` precision mediump float; precision mediump sampler2D; @@ -239,19 +239,19 @@ export const DivergenceShader = { gl_FragColor = vec4( pixel.r, C, div ); } ` - }; +}; /** * Multiplies the pressure by `value` uniform */ export const ClearShader = { - uniforms: { - uTexture: { value: null }, - value: { value: 0.317 }, //PRESSURE - texelSize: { value: null }, - }, - vertexShader, - fragmentShader:` + uniforms: { + uTexture: { value: null }, + value: { value: 0.317 }, //PRESSURE + texelSize: { value: null }, + }, + vertexShader, + fragmentShader: ` precision mediump float; precision mediump sampler2D; @@ -267,18 +267,18 @@ export const ClearShader = { gl_FragColor = pixel ; } ` - }; +}; /** * updates the pressure of the image */ export const PressureShader = { - uniforms: { - uPressureWithDivergence: { value: null }, - texelSize: { value: null }, - }, - vertexShader, - fragmentShader:` + uniforms: { + uPressureWithDivergence: { value: null }, + texelSize: { value: null }, + }, + vertexShader, + fragmentShader: ` precision mediump float; precision mediump sampler2D; @@ -305,16 +305,16 @@ export const PressureShader = { gl_FragColor = pixel; } ` - }; +}; export const GradientSubtractShader = { - uniforms: { - uPressureWithVelocity: { value: null }, - texelSize: { value: null }, - }, - vertexShader, - fragmentShader:` + uniforms: { + uPressureWithVelocity: { value: null }, + texelSize: { value: null }, + }, + vertexShader, + fragmentShader: ` precision mediump float; precision mediump sampler2D; @@ -339,24 +339,24 @@ export const GradientSubtractShader = { gl_FragColor = vec4( pixel.r, velocity, 0.0 ); } ` - }; +}; export const AdvectVelocityShader = { - uniforms: { - uVelocity: { value: null }, - uSource: { value: null }, - sourceIsVelocity: { value: null }, - texelSize: { value: null }, - dt: { value: 0 }, - dyeTexelSize: { value: null }, - dissipation: { value: 0.2 }, - }, - defines: { - MANUAL_FILTERING: false - }, - vertexShader, - fragmentShader:` + uniforms: { + uVelocity: { value: null }, + uSource: { value: null }, + sourceIsVelocity: { value: null }, + texelSize: { value: null }, + dt: { value: 0 }, + dyeTexelSize: { value: null }, + dissipation: { value: 0.2 }, + }, + defines: { + MANUAL_FILTERING: false + }, + vertexShader, + fragmentShader: ` precision highp float; precision highp sampler2D; @@ -406,4 +406,4 @@ export const AdvectVelocityShader = { } } ` - }; \ No newline at end of file +}; diff --git a/examples/screenshots/webgl_materials_fluidsim.jpg b/examples/screenshots/webgl_materials_fluidsim.jpg index dbbd7bf89e8db142c229bec3c675ad0cc8cfaae9..063e95c50e3974df26e9ae11f0974bd0ff6f2aca 100644 GIT binary patch literal 51081 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 diff --git a/examples/webgl_materials_fluidsim.html b/examples/webgl_materials_fluidsim.html index 273a18305fa948..ad9074dd4d6ecf 100644 --- a/examples/webgl_materials_fluidsim.html +++ b/examples/webgl_materials_fluidsim.html @@ -26,42 +26,39 @@ import Stats from 'three/addons/libs/stats.module.js'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { FluidSimulator } from 'three/addons/objects/FluidSimulator.js'; - import GUI from "three/addons/libs/lil-gui.module.min.js"; + import GUI from 'three/addons/libs/lil-gui.module.min.js'; - const panel = new GUI({ width: 310 }); + const panel = new GUI( { width: 310 } ); - let container, stats; - - let camera, scene, renderer; let time = 0; const clock = new THREE.Clock(); - container = document.createElement( 'div' ); + const container = document.createElement( 'div' ); document.body.appendChild( container ); - camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.01, 10 ); + const camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.01, 10 ); camera.position.set( 1, 1, 1 ); - camera.lookAt(0,0,0); + camera.lookAt( 0, 0, 0 ); // - scene = new THREE.Scene(); + const scene = new THREE.Scene(); scene.background = new THREE.Color( 0x444488 ); //scene.add( new THREE.AxesHelper()) - scene.add(new THREE.AmbientLight(0xffffff, 0.3)); + scene.add( new THREE.AmbientLight( 0xffffff, 0.3 ) ); const color = 0xffffff; const intensity = 3; - const light = new THREE.DirectionalLight(color, intensity); - light.position.set(4, 1, 0); + const light = new THREE.DirectionalLight( color, intensity ); + light.position.set( 4, 1, 0 ); light.castShadow = true; - scene.add(light); + scene.add( light ); // - renderer = new THREE.WebGLRenderer( { antialias: true } ); + const renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( animate ); @@ -69,46 +66,61 @@ // Materials - stats = new Stats(); + const stats = new Stats(); container.appendChild( stats.dom ); - new OrbitControls( camera, renderer.domElement ); + new OrbitControls( camera, renderer.domElement ); window.addEventListener( 'resize', onWindowResize ); //---------------------------------------- DEMO SCENE SETUP ------------------------------------------------ - const fluid = new FluidSimulator( renderer, 1024, 512 ); +const loader = new THREE.TextureLoader(); +loader.load( 'textures/planets/earth_night_4096.jpg', texture => { + + texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + texture.repeat.set( 1, 1 ); + + const material = new THREE.MeshStandardMaterial( { map: texture } ); + const geometry = new THREE.PlaneGeometry( 2, 1 ); + const mesh = new THREE.Mesh( geometry, material ); + mesh.rotation.x = - Math.PI / 2; + scene.add( mesh ); + +} ); + + const fluid = new FluidSimulator( renderer, 1024, 512, 200 ); scene.add( fluid ); - const myFluidMaterial = new THREE.MeshPhysicalMaterial({ - roughness:0.2, - color: new THREE.Color(0xfefefe), + const myFluidMaterial = new THREE.MeshPhysicalMaterial( { + roughness: 0.2, + color: new THREE.Color( 0xfefefe ), metalness: 0.1, - displacementScale:0.01 - }); + displacementScale: 0.01, + transparent: true + } ); - fluid.fixMaterial(myFluidMaterial); + fluid.fixMaterial( myFluidMaterial ); fluid.material = myFluidMaterial; - const ball = new THREE.Mesh( new THREE.SphereGeometry(.01,10,10), new THREE.MeshBasicMaterial({ color:0xff0000 })); + const ball = new THREE.Mesh( new THREE.SphereGeometry( .001, 10, 10 ), new THREE.MeshBasicMaterial( { color: 0xff0000 } ) ); scene.add( ball ); ball.position.y = .02; fluid.track( ball ); - //------------------- DEBUG PANEL - panel.add( fluid, "splatForce", -1000, 1000 ); - panel.add( fluid, "splatThickness", 0.001, 0.2 ); - panel.add( fluid, "vorticityInfluence", 0.1, 1 ); - panel.add( fluid, "swirlIntensity", 1, 100 ); - panel.add( fluid, "pressure", 0, 1 ); - panel.add( fluid, "velocityDissipation", 0, 1 ); - panel.add( fluid, "densityDissipation", 0, 1 ); - panel.add( myFluidMaterial, "displacementScale", -.1, .1 ); - panel.add( fluid, "pressureIterations", 1, 100, 1 ); - + //------------------- DEBUG PANEL + panel.add( fluid, 'splatForce', - 1000, 1000 ); + panel.add( fluid, 'splatThickness', 0.001, 0.2 ); + panel.add( fluid, 'vorticityInfluence', 0.1, 1 ); + panel.add( fluid, 'swirlIntensity', 1, 100 ); + panel.add( fluid, 'pressure', 0, 1 ); + panel.add( fluid, 'velocityDissipation', 0, 1 ); + panel.add( fluid, 'densityDissipation', 0, 1 ); + panel.add( myFluidMaterial, 'displacementScale', - .1, .1 ); + panel.add( fluid, 'pressureIterations', 1, 100, 1 ); + function onWindowResize() { @@ -131,20 +143,22 @@ } function render() { - + const delta = clock.getDelta(); - time += delta; + time += delta; - ball.position.x = Math.cos(time)*.1; - ball.position.z = Math.sin(time)*.1; + ball.position.x = Math.cos( time ) * .1; + ball.position.z = Math.sin( time ) * .1; fluid.update( delta ); myFluidMaterial.displacementMap = fluid.elevationTexture; + myFluidMaterial.map = fluid.elevationTexture; renderer.render( scene, camera ); - } + +}