VolumeNodeMaterial.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js';
  2. import { varying } from '../core/VaryingNode.js';
  3. import { property } from '../core/PropertyNode.js';
  4. import { materialReference } from '../accessors/MaterialReferenceNode.js';
  5. import { modelWorldMatrixInverse } from '../accessors/ModelNode.js';
  6. import { cameraPosition } from '../accessors/CameraNode.js';
  7. import { positionGeometry } from '../accessors/PositionNode.js';
  8. import { tslFn, vec2, vec3, vec4 } from '../shadernode/ShaderNode.js';
  9. import { min, max } from '../math/MathNode.js';
  10. import { loop, Break } from '../utils/LoopNode.js';
  11. import { texture3D } from '../accessors/Texture3DNode.js';
  12. class VolumeNodeMaterial extends NodeMaterial {
  13. constructor( params = {} ) {
  14. super();
  15. this.normals = false;
  16. this.lights = false;
  17. this.isVolumeNodeMaterial = true;
  18. this.testNode = null;
  19. this.setValues( params );
  20. }
  21. setup( builder ) {
  22. const map = texture3D( this.map, null, 0 );
  23. const hitBox = tslFn( ( { orig, dir } ) => {
  24. const box_min = vec3( - 0.5 );
  25. const box_max = vec3( 0.5 );
  26. const inv_dir = dir.reciprocal();
  27. const tmin_tmp = box_min.sub( orig ).mul( inv_dir );
  28. const tmax_tmp = box_max.sub( orig ).mul( inv_dir );
  29. const tmin = min( tmin_tmp, tmax_tmp );
  30. const tmax = max( tmin_tmp, tmax_tmp );
  31. const t0 = max( tmin.x, max( tmin.y, tmin.z ) );
  32. const t1 = min( tmax.x, min( tmax.y, tmax.z ) );
  33. return vec2( t0, t1 );
  34. } );
  35. this.fragmentNode = tslFn( () => {
  36. const vOrigin = varying( vec3( modelWorldMatrixInverse.mul( vec4( cameraPosition, 1.0 ) ) ) );
  37. const vDirection = varying( positionGeometry.sub( vOrigin ) );
  38. const rayDir = vDirection.normalize();
  39. const bounds = property( 'vec2', 'bounds' ).assign( hitBox( { orig: vOrigin, dir: rayDir } ) );
  40. bounds.x.greaterThan( bounds.y ).discard();
  41. bounds.assign( vec2( max( bounds.x, 0.0 ), bounds.y ) );
  42. const p = property( 'vec3', 'p' ).assign( vOrigin.add( bounds.x.mul( rayDir ) ) );
  43. const inc = property( 'vec3', 'inc' ).assign( vec3( rayDir.abs().reciprocal() ) );
  44. const delta = property( 'float', 'delta' ).assign( min( inc.x, min( inc.y, inc.z ) ) );
  45. delta.divAssign( materialReference( 'steps', 'float' ) );
  46. const ac = property( 'vec4', 'ac' ).assign( vec4( materialReference( 'base', 'color' ), 0.0 ) );
  47. loop( { type: 'float', start: bounds.x, end: bounds.y, update: '+= delta' }, () => {
  48. const d = property( 'float', 'd' ).assign( map.uv( p.add( 0.5 ) ).r );
  49. if ( this.testNode !== null ) {
  50. this.testNode( { map: map, mapValue: d, probe: p, finalColor: ac } ).append();
  51. } else {
  52. // default to show surface of mesh
  53. ac.a.assign( 1 );
  54. Break();
  55. }
  56. p.addAssign( rayDir.mul( delta ) );
  57. } );
  58. ac.a.equal( 0 ).discard();
  59. return vec4( ac );
  60. } )();
  61. super.setup( builder );
  62. }
  63. }
  64. export default VolumeNodeMaterial;
  65. addNodeMaterial( 'VolumeNodeMaterial', VolumeNodeMaterial );