UNPKG

@animech-public/playcanvas

Version:
260 lines (256 loc) 8.68 kB
import { math } from '../../core/math/math.js'; import { Color } from '../../core/math/color.js'; import { RenderPassShaderQuad } from '../../scene/graphics/render-pass-shader-quad.js'; import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; import { TONEMAP_ACES2, TONEMAP_NEUTRAL, TONEMAP_ACES, TONEMAP_HEJL, TONEMAP_FILMIC, TONEMAP_LINEAR } from '../../scene/constants.js'; const fragmentShader = ` varying vec2 uv0; uniform sampler2D sceneTexture; uniform vec2 sceneTextureInvRes; #ifdef BLOOM uniform sampler2D bloomTexture; uniform float bloomIntensity; #endif #ifdef GRADING uniform vec3 brightnessContrastSaturation; vec3 contrastSaturationBrightness(vec3 color, float brt, float sat, float con) { color = color * brt; float grey = dot(color, vec3(0.3, 0.59, 0.11)); color = mix(vec3(grey), color, sat); return max(mix(vec3(0.5), color, con), 0.0); } #endif #ifdef VIGNETTE uniform vec4 vignetterParams; float vignette(vec2 uv) { float inner = vignetterParams.x; float outer = vignetterParams.y; float curvature = vignetterParams.z; float intensity = vignetterParams.w; vec2 curve = pow(abs(uv * 2.0 -1.0), vec2(1.0 / curvature)); float edge = pow(length(curve), curvature); return 1.0 - intensity * smoothstep(inner, outer, edge); } #endif #ifdef FRINGING uniform float fringingIntensity; vec3 fringing(vec2 uv, vec3 color) { vec2 centerDistance = uv0 - 0.5; vec2 offset = fringingIntensity * pow(centerDistance, vec2(2.0, 2.0)); color.r = texture2D(sceneTexture, uv0 - offset).r; color.b = texture2D(sceneTexture, uv0 + offset).b; return color; } #endif #ifdef CAS uniform float sharpness; float maxComponent(float x, float y, float z) { return max(x, max(y, z)); } vec3 toSDR(vec3 c) { return c / (1.0 + maxComponent(c.r, c.g, c.b)); } vec3 toHDR(vec3 c) { return c / (1.0 - maxComponent(c.r, c.g, c.b)); } vec3 cas(vec3 color, vec2 uv, float sharpness) { float x = sceneTextureInvRes.x; float y = sceneTextureInvRes.y; vec3 a = toSDR(texture2DLodEXT(sceneTexture, uv + vec2(0.0, -y), 0.0).rgb); vec3 b = toSDR(texture2DLodEXT(sceneTexture, uv + vec2(-x, 0.0), 0.0).rgb); vec3 c = toSDR(color.rgb); vec3 d = toSDR(texture2DLodEXT(sceneTexture, uv + vec2(x, 0.0), 0.0).rgb); vec3 e = toSDR(texture2DLodEXT(sceneTexture, uv + vec2(0.0, y), 0.0).rgb); float min_g = min(a.g, min(b.g, min(c.g, min(d.g, e.g)))); float max_g = max(a.g, max(b.g, max(c.g, max(d.g, e.g)))); float sharpening_amount = sqrt(min(1.0 - max_g, min_g) / max_g); float w = sharpening_amount * sharpness; vec3 res = (w * (a + b + d + e) + c) / (4.0 * w + 1.0); return toHDR(res); } #endif void main() { vec2 uv = uv0; #ifdef TAA #ifdef WEBGPU uv.y = 1.0 - uv.y; #endif #endif vec4 scene = texture2DLodEXT(sceneTexture, uv, 0.0); vec3 result = scene.rgb; #ifdef CAS result = cas(result, uv, sharpness); #endif #ifdef FRINGING result = fringing(uv, result); #endif #ifdef BLOOM vec3 bloom = texture2DLodEXT(bloomTexture, uv, 0.0).rgb; result += bloom * bloomIntensity; #endif #ifdef GRADING result = contrastSaturationBrightness(result, brightnessContrastSaturation.x, brightnessContrastSaturation.z, brightnessContrastSaturation.y); #endif result = toneMap(result); #ifdef VIGNETTE result *= vignette(uv); #endif result = gammaCorrectOutput(result); gl_FragColor = vec4(result, scene.a); } `; class RenderPassCompose extends RenderPassShaderQuad { constructor(graphicsDevice) { super(graphicsDevice); this.sceneTexture = null; this.bloomIntensity = 0.01; this._bloomTexture = null; this._toneMapping = TONEMAP_ACES2; this._gradingEnabled = false; this.gradingSaturation = 1; this.gradingContrast = 1; this.gradingBrightness = 1; this._shaderDirty = true; this._vignetteEnabled = false; this.vignetteInner = 0.5; this.vignetteOuter = 1.0; this.vignetteCurvature = 0.5; this.vignetteIntensity = 0.3; this._fringingEnabled = false; this.fringingIntensity = 10; this._taaEnabled = false; this._sharpness = 0.5; this._key = ''; const { scope } = graphicsDevice; this.sceneTextureId = scope.resolve('sceneTexture'); this.bloomTextureId = scope.resolve('bloomTexture'); this.bloomIntensityId = scope.resolve('bloomIntensity'); this.bcsId = scope.resolve('brightnessContrastSaturation'); this.vignetterParamsId = scope.resolve('vignetterParams'); this.fringingIntensityId = scope.resolve('fringingIntensity'); this.sceneTextureInvResId = scope.resolve('sceneTextureInvRes'); this.sceneTextureInvResValue = new Float32Array(2); this.sharpnessId = scope.resolve('sharpness'); } set bloomTexture(value) { if (this._bloomTexture !== value) { this._bloomTexture = value; this._shaderDirty = true; } } get bloomTexture() { return this._bloomTexture; } set taaEnabled(value) { if (this._taaEnabled !== value) { this._taaEnabled = value; this._shaderDirty = true; } } get taaEnabled() { return this._taaEnabled; } set gradingEnabled(value) { if (this._gradingEnabled !== value) { this._gradingEnabled = value; this._shaderDirty = true; } } get gradingEnabled() { return this._gradingEnabled; } set vignetteEnabled(value) { if (this._vignetteEnabled !== value) { this._vignetteEnabled = value; this._shaderDirty = true; } } get vignetteEnabled() { return this._vignetteEnabled; } set fringingEnabled(value) { if (this._fringingEnabled !== value) { this._fringingEnabled = value; this._shaderDirty = true; } } get fringingEnabled() { return this._fringingEnabled; } set toneMapping(value) { if (this._toneMapping !== value) { this._toneMapping = value; this._shaderDirty = true; } } get toneMapping() { return this._toneMapping; } get toneMapChunk() { switch (this.toneMapping) { case TONEMAP_LINEAR: return shaderChunks.tonemappingLinearPS; case TONEMAP_FILMIC: return shaderChunks.tonemappingFilmicPS; case TONEMAP_HEJL: return shaderChunks.tonemappingHejlPS; case TONEMAP_ACES: return shaderChunks.tonemappingAcesPS; case TONEMAP_ACES2: return shaderChunks.tonemappingAces2PS; case TONEMAP_NEUTRAL: return shaderChunks.tonemappingNeutralPS; } return shaderChunks.tonemappingNonePS; } set sharpness(value) { if (this._sharpness !== value) { this._sharpness = value; this._shaderDirty = true; } } get sharpness() { return this._sharpness; } get isSharpnessEnabled() { return this._sharpness > 0; } postInit() { this.setClearColor(Color.BLACK); this.setClearDepth(1.0); this.setClearStencil(0); } frameUpdate() { if (this._shaderDirty) { this._shaderDirty = false; const key = `${this.toneMapping}` + `-${this.bloomTexture ? 'bloom' : 'nobloom'}` + `-${this.gradingEnabled ? 'grading' : 'nograding'}` + `-${this.vignetteEnabled ? 'vignette' : 'novignette'}` + `-${this.fringingEnabled ? 'fringing' : 'nofringing'}` + `-${this.taaEnabled ? 'taa' : 'notaa'}` + `-${this.isSharpnessEnabled ? 'cas' : 'nocas'}`; if (this._key !== key) { this._key = key; const defines = (this.bloomTexture ? '#define BLOOM\n' : '') + (this.gradingEnabled ? '#define GRADING\n' : '') + (this.vignetteEnabled ? '#define VIGNETTE\n' : '') + (this.fringingEnabled ? '#define FRINGING\n' : '') + (this.taaEnabled ? '#define TAA\n' : '') + (this.isSharpnessEnabled ? '#define CAS\n' : ''); const fsChunks = shaderChunks.decodePS + shaderChunks.gamma2_2PS + this.toneMapChunk; this.shader = this.createQuadShader(`ComposeShader-${key}`, defines + fsChunks + fragmentShader); } } } execute() { this.sceneTextureId.setValue(this.sceneTexture); this.sceneTextureInvResValue[0] = 1.0 / this.sceneTexture.width; this.sceneTextureInvResValue[1] = 1.0 / this.sceneTexture.height; this.sceneTextureInvResId.setValue(this.sceneTextureInvResValue); if (this._bloomTexture) { this.bloomTextureId.setValue(this._bloomTexture); this.bloomIntensityId.setValue(this.bloomIntensity); } if (this._gradingEnabled) { this.bcsId.setValue([this.gradingBrightness, this.gradingContrast, this.gradingSaturation]); } if (this._vignetteEnabled) { this.vignetterParamsId.setValue([this.vignetteInner, this.vignetteOuter, this.vignetteCurvature, this.vignetteIntensity]); } if (this._fringingEnabled) { this.fringingIntensityId.setValue(this.fringingIntensity / 1024); } if (this.isSharpnessEnabled) { this.sharpnessId.setValue(math.lerp(-0.125, -0.2, this.sharpness)); } super.execute(); } } export { RenderPassCompose };