diff --git a/src/config-hot.ts b/src/config-hot.ts index e64e73f..6a0ac08 100644 --- a/src/config-hot.ts +++ b/src/config-hot.ts @@ -2,9 +2,10 @@ export const RTINSPECTOR_MULTIPLE = false, // RTINSPECTOR_MULTIPLE = true, RTINSPECTOR_CAPTURE_NAME: string | null = null, - // RTINSPECTOR_CAPTURE_NAME: string | null = 'Bloom/swap0', + // RTINSPECTOR_CAPTURE_NAME: string | null = 'Bloom/swap1', + // RTINSPECTOR_CAPTURE_NAME: string | null = 'main/postSwap0', // RTINSPECTOR_CAPTURE_NAME: string | null = 'CameraEntity/cameraTarget', RTINSPECTOR_CAPTURE_INDEX = 0, COMPONENT_UPDATE_BREAKPOINT: string | null = null, - // COMPONENT_UPDATE_BREAKPOINT: string | null = 'Bloom/blitDup1', + // COMPONENT_UPDATE_BREAKPOINT: string | null = 'Bloom/quadUp1', COMPONENT_DRAW_BREAKPOINT: string | null = null; diff --git a/src/entities/Antialias.ts b/src/entities/Antialias.ts new file mode 100644 index 0000000..b630a0f --- /dev/null +++ b/src/entities/Antialias.ts @@ -0,0 +1,44 @@ +import { Entity } from '../heck/Entity'; +import { Material } from '../heck/Material'; +import { Quad } from '../heck/components/Quad'; +import { RenderTarget } from '../heck/RenderTarget'; +import fxaaFrag from '../shaders/fxaa.frag'; +import quadVert from '../shaders/quad.vert'; +import { BufferRenderTarget } from '../heck/BufferRenderTarget'; +import { quadGeometry } from '../globals/quadGeometry'; +import { dummyRenderTarget } from '../globals/dummyRenderTarget'; + +export interface PostOptions { + input: BufferRenderTarget; + target: RenderTarget; +} + +export class Antialias extends Entity { + public constructor( options: PostOptions ) { + super(); + + this.visible = false; + + // -- post ------------------------------------------------------------------------------------- + const material = new Material( + quadVert, + fxaaFrag, + { initOptions: { geometry: quadGeometry, target: dummyRenderTarget } }, + ); + material.addUniformTexture( 'sampler0', options.input.texture ); + + if ( process.env.DEV ) { + if ( module.hot ) { + module.hot.accept( '../shaders/fxaa.frag', () => { + material.replaceShader( quadVert, fxaaFrag ); + } ); + } + } + + this.components.push( new Quad( { + target: options.target, + material, + name: process.env.DEV && 'Antialias/quad', + } ) ); + } +} diff --git a/src/entities/Bloom.ts b/src/entities/Bloom.ts index 14223cf..107dc96 100644 --- a/src/entities/Bloom.ts +++ b/src/entities/Bloom.ts @@ -5,13 +5,12 @@ import { Quad } from '../heck/components/Quad'; import { RenderTarget } from '../heck/RenderTarget'; import { Swap } from '@fms-cat/experimental'; import quadVert from '../shaders/quad.vert'; -import bloomPreFrag from '../shaders/bloom-pre.frag'; -import bloomBlurFrag from '../shaders/bloom-blur.frag'; -import bloomPostFrag from '../shaders/bloom-post.frag'; +import bloomUpFrag from '../shaders/bloom-up.frag'; +import bloomDownFrag from '../shaders/bloom-down.frag'; import { quadGeometry } from '../globals/quadGeometry'; import { dummyRenderTarget } from '../globals/dummyRenderTarget'; -import { Blit } from '../heck/components/Blit'; import { gl } from '../globals/canvas'; +import { Blit } from '../heck/components/Blit'; export interface BloomOptions { input: BufferRenderTarget; @@ -37,68 +36,76 @@ export class Bloom extends Entity { } ), ); - // -- pre ---------------------------------------------------------------------------------------- - const materialBloomPre = new Material( - quadVert, - bloomPreFrag, - { initOptions: { target: dummyRenderTarget, geometry: quadGeometry } }, - ); - materialBloomPre.addUniformTexture( 'sampler0', options.input.texture ); - - this.components.push( new Quad( { - target: swap.o, - material: materialBloomPre, - range: [ -1.0, -1.0, 0.0, 0.0 ], - name: process.env.DEV && 'Bloom/quadPre', + // -- dry -------------------------------------------------------------------------------------- + this.components.push( new Blit( { + src: options.input, + dst: options.target, + name: 'Glitch/blitDry', } ) ); - swap.swap(); - - // -- dup ---------------------------------------------------------------------------------------- + // -- down ------------------------------------------------------------------------------------- for ( let i = 0; i < 6; i ++ ) { - this.components.push( new Blit( { - src: swap.i, - dst: swap.o, - dstRect: i === 0 ? null : [ width / 2, height / 2, width, height ], - name: `Bloom/blitDup${ i }`, - filter: gl.LINEAR, - } ) ); + const isFirst = i === 0; - swap.swap(); - } - - // -- blur --------------------------------------------------------------------------------------- - for ( let i = 0; i < 2; i ++ ) { const material = new Material( quadVert, - bloomBlurFrag, + bloomDownFrag, { initOptions: { target: dummyRenderTarget, geometry: quadGeometry } }, ); - material.addUniform( 'isVert', '1i', i ); - material.addUniformTexture( 'sampler0', swap.i.texture ); + + material.addUniform( 'level', '1f', i ); + material.addUniformTexture( + 'sampler0', + isFirst ? options.input.texture : swap.i.texture, + ); + + const p = 2.0 * Math.pow( 0.5, i ); + const range: [ number, number, number, number ] = isFirst + ? [ -1.0, -1.0, 0.0, 0.0 ] + : [ 1.0 - p, 1.0 - p, 1.0 - 0.5 * p, 1.0 - 0.5 * p ]; this.components.push( new Quad( { target: swap.o, material, - name: process.env.DEV && `Bloom/quadBlur${ i }`, + range, + name: `Bloom/quadDown${ i }`, } ) ); swap.swap(); } - // -- post --------------------------------------------------------------------------------------- - const materialBloomPost = new Material( - quadVert, - bloomPostFrag, - { initOptions: { target: dummyRenderTarget, geometry: quadGeometry } }, - ); - materialBloomPost.addUniformTexture( 'samplerDry', options.input.texture ); - materialBloomPost.addUniformTexture( 'samplerWet', swap.i.texture ); + // -- up --------------------------------------------------------------------------------------- + for ( let i = 5; i >= 0; i -- ) { + const isLast = i === 0; - this.components.push( new Quad( { - target: options.target, - material: materialBloomPost, - name: process.env.DEV && 'Bloom/quadPost', - } ) ); + const material = new Material( + quadVert, + bloomUpFrag, + { + initOptions: { target: dummyRenderTarget, geometry: quadGeometry }, + blend: [ gl.ONE, gl.ONE ], + }, + ); + + material.addUniform( 'level', '1f', i ); + material.addUniformTexture( + 'sampler0', + swap.i.texture, + ); + + const p = 4.0 * Math.pow( 0.5, i ); + const range: [ number, number, number, number ] = isLast + ? [ -1.0, -1.0, 1.0, 1.0 ] + : [ 1.0 - p, 1.0 - p, 1.0 - 0.5 * p, 1.0 - 0.5 * p ]; + + this.components.push( new Quad( { + target: isLast ? options.target : swap.o, + material, + range, + name: `Bloom/quadUp${ i }`, + } ) ); + + swap.swap(); + } } } diff --git a/src/heck/Material.ts b/src/heck/Material.ts index c0b887a..c9ac8b4 100644 --- a/src/heck/Material.ts +++ b/src/heck/Material.ts @@ -78,13 +78,14 @@ export class Material { ); } - public blend: [ GLenum, GLenum ] = [ gl.ONE, gl.ZERO ]; + public blend: [ GLenum, GLenum ]; public constructor( vert: string, frag: string, - { defines, linkOptions, initOptions }: { + { defines, blend, linkOptions, initOptions }: { defines?: { [ key: string ]: ( string | undefined ) }, + blend?: [ GLenum, GLenum ], linkOptions?: GLCatProgramLinkOptions, initOptions?: MaterialInitOptions, } = {}, @@ -93,6 +94,7 @@ export class Material { this.__frag = frag; this.__linkOptions = linkOptions ?? {}; this.__defines = defines ?? {}; + this.blend = blend ?? [ gl.ONE, gl.ZERO ]; if ( initOptions ) { this.d3dSucks( initOptions ); diff --git a/src/scene.ts b/src/scene.ts index 05c77f6..ae494b2 100644 --- a/src/scene.ts +++ b/src/scene.ts @@ -1,4 +1,5 @@ import { Swap, Vector3 } from '@fms-cat/experimental'; +import { Antialias } from './entities/Antialias'; import { Bloom } from './entities/Bloom'; import { CameraEntity } from './entities/CameraEntity'; import { Condition } from './entities/Condition'; @@ -283,6 +284,13 @@ camera.components.unshift( new Lambda( { } ) ); dog.root.children.push( camera ); +swap.swap(); +const antialias = new Antialias( { + input: swap.i, + target: swap.o +} ); +dog.root.children.push( antialias ); + swap.swap(); const bloom = new Bloom( { input: swap.i, diff --git a/src/shaders/bloom-blur.frag b/src/shaders/bloom-blur.frag deleted file mode 100644 index 4bb3731..0000000 --- a/src/shaders/bloom-blur.frag +++ /dev/null @@ -1,47 +0,0 @@ -#version 300 es - -precision highp float; - -const float OFFSET1 = 1.411764705882353; -const float OFFSET2 = 3.2941176470588234; -const float OFFSET3 = 5.176470588235294; -const float CONTRIBUTION0 = 0.1964825501511404; -const float CONTRIBUTION1 = 0.2969069646728344; -const float CONTRIBUTION2 = 0.09447039785044732; -const float CONTRIBUTION3 = 0.010381362401148057; - -in vec2 vUv; - -out vec4 fragColor; - -uniform bool isVert; -uniform vec2 resolution; -uniform sampler2D sampler0; - -void main() { - vec2 halfTexel = 1.0 / resolution; // * 0.5; - - float lv = ceil( -log( 1.0 - min( vUv.x, vUv.y ) ) / log( 2.0 ) ); - float p = pow( 0.5, lv ); - vec2 clampMin = floor( vUv / p ) * p + halfTexel; - vec2 clampMax = clampMin + p - 2.0 * halfTexel; - - vec2 bv = halfTexel * ( isVert ? vec2( 0.0, 1.0 ) : vec2( 1.0, 0.0 ) ); - vec4 sum = vec4( 0.0 ); - - sum += CONTRIBUTION0 * texture( sampler0, vUv ); - vec2 suv = clamp( vUv - bv * OFFSET1, clampMin, clampMax ); - sum += CONTRIBUTION1 * texture( sampler0, suv ); - suv = clamp( vUv + bv * OFFSET1, clampMin, clampMax ); - sum += CONTRIBUTION1 * texture( sampler0, suv ); - suv = clamp( vUv - bv * OFFSET2, clampMin, clampMax ); - sum += CONTRIBUTION2 * texture( sampler0, suv ); - suv = clamp( vUv + bv * OFFSET2, clampMin, clampMax ); - sum += CONTRIBUTION2 * texture( sampler0, suv ); - suv = clamp( vUv - bv * OFFSET3, clampMin, clampMax ); - sum += CONTRIBUTION3 * texture( sampler0, suv ); - suv = clamp( vUv + bv * OFFSET3, clampMin, clampMax ); - sum += CONTRIBUTION3 * texture( sampler0, suv ); - - fragColor = vec4( sum.xyz, 1.0 ); -} diff --git a/src/shaders/bloom-down.frag b/src/shaders/bloom-down.frag new file mode 100644 index 0000000..2dd0865 --- /dev/null +++ b/src/shaders/bloom-down.frag @@ -0,0 +1,58 @@ +#version 300 es + +precision highp float; + +const float WEIGHT_1 = 1.0; +const float WEIGHT_2 = 2.0; +const float WEIGHT_4 = 4.0; +const vec3 LUMA = vec3( 0.299, 0.587, 0.114 ); + +in vec2 vUv; + +out vec4 fragColor; + +uniform float level; +uniform vec2 resolution; +uniform sampler2D sampler0; + +vec4 fetchWithWeight( vec2 uv ) { + vec4 tex = texture( sampler0, uv ); + float luma = dot( LUMA, tex.xyz ); + return vec4( tex.xyz, 1.0 + luma ); +} + +void main() { + float p = 2.0 * pow( 0.5, level ); // 2.0, 1.0, 0.5, 0.25... + + vec2 deltaTexel = 1.0 / resolution; + + vec2 uv0 = step( 0.5, level ) * vec2( 1.0 - p ); + vec2 uv1 = vec2( 1.0 - step( 0.5, level ) * 0.5 * p ); + vec2 uv = mix( uv0, uv1, vUv ); + uv = clamp( uv, uv0 + deltaTexel, uv1 - deltaTexel ); + + // http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare + vec4 tex = WEIGHT_1 * fetchWithWeight( uv - deltaTexel * vec2( -1.0, -1.0 ) ); + tex += WEIGHT_2 * fetchWithWeight( uv - deltaTexel * vec2( 0.0, -1.0 ) ); + tex += WEIGHT_1 * fetchWithWeight( uv - deltaTexel * vec2( 1.0, -1.0 ) ); + tex += WEIGHT_4 * fetchWithWeight( uv - deltaTexel * vec2( -0.5, -0.5 ) ); + tex += WEIGHT_4 * fetchWithWeight( uv - deltaTexel * vec2( 0.5, -0.5 ) ); + tex += WEIGHT_2 * fetchWithWeight( uv - deltaTexel * vec2( -1.0, 0.0 ) ); + tex += WEIGHT_4 * fetchWithWeight( uv - deltaTexel * vec2( 0.0, 0.0 ) ); + tex += WEIGHT_2 * fetchWithWeight( uv - deltaTexel * vec2( 1.0, 0.0 ) ); + tex += WEIGHT_4 * fetchWithWeight( uv - deltaTexel * vec2( -0.5, 0.5 ) ); + tex += WEIGHT_4 * fetchWithWeight( uv - deltaTexel * vec2( 0.5, 0.5 ) ); + tex += WEIGHT_1 * fetchWithWeight( uv - deltaTexel * vec2( -1.0, 1.0 ) ); + tex += WEIGHT_2 * fetchWithWeight( uv - deltaTexel * vec2( 0.0, 1.0 ) ); + tex += WEIGHT_1 * fetchWithWeight( uv - deltaTexel * vec2( 1.0, 1.0 ) ); + + vec3 col = tex.rgb / tex.w; + + if ( level == 0.0 ) { + float brightness = dot( LUMA, col ); + vec3 normalized = brightness < 1E-4 ? vec3( brightness ) : col / brightness; + col = max( 0.0, brightness - 0.5 ) * normalized; + } + + fragColor = vec4( col, 1.0 ); +} diff --git a/src/shaders/bloom-post.frag b/src/shaders/bloom-post.frag deleted file mode 100644 index e0624df..0000000 --- a/src/shaders/bloom-post.frag +++ /dev/null @@ -1,29 +0,0 @@ -#version 300 es - -precision highp float; - -in vec2 vUv; - -out vec4 fragColor; - -uniform vec2 resolution; -uniform sampler2D samplerDry; -uniform sampler2D samplerWet; - -vec4 sampleLOD( vec2 uv, float lv ) { - float p = pow( 0.5, float( lv ) ); - vec2 halfPixel = 0.5 / resolution; - vec2 uv00 = vec2( 1.0 - p ); - vec2 uv11 = vec2( 1.0 - 0.5 * p ); - vec2 uvt = mix( uv00, uv11, uv ); - uvt = clamp( uvt, uv00 + halfPixel, uv11 - halfPixel ); - return texture( samplerWet, uvt ); -} - -void main() { - fragColor = texture( samplerDry, vUv ); - for ( int i = 0; i < 5; i ++ ) { - fragColor += sampleLOD( vUv, float( i ) ); - } - fragColor.xyz = max( vec3( 0.0 ), fragColor.xyz ); -} diff --git a/src/shaders/bloom-pre.frag b/src/shaders/bloom-pre.frag deleted file mode 100644 index 96bdb14..0000000 --- a/src/shaders/bloom-pre.frag +++ /dev/null @@ -1,20 +0,0 @@ -#version 300 es - -precision highp float; - -in vec2 vUv; - -out vec4 fragColor; - -uniform sampler2D sampler0; - -void main() { - vec3 tex = texture( sampler0, vUv ).xyz; - float brightness = dot( tex, vec3( 0.299, 0.587, 0.114 ) ); - vec3 normalized = brightness < 0.01 ? vec3( brightness ) : tex / brightness; - - fragColor = vec4( - max( 0.0, brightness - 1.0 ) * normalized, - 1.0 - ); -} diff --git a/src/shaders/bloom-up.frag b/src/shaders/bloom-up.frag new file mode 100644 index 0000000..babf8aa --- /dev/null +++ b/src/shaders/bloom-up.frag @@ -0,0 +1,41 @@ +#version 300 es + +precision highp float; + +const float WEIGHT_1 = 1.0 / 16.0; +const float WEIGHT_2 = 2.0 / 16.0; +const float WEIGHT_4 = 4.0 / 16.0; + +in vec2 vUv; + +out vec4 fragColor; + +uniform float level; +uniform vec2 resolution; +uniform sampler2D sampler0; + +void main() { + float p = pow( 0.5, level ); // 1.0, 0.5, 0.25... + + vec2 deltaTexel = 1.0 / resolution; + + vec2 uv0 = vec2( 1.0 - p ); + vec2 uv1 = vec2( 1.0 - 0.5 * p ); + vec2 uv = mix( uv0, uv1, vUv ); + uv = clamp( uv, uv0 + 1.5 * deltaTexel, uv1 - 1.5 * deltaTexel ); + + // http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare + vec4 tex = WEIGHT_1 * texture( sampler0, uv - deltaTexel * vec2( -1.0, -1.0 ) ); + tex += WEIGHT_2 * texture( sampler0, uv - deltaTexel * vec2( 0.0, -1.0 ) ); + tex += WEIGHT_1 * texture( sampler0, uv - deltaTexel * vec2( 1.0, -1.0 ) ); + tex += WEIGHT_2 * texture( sampler0, uv - deltaTexel * vec2( -1.0, 0.0 ) ); + tex += WEIGHT_4 * texture( sampler0, uv - deltaTexel * vec2( 0.0, 0.0 ) ); + tex += WEIGHT_2 * texture( sampler0, uv - deltaTexel * vec2( 1.0, 0.0 ) ); + tex += WEIGHT_1 * texture( sampler0, uv - deltaTexel * vec2( -1.0, 1.0 ) ); + tex += WEIGHT_2 * texture( sampler0, uv - deltaTexel * vec2( 0.0, 1.0 ) ); + tex += WEIGHT_1 * texture( sampler0, uv - deltaTexel * vec2( 1.0, 1.0 ) ); + + vec3 col = tex.rgb; + + fragColor = vec4( col, 1.0 ); +} diff --git a/src/shaders/fxaa.frag b/src/shaders/fxaa.frag new file mode 100644 index 0000000..f1fc2b9 --- /dev/null +++ b/src/shaders/fxaa.frag @@ -0,0 +1,69 @@ +#version 300 es + +precision highp float; + +const float FXAA_REDUCE_MIN = 1.0 / 128.0; +const float FXAA_REDUCE_MUL = 1.0 / 8.0; +const float FXAA_SPAN_MAX = 16.0; + +in vec2 vUv; + +out vec4 fragColor; + +uniform vec2 resolution; +uniform sampler2D sampler0; + +void main() { + vec2 uv = vUv; + vec4 neighbor = vec4( -1.0, -1.0, 1.0, 1.0 ) / resolution.xyxy; + + vec3 rgb11 = texture( sampler0, vUv ).rgb; + vec3 rgb00 = texture( sampler0, vUv + neighbor.xy ).rgb; + vec3 rgb02 = texture( sampler0, vUv + neighbor.xw ).rgb; + vec3 rgb20 = texture( sampler0, vUv + neighbor.zy ).rgb; + vec3 rgb22 = texture( sampler0, vUv + neighbor.zw ).rgb; + + vec3 luma = vec3( 0.299, 0.587, 0.114 ); + float luma11 = dot( luma, rgb11 ); + float luma00 = dot( luma, rgb00 ); + float luma02 = dot( luma, rgb02 ); + float luma20 = dot( luma, rgb20 ); + float luma22 = dot( luma, rgb22 ); + + float lumaMin = min( luma00, min( min( luma00, luma02 ), min( luma20, luma22 ) ) ); + float lumaMax = max( luma00, max( max( luma00, luma02 ), max( luma20, luma22 ) ) ); + + vec2 dir = vec2( + -( ( luma00 + luma20 ) - ( luma02 + luma22 ) ), + ( ( luma00 + luma02 ) - ( luma20 + luma22 ) ) + ); + + float dirReduce = max( + ( luma00 + luma02 + luma20 + luma22 ) * 0.25 * FXAA_REDUCE_MUL, + FXAA_REDUCE_MIN + ); + float rcpDirMin = 1.0 / ( min( abs( dir.x ), abs( dir.y ) ) + dirReduce ); + dir = min( + vec2( FXAA_SPAN_MAX ), + max( + vec2( -FXAA_SPAN_MAX ), + dir * rcpDirMin + ) + ) / resolution; + + vec3 rgbA = 0.5 * ( + texture( sampler0, uv + dir * ( 1.0 / 3.0 - 0.5 ) ).xyz + + texture( sampler0, uv + dir * ( 2.0 / 3.0 - 0.5 ) ).xyz + ); + vec3 rgbB = rgbA * 0.5 + 0.25 * ( + texture( sampler0, uv - dir * 0.5 ).xyz + + texture( sampler0, uv + dir * 0.5 ).xyz + ); + + float lumaB = dot( rgbB, luma ); + fragColor = ( + ( ( lumaB < lumaMin ) || ( lumaMax < lumaB ) ) ? + vec4( rgbA, 1.0 ) : + vec4( rgbB, 1.0 ) + ); +} diff --git a/src/shaders/return.frag b/src/shaders/nop.frag similarity index 100% rename from src/shaders/return.frag rename to src/shaders/nop.frag