webgl_gpgpu_water.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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 { HDRLoader } from 'three/addons/loaders/HDRLoader.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. const tmpQuat = new THREE.Quaternion();
  108. const tmpQuatX = new THREE.Quaternion();
  109. const tmpQuatZ = new THREE.Quaternion();
  110. let duckModel = null;
  111. let container, stats;
  112. let camera, scene, renderer, controls;
  113. let mousedown = false;
  114. const mouseCoords = new THREE.Vector2();
  115. const raycaster = new THREE.Raycaster();
  116. let sun;
  117. let waterMesh;
  118. let poolBorder;
  119. let meshRay;
  120. let gpuCompute;
  121. let heightmapVariable;
  122. // let smoothShader;
  123. let readWaterLevelShader;
  124. let readWaterLevelRenderTarget;
  125. let readWaterLevelImage;
  126. const waterNormal = new THREE.Vector3();
  127. const NUM_DUCK = 12;
  128. const ducks = [];
  129. let ducksEnabled = true;
  130. const simplex = new SimplexNoise();
  131. let frame = 0;
  132. const effectController = {
  133. mouseSize: 0.2,
  134. mouseDeep: 0.01,
  135. viscosity: 0.93,
  136. speed: 5,
  137. ducksEnabled: ducksEnabled,
  138. wireframe: false,
  139. shadow: false,
  140. };
  141. init();
  142. async function init() {
  143. container = document.createElement( 'div' );
  144. document.body.appendChild( container );
  145. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
  146. camera.position.set( 0, 2.00, 4 );
  147. camera.lookAt( 0, 0, 0 );
  148. scene = new THREE.Scene();
  149. sun = new THREE.DirectionalLight( 0xFFFFFF, 4.0 );
  150. sun.position.set( - 1, 2.6, 1.4 );
  151. scene.add( sun );
  152. renderer = new THREE.WebGLRenderer( { antialias: true } );
  153. renderer.setPixelRatio( window.devicePixelRatio );
  154. renderer.setSize( window.innerWidth, window.innerHeight );
  155. renderer.toneMapping = THREE.ACESFilmicToneMapping;
  156. renderer.toneMappingExposure = 0.5;
  157. container.appendChild( renderer.domElement );
  158. controls = new OrbitControls( camera, container );
  159. stats = new Stats();
  160. container.appendChild( stats.dom );
  161. container.style.touchAction = 'none';
  162. container.addEventListener( 'pointermove', onPointerMove );
  163. container.addEventListener( 'pointerdown', onPointerDown );
  164. container.addEventListener( 'pointerup', onPointerUp );
  165. window.addEventListener( 'resize', onWindowResize );
  166. const hdrLoader = new HDRLoader().setPath( './textures/equirectangular/' );
  167. const glbloader = new GLTFLoader().setPath( 'models/gltf/' );
  168. glbloader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) );
  169. const [ env, model ] = await Promise.all( [ hdrLoader.loadAsync( 'blouberg_sunrise_2_1k.hdr' ), glbloader.loadAsync( 'duck.glb' ) ] );
  170. env.mapping = THREE.EquirectangularReflectionMapping;
  171. scene.environment = env;
  172. scene.background = env;
  173. scene.backgroundBlurriness = 0.3;
  174. scene.environmentIntensity = 1.25;
  175. duckModel = model.scene.children[ 0 ];
  176. duckModel.receiveShadow = true;
  177. duckModel.castShadow = true;
  178. const gui = new GUI();
  179. gui.domElement.style.right = '0px';
  180. const valuesChanger = function () {
  181. heightmapVariable.material.uniforms[ 'mouseSize' ].value = effectController.mouseSize;
  182. heightmapVariable.material.uniforms[ 'deep' ].value = effectController.mouseDeep;
  183. heightmapVariable.material.uniforms[ 'viscosity' ].value = effectController.viscosity;
  184. ducksEnabled = effectController.ducksEnabled;
  185. let i = NUM_DUCK;
  186. while ( i -- ) {
  187. if ( ducks[ i ] ) ducks[ i ].visible = ducksEnabled;
  188. }
  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. renderer.setAnimationLoop( 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. poolBorder.receiveShadow = true;
  231. poolBorder.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. const shadow = sun.shadow;
  306. shadow.mapSize.width = shadow.mapSize.height = 2048;
  307. shadow.radius = 2;
  308. shadow.bias = - 0.0005;
  309. const 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. const 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. const decal = 0.001;
  367. const 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. const startNormal = new THREE.Vector3( pixels[ 1 ], 1, - pixels[ 2 ] ).normalize();
  384. const dir = startPos.sub( pos );
  385. dir.y = 0;
  386. dir.normalize();
  387. const yAxis = new THREE.Vector3( 0, 1, 0 );
  388. const 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() {
  402. mousedown = true;
  403. }
  404. function onPointerUp() {
  405. mousedown = false;
  406. controls.enabled = true;
  407. }
  408. function onPointerMove( event ) {
  409. const 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. render();
  431. stats.update();
  432. }
  433. function render() {
  434. raycast();
  435. frame ++;
  436. if ( frame >= 7 - effectController.speed ) {
  437. // Do the gpu computation
  438. gpuCompute.compute();
  439. tmpHeightmap = gpuCompute.getCurrentRenderTarget( heightmapVariable ).texture;
  440. if ( ducksEnabled ) duckDynamics();
  441. // Get compute output in custom uniform
  442. if ( waterMesh ) waterMesh.material.heightmap = tmpHeightmap;
  443. frame = 0;
  444. }
  445. // Render
  446. renderer.render( scene, camera );
  447. }
  448. //----------------------
  449. class WaterMaterial extends THREE.MeshStandardMaterial {
  450. constructor( parameters ) {
  451. super();
  452. this.defines = {
  453. 'STANDARD': '',
  454. 'USE_UV': '',
  455. 'WIDTH': WIDTH.toFixed( 1 ),
  456. 'BOUNDS': BOUNDS.toFixed( 1 ),
  457. };
  458. this.extra = {};
  459. this.addParameter( 'heightmap', null );
  460. this.setValues( parameters );
  461. }
  462. addParameter( name, value ) {
  463. this.extra[ name ] = value;
  464. Object.defineProperty( this, name, {
  465. get: () => ( this.extra[ name ] ),
  466. set: ( v ) => {
  467. this.extra[ name ] = v;
  468. if ( this.userData.shader ) this.userData.shader.uniforms[ name ].value = this.extra[ name ];
  469. }
  470. } );
  471. }
  472. onBeforeCompile( shader ) {
  473. for ( const name in this.extra ) {
  474. shader.uniforms[ name ] = { value: this.extra[ name ] };
  475. }
  476. shader.vertexShader = shader.vertexShader.replace( '#include <common>', shaderChange.common );
  477. //shader.vertexShader = 'uniform sampler2D heightmap;\n' + shader.vertexShader;
  478. shader.vertexShader = shader.vertexShader.replace( '#include <beginnormal_vertex>', shaderChange.beginnormal_vertex );
  479. shader.vertexShader = shader.vertexShader.replace( '#include <begin_vertex>', shaderChange.begin_vertex );
  480. this.userData.shader = shader;
  481. }
  482. }
  483. const shaderChange = {
  484. heightmap_frag: /* glsl */`
  485. #include <common>
  486. uniform vec2 mousePos;
  487. uniform float mouseSize;
  488. uniform float viscosity;
  489. uniform float deep;
  490. void main() {
  491. vec2 cellSize = 1.0 / resolution.xy;
  492. vec2 uv = gl_FragCoord.xy * cellSize;
  493. // heightmapValue.x == height from previous frame
  494. // heightmapValue.y == height from penultimate frame
  495. // heightmapValue.z, heightmapValue.w not used
  496. vec4 heightmapValue = texture2D( heightmap, uv );
  497. // Get neighbours
  498. vec4 north = texture2D( heightmap, uv + vec2( 0.0, cellSize.y ) );
  499. vec4 south = texture2D( heightmap, uv + vec2( 0.0, - cellSize.y ) );
  500. vec4 east = texture2D( heightmap, uv + vec2( cellSize.x, 0.0 ) );
  501. vec4 west = texture2D( heightmap, uv + vec2( - cellSize.x, 0.0 ) );
  502. //float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - heightmapValue.y ) * viscosity;
  503. float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - (heightmapValue.y) ) * viscosity;
  504. // Mouse influence
  505. float mousePhase = clamp( length( ( uv - vec2( 0.5 ) ) * BOUNDS - vec2( mousePos.x, - mousePos.y ) ) * PI / mouseSize, 0.0, PI );
  506. //newHeight += ( cos( mousePhase ) + 1.0 ) * 0.28 * 10.0;
  507. newHeight -= ( cos( mousePhase ) + 1.0 ) * deep;
  508. heightmapValue.y = heightmapValue.x;
  509. heightmapValue.x = newHeight;
  510. gl_FragColor = heightmapValue;
  511. }
  512. `,
  513. // FOR MATERIAL
  514. common: /* glsl */`
  515. #include <common>
  516. uniform sampler2D heightmap;
  517. `,
  518. beginnormal_vertex: /* glsl */`
  519. vec2 cellSize = vec2( 1.0 / WIDTH, 1.0 / WIDTH );
  520. vec3 objectNormal = vec3(
  521. ( texture2D( heightmap, uv + vec2( - cellSize.x, 0 ) ).x - texture2D( heightmap, uv + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
  522. ( texture2D( heightmap, uv + vec2( 0, - cellSize.y ) ).x - texture2D( heightmap, uv + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS,
  523. 1.0 );
  524. #ifdef USE_TANGENT
  525. vec3 objectTangent = vec3( tangent.xyz );
  526. #endif
  527. `,
  528. begin_vertex: /* glsl */`
  529. float heightValue = texture2D( heightmap, uv ).x;
  530. vec3 transformed = vec3( position.x, position.y, heightValue );
  531. #ifdef USE_ALPHAHASH
  532. vPosition = vec3( position );
  533. #endif
  534. `,
  535. };
  536. </script>
  537. </body>
  538. </html>
粤ICP备19079148号