webgl_gpgpu_water.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - gpgpu - water</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <meta property="og:title" content="three.js webgl - gpgpu - water">
  8. <meta property="og:type" content="website">
  9. <meta property="og:url" content="https://threejs.org/examples/webgl_gpgpu_water.html">
  10. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgl_gpgpu_water.jpg">
  11. <link type="text/css" rel="stylesheet" href="main.css">
  12. </head>
  13. <body>
  14. <div id="info">
  15. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - <span id="waterSize"></span> webgl gpgpu water<br/>
  16. Click and Move mouse to disturb water.
  17. </div>
  18. <!-- This is just a smoothing 'compute shader' for using manually: -->
  19. <script id="smoothFragmentShader" type="x-shader/x-fragment">
  20. uniform sampler2D smoothTexture;
  21. void main() {
  22. vec2 cellSize = 1.0 / resolution.xy;
  23. vec2 uv = gl_FragCoord.xy * cellSize;
  24. // Computes the mean of texel and 4 neighbours
  25. vec4 textureValue = texture2D( smoothTexture, uv );
  26. textureValue += texture2D( smoothTexture, uv + vec2( 0.0, cellSize.y ) );
  27. textureValue += texture2D( smoothTexture, uv + vec2( 0.0, - cellSize.y ) );
  28. textureValue += texture2D( smoothTexture, uv + vec2( cellSize.x, 0.0 ) );
  29. textureValue += texture2D( smoothTexture, uv + vec2( - cellSize.x, 0.0 ) );
  30. textureValue /= 5.0;
  31. gl_FragColor = textureValue;
  32. }
  33. </script>
  34. <!-- This is a 'compute shader' to read the current level and normal of water at a point -->
  35. <!-- It is used with a variable of size 1x1 -->
  36. <script id="readWaterLevelFragmentShader" type="x-shader/x-fragment">
  37. uniform vec2 point1;
  38. uniform sampler2D levelTexture;
  39. // Integer to float conversion from https://stackoverflow.com/questions/17981163/webgl-read-pixels-from-floating-point-render-target
  40. float shift_right( float v, float amt ) {
  41. v = floor( v ) + 0.5;
  42. return floor( v / exp2( amt ) );
  43. }
  44. float shift_left( float v, float amt ) {
  45. return floor( v * exp2( amt ) + 0.5 );
  46. }
  47. float mask_last( float v, float bits ) {
  48. return mod( v, shift_left( 1.0, bits ) );
  49. }
  50. float extract_bits( float num, float from, float to ) {
  51. from = floor( from + 0.5 ); to = floor( to + 0.5 );
  52. return mask_last( shift_right( num, from ), to - from );
  53. }
  54. vec4 encode_float( float val ) {
  55. if ( val == 0.0 ) return vec4( 0, 0, 0, 0 );
  56. float sign = val > 0.0 ? 0.0 : 1.0;
  57. val = abs( val );
  58. float exponent = floor( log2( val ) );
  59. float biased_exponent = exponent + 127.0;
  60. float fraction = ( ( val / exp2( exponent ) ) - 1.0 ) * 8388608.0;
  61. float t = biased_exponent / 2.0;
  62. float last_bit_of_biased_exponent = fract( t ) * 2.0;
  63. float remaining_bits_of_biased_exponent = floor( t );
  64. float byte4 = extract_bits( fraction, 0.0, 8.0 ) / 255.0;
  65. float byte3 = extract_bits( fraction, 8.0, 16.0 ) / 255.0;
  66. float byte2 = ( last_bit_of_biased_exponent * 128.0 + extract_bits( fraction, 16.0, 23.0 ) ) / 255.0;
  67. float byte1 = ( sign * 128.0 + remaining_bits_of_biased_exponent ) / 255.0;
  68. return vec4( byte4, byte3, byte2, byte1 );
  69. }
  70. void main() {
  71. vec2 cellSize = 1.0 / resolution.xy;
  72. float waterLevel = texture2D( levelTexture, point1 ).x;
  73. vec2 normal = vec2(
  74. ( texture2D( levelTexture, point1 + vec2( - cellSize.x, 0 ) ).x - texture2D( levelTexture, point1 + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
  75. ( texture2D( levelTexture, point1 + vec2( 0, - cellSize.y ) ).x - texture2D( levelTexture, point1 + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS );
  76. if ( gl_FragCoord.x < 1.5 ) {
  77. gl_FragColor = encode_float( waterLevel );
  78. } else if ( gl_FragCoord.x < 2.5 ) {
  79. gl_FragColor = encode_float( normal.x );
  80. } else if ( gl_FragCoord.x < 3.5 ) {
  81. gl_FragColor = encode_float( normal.y );
  82. } else {
  83. gl_FragColor = encode_float( 0.0 );
  84. }
  85. }
  86. </script>
  87. <script type="importmap">
  88. {
  89. "imports": {
  90. "three": "../build/three.module.js",
  91. "three/addons/": "./jsm/"
  92. }
  93. }
  94. </script>
  95. <script type="module">
  96. import * as THREE from 'three';
  97. import Stats from 'three/addons/libs/stats.module.js';
  98. import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
  99. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  100. import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
  101. import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
  102. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
  103. import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
  104. import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
  105. // Texture width for simulation
  106. const WIDTH = 128;
  107. // Water size in system units
  108. const BOUNDS = 6;
  109. const BOUNDS_HALF = BOUNDS * 0.5;
  110. let tmpHeightmap = null;
  111. const tmpQuat = new THREE.Quaternion();
  112. const tmpQuatX = new THREE.Quaternion();
  113. const tmpQuatZ = new THREE.Quaternion();
  114. let duckModel = null;
  115. let container, stats;
  116. let camera, scene, renderer, controls;
  117. let mousedown = false;
  118. const mouseCoords = new THREE.Vector2();
  119. const raycaster = new THREE.Raycaster();
  120. let sun;
  121. let waterMesh;
  122. let poolBorder;
  123. let meshRay;
  124. let gpuCompute;
  125. let heightmapVariable;
  126. // let smoothShader;
  127. let readWaterLevelShader;
  128. let readWaterLevelRenderTarget;
  129. let readWaterLevelImage;
  130. const waterNormal = new THREE.Vector3();
  131. const NUM_DUCK = 12;
  132. const ducks = [];
  133. let ducksEnabled = true;
  134. const simplex = new SimplexNoise();
  135. let frame = 0;
  136. const effectController = {
  137. mouseSize: 0.2,
  138. mouseDeep: 0.01,
  139. viscosity: 0.93,
  140. speed: 5,
  141. ducksEnabled: ducksEnabled,
  142. wireframe: false,
  143. shadow: false,
  144. };
  145. init();
  146. async function init() {
  147. container = document.createElement( 'div' );
  148. document.body.appendChild( container );
  149. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
  150. camera.position.set( 0, 2.00, 4 );
  151. camera.lookAt( 0, 0, 0 );
  152. scene = new THREE.Scene();
  153. sun = new THREE.DirectionalLight( 0xFFFFFF, 4.0 );
  154. sun.position.set( - 1, 2.6, 1.4 );
  155. scene.add( sun );
  156. renderer = new THREE.WebGLRenderer( { antialias: true } );
  157. renderer.setPixelRatio( window.devicePixelRatio );
  158. renderer.setSize( window.innerWidth, window.innerHeight );
  159. renderer.toneMapping = THREE.ACESFilmicToneMapping;
  160. renderer.toneMappingExposure = 0.5;
  161. container.appendChild( renderer.domElement );
  162. controls = new OrbitControls( camera, container );
  163. stats = new Stats();
  164. container.appendChild( stats.dom );
  165. container.style.touchAction = 'none';
  166. container.addEventListener( 'pointermove', onPointerMove );
  167. container.addEventListener( 'pointerdown', onPointerDown );
  168. container.addEventListener( 'pointerup', onPointerUp );
  169. window.addEventListener( 'resize', onWindowResize );
  170. const hdrLoader = new HDRLoader().setPath( './textures/equirectangular/' );
  171. const glbloader = new GLTFLoader().setPath( 'models/gltf/' );
  172. glbloader.setDRACOLoader( new DRACOLoader().setDecoderPath( DRACO_GLTF_CONFIG ) );
  173. const [ env, model ] = await Promise.all( [ hdrLoader.loadAsync( 'blouberg_sunrise_2_1k.hdr' ), glbloader.loadAsync( 'duck.glb' ) ] );
  174. env.mapping = THREE.EquirectangularReflectionMapping;
  175. scene.environment = env;
  176. scene.background = env;
  177. scene.backgroundBlurriness = 0.3;
  178. scene.environmentIntensity = 1.25;
  179. duckModel = model.scene.children[ 0 ];
  180. duckModel.receiveShadow = true;
  181. duckModel.castShadow = true;
  182. const gui = new GUI();
  183. gui.domElement.style.right = '0px';
  184. const valuesChanger = function () {
  185. heightmapVariable.material.uniforms[ 'mouseSize' ].value = effectController.mouseSize;
  186. heightmapVariable.material.uniforms[ 'deep' ].value = effectController.mouseDeep;
  187. heightmapVariable.material.uniforms[ 'viscosity' ].value = effectController.viscosity;
  188. ducksEnabled = effectController.ducksEnabled;
  189. let i = NUM_DUCK;
  190. while ( i -- ) {
  191. if ( ducks[ i ] ) ducks[ i ].visible = ducksEnabled;
  192. }
  193. };
  194. gui.add( effectController, 'mouseSize', 0.1, 1.0, 0.1 ).onChange( valuesChanger );
  195. gui.add( effectController, 'mouseDeep', 0.01, 1.0, 0.01 ).onChange( valuesChanger );
  196. gui.add( effectController, 'viscosity', 0.9, 0.999, 0.001 ).onChange( valuesChanger );
  197. gui.add( effectController, 'speed', 1, 6, 1 );
  198. gui.add( effectController, 'ducksEnabled' ).onChange( valuesChanger );
  199. gui.add( effectController, 'wireframe' ).onChange( ( v )=>{
  200. waterMesh.material.wireframe = v;
  201. poolBorder.material.wireframe = v;
  202. } );
  203. gui.add( effectController, 'shadow' ).onChange( addShadow );
  204. //const buttonSmooth = { smoothWater: function () {smoothWater();} };
  205. //gui.add( buttonSmooth, 'smoothWater' );
  206. initWater();
  207. createducks();
  208. valuesChanger();
  209. renderer.setAnimationLoop( animate );
  210. }
  211. function initWater() {
  212. const geometry = new THREE.PlaneGeometry( BOUNDS, BOUNDS, WIDTH - 1, WIDTH - 1 );
  213. const material = new WaterMaterial( {
  214. color: 0x9bd2ec,
  215. metalness: 0.9,
  216. roughness: 0,
  217. transparent: true,
  218. opacity: 0.8,
  219. side: THREE.DoubleSide
  220. } );
  221. waterMesh = new THREE.Mesh( geometry, material );
  222. waterMesh.rotation.x = - Math.PI * 0.5;
  223. waterMesh.matrixAutoUpdate = false;
  224. waterMesh.updateMatrix();
  225. waterMesh.receiveShadow = true;
  226. waterMesh.castShadow = true;
  227. scene.add( waterMesh );
  228. // pool border
  229. const borderGeom = new THREE.TorusGeometry( 4.2, 0.1, 12, 4 );
  230. borderGeom.rotateX( Math.PI * 0.5 );
  231. borderGeom.rotateY( Math.PI * 0.25 );
  232. poolBorder = new THREE.Mesh( borderGeom, new THREE.MeshStandardMaterial( { color: 0x908877, roughness: 0.2 } ) );
  233. scene.add( poolBorder );
  234. poolBorder.receiveShadow = true;
  235. poolBorder.castShadow = true;
  236. // THREE.Mesh just for mouse raycasting
  237. const geometryRay = new THREE.PlaneGeometry( BOUNDS, BOUNDS, 1, 1 );
  238. meshRay = new THREE.Mesh( geometryRay, new THREE.MeshBasicMaterial( { color: 0xFFFFFF, visible: false } ) );
  239. meshRay.rotation.x = - Math.PI / 2;
  240. meshRay.matrixAutoUpdate = false;
  241. meshRay.updateMatrix();
  242. scene.add( meshRay );
  243. // Creates the gpu computation class and sets it up
  244. gpuCompute = new GPUComputationRenderer( WIDTH, WIDTH, renderer );
  245. const heightmap0 = gpuCompute.createTexture();
  246. fillTexture( heightmap0 );
  247. heightmapVariable = gpuCompute.addVariable( 'heightmap', shaderChange.heightmap_frag, heightmap0 );
  248. gpuCompute.setVariableDependencies( heightmapVariable, [ heightmapVariable ] );
  249. heightmapVariable.material.uniforms[ 'mousePos' ] = { value: new THREE.Vector2( 10000, 10000 ) };
  250. heightmapVariable.material.uniforms[ 'mouseSize' ] = { value: 0.2 };
  251. heightmapVariable.material.uniforms[ 'viscosity' ] = { value: 0.93 };
  252. heightmapVariable.material.uniforms[ 'deep' ] = { value: 0.01 };
  253. heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed( 1 );
  254. const error = gpuCompute.init();
  255. if ( error !== null ) console.error( error );
  256. // Create compute shader to smooth the water surface and velocity
  257. //smoothShader = gpuCompute.createShaderMaterial( document.getElementById( 'smoothFragmentShader' ).textContent, { smoothTexture: { value: null } } );
  258. // Create compute shader to read water level
  259. readWaterLevelShader = gpuCompute.createShaderMaterial( document.getElementById( 'readWaterLevelFragmentShader' ).textContent, {
  260. point1: { value: new THREE.Vector2() },
  261. levelTexture: { value: null }
  262. } );
  263. readWaterLevelShader.defines.WIDTH = WIDTH.toFixed( 1 );
  264. readWaterLevelShader.defines.BOUNDS = BOUNDS.toFixed( 1 );
  265. // Create a 4x1 pixel image and a render target (Uint8, 4 channels, 1 byte per channel) to read water height and orientation
  266. readWaterLevelImage = new Uint8Array( 4 * 1 * 4 );
  267. readWaterLevelRenderTarget = new THREE.WebGLRenderTarget( 4, 1, {
  268. wrapS: THREE.ClampToEdgeWrapping,
  269. wrapT: THREE.ClampToEdgeWrapping,
  270. minFilter: THREE.NearestFilter,
  271. magFilter: THREE.NearestFilter,
  272. format: THREE.RGBAFormat,
  273. type: THREE.UnsignedByteType,
  274. depthBuffer: false
  275. } );
  276. }
  277. function fillTexture( texture ) {
  278. const waterMaxHeight = 0.1;
  279. function noise( x, y ) {
  280. let multR = waterMaxHeight;
  281. let mult = 0.025;
  282. let r = 0;
  283. for ( let i = 0; i < 15; i ++ ) {
  284. r += multR * simplex.noise( x * mult, y * mult );
  285. multR *= 0.53 + 0.025 * i;
  286. mult *= 1.25;
  287. }
  288. return r;
  289. }
  290. const pixels = texture.image.data;
  291. let p = 0;
  292. for ( let j = 0; j < WIDTH; j ++ ) {
  293. for ( let i = 0; i < WIDTH; i ++ ) {
  294. const x = i * 128 / WIDTH;
  295. const y = j * 128 / WIDTH;
  296. pixels[ p + 0 ] = noise( x, y );
  297. pixels[ p + 1 ] = pixels[ p + 0 ];
  298. pixels[ p + 2 ] = 0;
  299. pixels[ p + 3 ] = 1;
  300. p += 4;
  301. }
  302. }
  303. }
  304. function addShadow( v ) {
  305. renderer.shadowMap.enabled = v;
  306. sun.castShadow = v;
  307. if ( v ) {
  308. renderer.shadowMap.type = THREE.VSMShadowMap;
  309. const shadow = sun.shadow;
  310. shadow.mapSize.width = shadow.mapSize.height = 2048;
  311. shadow.radius = 2;
  312. shadow.bias = - 0.0005;
  313. const shadowCam = shadow.camera, s = 5;
  314. shadowCam.near = 0.1;
  315. shadowCam.far = 6;
  316. shadowCam.right = shadowCam.top = s;
  317. shadowCam.left = shadowCam.bottom = - s;
  318. } else {
  319. if ( sun.shadow ) sun.shadow.dispose();
  320. }
  321. // debug shadow
  322. //scene.add( new THREE.CameraHelper(shadowCam) );
  323. }
  324. // function smoothWater() {
  325. // const currentRenderTarget = gpuCompute.getCurrentRenderTarget( heightmapVariable );
  326. // const alternateRenderTarget = gpuCompute.getAlternateRenderTarget( heightmapVariable );
  327. // for ( let i = 0; i < 10; i ++ ) {
  328. // smoothShader.uniforms[ 'smoothTexture' ].value = currentRenderTarget.texture;
  329. // gpuCompute.doRenderTarget( smoothShader, alternateRenderTarget );
  330. // smoothShader.uniforms[ 'smoothTexture' ].value = alternateRenderTarget.texture;
  331. // gpuCompute.doRenderTarget( smoothShader, currentRenderTarget );
  332. // }
  333. // }
  334. function createducks() {
  335. for ( let i = 0; i < NUM_DUCK; i ++ ) {
  336. let sphere = duckModel;
  337. if ( i < NUM_DUCK - 1 ) {
  338. sphere = duckModel.clone();
  339. }
  340. sphere.position.x = ( Math.random() - 0.5 ) * BOUNDS * 0.7;
  341. sphere.position.z = ( Math.random() - 0.5 ) * BOUNDS * 0.7;
  342. sphere.userData.velocity = new THREE.Vector3();
  343. scene.add( sphere );
  344. ducks[ i ] = sphere;
  345. }
  346. }
  347. function duckDynamics() {
  348. readWaterLevelShader.uniforms[ 'levelTexture' ].value = tmpHeightmap;
  349. for ( let i = 0; i < NUM_DUCK; i ++ ) {
  350. const sphere = ducks[ i ];
  351. if ( sphere ) {
  352. // Read water level and orientation
  353. const u = 0.5 * sphere.position.x / BOUNDS_HALF + 0.5;
  354. const v = 1 - ( 0.5 * sphere.position.z / BOUNDS_HALF + 0.5 );
  355. readWaterLevelShader.uniforms[ 'point1' ].value.set( u, v );
  356. gpuCompute.doRenderTarget( readWaterLevelShader, readWaterLevelRenderTarget );
  357. renderer.readRenderTargetPixels( readWaterLevelRenderTarget, 0, 0, 4, 1, readWaterLevelImage );
  358. const pixels = new Float32Array( readWaterLevelImage.buffer );
  359. // Get orientation
  360. waterNormal.set( pixels[ 1 ], 0, - pixels[ 2 ] );
  361. const pos = sphere.position;
  362. const startPos = pos.clone();
  363. // Set height
  364. pos.y = pixels[ 0 ];
  365. // Move sphere
  366. waterNormal.multiplyScalar( 0.01 );
  367. sphere.userData.velocity.add( waterNormal );
  368. sphere.userData.velocity.multiplyScalar( 0.998 );
  369. pos.add( sphere.userData.velocity );
  370. const decal = 0.001;
  371. const limit = BOUNDS_HALF - 0.2;
  372. if ( pos.x < - limit ) {
  373. pos.x = - limit + decal;
  374. sphere.userData.velocity.x *= - 0.3;
  375. } else if ( pos.x > limit ) {
  376. pos.x = limit - decal;
  377. sphere.userData.velocity.x *= - 0.3;
  378. }
  379. if ( pos.z < - limit ) {
  380. pos.z = - limit + decal;
  381. sphere.userData.velocity.z *= - 0.3;
  382. } else if ( pos.z > limit ) {
  383. pos.z = limit - decal;
  384. sphere.userData.velocity.z *= - 0.3;
  385. }
  386. // duck orientation test
  387. const startNormal = new THREE.Vector3( pixels[ 1 ], 1, - pixels[ 2 ] ).normalize();
  388. const dir = startPos.sub( pos );
  389. dir.y = 0;
  390. dir.normalize();
  391. const yAxis = new THREE.Vector3( 0, 1, 0 );
  392. const zAxis = new THREE.Vector3( 0, 0, - 1 );
  393. tmpQuatX.setFromUnitVectors( zAxis, dir );
  394. tmpQuatZ.setFromUnitVectors( yAxis, startNormal );
  395. tmpQuat.multiplyQuaternions( tmpQuatZ, tmpQuatX );
  396. sphere.quaternion.slerp( tmpQuat, 0.017 );
  397. }
  398. }
  399. }
  400. function onWindowResize() {
  401. camera.aspect = window.innerWidth / window.innerHeight;
  402. camera.updateProjectionMatrix();
  403. renderer.setSize( window.innerWidth, window.innerHeight );
  404. }
  405. function onPointerDown() {
  406. mousedown = true;
  407. }
  408. function onPointerUp() {
  409. mousedown = false;
  410. controls.enabled = true;
  411. }
  412. function onPointerMove( event ) {
  413. const dom = renderer.domElement;
  414. mouseCoords.set( ( event.clientX / dom.clientWidth ) * 2 - 1, - ( event.clientY / dom.clientHeight ) * 2 + 1 );
  415. }
  416. function raycast() {
  417. // Set uniforms: mouse interaction
  418. const uniforms = heightmapVariable.material.uniforms;
  419. if ( mousedown ) {
  420. raycaster.setFromCamera( mouseCoords, camera );
  421. const intersects = raycaster.intersectObject( meshRay );
  422. if ( intersects.length > 0 ) {
  423. const point = intersects[ 0 ].point;
  424. uniforms[ 'mousePos' ].value.set( point.x, point.z );
  425. if ( controls.enabled ) controls.enabled = false;
  426. } else {
  427. uniforms[ 'mousePos' ].value.set( 10000, 10000 );
  428. }
  429. } else {
  430. uniforms[ 'mousePos' ].value.set( 10000, 10000 );
  431. }
  432. }
  433. function animate() {
  434. render();
  435. stats.update();
  436. }
  437. function render() {
  438. raycast();
  439. frame ++;
  440. if ( frame >= 7 - effectController.speed ) {
  441. // Do the gpu computation
  442. gpuCompute.compute();
  443. tmpHeightmap = gpuCompute.getCurrentRenderTarget( heightmapVariable ).texture;
  444. if ( ducksEnabled ) duckDynamics();
  445. // Get compute output in custom uniform
  446. if ( waterMesh ) waterMesh.material.heightmap = tmpHeightmap;
  447. frame = 0;
  448. }
  449. // Render
  450. renderer.render( scene, camera );
  451. }
  452. //----------------------
  453. class WaterMaterial extends THREE.MeshStandardMaterial {
  454. constructor( parameters ) {
  455. super();
  456. this.defines = {
  457. 'STANDARD': '',
  458. 'USE_UV': '',
  459. 'WIDTH': WIDTH.toFixed( 1 ),
  460. 'BOUNDS': BOUNDS.toFixed( 1 ),
  461. };
  462. this.extra = {};
  463. this.addParameter( 'heightmap', null );
  464. this.setValues( parameters );
  465. }
  466. addParameter( name, value ) {
  467. this.extra[ name ] = value;
  468. Object.defineProperty( this, name, {
  469. get: () => ( this.extra[ name ] ),
  470. set: ( v ) => {
  471. this.extra[ name ] = v;
  472. if ( this.userData.shader ) this.userData.shader.uniforms[ name ].value = this.extra[ name ];
  473. }
  474. } );
  475. }
  476. onBeforeCompile( shader ) {
  477. for ( const name in this.extra ) {
  478. shader.uniforms[ name ] = { value: this.extra[ name ] };
  479. }
  480. shader.vertexShader = shader.vertexShader.replace( '#include <common>', shaderChange.common );
  481. //shader.vertexShader = 'uniform sampler2D heightmap;\n' + shader.vertexShader;
  482. shader.vertexShader = shader.vertexShader.replace( '#include <beginnormal_vertex>', shaderChange.beginnormal_vertex );
  483. shader.vertexShader = shader.vertexShader.replace( '#include <begin_vertex>', shaderChange.begin_vertex );
  484. this.userData.shader = shader;
  485. }
  486. }
  487. const shaderChange = {
  488. heightmap_frag: /* glsl */`
  489. #include <common>
  490. uniform vec2 mousePos;
  491. uniform float mouseSize;
  492. uniform float viscosity;
  493. uniform float deep;
  494. void main() {
  495. vec2 cellSize = 1.0 / resolution.xy;
  496. vec2 uv = gl_FragCoord.xy * cellSize;
  497. // heightmapValue.x == height from previous frame
  498. // heightmapValue.y == height from penultimate frame
  499. // heightmapValue.z, heightmapValue.w not used
  500. vec4 heightmapValue = texture2D( heightmap, uv );
  501. // Get neighbours
  502. vec4 north = texture2D( heightmap, uv + vec2( 0.0, cellSize.y ) );
  503. vec4 south = texture2D( heightmap, uv + vec2( 0.0, - cellSize.y ) );
  504. vec4 east = texture2D( heightmap, uv + vec2( cellSize.x, 0.0 ) );
  505. vec4 west = texture2D( heightmap, uv + vec2( - cellSize.x, 0.0 ) );
  506. //float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - heightmapValue.y ) * viscosity;
  507. float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - (heightmapValue.y) ) * viscosity;
  508. // Mouse influence
  509. float mousePhase = clamp( length( ( uv - vec2( 0.5 ) ) * BOUNDS - vec2( mousePos.x, - mousePos.y ) ) * PI / mouseSize, 0.0, PI );
  510. //newHeight += ( cos( mousePhase ) + 1.0 ) * 0.28 * 10.0;
  511. newHeight -= ( cos( mousePhase ) + 1.0 ) * deep;
  512. heightmapValue.y = heightmapValue.x;
  513. heightmapValue.x = newHeight;
  514. gl_FragColor = heightmapValue;
  515. }
  516. `,
  517. // FOR MATERIAL
  518. common: /* glsl */`
  519. #include <common>
  520. uniform sampler2D heightmap;
  521. `,
  522. beginnormal_vertex: /* glsl */`
  523. vec2 cellSize = vec2( 1.0 / WIDTH, 1.0 / WIDTH );
  524. vec3 objectNormal = vec3(
  525. ( texture2D( heightmap, uv + vec2( - cellSize.x, 0 ) ).x - texture2D( heightmap, uv + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
  526. ( texture2D( heightmap, uv + vec2( 0, - cellSize.y ) ).x - texture2D( heightmap, uv + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS,
  527. 1.0 );
  528. #ifdef USE_TANGENT
  529. vec3 objectTangent = vec3( tangent.xyz );
  530. #endif
  531. `,
  532. begin_vertex: /* glsl */`
  533. float heightValue = texture2D( heightmap, uv ).x;
  534. vec3 transformed = vec3( position.x, position.y, heightValue );
  535. #ifdef USE_ALPHAHASH
  536. vPosition = vec3( position );
  537. #endif
  538. `,
  539. };
  540. </script>
  541. </body>
  542. </html>
粤ICP备19079148号