123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- import { NearestFilter, Vector4, TempNode, NodeUpdateType, PassNode } from 'three/webgpu';
- import { nodeObject, Fn, float, uv, uniform, convertToTexture, vec2, vec3, clamp, floor, dot, smoothstep, If, sign, step, mrt, output, normalView, property } from 'three/tsl';
- /**
- * A inner node definition that implements the actual pixelation TSL code.
- *
- * @inner
- * @augments TempNode
- */
- class PixelationNode extends TempNode {
- static get type() {
- return 'PixelationNode';
- }
- /**
- * Constructs a new pixelation node.
- *
- * @param {TextureNode} textureNode - The texture node that represents the beauty pass.
- * @param {TextureNode} depthNode - The texture that represents the beauty's depth.
- * @param {TextureNode} normalNode - The texture that represents the beauty's normals.
- * @param {Node<float>} pixelSize - The pixel size.
- * @param {Node<float>} normalEdgeStrength - The normal edge strength.
- * @param {Node<float>} depthEdgeStrength - The depth edge strength.
- */
- constructor( textureNode, depthNode, normalNode, pixelSize, normalEdgeStrength, depthEdgeStrength ) {
- super( 'vec4' );
- /**
- * The texture node that represents the beauty pass.
- *
- * @type {TextureNode}
- */
- this.textureNode = textureNode;
- /**
- * The texture that represents the beauty's depth.
- *
- * @type {TextureNode}
- */
- this.depthNode = depthNode;
- /**
- * The texture that represents the beauty's normals.
- *
- * @type {TextureNode}
- */
- this.normalNode = normalNode;
- /**
- * The pixel size.
- *
- * @type {Node<float>}
- */
- this.pixelSize = pixelSize;
- /**
- * The pixel size.
- *
- * @type {Node<float>}
- */
- this.normalEdgeStrength = normalEdgeStrength;
- /**
- * The depth edge strength.
- *
- * @type {Node<float>}
- */
- this.depthEdgeStrength = depthEdgeStrength;
- /**
- * Uniform node that represents the resolution.
- *
- * @type {Node<vec4>}
- */
- this._resolution = uniform( new Vector4() );
- /**
- * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates
- * its internal uniforms once per frame in `updateBefore()`.
- *
- * @type {string}
- * @default 'frame'
- */
- this.updateBeforeType = NodeUpdateType.FRAME;
- }
- /**
- * This method is used to update uniforms once per frame.
- *
- * @param {NodeFrame} frame - The current node frame.
- */
- updateBefore() {
- const map = this.textureNode.value;
- const width = map.image.width;
- const height = map.image.height;
- this._resolution.value.set( width, height, 1 / width, 1 / height );
- }
- /**
- * This method is used to setup the effect's TSL code.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {ShaderCallNodeInternal}
- */
- setup() {
- const { textureNode, depthNode, normalNode } = this;
- const uvNodeTexture = textureNode.uvNode || uv();
- const uvNodeDepth = depthNode.uvNode || uv();
- const uvNodeNormal = normalNode.uvNode || uv();
- const sampleTexture = () => textureNode.sample( uvNodeTexture );
- const sampleDepth = ( x, y ) => depthNode.sample( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r;
- const sampleNormal = ( x, y ) => normalNode.sample( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.normalize();
- const depthEdgeIndicator = ( depth ) => {
- const diff = property( 'float', 'diff' );
- diff.addAssign( clamp( sampleDepth( 1, 0 ).sub( depth ) ) );
- diff.addAssign( clamp( sampleDepth( - 1, 0 ).sub( depth ) ) );
- diff.addAssign( clamp( sampleDepth( 0, 1 ).sub( depth ) ) );
- diff.addAssign( clamp( sampleDepth( 0, - 1 ).sub( depth ) ) );
- return floor( smoothstep( 0.01, 0.02, diff ).mul( 2 ) ).div( 2 );
- };
- const neighborNormalEdgeIndicator = ( x, y, depth, normal ) => {
- const depthDiff = sampleDepth( x, y ).sub( depth );
- const neighborNormal = sampleNormal( x, y );
- // Edge pixels should yield to faces who's normals are closer to the bias normal.
- const normalEdgeBias = vec3( 1, 1, 1 ); // This should probably be a parameter.
- const normalDiff = dot( normal.sub( neighborNormal ), normalEdgeBias );
- const normalIndicator = clamp( smoothstep( - 0.01, 0.01, normalDiff ), 0.0, 1.0 );
- // Only the shallower pixel should detect the normal edge.
- const depthIndicator = clamp( sign( depthDiff.mul( .25 ).add( .0025 ) ), 0.0, 1.0 );
- return float( 1.0 ).sub( dot( normal, neighborNormal ) ).mul( depthIndicator ).mul( normalIndicator );
- };
- const normalEdgeIndicator = ( depth, normal ) => {
- const indicator = property( 'float', 'indicator' );
- indicator.addAssign( neighborNormalEdgeIndicator( 0, - 1, depth, normal ) );
- indicator.addAssign( neighborNormalEdgeIndicator( 0, 1, depth, normal ) );
- indicator.addAssign( neighborNormalEdgeIndicator( - 1, 0, depth, normal ) );
- indicator.addAssign( neighborNormalEdgeIndicator( 1, 0, depth, normal ) );
- return step( 0.1, indicator );
- };
- const pixelation = Fn( () => {
- const texel = sampleTexture();
- const depth = property( 'float', 'depth' );
- const normal = property( 'vec3', 'normal' );
- If( this.depthEdgeStrength.greaterThan( 0.0 ).or( this.normalEdgeStrength.greaterThan( 0.0 ) ), () => {
- depth.assign( sampleDepth( 0, 0 ) );
- normal.assign( sampleNormal( 0, 0 ) );
- } );
- const dei = property( 'float', 'dei' );
- If( this.depthEdgeStrength.greaterThan( 0.0 ), () => {
- dei.assign( depthEdgeIndicator( depth ) );
- } );
- const nei = property( 'float', 'nei' );
- If( this.normalEdgeStrength.greaterThan( 0.0 ), () => {
- nei.assign( normalEdgeIndicator( depth, normal ) );
- } );
- const strength = dei.greaterThan( 0 ).select( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) );
- return texel.mul( strength );
- } );
- const outputNode = pixelation();
- return outputNode;
- }
- }
- 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 ) ) );
- /**
- * A special render pass node that renders the scene with a pixelation effect.
- *
- * @augments PassNode
- * @three_import import { pixelationPass } from 'three/addons/tsl/display/PixelationPassNode.js';
- */
- class PixelationPassNode extends PassNode {
- static get type() {
- return 'PixelationPassNode';
- }
- /**
- * Constructs a new pixelation pass node.
- *
- * @param {Scene} scene - The scene to render.
- * @param {Camera} camera - The camera to render the scene with.
- * @param {Node<float> | number} [pixelSize=6] - The pixel size.
- * @param {Node<float> | number} [normalEdgeStrength=0.3] - The normal edge strength.
- * @param {Node<float> | number} [depthEdgeStrength=0.4] - The depth edge strength.
- */
- constructor( scene, camera, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) {
- super( PassNode.COLOR, scene, camera, { minFilter: NearestFilter, magFilter: NearestFilter } );
- /**
- * The pixel size.
- *
- * @type {number}
- * @default 6
- */
- this.pixelSize = pixelSize;
- /**
- * The normal edge strength.
- *
- * @type {number}
- * @default 0.3
- */
- this.normalEdgeStrength = normalEdgeStrength;
- /**
- * The depth edge strength.
- *
- * @type {number}
- * @default 0.4
- */
- this.depthEdgeStrength = depthEdgeStrength;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isPixelationPassNode = true;
- this._mrt = mrt( {
- output: output,
- normal: normalView
- } );
- }
- /**
- * Sets the size of the pass.
- *
- * @param {number} width - The width of the pass.
- * @param {number} height - The height of the pass.
- */
- setSize( width, height ) {
- const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize;
- const adjustedWidth = Math.floor( width / pixelSize );
- const adjustedHeight = Math.floor( height / pixelSize );
- super.setSize( adjustedWidth, adjustedHeight );
- }
- /**
- * This method is used to setup the effect's TSL code.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {PixelationNode}
- */
- setup() {
- const color = super.getTextureNode( 'output' );
- const depth = super.getTextureNode( 'depth' );
- const normal = super.getTextureNode( 'normal' );
- return pixelation( color, depth, normal, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength );
- }
- }
- /**
- * TSL function for creating a pixelation render pass node for post processing.
- *
- * @tsl
- * @function
- * @param {Scene} scene - The scene to render.
- * @param {Camera} camera - The camera to render the scene with.
- * @param {Node<float> | number} [pixelSize=6] - The pixel size.
- * @param {Node<float> | number} [normalEdgeStrength=0.3] - The normal edge strength.
- * @param {Node<float> | number} [depthEdgeStrength=0.4] - The depth edge strength.
- * @returns {PixelationPassNode}
- */
- export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) );
- export default PixelationPassNode;
|