SDFGeometryGenerator.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /**
  2. * @author santiago / @glitch_life
  3. * wrapper of https://www.npmjs.com/package/isosurface by https://github.com/mikolalysenko
  4. *
  5. * Returns BufferGeometry from SDF
  6. */
  7. import {
  8. BufferAttribute,
  9. BufferGeometry,
  10. FloatType,
  11. Mesh,
  12. OrthographicCamera,
  13. PlaneGeometry,
  14. Scene,
  15. ShaderMaterial,
  16. Vector2,
  17. WebGLRenderTarget
  18. } from 'three';
  19. import { surfaceNet } from './../libs/surfaceNet.js';
  20. class SDFGeometryGenerator {
  21. constructor( renderer ) {
  22. this.renderer = renderer;
  23. }
  24. generate( res = 64, distFunc = 'float dist( vec3 p ){ return length(p) - 0.5; }', bounds = 1 ) {
  25. let w, h;
  26. if ( res == 8 ) [ w, h ] = [ 32, 16 ];
  27. else if ( res == 16 ) [ w, h ] = [ 64, 64 ];
  28. else if ( res == 32 ) [ w, h ] = [ 256, 128 ];
  29. else if ( res == 64 ) [ w, h ] = [ 512, 512 ];
  30. else if ( res == 128 ) [ w, h ] = [ 2048, 1024 ];
  31. else if ( res == 256 ) [ w, h ] = [ 4096, 4096 ];
  32. else if ( res == 512 ) [ w, h ] = [ 16384, 8096 ];
  33. else if ( res == 1024 ) [ w, h ] = [ 32768, 32768 ];
  34. else throw new Error( 'THREE.SDFGeometryGenerator: Resolution must be in range 8 < res < 1024 and must be ^2' );
  35. const maxTexSize = this.renderer.capabilities.maxTextureSize;
  36. if ( w > maxTexSize || h > maxTexSize ) throw new Error( 'THREE.SDFGeometryGenerator: Your device does not support this resolution ( ' + res + ' ), decrease [res] param.' );
  37. const [ tilesX, tilesY ] = [ ( w / res ), ( h / res ) ];
  38. const sdfCompute = `
  39. varying vec2 vUv;
  40. uniform float tileNum;
  41. uniform float bounds;
  42. [#dist#]
  43. void main() { gl_FragColor=vec4( ( dist( vec3( vUv, tileNum ) * 2.0 * bounds - vec3( bounds ) ) < 0.00001 ) ? 1.0 : 0.0 ); }
  44. `;
  45. const sdfRT = this.computeSDF( w, h, tilesX, tilesY, bounds, sdfCompute.replace( '[#dist#]', distFunc ) );
  46. const read = new Float32Array( w * h * 4 );
  47. this.renderer.readRenderTargetPixels( sdfRT, 0, 0, w, h, read );
  48. sdfRT.dispose();
  49. //
  50. const mesh = surfaceNet( [ res, res, res ], ( x, y, z ) => {
  51. x = ( x + bounds ) * ( res / ( bounds * 2 ) );
  52. y = ( y + bounds ) * ( res / ( bounds * 2 ) );
  53. z = ( z + bounds ) * ( res / ( bounds * 2 ) );
  54. let p = ( x + ( z % tilesX ) * res ) + y * w + ( Math.floor( z / tilesX ) * res * w );
  55. p *= 4;
  56. return ( read[ p + 3 ] > 0 ) ? - 0.000000001 : 1;
  57. }, [[ - bounds, - bounds, - bounds ], [ bounds, bounds, bounds ]] );
  58. const ps = [], ids = [];
  59. const geometry = new BufferGeometry();
  60. mesh.positions.forEach( p => {
  61. ps.push( p[ 0 ], p[ 1 ], p[ 2 ] );
  62. } );
  63. mesh.cells.forEach( p => ids.push( p[ 0 ], p[ 1 ], p[ 2 ] ) );
  64. geometry.setAttribute( 'position', new BufferAttribute( new Float32Array( ps ), 3 ) );
  65. geometry.setIndex( ids );
  66. return geometry;
  67. }
  68. computeSDF( width, height, tilesX, tilesY, bounds, shader ) {
  69. const rt = new WebGLRenderTarget( width, height, { type: FloatType } );
  70. const scn = new Scene();
  71. const cam = new OrthographicCamera();
  72. const tiles = tilesX * tilesY;
  73. let currentTile = 0;
  74. Object.assign( cam, { left: width / - 2, right: width / 2, top: height / 2, bottom: height / - 2 } ).updateProjectionMatrix();
  75. cam.position.z = 2;
  76. const tileSize = width / tilesX;
  77. const geometry = new PlaneGeometry( tileSize, tileSize );
  78. while ( currentTile ++ < tiles ) {
  79. const c = currentTile - 1;
  80. const [ px, py ] = [ ( tileSize ) / 2 + ( c % tilesX ) * ( tileSize ) - width / 2, ( tileSize ) / 2 + Math.floor( c / tilesX ) * ( tileSize ) - height / 2 ];
  81. const compPlane = new Mesh( geometry, new ShaderMaterial( {
  82. uniforms: {
  83. res: { value: new Vector2( width, height ) },
  84. tileNum: { value: c / ( tilesX * tilesY - 1 ) },
  85. bounds: { value: bounds }
  86. },
  87. vertexShader: 'varying vec2 vUv;void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}',
  88. fragmentShader: shader
  89. } ) );
  90. compPlane.position.set( px, py, 0 );
  91. scn.add( compPlane );
  92. }
  93. this.renderer.setRenderTarget( rt );
  94. this.renderer.render( scn, cam );
  95. this.renderer.setRenderTarget( null );
  96. //
  97. geometry.dispose();
  98. scn.traverse( function ( object ) {
  99. if ( object.material !== undefined ) object.material.dispose();
  100. } );
  101. return rt;
  102. }
  103. }
  104. export { SDFGeometryGenerator };