Water2Mesh.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import {
  2. Color,
  3. Mesh,
  4. NodeMaterial,
  5. Vector2,
  6. Vector3
  7. } from 'three';
  8. import { vec2, viewportSafeUV, viewportSharedTexture, reflector, pow, float, abs, texture, uniform, TempNode, NodeUpdateType, vec4, tslFn, cameraPosition, positionWorld, uv, mix, vec3, normalize, max, dot, viewportTopLeft } from 'three/tsl';
  9. /**
  10. * References:
  11. * https://alex.vlachos.com/graphics/Vlachos-SIGGRAPH10-WaterFlow.pdf
  12. * http://graphicsrunner.blogspot.de/2010/08/water-using-flow-maps.html
  13. *
  14. */
  15. class WaterMesh extends Mesh {
  16. constructor( geometry, options = {} ) {
  17. const material = new NodeMaterial();
  18. super( geometry, material );
  19. this.isWater = true;
  20. material.normals = false;
  21. material.fragmentNode = new WaterNode( options, this );
  22. }
  23. }
  24. class WaterNode extends TempNode {
  25. constructor( options, waterBody ) {
  26. super( 'vec4' );
  27. this.waterBody = waterBody;
  28. this.normalMap0 = texture( options.normalMap0 );
  29. this.normalMap1 = texture( options.normalMap1 );
  30. this.flowMap = texture( options.flowMap !== undefined ? options.flowMap : null );
  31. this.color = uniform( options.color !== undefined ? new Color( options.color ) : new Color( 0xffffff ) );
  32. this.flowDirection = uniform( options.flowDirection !== undefined ? options.flowDirection : new Vector2( 1, 0 ) );
  33. this.flowSpeed = uniform( options.flowSpeed !== undefined ? options.flowSpeed : 0.03 );
  34. this.reflectivity = uniform( options.reflectivity !== undefined ? options.reflectivity : 0.02 );
  35. this.scale = uniform( options.scale !== undefined ? options.scale : 1 );
  36. this.flowConfig = uniform( new Vector3() );
  37. this.updateBeforeType = NodeUpdateType.RENDER;
  38. this._cycle = 0.15; // a cycle of a flow map phase
  39. this._halfCycle = this._cycle * 0.5;
  40. this._USE_FLOW = options.flowMap !== undefined;
  41. }
  42. updateFlow( delta ) {
  43. this.flowConfig.value.x += this.flowSpeed.value * delta; // flowMapOffset0
  44. this.flowConfig.value.y = this.flowConfig.value.x + this._halfCycle; // flowMapOffset1
  45. // Important: The distance between offsets should be always the value of "halfCycle".
  46. // Moreover, both offsets should be in the range of [ 0, cycle ].
  47. // This approach ensures a smooth water flow and avoids "reset" effects.
  48. if ( this.flowConfig.value.x >= this._cycle ) {
  49. this.flowConfig.value.x = 0;
  50. this.flowConfig.value.y = this._halfCycle;
  51. } else if ( this.flowConfig.value.y >= this._cycle ) {
  52. this.flowConfig.value.y = this.flowConfig.value.y - this._cycle;
  53. }
  54. this.flowConfig.value.z = this._halfCycle;
  55. }
  56. updateBefore( frame ) {
  57. this.updateFlow( frame.deltaTime );
  58. }
  59. setup() {
  60. const outputNode = tslFn( () => {
  61. const flowMapOffset0 = this.flowConfig.x;
  62. const flowMapOffset1 = this.flowConfig.y;
  63. const halfCycle = this.flowConfig.z;
  64. const toEye = normalize( cameraPosition.sub( positionWorld ) );
  65. let flow;
  66. if ( this._USE_FLOW === true ) {
  67. flow = this.flowMap.rg.mul( 2 ).sub( 1 );
  68. } else {
  69. flow = vec2( this.flowDirection.x, this.flowDirection.y );
  70. }
  71. flow.x.mulAssign( - 1 );
  72. // sample normal maps (distort uvs with flowdata)
  73. const uvs = uv();
  74. const normalUv0 = uvs.mul( this.scale ).add( flow.mul( flowMapOffset0 ) );
  75. const normalUv1 = uvs.mul( this.scale ).add( flow.mul( flowMapOffset1 ) );
  76. const normalColor0 = this.normalMap0.uv( normalUv0 );
  77. const normalColor1 = this.normalMap1.uv( normalUv1 );
  78. // linear interpolate to get the final normal color
  79. const flowLerp = abs( halfCycle.sub( flowMapOffset0 ) ).div( halfCycle );
  80. const normalColor = mix( normalColor0, normalColor1, flowLerp );
  81. // calculate normal vector
  82. const normal = normalize( vec3( normalColor.r.mul( 2 ).sub( 1 ), normalColor.b, normalColor.g.mul( 2 ).sub( 1 ) ) );
  83. // calculate the fresnel term to blend reflection and refraction maps
  84. const theta = max( dot( toEye, normal ), 0 );
  85. const reflectance = pow( float( 1.0 ).sub( theta ), 5.0 ).mul( float( 1.0 ).sub( this.reflectivity ) ).add( this.reflectivity );
  86. // reflector, refractor
  87. const offset = normal.xz.mul( 0.05 ).toVar();
  88. const reflectionSampler = reflector();
  89. this.waterBody.add( reflectionSampler.target );
  90. reflectionSampler.uvNode = reflectionSampler.uvNode.add( offset );
  91. const refractorUV = viewportTopLeft.add( offset );
  92. const refractionSampler = viewportSharedTexture( viewportSafeUV( refractorUV ) );
  93. // calculate final uv coords
  94. return vec4( this.color, 1.0 ).mul( mix( refractionSampler, reflectionSampler, reflectance ) );
  95. } )();
  96. return outputNode;
  97. }
  98. }
  99. export { WaterMesh };
粤ICP备19079148号