VolumeNodeMaterial.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import NodeMaterial from './NodeMaterial.js';
  2. import { property } from '../../nodes/core/PropertyNode.js';
  3. import { materialReference } from '../../nodes/accessors/MaterialReferenceNode.js';
  4. import { modelWorldMatrixInverse } from '../../nodes/accessors/ModelNode.js';
  5. import { cameraPosition } from '../../nodes/accessors/Camera.js';
  6. import { positionGeometry } from '../../nodes/accessors/Position.js';
  7. import { Fn, varying, float, vec2, vec3, vec4 } from '../../nodes/tsl/TSLBase.js';
  8. import { min, max } from '../../nodes/math/MathNode.js';
  9. import { Loop, Break } from '../../nodes/utils/LoopNode.js';
  10. import { texture3D } from '../../nodes/accessors/Texture3DNode.js';
  11. import { Color } from '../../math/Color.js';
  12. /** @module VolumeNodeMaterial **/
  13. /**
  14. * Node material intended for volume rendering. The volumetic data are
  15. * defined with an instance of {@link Data3DTexture}.
  16. *
  17. * @augments NodeMaterial
  18. */
  19. class VolumeNodeMaterial extends NodeMaterial {
  20. static get type() {
  21. return 'VolumeNodeMaterial';
  22. }
  23. /**
  24. * Constructs a new volume node material.
  25. *
  26. * @param {Object?} parameters - The configuration parameter.
  27. */
  28. constructor( parameters ) {
  29. super();
  30. /**
  31. * This flag can be used for type testing.
  32. *
  33. * @type {Boolean}
  34. * @readonly
  35. * @default true
  36. */
  37. this.isVolumeNodeMaterial = true;
  38. /**
  39. * The base color of the volume.
  40. *
  41. * @type {Color}
  42. * @default 100
  43. */
  44. this.base = new Color( 0xffffff );
  45. /**
  46. * A 3D data texture holding the volumetric data.
  47. *
  48. * @type {Data3DTexture?}
  49. * @default null
  50. */
  51. this.map = null;
  52. /**
  53. * This number of samples for each ray that hits the mesh's surface
  54. * and travels through the volume.
  55. *
  56. * @type {Number}
  57. * @default 100
  58. */
  59. this.steps = 100;
  60. /**
  61. * Callback for {@link VolumeNodeMaterial#testNode}.
  62. *
  63. * @callback testNodeCallback
  64. * @param {Data3DTexture<float>} map - The 3D texture.
  65. * @param {Node<float>} mapValue - The sampled value inside the volume.
  66. * @param {Node<vec3>} probe - The probe which is the entry point of the ray on the mesh's surface.
  67. * @param {Node<vec4>} finalColor - The final color.
  68. */
  69. /**
  70. * The volume rendering of this material works by shooting rays
  71. * from the camera position through each fragment of the mesh's
  72. * surface and sample the inner volume in a raymarching fashion
  73. * mutiple times.
  74. *
  75. * This node can be used to assign a callback function of type `Fn`
  76. * that will be exexuted per sample. The callback receives the
  77. * texture, the sampled texture value as well as position on the surface
  78. * where the rays enters the volume. The last parameter is a color
  79. * that allows the callback to determine the final color.
  80. *
  81. * @type {testNodeCallback?}
  82. * @default null
  83. */
  84. this.testNode = null;
  85. this.setValues( parameters );
  86. }
  87. /**
  88. * Setups the vertex and fragment stage of this node material.
  89. *
  90. * @param {NodeBuilder} builder - The current node builder.
  91. */
  92. setup( builder ) {
  93. const map = texture3D( this.map, null, 0 );
  94. const hitBox = Fn( ( { orig, dir } ) => {
  95. const box_min = vec3( - 0.5 );
  96. const box_max = vec3( 0.5 );
  97. const inv_dir = dir.reciprocal();
  98. const tmin_tmp = box_min.sub( orig ).mul( inv_dir );
  99. const tmax_tmp = box_max.sub( orig ).mul( inv_dir );
  100. const tmin = min( tmin_tmp, tmax_tmp );
  101. const tmax = max( tmin_tmp, tmax_tmp );
  102. const t0 = max( tmin.x, max( tmin.y, tmin.z ) );
  103. const t1 = min( tmax.x, min( tmax.y, tmax.z ) );
  104. return vec2( t0, t1 );
  105. } );
  106. this.fragmentNode = Fn( () => {
  107. const vOrigin = varying( vec3( modelWorldMatrixInverse.mul( vec4( cameraPosition, 1.0 ) ) ) );
  108. const vDirection = varying( positionGeometry.sub( vOrigin ) );
  109. const rayDir = vDirection.normalize();
  110. const bounds = vec2( hitBox( { orig: vOrigin, dir: rayDir } ) ).toVar();
  111. bounds.x.greaterThan( bounds.y ).discard();
  112. bounds.assign( vec2( max( bounds.x, 0.0 ), bounds.y ) );
  113. const p = vec3( vOrigin.add( bounds.x.mul( rayDir ) ) ).toVar();
  114. const inc = vec3( rayDir.abs().reciprocal() ).toVar();
  115. const delta = float( min( inc.x, min( inc.y, inc.z ) ) ).toVar( 'delta' ); // used 'delta' name in loop
  116. delta.divAssign( materialReference( 'steps', 'float' ) );
  117. const ac = vec4( materialReference( 'base', 'color' ), 0.0 ).toVar();
  118. Loop( { type: 'float', start: bounds.x, end: bounds.y, update: '+= delta' }, () => {
  119. const d = property( 'float', 'd' ).assign( map.sample( p.add( 0.5 ) ).r );
  120. if ( this.testNode !== null ) {
  121. this.testNode( { map: map, mapValue: d, probe: p, finalColor: ac } ).append();
  122. } else {
  123. // default to show surface of mesh
  124. ac.a.assign( 1 );
  125. Break();
  126. }
  127. p.addAssign( rayDir.mul( delta ) );
  128. } );
  129. ac.a.equal( 0 ).discard();
  130. return vec4( ac );
  131. } )();
  132. super.setup( builder );
  133. }
  134. }
  135. export default VolumeNodeMaterial;
粤ICP备19079148号