import TempNode from '../core/TempNode.js'; import { texture } from '../accessors/TextureNode.js'; import { addNodeClass } from '../core/Node.js'; import { uv } from '../accessors/UVNode.js'; import { normalView } from '../accessors/NormalNode.js'; import { positionView } from '../accessors/PositionNode.js'; import { faceDirection } from './FrontFacingNode.js'; import { tslFn, nodeProxy, vec2 } from '../shadernode/ShaderNode.js'; // Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen // https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2) const dHdxy_fwd = tslFn( ( { bumpTexture, bumpScale } ) => { const uvNode = uv(); const Hll = texture( bumpTexture, uvNode ).x; return vec2( texture( bumpTexture, uvNode.add( uvNode.dFdx() ) ).x.sub( Hll ), texture( bumpTexture, uvNode.add( uvNode.dFdy() ) ).x.sub( Hll ) ).mul( bumpScale ); } ); const perturbNormalArb = tslFn( ( inputs ) => { const { surf_pos, surf_norm, dHdxy } = inputs; const vSigmaX = surf_pos.dFdx(); const vSigmaY = surf_pos.dFdy(); const vN = surf_norm; // normalized const R1 = vSigmaY.cross( vN ); const R2 = vN.cross( vSigmaX ); const fDet = vSigmaX.dot( R1 ).mul( faceDirection ); const vGrad = fDet.sign().mul( dHdxy.x.mul( R1 ).add( dHdxy.y.mul( R2 ) ) ); return fDet.abs().mul( surf_norm ).sub( vGrad ).normalize(); } ); class BumpMapNode extends TempNode { constructor( texture, scaleNode = null ) { super( 'vec3' ); this.texture = texture; this.scaleNode = scaleNode; } construct() { const bumpScale = this.scaleNode !== null ? this.scaleNode : 1; const dHdxy = dHdxy_fwd( { bumpTexture: this.texture, bumpScale } ); return perturbNormalArb( { surf_pos: positionView.negate(), surf_norm: normalView, dHdxy } ); } } export default BumpMapNode; export const bumpMap = nodeProxy( BumpMapNode ); addNodeClass( BumpMapNode );