playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
167 lines (166 loc) • 6.63 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { BlueNoise } from "../../core/math/blue-noise.js";
import { Color } from "../../core/math/color.js";
import { math } from "../../core/math/math.js";
import {
PIXELFORMAT_R8,
SEMANTIC_POSITION,
SHADERLANGUAGE_GLSL,
SHADERLANGUAGE_WGSL
} from "../../platform/graphics/constants.js";
import { RenderTarget } from "../../platform/graphics/render-target.js";
import { Texture } from "../../platform/graphics/texture.js";
import { RenderPassShaderQuad } from "../../scene/graphics/render-pass-shader-quad.js";
import { RenderPassDepthAwareBlur } from "./render-pass-depth-aware-blur.js";
import { ShaderChunks } from "../../scene/shader-lib/shader-chunks.js";
import { ShaderUtils } from "../../scene/shader-lib/shader-utils.js";
import glslSsaoPS from "../../scene/shader-lib/glsl/chunks/render-pass/frag/ssao.js";
import wgslSsaoPS from "../../scene/shader-lib/wgsl/chunks/render-pass/frag/ssao.js";
class RenderPassSsao extends RenderPassShaderQuad {
constructor(device, sourceTexture, cameraComponent, blurEnabled) {
super(device);
/**
* The filter radius.
*/
__publicField(this, "radius", 5);
/**
* The intensity.
*/
__publicField(this, "intensity", 1);
/**
* The power controlling the falloff curve.
*/
__publicField(this, "power", 1);
/**
* The number of samples to take.
*/
__publicField(this, "sampleCount", 10);
/**
* The minimum angle in degrees that creates an occlusion. Helps to reduce fake occlusions due
* to low geometry tessellation.
*/
__publicField(this, "minAngle", 5);
/**
* Enable randomization of the sample pattern. Useful when TAA is used to remove the noise,
* instead of blurring.
*/
__publicField(this, "randomize", false);
/**
* The texture containing the occlusion information in the red channel.
*
* @type {Texture}
* @readonly
*/
__publicField(this, "ssaoTexture");
/** @type {number} */
__publicField(this, "_scale", 1);
__publicField(this, "_blueNoise", new BlueNoise(19));
this.sourceTexture = sourceTexture;
this.cameraComponent = cameraComponent;
ShaderChunks.get(device, SHADERLANGUAGE_GLSL).set("ssaoPS", glslSsaoPS);
ShaderChunks.get(device, SHADERLANGUAGE_WGSL).set("ssaoPS", wgslSsaoPS);
const defines = /* @__PURE__ */ new Map();
ShaderUtils.addScreenDepthChunkDefines(device, cameraComponent.shaderParams, defines);
this.shader = ShaderUtils.createShader(device, {
uniqueName: "SsaoShader",
attributes: { aPosition: SEMANTIC_POSITION },
vertexChunk: "quadVS",
fragmentChunk: "ssaoPS",
fragmentDefines: defines
});
const rt = this.createRenderTarget("SsaoFinalTexture");
this.ssaoTexture = rt.colorBuffer;
this.init(rt, {
resizeSource: this.sourceTexture
});
const clearColor = new Color(0, 0, 0, 0);
this.setClearColor(clearColor);
if (blurEnabled) {
const blurRT = this.createRenderTarget("SsaoTempTexture");
const blurPassHorizontal = new RenderPassDepthAwareBlur(device, rt.colorBuffer, cameraComponent, true);
blurPassHorizontal.init(blurRT, {
resizeSource: rt.colorBuffer
});
blurPassHorizontal.setClearColor(clearColor);
const blurPassVertical = new RenderPassDepthAwareBlur(device, blurRT.colorBuffer, cameraComponent, false);
blurPassVertical.init(rt, {
resizeSource: rt.colorBuffer
});
blurPassVertical.setClearColor(clearColor);
this.afterPasses.push(blurPassHorizontal);
this.afterPasses.push(blurPassVertical);
}
this.ssaoTextureId = device.scope.resolve("ssaoTexture");
this.ssaoTextureSizeInvId = device.scope.resolve("ssaoTextureSizeInv");
}
destroy() {
this.renderTarget?.destroyTextureBuffers();
this.renderTarget?.destroy();
this.renderTarget = null;
if (this.afterPasses.length > 0) {
const blurRt = this.afterPasses[0].renderTarget;
blurRt?.destroyTextureBuffers();
blurRt?.destroy();
}
this.afterPasses.forEach((pass) => pass.destroy());
this.afterPasses.length = 0;
super.destroy();
}
/**
* The scale multiplier for the render target size.
*
* @type {number}
*/
set scale(value) {
this._scale = value;
this.scaleX = value;
this.scaleY = value;
}
get scale() {
return this._scale;
}
createRenderTarget(name) {
return new RenderTarget({
depth: false,
colorBuffer: Texture.createDataTexture2D(this.device, name, 1, 1, PIXELFORMAT_R8)
});
}
execute() {
const { device, sourceTexture, sampleCount, minAngle, scale } = this;
const { width, height } = this.renderTarget.colorBuffer;
const scope = device.scope;
scope.resolve("uAspect").setValue(width / height);
scope.resolve("uInvResolution").setValue([1 / width, 1 / height]);
scope.resolve("uSampleCount").setValue([sampleCount, 1 / sampleCount]);
const minAngleSin = Math.sin(minAngle * math.DEG_TO_RAD);
scope.resolve("uMinHorizonAngleSineSquared").setValue(minAngleSin * minAngleSin);
const spiralTurns = 10;
const step = 1 / (sampleCount - 0.5) * spiralTurns * 2 * 3.141;
const radius = this.radius / scale;
const bias = 1e-3;
const peak = 0.1 * radius;
const intensity = 2 * (peak * 2 * 3.141) * this.intensity / sampleCount;
const projectionScale = 0.5 * sourceTexture.height;
scope.resolve("uSpiralTurns").setValue(spiralTurns);
scope.resolve("uAngleIncCosSin").setValue([Math.cos(step), Math.sin(step)]);
scope.resolve("uMaxLevel").setValue(0);
scope.resolve("uInvRadiusSquared").setValue(1 / (radius * radius));
scope.resolve("uBias").setValue(bias);
scope.resolve("uPeak2").setValue(peak * peak);
scope.resolve("uIntensity").setValue(intensity);
scope.resolve("uPower").setValue(this.power);
scope.resolve("uProjectionScaleRadius").setValue(projectionScale * radius);
scope.resolve("uRandomize").setValue(this.randomize ? this._blueNoise.value() : 0);
super.execute();
}
after() {
this.ssaoTextureId.setValue(this.ssaoTexture);
const srcTexture = this.sourceTexture;
this.ssaoTextureSizeInvId.setValue([1 / srcTexture.width, 1 / srcTexture.height]);
}
}
export {
RenderPassSsao
};