UNPKG

@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.

268 lines (245 loc) 11.9 kB
import { __decorate } from "../../../../tslib.es6.js"; import { NodeMaterialBlock } from "../../nodeMaterialBlock.js"; import { NodeMaterialBlockConnectionPointTypes } from "../../Enums/nodeMaterialBlockConnectionPointTypes.js"; import { NodeMaterialBlockTargets } from "../../Enums/nodeMaterialBlockTargets.js"; import { RegisterClass } from "../../../../Misc/typeStore.js"; import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject.js"; import { RandomRange } from "../../../../Maths/math.scalar.functions.js"; import { RawTexture } from "../../../Textures/rawTexture.js"; import { Texture } from "../../../Textures/texture.js"; import { editableInPropertyPage } from "../../../../Decorators/nodeDecorator.js"; import { ImageSourceBlock } from "../Dual/imageSourceBlock.js"; /** * Block used to evaluate screen spaceambient occlusion in a shader */ export class AmbientOcclusionBlock extends NodeMaterialBlock { /** * Create a new AmbientOcclusionBlock * @param name defines the block name */ constructor(name) { super(name, NodeMaterialBlockTargets.Fragment); /** * Defines the radius around the analyzed pixel used by the SSAO post-process */ this.radius = 0.0001; /** * Related to fallOff, used to interpolate SSAO samples (first interpolate function input) based on the occlusion difference of each pixel * Must not be equal to fallOff and superior to fallOff. */ this.area = 0.0075; /** * Related to area, used to interpolate SSAO samples (second interpolate function input) based on the occlusion difference of each pixel * Must not be equal to area and inferior to area. */ this.fallOff = 0.000001; this.registerInput("source", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.VertexAndFragment, new NodeMaterialConnectionPointCustomObject("source", this, 0 /* NodeMaterialConnectionPointDirection.Input */, ImageSourceBlock, "ImageSourceBlock")); this.registerInput("screenSize", NodeMaterialBlockConnectionPointTypes.Vector2); this.registerOutput("occlusion", NodeMaterialBlockConnectionPointTypes.Float); } /** * Gets the current class name * @returns the class name */ getClassName() { return "AmbientOcclusionBlock"; } /** * Gets the source component */ get source() { return this._inputs[0]; } /** * Gets the screenSize component */ get screenSize() { return this._inputs[1]; } /** * Gets the occlusion output */ get occlusion() { return this._outputs[0]; } bind(effect) { if (!this._randomTexture) { this._createRandomTexture(effect.getEngine()); } effect.setTexture(this._randomSamplerName, this._randomTexture); } _createRandomTexture(engine) { const size = 512; const data = new Uint8Array(size * size * 4); for (let index = 0; index < data.length;) { data[index++] = Math.floor(Math.max(0.0, RandomRange(-1.0, 1.0)) * 255); data[index++] = Math.floor(Math.max(0.0, RandomRange(-1.0, 1.0)) * 255); data[index++] = Math.floor(Math.max(0.0, RandomRange(-1.0, 1.0)) * 255); data[index++] = 255; } const texture = RawTexture.CreateRGBATexture(data, size, size, engine, false, false, 2); texture.name = "SSAORandomTexture"; texture.wrapU = Texture.WRAP_ADDRESSMODE; texture.wrapV = Texture.WRAP_ADDRESSMODE; this._randomTexture = texture; } _buildBlock(state) { super._buildBlock(state); if (!this.source.connectedPoint) { return this; } state.sharedData.bindableBlocks.push(this); const depthSource = this.source.connectedPoint.ownerBlock; const occlusion = this._outputs[0]; const screenSize = this.screenSize; let functionString; // Get view position from depth if (state.shaderLanguage === 1 /* ShaderLanguage.WGSL */) { functionString = `fn normalFromDepth(depth: f32, coords: vec2f, radius: f32) -> vec3f { let offset1: vec2f = vec2f(0.0, radius); let offset2: vec2f = vec2f(radius, 0.0); let depth1: f32 = textureSampleLevel(${depthSource.samplerName}, ${depthSource.samplerName}Sampler, coords + offset1, 0.0).r; let depth2: f32 = textureSampleLevel(${depthSource.samplerName}, ${depthSource.samplerName}Sampler, coords + offset2, 0.0).r; let p1: vec3f = vec3f(offset1, depth1 - depth); let p2: vec3f = vec3f(offset2, depth2 - depth); var normal: vec3f = cross(p1, p2); normal.z = -normal.z; return normalize(normal); } `; } else { functionString = `vec3 normalFromDepth(float depth, vec2 coords, float radius) { vec2 offset1 = vec2(0.0, radius); vec2 offset2 = vec2(radius, 0.0); float depth1 = textureLod(${depthSource.samplerName}, coords + offset1, 0.0).r; float depth2 = textureLod(${depthSource.samplerName}, coords + offset2, 0.0).r; vec3 p1 = vec3(offset1, depth1 - depth); vec3 p2 = vec3(offset2, depth2 - depth); vec3 normal = cross(p1, p2); normal.z = -normal.z; return normalize(normal); } `; } state._emitFunction("normalFromDepth", functionString, "// normalFromDepth function"); // Calculate ambient occlusion this._randomSamplerName = state._getFreeVariableName("randomSampler"); state._emit2DSampler(this._randomSamplerName); if (state.shaderLanguage === 1 /* ShaderLanguage.WGSL */) { functionString = ` const sampleSphere: array<vec3f, 16> = array<vec3f, 16>( vec3f( 0.5381, 0.1856, -0.4319), vec3f( 0.1379, 0.2486, 0.4430), vec3f( 0.3371, 0.5679, -0.0057), vec3f(-0.6999, -0.0451, -0.0019), vec3f( 0.0689, -0.1598, -0.8547), vec3f( 0.0560, 0.0069, -0.1843), vec3f(-0.0146, 0.1402, 0.0762), vec3f( 0.0100, -0.1924, -0.0344), vec3f(-0.3577, -0.5301, -0.4358), vec3f(-0.3169, 0.1063, 0.0158), vec3f( 0.0103, -0.5869, 0.0046), vec3f(-0.0897, -0.4940, 0.3287), vec3f( 0.7119, -0.0154, -0.0918), vec3f(-0.0533, 0.0596, -0.5411), vec3f( 0.0352, -0.0631, 0.5460), vec3f(-0.4776, 0.2847, -0.0271) ); fn computeOcclusion(screenSize: vec2f) -> f32 { let uv: vec2f = fragmentInputs.position.xy / screenSize; let random: vec3f = normalize(textureSampleLevel(${this._randomSamplerName}, ${this._randomSamplerName}Sampler, uv * 4.0, 0.0).rgb); let depth: f32 = textureSampleLevel(${depthSource.samplerName}, ${depthSource.samplerName}Sampler, uv, 0.0).r; let position: vec3f = vec3f(uv, depth); let normal: vec3f = normalFromDepth(depth, uv, ${this.radius}f); let radiusDepth: f32 = ${this.radius}f / depth; var occlusion: f32 = 0.0; var ray: vec3f; var hemiRay: vec3f; var occlusionDepth: f32; var difference: f32; for (var i: i32 = 0; i < 16; i++) { ray = radiusDepth * reflect(sampleSphere[i], random); hemiRay = position + sign(dot(ray, normal)) * ray; occlusionDepth = textureSample(${depthSource.samplerName}, ${depthSource.samplerName}Sampler, clamp(hemiRay.xy, vec2f(0.001, 0.001), vec2f(0.999, 0.999))).r; difference = depth - occlusionDepth; occlusion += step(${this.fallOff}f, difference) * (1.0 - smoothstep(${this.fallOff}f, ${this.area}f, difference)); } return clamp(1.0 - occlusion / 16.0, 0.0, 1.0); } `; } else { functionString = ` const vec3 sampleSphere[16] = vec3[]( vec3( 0.5381, 0.1856, -0.4319), vec3( 0.1379, 0.2486, 0.4430), vec3( 0.3371, 0.5679, -0.0057), vec3(-0.6999, -0.0451, -0.0019), vec3( 0.0689, -0.1598, -0.8547), vec3( 0.0560, 0.0069, -0.1843), vec3(-0.0146, 0.1402, 0.0762), vec3( 0.0100, -0.1924, -0.0344), vec3(-0.3577, -0.5301, -0.4358), vec3(-0.3169, 0.1063, 0.0158), vec3( 0.0103, -0.5869, 0.0046), vec3(-0.0897, -0.4940, 0.3287), vec3( 0.7119, -0.0154, -0.0918), vec3(-0.0533, 0.0596, -0.5411), vec3( 0.0352, -0.0631, 0.5460), vec3(-0.4776, 0.2847, -0.0271) ); float computeOcclusion(vec2 screenSize) { vec2 uv = gl_FragCoord.xy / screenSize; vec3 random = normalize(textureLod(${this._randomSamplerName}, uv * 4., 0.0).rgb); float depth = textureLod(${depthSource.samplerName}, uv, 0.0).r; vec3 position = vec3(uv, depth); vec3 normal = normalFromDepth(depth, uv, ${this.radius} ); float radiusDepth = ${this.radius} / depth; float occlusion = 0.0; vec3 ray; vec3 hemiRay; float occlusionDepth; float difference; for (int i = 0; i < 16; i++) { ray = radiusDepth * reflect(sampleSphere[i], random); hemiRay = position + sign(dot(ray, normal)) * ray; occlusionDepth = texture2D(${depthSource.samplerName}, clamp(hemiRay.xy, vec2(0.001, 0.001), vec2(0.999, 0.999))).r; difference = depth - occlusionDepth; occlusion += step(${this.fallOff}, difference) * (1.0 - smoothstep(${this.fallOff}, ${this.area}, difference)); } return clamp(1.0 - occlusion / 16.0, 0.0, 1.0); } `; } state._emitFunction("computeOcclusion", functionString, "// computeOcclusion function"); state.compilationString += state._declareOutput(occlusion) + ` = computeOcclusion(${screenSize.associatedVariableName});`; return this; } dispose() { if (this._randomTexture) { this._randomTexture.dispose(); this._randomTexture = null; } super.dispose(); } } __decorate([ editableInPropertyPage("radius", 1 /* PropertyTypeForEdition.Float */, "ADVANCED", { min: 0.0001, }) ], AmbientOcclusionBlock.prototype, "radius", void 0); __decorate([ editableInPropertyPage("area", 1 /* PropertyTypeForEdition.Float */, "ADVANCED", { min: 0, }) ], AmbientOcclusionBlock.prototype, "area", void 0); __decorate([ editableInPropertyPage("fallOff", 1 /* PropertyTypeForEdition.Float */, "ADVANCED", { min: 0, }) ], AmbientOcclusionBlock.prototype, "fallOff", void 0); RegisterClass("BABYLON.AmbientOcclusionBlock", AmbientOcclusionBlock); //# sourceMappingURL=ambientOcclusionBlock.js.map