@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
243 lines • 10 kB
JavaScript
import { EffectWrapper } from "../Materials/effectRenderer.js";
import { Engine } from "../Engines/engine.js";
/**
* Post process used to apply a blur effect
*/
export class ThinBlurPostProcess extends EffectWrapper {
_gatherImports(useWebGPU, list) {
if (useWebGPU) {
this._webGPUReady = true;
list.push(Promise.all([import("../ShadersWGSL/kernelBlur.fragment.js"), import("../ShadersWGSL/kernelBlur.vertex.js")]));
}
else {
list.push(Promise.all([import("../Shaders/kernelBlur.fragment.js"), import("../Shaders/kernelBlur.vertex.js")]));
}
}
/**
* Constructs a new blur post process
* @param name Name of the effect
* @param engine Engine to use to render the effect. If not provided, the last created engine will be used
* @param direction Direction in which to apply the blur
* @param kernel Kernel size of the blur
* @param options Options to configure the effect
*/
constructor(name, engine = null, direction, kernel, options) {
const blockCompilationFinal = !!options?.blockCompilation;
super({
...options,
name,
engine: engine || Engine.LastCreatedEngine,
useShaderStore: true,
useAsPostProcess: true,
fragmentShader: ThinBlurPostProcess.FragmentUrl,
uniforms: ThinBlurPostProcess.Uniforms,
samplers: ThinBlurPostProcess.Samplers,
vertexUrl: ThinBlurPostProcess.VertexUrl,
blockCompilation: true,
});
this._packedFloat = false;
this._staticDefines = "";
/**
* Width of the texture to apply the blur on
*/
this.textureWidth = 0;
/**
* Height of the texture to apply the blur on
*/
this.textureHeight = 0;
this._staticDefines = options ? (Array.isArray(options.defines) ? options.defines.join("\n") : options.defines || "") : "";
this.options.blockCompilation = blockCompilationFinal;
if (direction !== undefined) {
this.direction = direction;
}
if (kernel !== undefined) {
this.kernel = kernel;
}
}
/**
* Sets the length in pixels of the blur sample region
*/
set kernel(v) {
if (this._idealKernel === v) {
return;
}
v = Math.max(v, 1);
this._idealKernel = v;
this._kernel = this._nearestBestKernel(v);
if (!this.options.blockCompilation) {
this._updateParameters();
}
}
/**
* Gets the length in pixels of the blur sample region
*/
get kernel() {
return this._idealKernel;
}
/**
* Sets whether or not the blur needs to unpack/repack floats
*/
set packedFloat(v) {
if (this._packedFloat === v) {
return;
}
this._packedFloat = v;
if (!this.options.blockCompilation) {
this._updateParameters();
}
}
/**
* Gets whether or not the blur is unpacking/repacking floats
*/
get packedFloat() {
return this._packedFloat;
}
bind(noDefaultBindings = false) {
super.bind(noDefaultBindings);
this._drawWrapper.effect.setFloat2("delta", (1 / this.textureWidth) * this.direction.x, (1 / this.textureHeight) * this.direction.y);
}
/** @internal */
_updateParameters(onCompiled, onError) {
// Generate sampling offsets and weights
const n = this._kernel;
const centerIndex = (n - 1) / 2;
// Generate Gaussian sampling weights over kernel
let offsets = [];
let weights = [];
let totalWeight = 0;
for (let i = 0; i < n; i++) {
const u = i / (n - 1);
const w = this._gaussianWeight(u * 2.0 - 1);
offsets[i] = i - centerIndex;
weights[i] = w;
totalWeight += w;
}
// Normalize weights
for (let i = 0; i < weights.length; i++) {
weights[i] /= totalWeight;
}
// Optimize: combine samples to take advantage of hardware linear sampling
// Walk from left to center, combining pairs (symmetrically)
const linearSamplingWeights = [];
const linearSamplingOffsets = [];
const linearSamplingMap = [];
for (let i = 0; i <= centerIndex; i += 2) {
const j = Math.min(i + 1, Math.floor(centerIndex));
const singleCenterSample = i === j;
if (singleCenterSample) {
linearSamplingMap.push({ o: offsets[i], w: weights[i] });
}
else {
const sharedCell = j === centerIndex;
const weightLinear = weights[i] + weights[j] * (sharedCell ? 0.5 : 1);
const offsetLinear = offsets[i] + 1 / (1 + weights[i] / weights[j]);
if (offsetLinear === 0) {
linearSamplingMap.push({ o: offsets[i], w: weights[i] });
linearSamplingMap.push({ o: offsets[i + 1], w: weights[i + 1] });
}
else {
linearSamplingMap.push({ o: offsetLinear, w: weightLinear });
linearSamplingMap.push({ o: -offsetLinear, w: weightLinear });
}
}
}
for (let i = 0; i < linearSamplingMap.length; i++) {
linearSamplingOffsets[i] = linearSamplingMap[i].o;
linearSamplingWeights[i] = linearSamplingMap[i].w;
}
// Replace with optimized
offsets = linearSamplingOffsets;
weights = linearSamplingWeights;
// Generate shaders
const maxVaryingRows = this.options.engine.getCaps().maxVaryingVectors - (this.options.shaderLanguage === 1 /* ShaderLanguage.WGSL */ ? 1 : 0); // Because of the additional builtins
const freeVaryingVec2 = Math.max(maxVaryingRows, 0) - 1; // Because of sampleCenter
let varyingCount = Math.min(offsets.length, freeVaryingVec2);
let defines = "";
defines += this._staticDefines;
// The DOF fragment should ignore the center pixel when looping as it is handled manually in the fragment shader.
if (this._staticDefines.indexOf("DOF") != -1) {
defines += `#define CENTER_WEIGHT ${this._glslFloat(weights[varyingCount - 1])}\n`;
varyingCount--;
}
for (let i = 0; i < varyingCount; i++) {
defines += `#define KERNEL_OFFSET${i} ${this._glslFloat(offsets[i])}\n`;
defines += `#define KERNEL_WEIGHT${i} ${this._glslFloat(weights[i])}\n`;
}
let depCount = 0;
for (let i = freeVaryingVec2; i < offsets.length; i++) {
defines += `#define KERNEL_DEP_OFFSET${depCount} ${this._glslFloat(offsets[i])}\n`;
defines += `#define KERNEL_DEP_WEIGHT${depCount} ${this._glslFloat(weights[i])}\n`;
depCount++;
}
if (this.packedFloat) {
defines += `#define PACKEDFLOAT 1`;
}
this.options.blockCompilation = false;
this.updateEffect(defines, null, null, {
varyingCount: varyingCount,
depCount: depCount,
}, onCompiled, onError);
}
/**
* Best kernels are odd numbers that when divided by 2, their integer part is even, so 5, 9 or 13.
* Other odd kernels optimize correctly but require proportionally more samples, even kernels are
* possible but will produce minor visual artifacts. Since each new kernel requires a new shader we
* want to minimize kernel changes, having gaps between physical kernels is helpful in that regard.
* The gaps between physical kernels are compensated for in the weighting of the samples
* @param idealKernel Ideal blur kernel.
* @returns Nearest best kernel.
*/
_nearestBestKernel(idealKernel) {
const v = Math.round(idealKernel);
for (const k of [v, v - 1, v + 1, v - 2, v + 2]) {
if (k % 2 !== 0 && Math.floor(k / 2) % 2 === 0 && k > 0) {
return Math.max(k, 3);
}
}
return Math.max(v, 3);
}
/**
* Calculates the value of a Gaussian distribution with sigma 3 at a given point.
* @param x The point on the Gaussian distribution to sample.
* @returns the value of the Gaussian function at x.
*/
_gaussianWeight(x) {
//reference: Engines/ImageProcessingBlur.cpp #dcc760
// We are evaluating the Gaussian (normal) distribution over a kernel parameter space of [-1,1],
// so we truncate at three standard deviations by setting stddev (sigma) to 1/3.
// The choice of 3-sigma truncation is common but arbitrary, and means that the signal is
// truncated at around 1.3% of peak strength.
//the distribution is scaled to account for the difference between the actual kernel size and the requested kernel size
const sigma = 1 / 3;
const denominator = Math.sqrt(2.0 * Math.PI) * sigma;
const exponent = -((x * x) / (2.0 * sigma * sigma));
const weight = (1.0 / denominator) * Math.exp(exponent);
return weight;
}
/**
* Generates a string that can be used as a floating point number in GLSL.
* @param x Value to print.
* @param decimalFigures Number of decimal places to print the number to (excluding trailing 0s).
* @returns GLSL float string.
*/
_glslFloat(x, decimalFigures = 8) {
return x.toFixed(decimalFigures).replace(/0+$/, "");
}
}
/**
* The vertex shader url
*/
ThinBlurPostProcess.VertexUrl = "kernelBlur";
/**
* The fragment shader url
*/
ThinBlurPostProcess.FragmentUrl = "kernelBlur";
/**
* The list of uniforms used by the effect
*/
ThinBlurPostProcess.Uniforms = ["delta", "direction"];
/**
* The list of samplers used by the effect
*/
ThinBlurPostProcess.Samplers = ["circleOfConfusionSampler"];
//# sourceMappingURL=thinBlurPostProcess.js.map