webgl_gpgpu_water.html 22 KB

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