Spaces:
Running
Running
File size: 4,490 Bytes
a28eca3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
import { Vector2, TempNode, NodeUpdateType } from 'three/webgpu';
import { nodeObject, Fn, uv, uniform, convertToTexture, vec2, vec3, vec4, mat3, luminance, add } from 'three/tsl';
/** @module SobelOperatorNode **/
/**
* Post processing node for detecting edges with a sobel filter.
* A sobel filter should be applied after tone mapping and output color
* space conversion.
*
* @augments TempNode
*/
class SobelOperatorNode extends TempNode {
static get type() {
return 'SobelOperatorNode';
}
/**
* Constructs a new sobel operator node.
*
* @param {TextureNode} textureNode - The texture node that represents the input of the effect.
*/
constructor( textureNode ) {
super( 'vec4' );
/**
* The texture node that represents the input of the effect.
*
* @type {TextureNode}
*/
this.textureNode = textureNode;
/**
* 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;
/**
* A uniform node holding the inverse resolution value.
*
* @private
* @type {UniformNode<vec2>}
*/
this._invSize = uniform( new Vector2() );
}
/**
* This method is used to update the effect's uniforms once per frame.
*
* @param {NodeFrame} frame - The current node frame.
*/
updateBefore( /* frame */ ) {
const map = this.textureNode.value;
this._invSize.value.set( 1 / map.image.width, 1 / map.image.height );
}
/**
* This method is used to setup the effect's TSL code.
*
* @param {NodeBuilder} builder - The current node builder.
* @return {ShaderCallNodeInternal}
*/
setup( /* builder */ ) {
const { textureNode } = this;
const uvNode = textureNode.uvNode || uv();
const sampleTexture = ( uv ) => textureNode.sample( uv );
const sobel = Fn( () => {
// Sobel Edge Detection (see https://youtu.be/uihBwtPIBxM)
const texel = this._invSize;
// kernel definition (in glsl matrices are filled in column-major order)
const Gx = mat3( - 1, - 2, - 1, 0, 0, 0, 1, 2, 1 ); // x direction kernel
const Gy = mat3( - 1, 0, 1, - 2, 0, 2, - 1, 0, 1 ); // y direction kernel
// fetch the 3x3 neighbourhood of a fragment
// first column
const tx0y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, - 1 ) ) ) ).xyz );
const tx0y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, 0 ) ) ) ).xyz );
const tx0y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, 1 ) ) ) ).xyz );
// second column
const tx1y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, - 1 ) ) ) ).xyz );
const tx1y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, 0 ) ) ) ).xyz );
const tx1y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, 1 ) ) ) ).xyz );
// third column
const tx2y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, - 1 ) ) ) ).xyz );
const tx2y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, 0 ) ) ) ).xyz );
const tx2y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, 1 ) ) ) ).xyz );
// gradient value in x direction
const valueGx = add(
Gx[ 0 ][ 0 ].mul( tx0y0 ),
Gx[ 1 ][ 0 ].mul( tx1y0 ),
Gx[ 2 ][ 0 ].mul( tx2y0 ),
Gx[ 0 ][ 1 ].mul( tx0y1 ),
Gx[ 1 ][ 1 ].mul( tx1y1 ),
Gx[ 2 ][ 1 ].mul( tx2y1 ),
Gx[ 0 ][ 2 ].mul( tx0y2 ),
Gx[ 1 ][ 2 ].mul( tx1y2 ),
Gx[ 2 ][ 2 ].mul( tx2y2 )
);
// gradient value in y direction
const valueGy = add(
Gy[ 0 ][ 0 ].mul( tx0y0 ),
Gy[ 1 ][ 0 ].mul( tx1y0 ),
Gy[ 2 ][ 0 ].mul( tx2y0 ),
Gy[ 0 ][ 1 ].mul( tx0y1 ),
Gy[ 1 ][ 1 ].mul( tx1y1 ),
Gy[ 2 ][ 1 ].mul( tx2y1 ),
Gy[ 0 ][ 2 ].mul( tx0y2 ),
Gy[ 1 ][ 2 ].mul( tx1y2 ),
Gy[ 2 ][ 2 ].mul( tx2y2 )
);
// magnitude of the total gradient
const G = valueGx.mul( valueGx ).add( valueGy.mul( valueGy ) ).sqrt();
return vec4( vec3( G ), 1 );
} );
const outputNode = sobel();
return outputNode;
}
}
export default SobelOperatorNode;
/**
* TSL function for creating a sobel operator node which performs edge detection with a sobel filter.
*
* @function
* @param {Node<vec4>} node - The node that represents the input of the effect.
* @returns {SobelOperatorNode}
*/
export const sobel = ( node ) => nodeObject( new SobelOperatorNode( convertToTexture( node ) ) );
|