PixelationPassNode.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. import { NearestFilter, Vector4, TempNode, NodeUpdateType, PassNode } from 'three/webgpu';
  2. import { nodeObject, Fn, float, uv, uniform, convertToTexture, vec2, vec3, clamp, floor, dot, smoothstep, If, sign, step, mrt, output, normalView, property } from 'three/tsl';
  3. /**
  4. * A inner node definition that implements the actual pixelation TSL code.
  5. *
  6. * @inner
  7. * @augments TempNode
  8. */
  9. class PixelationNode extends TempNode {
  10. static get type() {
  11. return 'PixelationNode';
  12. }
  13. /**
  14. * Constructs a new pixelation node.
  15. *
  16. * @param {TextureNode} textureNode - The texture node that represents the beauty pass.
  17. * @param {TextureNode} depthNode - The texture that represents the beauty's depth.
  18. * @param {TextureNode} normalNode - The texture that represents the beauty's normals.
  19. * @param {Node<float>} pixelSize - The pixel size.
  20. * @param {Node<float>} normalEdgeStrength - The normal edge strength.
  21. * @param {Node<float>} depthEdgeStrength - The depth edge strength.
  22. */
  23. constructor( textureNode, depthNode, normalNode, pixelSize, normalEdgeStrength, depthEdgeStrength ) {
  24. super( 'vec4' );
  25. /**
  26. * The texture node that represents the beauty pass.
  27. *
  28. * @type {TextureNode}
  29. */
  30. this.textureNode = textureNode;
  31. /**
  32. * The texture that represents the beauty's depth.
  33. *
  34. * @type {TextureNode}
  35. */
  36. this.depthNode = depthNode;
  37. /**
  38. * The texture that represents the beauty's normals.
  39. *
  40. * @type {TextureNode}
  41. */
  42. this.normalNode = normalNode;
  43. /**
  44. * The pixel size.
  45. *
  46. * @type {Node<float>}
  47. */
  48. this.pixelSize = pixelSize;
  49. /**
  50. * The pixel size.
  51. *
  52. * @type {Node<float>}
  53. */
  54. this.normalEdgeStrength = normalEdgeStrength;
  55. /**
  56. * The depth edge strength.
  57. *
  58. * @type {Node<float>}
  59. */
  60. this.depthEdgeStrength = depthEdgeStrength;
  61. /**
  62. * Uniform node that represents the resolution.
  63. *
  64. * @private
  65. * @type {Node<vec4>}
  66. */
  67. this._resolution = uniform( new Vector4() );
  68. /**
  69. * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates
  70. * its internal uniforms once per frame in `updateBefore()`.
  71. *
  72. * @type {string}
  73. * @default 'frame'
  74. */
  75. this.updateBeforeType = NodeUpdateType.FRAME;
  76. }
  77. /**
  78. * This method is used to update uniforms once per frame.
  79. *
  80. * @param {NodeFrame} frame - The current node frame.
  81. */
  82. updateBefore() {
  83. const map = this.textureNode.value;
  84. const width = map.image.width;
  85. const height = map.image.height;
  86. this._resolution.value.set( width, height, 1 / width, 1 / height );
  87. }
  88. /**
  89. * This method is used to setup the effect's TSL code.
  90. *
  91. * @param {NodeBuilder} builder - The current node builder.
  92. * @return {ShaderCallNodeInternal}
  93. */
  94. setup() {
  95. const { textureNode, depthNode, normalNode } = this;
  96. const uvNodeTexture = textureNode.uvNode || uv();
  97. const uvNodeDepth = depthNode.uvNode || uv();
  98. const uvNodeNormal = normalNode.uvNode || uv();
  99. const sampleTexture = () => textureNode.sample( uvNodeTexture );
  100. const sampleDepth = ( x, y ) => depthNode.sample( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r;
  101. const sampleNormal = ( x, y ) => normalNode.sample( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.normalize();
  102. const depthEdgeIndicator = ( depth ) => {
  103. const diff = property( 'float', 'diff' );
  104. diff.addAssign( clamp( sampleDepth( 1, 0 ).sub( depth ) ) );
  105. diff.addAssign( clamp( sampleDepth( - 1, 0 ).sub( depth ) ) );
  106. diff.addAssign( clamp( sampleDepth( 0, 1 ).sub( depth ) ) );
  107. diff.addAssign( clamp( sampleDepth( 0, - 1 ).sub( depth ) ) );
  108. return floor( smoothstep( 0.01, 0.02, diff ).mul( 2 ) ).div( 2 );
  109. };
  110. const neighborNormalEdgeIndicator = ( x, y, depth, normal ) => {
  111. const depthDiff = sampleDepth( x, y ).sub( depth );
  112. const neighborNormal = sampleNormal( x, y );
  113. // Edge pixels should yield to faces who's normals are closer to the bias normal.
  114. const normalEdgeBias = vec3( 1, 1, 1 ); // This should probably be a parameter.
  115. const normalDiff = dot( normal.sub( neighborNormal ), normalEdgeBias );
  116. const normalIndicator = clamp( smoothstep( - 0.01, 0.01, normalDiff ), 0.0, 1.0 );
  117. // Only the shallower pixel should detect the normal edge.
  118. const depthIndicator = clamp( sign( depthDiff.mul( .25 ).add( .0025 ) ), 0.0, 1.0 );
  119. return float( 1.0 ).sub( dot( normal, neighborNormal ) ).mul( depthIndicator ).mul( normalIndicator );
  120. };
  121. const normalEdgeIndicator = ( depth, normal ) => {
  122. const indicator = property( 'float', 'indicator' );
  123. indicator.addAssign( neighborNormalEdgeIndicator( 0, - 1, depth, normal ) );
  124. indicator.addAssign( neighborNormalEdgeIndicator( 0, 1, depth, normal ) );
  125. indicator.addAssign( neighborNormalEdgeIndicator( - 1, 0, depth, normal ) );
  126. indicator.addAssign( neighborNormalEdgeIndicator( 1, 0, depth, normal ) );
  127. return step( 0.1, indicator );
  128. };
  129. const pixelation = Fn( () => {
  130. const texel = sampleTexture();
  131. const depth = property( 'float', 'depth' );
  132. const normal = property( 'vec3', 'normal' );
  133. If( this.depthEdgeStrength.greaterThan( 0.0 ).or( this.normalEdgeStrength.greaterThan( 0.0 ) ), () => {
  134. depth.assign( sampleDepth( 0, 0 ) );
  135. normal.assign( sampleNormal( 0, 0 ) );
  136. } );
  137. const dei = property( 'float', 'dei' );
  138. If( this.depthEdgeStrength.greaterThan( 0.0 ), () => {
  139. dei.assign( depthEdgeIndicator( depth ) );
  140. } );
  141. const nei = property( 'float', 'nei' );
  142. If( this.normalEdgeStrength.greaterThan( 0.0 ).and( normal.length().greaterThan( 0 ) ), () => {
  143. nei.assign( normalEdgeIndicator( depth, normal ) );
  144. } );
  145. const strength = dei.greaterThan( 0 ).select( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) );
  146. return texel.mul( strength );
  147. } );
  148. const outputNode = pixelation();
  149. return outputNode;
  150. }
  151. }
  152. const pixelation = ( node, depthNode, normalNode, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( convertToTexture( node ), convertToTexture( depthNode ), convertToTexture( normalNode ), nodeObject( pixelSize ), nodeObject( normalEdgeStrength ), nodeObject( depthEdgeStrength ) ) );
  153. /**
  154. * A special render pass node that renders the scene with a pixelation effect.
  155. *
  156. * @augments PassNode
  157. * @three_import import { pixelationPass } from 'three/addons/tsl/display/PixelationPassNode.js';
  158. */
  159. class PixelationPassNode extends PassNode {
  160. static get type() {
  161. return 'PixelationPassNode';
  162. }
  163. /**
  164. * Constructs a new pixelation pass node.
  165. *
  166. * @param {Scene} scene - The scene to render.
  167. * @param {Camera} camera - The camera to render the scene with.
  168. * @param {Node<float> | number} [pixelSize=6] - The pixel size.
  169. * @param {Node<float> | number} [normalEdgeStrength=0.3] - The normal edge strength.
  170. * @param {Node<float> | number} [depthEdgeStrength=0.4] - The depth edge strength.
  171. */
  172. constructor( scene, camera, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) {
  173. super( PassNode.COLOR, scene, camera, { minFilter: NearestFilter, magFilter: NearestFilter } );
  174. /**
  175. * The pixel size.
  176. *
  177. * @type {number}
  178. * @default 6
  179. */
  180. this.pixelSize = pixelSize;
  181. /**
  182. * The normal edge strength.
  183. *
  184. * @type {number}
  185. * @default 0.3
  186. */
  187. this.normalEdgeStrength = normalEdgeStrength;
  188. /**
  189. * The depth edge strength.
  190. *
  191. * @type {number}
  192. * @default 0.4
  193. */
  194. this.depthEdgeStrength = depthEdgeStrength;
  195. /**
  196. * This flag can be used for type testing.
  197. *
  198. * @type {boolean}
  199. * @readonly
  200. * @default true
  201. */
  202. this.isPixelationPassNode = true;
  203. this._mrt = mrt( {
  204. output: output,
  205. normal: normalView
  206. } );
  207. }
  208. /**
  209. * Sets the size of the pass.
  210. *
  211. * @param {number} width - The width of the pass.
  212. * @param {number} height - The height of the pass.
  213. */
  214. setSize( width, height ) {
  215. const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize;
  216. const adjustedWidth = Math.floor( width / pixelSize );
  217. const adjustedHeight = Math.floor( height / pixelSize );
  218. super.setSize( adjustedWidth, adjustedHeight );
  219. }
  220. /**
  221. * This method is used to setup the effect's TSL code.
  222. *
  223. * @param {NodeBuilder} builder - The current node builder.
  224. * @return {PixelationNode}
  225. */
  226. setup() {
  227. const color = super.getTextureNode( 'output' );
  228. const depth = super.getTextureNode( 'depth' );
  229. const normal = super.getTextureNode( 'normal' );
  230. return pixelation( color, depth, normal, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength );
  231. }
  232. }
  233. /**
  234. * TSL function for creating a pixelation render pass node for post processing.
  235. *
  236. * @tsl
  237. * @function
  238. * @param {Scene} scene - The scene to render.
  239. * @param {Camera} camera - The camera to render the scene with.
  240. * @param {Node<float> | number} [pixelSize=6] - The pixel size.
  241. * @param {Node<float> | number} [normalEdgeStrength=0.3] - The normal edge strength.
  242. * @param {Node<float> | number} [depthEdgeStrength=0.4] - The depth edge strength.
  243. * @returns {PixelationPassNode}
  244. */
  245. export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) );
  246. export default PixelationPassNode;
粤ICP备19079148号