From 1d0c2fcc5a5f11dc2c8f6737d0445ddd61e5477c Mon Sep 17 00:00:00 2001 From: Willy Scheibel Date: Wed, 5 Jul 2023 10:51:16 +0200 Subject: [PATCH] SSAO example (#288) * Add SSAO Example * Fine-tune SSAO example --- .vscode/settings.json | 4 +- examples/ambient-occlusion-example.ts | 526 +++++++++++++++++++++++++ examples/data/ssao/blur.frag | 84 ++++ examples/data/ssao/composition.frag | 32 ++ examples/data/ssao/geometry.frag | 36 ++ examples/data/ssao/geometry.vert | 39 ++ examples/data/ssao/ssao.frag | 107 +++++ examples/examples.json | 6 + source/scene/forwardscenerenderpass.ts | 6 +- website/examples.pug | 4 +- 10 files changed, 837 insertions(+), 7 deletions(-) create mode 100644 examples/ambient-occlusion-example.ts create mode 100644 examples/data/ssao/blur.frag create mode 100644 examples/data/ssao/composition.frag create mode 100644 examples/data/ssao/geometry.frag create mode 100644 examples/data/ssao/geometry.vert create mode 100644 examples/data/ssao/ssao.frag diff --git a/.vscode/settings.json b/.vscode/settings.json index 4f945d24..a005b8f9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,13 +5,13 @@ "[json]": { "editor.insertSpaces": true, "editor.tabSize": 4, - "editor.autoIndent": false, + "editor.autoIndent": "advanced", "editor.formatOnSave": false }, "[pug]": { "editor.insertSpaces": true, "editor.tabSize": 2, - "editor.autoIndent": true + "editor.autoIndent": "full" }, "files.associations": { "*.bithoundrc": "json", diff --git a/examples/ambient-occlusion-example.ts b/examples/ambient-occlusion-example.ts new file mode 100644 index 00000000..c0d04075 --- /dev/null +++ b/examples/ambient-occlusion-example.ts @@ -0,0 +1,526 @@ +/* spellchecker: disable */ + +import { mat4, vec3 } from 'gl-matrix'; + +import { auxiliaries } from 'webgl-operate'; + +import { + Camera, + Canvas, + Context, + DefaultFramebuffer, + EventProvider, + ForwardSceneRenderPass, + Framebuffer, + Geometry, + GLTFLoader, + GLTFPbrMaterial, + Invalidate, + Material, + Navigation, + NdcFillingTriangle, + Program, + Renderer, + Renderbuffer, + Shader, + Texture2D, + Wizard, +} from 'webgl-operate'; + +import { Example } from './example'; + +/* spellchecker: enable */ + + +// tslint:disable:max-classes-per-file + +export class AmbientOcclusionRenderer extends Renderer { + + protected _loader: GLTFLoader; + + /* Shared Across Passes */ + + protected _ndcGeometry: NdcFillingTriangle; + + /* Scene Pass */ + protected _navigation: Navigation; + protected _camera: Camera; + protected _forwardPass: ForwardSceneRenderPass; + + protected _sceneColor: Texture2D; + protected _sceneNormal: Texture2D; + protected _sceneLinearDepth: Texture2D; + protected _sceneDepth: Renderbuffer; + + protected _sceneProgram: Program; + + protected _uViewProjectionLocation: WebGLUniformLocation; + protected _uModelLocation: WebGLUniformLocation; + protected _uNormalLocation: WebGLUniformLocation; + protected _uSceneFrameSizeLocation: WebGLUniformLocation; + protected _uEyeLocation: WebGLUniformLocation; + /* // Scene Pass */ + + protected _sceneFbo: Framebuffer; + + /* Ambient Occlusion Pass */ + protected _aoNoisyMap: Texture2D; + + protected _aoProgram: Program; + + protected _uAOFrameSizeLocation: WebGLUniformLocation; + protected _uNormalTextureLocation: WebGLUniformLocation; + protected _uDepthTextureLocation: WebGLUniformLocation; + protected _uViewProjectionLocation2: WebGLUniformLocation; + protected _uViewProjectionInverseLocation: WebGLUniformLocation; + /* // Ambient Occlusion Pass */ + + protected _aoFbo: Framebuffer; + + /* Blur Pass */ + protected _aoMap: Texture2D; + + protected _blurProgram: Program; + + protected _uKernelSizeLocation: WebGLUniformLocation; + protected _uSourceLocation: WebGLUniformLocation; + protected _uBlurFrameSizeLocation: WebGLUniformLocation; + /* // Blur Pass */ + + protected _blurredAOFbo: Framebuffer; + + /* Composition Pass */ + protected _compositionProgram: Program; + + protected _uColorLocation: WebGLUniformLocation; + protected _uDepthLocation: WebGLUniformLocation; + protected _uAOMapLocation: WebGLUniformLocation; + protected _uCompositionFrameSizeLocation: WebGLUniformLocation; + /* // Composition Pass */ + + protected _outputFbo: DefaultFramebuffer; + + /** + * Initializes and sets up rendering passes, navigation, loads a font face and links shaders with program. + * @param context - valid context to create the object for. + * @param identifier - meaningful name for identification of this instance. + * @param eventProvider - required for mouse interaction + * @returns - whether initialization was successful + */ + protected onInitialize(context: Context, callback: Invalidate, eventProvider: EventProvider): boolean { + + const gl = this._context.gl; + + this._context.enable(['OES_standard_derivatives', 'WEBGL_color_buffer_float', + 'OES_texture_float', 'OES_texture_float_linear']); + + this._loader = new GLTFLoader(this._context); + + /* Initialize Shared Geometry for Postprocessing Passes */ + + this._ndcGeometry = new NdcFillingTriangle(this._context); + this._ndcGeometry.initialize(); + + /* Create and configure camera. */ + + this._camera = new Camera(); + this._camera.center = vec3.fromValues(0.0, 0.0, 0.0); + this._camera.up = vec3.fromValues(0.0, 1.0, 0.0); + this._camera.eye = vec3.fromValues(0.0, 1.0, 2.0); + this._camera.near = 0.05; + this._camera.far = 12.0; + + /* Create and configure navigation */ + + this._navigation = new Navigation(callback, eventProvider); + this._navigation.camera = this._camera; + + /* Initialize FBOs */ + + this._sceneColor = new Texture2D(this._context); + this._sceneColor.initialize(1, 1, gl.RGB8, gl.RGB, gl.UNSIGNED_BYTE); + this._sceneColor.filter(gl.LINEAR, gl.LINEAR); + + this._sceneNormal = new Texture2D(this._context); + this._sceneNormal.initialize(1, 1, gl.RGBA32F, gl.RGBA, gl.FLOAT); + this._sceneNormal.filter(gl.LINEAR, gl.LINEAR); + + this._sceneLinearDepth = new Texture2D(this._context); + this._sceneLinearDepth.initialize(1, 1, gl.R32F, gl.RED, gl.FLOAT); + this._sceneLinearDepth.filter(gl.LINEAR, gl.LINEAR); + + this._sceneDepth = new Renderbuffer(this._context, 'Scene_Depth'); + this._sceneDepth.initialize(1, 1, gl.DEPTH_COMPONENT16); + + this._sceneFbo = new Framebuffer(this._context, 'Scene_FBO'); + this._sceneFbo.initialize([ + [gl.COLOR_ATTACHMENT0, this._sceneColor], + [gl.COLOR_ATTACHMENT0 + 1, this._sceneNormal], + [gl.COLOR_ATTACHMENT0 + 2, this._sceneLinearDepth], + [gl.DEPTH_ATTACHMENT, this._sceneDepth] + ]); + this._sceneFbo.clearColor(this._clearColor, gl.COLOR_ATTACHMENT0); + this._sceneFbo.clearColor([0.5, 0.5, 0.5, 0.0], gl.COLOR_ATTACHMENT0 + 1); + this._sceneFbo.clearColor([1.0, 1.0, 1.0, 0.0], gl.COLOR_ATTACHMENT0 + 2); + + this._aoNoisyMap = new Texture2D(this._context); + this._aoNoisyMap.initialize(1, 1, gl.RGBA32F, gl.RGBA, gl.FLOAT); + this._aoNoisyMap.filter(gl.LINEAR, gl.LINEAR); + + this._aoFbo = new Framebuffer(this._context, 'AO_FBO'); + this._aoFbo.initialize([ + [gl.COLOR_ATTACHMENT0, this._aoNoisyMap] + ]); + + this._aoMap = new Texture2D(this._context); + this._aoMap.initialize(1, 1, gl.RGBA32F, gl.RGBA, gl.FLOAT); + this._aoMap.filter(gl.LINEAR, gl.LINEAR); + + this._blurredAOFbo = new Framebuffer(this._context, 'BlurredAO_FBO'); + this._blurredAOFbo.initialize([ + [gl.COLOR_ATTACHMENT0, this._aoMap] + ]); + + this._outputFbo = new DefaultFramebuffer(this._context, 'Default_FBO'); + this._outputFbo.initialize(); + + /* Initialize Programs */ + + /* Scene Pass */ + + const scene_vert = new Shader(context, gl.VERTEX_SHADER, 'geometry.vert'); + scene_vert.initialize(require('./data/ssao/geometry.vert')); + const scene_frag = new Shader(context, gl.FRAGMENT_SHADER, 'geometry.frag'); + scene_frag.initialize(require('./data/ssao/geometry.frag')); + + this._sceneProgram = new Program(context, 'SceneProgram'); + this._sceneProgram.initialize([scene_vert, scene_frag], true); + this._sceneProgram.link(); + this._sceneProgram.bind(); + + this._uViewProjectionLocation = this._sceneProgram.uniform('u_viewProjection'); + this._uModelLocation = this._sceneProgram.uniform('u_model'); + this._uEyeLocation = this._sceneProgram.uniform('u_eye'); + + this._uNormalLocation = this._sceneProgram.uniform('u_normal'); + this._uSceneFrameSizeLocation = this._sceneProgram.uniform('u_frameSize'); + + /* Shared Programs */ + + const ndc_triangle_vert = new Shader(context, gl.VERTEX_SHADER, 'ndctriangle.vert'); + ndc_triangle_vert.initialize(require('../source/shaders/ndcvertices.vert')); + + /* AO Pass */ + + const ao_frag = new Shader(context, gl.FRAGMENT_SHADER, 'ssao.frag'); + ao_frag.initialize(require('./data/ssao/ssao.frag')); + + this._aoProgram = new Program(context, 'AOProgram'); + this._aoProgram.initialize([ndc_triangle_vert, ao_frag], true); + this._aoProgram.link(); + this._aoProgram.bind(); + + this._uDepthTextureLocation = this._aoProgram.uniform('u_depth'); + this._uNormalTextureLocation = this._aoProgram.uniform('u_normal'); + this._uAOFrameSizeLocation = this._aoProgram.uniform('u_frameSize'); + this._uViewProjectionLocation2 = this._aoProgram.uniform('u_viewProjection'); + this._uViewProjectionInverseLocation = this._aoProgram.uniform('u_viewProjectionInverse'); + + /* Blur Pass */ + + const blur_frag = new Shader(context, gl.FRAGMENT_SHADER, 'blur.frag'); + blur_frag.initialize(require('./data/ssao/blur.frag')); + + this._blurProgram = new Program(context, 'BlurProgram'); + this._blurProgram.initialize([ndc_triangle_vert, blur_frag], true); + this._blurProgram.link(); + this._blurProgram.bind(); + + this._uKernelSizeLocation = this._blurProgram.uniform('u_kernelSize'); + this._uSourceLocation = this._blurProgram.uniform('u_source'); + this._uBlurFrameSizeLocation = this._blurProgram.uniform('u_frameSize'); + + /* Composition Pass */ + + const composition_frag = new Shader(context, gl.FRAGMENT_SHADER, 'composition.frag'); + composition_frag.initialize(require('./data/ssao/composition.frag')); + + this._compositionProgram = new Program(context, 'CompositionProgram'); + this._compositionProgram.initialize([ndc_triangle_vert, composition_frag], true); + this._compositionProgram.link(); + this._compositionProgram.bind(); + + this._uColorLocation = this._compositionProgram.uniform('u_color'); + this._uDepthLocation = this._compositionProgram.uniform('u_depth'); + this._uAOMapLocation = this._compositionProgram.uniform('u_aoMap'); + this._uCompositionFrameSizeLocation = this._compositionProgram.uniform('u_frameSize'); + + /* Create and Configure Scene Forward Pass. */ + + this._forwardPass = new ForwardSceneRenderPass(context); + this._forwardPass.initialize(); + + this._forwardPass.camera = this._camera; + this._forwardPass.target = this._sceneFbo; + + this._forwardPass.program = this._sceneProgram; + this._forwardPass.updateModelTransform = (matrix: mat4) => { + gl.uniformMatrix4fv(this._uModelLocation, false, matrix); + }; + this._forwardPass.updateViewProjectionTransform = (matrix: mat4) => { + gl.uniformMatrix4fv(this._uViewProjectionLocation, false, matrix); + }; + + this._forwardPass.bindUniforms = () => { + gl.uniform3fv(this._uEyeLocation, this._camera.eye); + gl.uniform1i(this._uNormalLocation, 2); + gl.uniform2f(this._uSceneFrameSizeLocation, this._frameSize[0], this._frameSize[1]); + }; + + this._forwardPass.bindGeometry = (geometry: Geometry) => { + }; + + this._forwardPass.bindMaterial = (material: Material) => { + const pbrMaterial = material as GLTFPbrMaterial; + auxiliaries.assert(pbrMaterial !== undefined, `Material ${material.name} is not a PBR material.`); + + pbrMaterial.normalTexture!.bind(gl.TEXTURE2); + }; + + this.loadAsset(); + + return true; + } + + /** + * Uninitializes Buffers, Textures, and Program. + */ + protected onUninitialize(): void { + super.uninitialize(); + + // ToDo: make sure that all meshes and programs inside of the scene get cleaned + + // this._mesh.uninitialize(); + // this._meshProgram.uninitialize(); + } + + protected onDiscarded(): void { + this._altered.alter('canvasSize'); + this._altered.alter('clearColor'); + this._altered.alter('frameSize'); + this._altered.alter('multiFrameNumber'); + } + + /** + * This is invoked in order to check if rendering of a frame is required by means of implementation specific + * evaluation (e.g., lazy non continuous rendering). Regardless of the return value a new frame (preparation, + * frame, swap) might be invoked anyway, e.g., when update is forced or canvas or context properties have + * changed or the renderer was invalidated @see{@link invalidate}. + * Updates the navigaten and the AntiAliasingKernel. + * @returns whether to redraw + */ + protected onUpdate(): boolean { + const gl = this._context.gl; + + if (this._altered.frameSize) { + this._camera.viewport = [this._frameSize[0], this._frameSize[1]]; + this._camera.aspect = this._frameSize[0] / this._frameSize[1]; + } + + if (this._altered.clearColor) { + this._sceneFbo.clearColor(this._clearColor, gl.COLOR_ATTACHMENT0); + this._forwardPass.clearColor = this._clearColor; + } + + this._navigation.update(); + this._forwardPass.update(); + + if (this._camera.altered) { + this._aoProgram.bind(); + gl.uniformMatrix4fv(this._uViewProjectionLocation2, false, this._camera.viewProjection); + gl.uniformMatrix4fv(this._uViewProjectionInverseLocation, false, this._camera.viewProjectionInverse); + } + + return this._altered.any || this._camera.altered; + } + + /** + * This is invoked in order to prepare rendering of one or more frames, regarding multi-frame rendering and + * camera-updates. + */ + protected onPrepare(): void { + const gl = this._context.gl; + + /* Resize FBOs */ + + if (this._altered.frameSize) { + this._sceneFbo.resize(this._frameSize[0], this._frameSize[1]); + this._aoFbo.resize(this._frameSize[0], this._frameSize[1]); + this._blurredAOFbo.resize(this._frameSize[0], this._frameSize[1]); + } + + /* Prepare Scene Render Pass */ + + this._forwardPass.prepare(); + + /* Prepare AO Map Pass */ + + this._aoProgram.bind(); + gl.uniform2f(this._uAOFrameSizeLocation, this._frameSize[0], this._frameSize[1]); + gl.uniform1i(this._uNormalTextureLocation, 0); + gl.uniform1i(this._uDepthTextureLocation, 1); + + /* Prepare Blur Pass */ + + this._blurProgram.bind(); + gl.uniform1i(this._uKernelSizeLocation, 5); + gl.uniform1i(this._uSourceLocation, 0); + gl.uniform2f(this._uBlurFrameSizeLocation, this._frameSize[0], this._frameSize[1]); + + /* Prepare Composition Pass */ + + this._compositionProgram.bind(); + gl.uniform1i(this._uColorLocation, 0); + gl.uniform1i(this._uDepthLocation, 1); + gl.uniform1i(this._uAOMapLocation, 2); + gl.uniform2f(this._uCompositionFrameSizeLocation, this._frameSize[0], this._frameSize[1]); + + /* Remainder */ + + this._altered.reset(); + this._camera.altered = false; + } + + protected onFrame(frameNumber: number): void { + const gl = this._context.gl; + + if (this.isLoading) { + return; + } + + /* Scene Pass */ + + this._sceneFbo.bind(); + + gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT0 + 1, gl.COLOR_ATTACHMENT0 + 2]); + + this._sceneFbo.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, false, false); + + gl.clearBufferfv(gl.COLOR, 1, [0.5, 0.5, 0.5, 1.0]); + gl.clearBufferfv(gl.COLOR, 2, [1.0, 1.0, 1.0, 1.0]); + + this._sceneProgram.bind(); + + this._forwardPass.frame(); + + /* Ambient Occlusion Pass */ + + this._aoFbo.bind(); + + gl.drawBuffers([gl.COLOR_ATTACHMENT0]); + + this._aoFbo.clear(gl.COLOR_BUFFER_BIT, false, false); + + this._sceneNormal.bind(gl.TEXTURE0); + this._sceneLinearDepth.bind(gl.TEXTURE1); + + this._aoProgram.bind(); + + this._ndcGeometry.bind(); + this._ndcGeometry.draw(); + this._ndcGeometry.unbind(); + + /* Blur Pass */ + + this._blurredAOFbo.bind(); + + gl.drawBuffers([gl.COLOR_ATTACHMENT0]); + + this._blurredAOFbo.clear(gl.COLOR_BUFFER_BIT, false, false); + + this._aoNoisyMap.bind(gl.TEXTURE0); + + this._blurProgram.bind(); + + this._ndcGeometry.bind(); + this._ndcGeometry.draw(); + this._ndcGeometry.unbind(); + } + + protected onSwap(): void { + const gl = this._context.gl; + + if (this.isLoading) { + return; + } + + /* Composition Pass */ + + this._outputFbo.bind(); + + gl.drawBuffers([gl.BACK]); + + this._outputFbo.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, false, false); + + this._sceneColor.bind(gl.TEXTURE0); + this._sceneLinearDepth.bind(gl.TEXTURE1); + this._aoMap.bind(gl.TEXTURE2); + + this._compositionProgram.bind(); + + this._ndcGeometry.bind(); + this._ndcGeometry.draw(); + this._ndcGeometry.unbind(); + } + + /** + * Load asset from URI specified by the HTML select + */ + protected loadAsset(): void { + const uri = '/examples/data/matrix-chair.glb'; + this._forwardPass.scene = undefined; + + this._loader.uninitialize(); + this._loader.loadAsset(uri) + .then(() => { + this._forwardPass.scene = this._loader.defaultScene; + this.finishLoading(); + this.invalidate(true); + }); + } +} + + +export class AmbientOcclusionExample extends Example { + + private _canvas: Canvas; + private _renderer: AmbientOcclusionRenderer; + + onInitialize(element: HTMLCanvasElement | string): boolean { + + this._canvas = new Canvas(element, { antialias: false }); + + this._canvas.controller.multiFrameNumber = 1; + this._canvas.framePrecision = Wizard.Precision.byte; + this._canvas.frameScale = [1.0, 1.0]; + + this._renderer = new AmbientOcclusionRenderer(); + this._canvas.renderer = this._renderer; + + return true; + } + + onUninitialize(): void { + this._canvas.dispose(); + (this._renderer as Renderer).uninitialize(); + } + + get canvas(): Canvas { + return this._canvas; + } + + get renderer(): AmbientOcclusionRenderer { + return this._renderer; + } + +} diff --git a/examples/data/ssao/blur.frag b/examples/data/ssao/blur.frag new file mode 100644 index 00000000..d27721af --- /dev/null +++ b/examples/data/ssao/blur.frag @@ -0,0 +1,84 @@ +precision highp float; + +@import ../../../source/shaders/facade.frag; + +#if __VERSION__ == 100 + #define fragColor gl_FragColor +#else + layout(location = 0) out vec4 fragColor; +#endif + +in vec2 v_uv; + +uniform int u_kernelSize; +uniform vec2 u_frameSize; + +uniform sampler2D u_source; + +#define TARGET_KERNEL_SIZE 5 + +#if TARGET_KERNEL_SIZE==5 + +const int kernelSize = 5; +const int kernelHalfSize = 2; +const float kernel[25] = float[25]( + 1.0, 4.0, 6.0, 4.0, 1.0, + 4.0, 16.0, 24.0, 16.0, 4.0, + 6.0, 24.0, 36.0, 24.0, 6.0, + 4.0, 16.0, 24.0, 16.0, 4.0, + 1.0, 4.0, 6.0, 4.0, 1.0 +); + +#elif TARGET_KERNEL_SIZE==7 + +const int kernelSize = 7; +const int kernelHalfSize = 3; +const float kernel[49] = float[49]( + 1.0, 6.0, 15.0, 20.0, 15.0, 6.0, 1.0, + 6.0, 36.0, 90.0,120.0, 90.0, 36.0, 6.0, + 15.0, 90.0,225.0,300.0,225.0, 90.0, 15.0, + 20.0,120.0,300.0,400.0,300.0,120.0, 20.0, + 15.0, 90.0,225.0,300.0,225.0, 90.0, 15.0, + 6.0, 36.0, 90.0,120.0, 90.0, 36.0, 6.0, + 1.0, 6.0, 15.0, 20.0, 15.0, 6.0, 1.0 +); + +#else + +const int kernelSize = 1; +const int kernelHalfSize = 0; +const float kernel[1] = float[1]( + 1.0 +); + +#endif + +void main(void) +{ + vec2 offset = vec2(1.0) / vec2(u_frameSize); + + vec4 result = vec4(0.0); + float weight_sum = 0.0; + + for (int y = 0; y < kernelSize; ++y) { + for (int x = 0; x < kernelSize; ++x) { + vec2 local_uv = v_uv + vec2(x - kernelHalfSize, y - kernelHalfSize) * offset; + + if (local_uv.x < 0.0 || local_uv.x > 1.0 || local_uv.y < 0.0 || local_uv.y > 1.0) { + continue; + } + + vec4 color = texture(u_source, local_uv); + float weight = kernel[y * kernelSize + x]; + result += weight * color; + weight_sum += weight; + } + } + + if (weight_sum < 0.0001) { + fragColor = vec4(0.0, 0.0, 0.0, 1.0); + return; + } + + fragColor = vec4(result.rgb / vec3(weight_sum), 1.0); +} diff --git a/examples/data/ssao/composition.frag b/examples/data/ssao/composition.frag new file mode 100644 index 00000000..f14d6b58 --- /dev/null +++ b/examples/data/ssao/composition.frag @@ -0,0 +1,32 @@ +precision highp float; + +@import ../../../source/shaders/facade.frag; + +#if __VERSION__ == 100 + #define fragColor gl_FragColor +#else + layout(location = 0) out vec4 fragColor; +#endif + +in vec2 v_uv; + +uniform vec2 u_frameSize; + +uniform sampler2D u_color; +uniform sampler2D u_depth; +uniform sampler2D u_aoMap; + +const float aoPower = 1.0; +const float aoFocus = 0.4; + +void main(void) +{ + vec4 colorValue = texture(u_color, v_uv); + // vec4 depthValue = texture(u_depth, v_uv); + vec4 aoValue = texture(u_aoMap, v_uv); + + vec3 aoScale = pow(aoValue.rrr, vec3(aoPower)); + vec3 finalColor = mix(colorValue.rgb * aoScale, aoScale, aoFocus); + + fragColor = vec4(finalColor, 1.0); +} diff --git a/examples/data/ssao/geometry.frag b/examples/data/ssao/geometry.frag new file mode 100644 index 00000000..752b2645 --- /dev/null +++ b/examples/data/ssao/geometry.frag @@ -0,0 +1,36 @@ +precision highp float; + +@import ../../../source/shaders/facade.frag; +@import ../../../source/shaders/pbr_normal; + +@import ../phong; + +#if __VERSION__ == 100 + #define fragColor gl_FragColor +#else + layout(location = 0) out vec4 fragColor; + layout(location = 1) out vec3 fragNormal; + layout(location = 2) out float fragDepth; +#endif + +uniform vec2 u_frameSize; +uniform vec3 u_eye; + +uniform sampler2D u_normal; + +varying vec3 v_position; +varying vec2 v_uv; +varying vec3 v_normal; + + +void main(void) +{ + vec3 normal = pbrNormal(v_position, v_uv, v_normal, u_normal); + vec3 f_phong = phong(v_position, normal, u_eye); + + vec3 color = f_phong; + + fragColor = vec4(color, 1.0); + fragNormal = vec3(normal); + fragDepth = gl_FragCoord.z; +} diff --git a/examples/data/ssao/geometry.vert b/examples/data/ssao/geometry.vert new file mode 100644 index 00000000..5705ee63 --- /dev/null +++ b/examples/data/ssao/geometry.vert @@ -0,0 +1,39 @@ +precision highp float; + +// Adapted from https://github.com/KhronosGroup/glTF-WebGL-PBR + +@import ../../../source/shaders/facade.vert; + + +#if __VERSION__ == 100 + attribute vec4 a_position; + attribute vec4 a_normal; + attribute vec2 a_texcoord_0; +#else + layout (location = 0) in vec4 a_position; + layout (location = 1) in vec3 a_normal; + layout (location = 3) in vec2 a_texcoord_0; +#endif + + +uniform mat4 u_viewProjection; +uniform mat4 u_model; + +uniform vec3 u_eye; + +varying vec3 v_position; +varying vec2 v_uv; +varying vec3 v_normal; + + +void main(void) +{ + vec4 pos = u_model * a_position; + + v_position = vec3(pos.xyz) / pos.w; + v_normal = normalize(vec3(u_model * vec4(a_normal.xyz, 0.0))); + + v_uv = a_texcoord_0; + + gl_Position = u_viewProjection * u_model * a_position; +} diff --git a/examples/data/ssao/ssao.frag b/examples/data/ssao/ssao.frag new file mode 100644 index 00000000..74a8dd21 --- /dev/null +++ b/examples/data/ssao/ssao.frag @@ -0,0 +1,107 @@ +precision highp float; + +@import ../../../source/shaders/facade.frag; + +#if __VERSION__ == 100 + #define fragColor gl_FragColor +#else + layout(location = 0) out vec4 fragColor; +#endif + +in vec2 v_uv; + +uniform vec2 u_frameSize; +uniform mat4 u_viewProjectionInverse; +uniform mat4 u_viewProjection; + +uniform sampler2D u_normal; +uniform sampler2D u_depth; + +/* RANDOM */ + +uint seed = 4u; +void hash(){ + seed ^= 2747636419u; + seed *= 2654435769u; + seed ^= seed >> 16; + seed *= 2654435769u; + seed ^= seed >> 16; + seed *= 2654435769u; +} + +float random(){ + hash(); + return float(seed)/4294967295.0; +} + +void initRandomGenerator(vec2 uv, vec2 dim, int f){ + seed = uint(uv.y*dim.x + uv.x) + uint(f)*uint(dim.x)*uint(dim.y); +} + +/* // RANDOM */ + +/* SAMPLING KERNEL */ + +#define PI 3.14159265359 +vec3 newDir(vec3 n){ + float theta = random()*2.0*PI; + float z = random()*2.0-1.0; + vec3 v = vec3(sqrt(1.0-z*z)*cos(theta), sqrt(1.0-z*z)*sin(theta), z); + if(dot(n, v) < 0.0) return -v; + return v; +} + +vec3 sampleVec(vec3 n, float radius){ + vec3 d = newDir(n); + float r = random(); + d *= mix(0.001, radius, r*r); + return d; +} + +/* // SAMPLING KERNEL */ + +/* MAIN */ + +void main(void) +{ + initRandomGenerator(v_uv, u_frameSize, int(2034.3423*v_uv.x+324.234564543*v_uv.y)); + + //ssao + int max_sample = 64; + float radius = 0.15; + + vec3 n = texture(u_normal, v_uv).xyz; + float depth = texture(u_depth, v_uv).x; + + float ao = 0.0; + float number_of_samples = 0.0; + + vec4 pos_ndc = vec4(2.0*v_uv - vec2(1.0), depth, 1.0); + vec4 pos_world = u_viewProjectionInverse * pos_ndc; + vec3 pos = pos_world.xyz / pos_world.w; + + for(int i = 0; i < max_sample; i++) { + vec3 samplePos = pos + sampleVec(n, radius); + + vec4 ndc = u_viewProjection * vec4(samplePos, 1.0); + ndc /= ndc.w; + vec2 uv2 = ndc.xy * 0.5 + 0.5; + + if (ndc.z < 1.0 && ndc.z > 0.0 && uv2.x >= 0.0 && uv2.x <= 1.0 && uv2.y >= 0.0 && uv2.y <= 1.0) { + float sampleDepth = texture(u_depth, uv2).r; + + ao += float(sampleDepth < ndc.z); + number_of_samples += 1.0; + } + } + + if (number_of_samples < 1.0) { + fragColor = vec4(1.0, 1.0, 1.0, 1.0); + return; + } + + ao /= number_of_samples; + ao = 1.0 - ao; + + fragColor = vec4(ao, ao, ao, 1.0); +} diff --git a/examples/examples.json b/examples/examples.json index 0052766b..b1e3f536 100644 --- a/examples/examples.json +++ b/examples/examples.json @@ -1,4 +1,10 @@ [ + { + "target": "ambient-occlusion-example", + "title": "Ambient Occlusion Shading", + "object": "AmbientOcclusionExample", + "caption": "Default implementation of Screen-space Ambient Occlusion." + }, { "target": "arealight-example", "title": "Area Lights", diff --git a/source/scene/forwardscenerenderpass.ts b/source/scene/forwardscenerenderpass.ts index f0392eb1..2212f84a 100644 --- a/source/scene/forwardscenerenderpass.ts +++ b/source/scene/forwardscenerenderpass.ts @@ -213,10 +213,10 @@ export class ForwardSceneRenderPass extends SceneRenderPass { const size = this._target.size; gl.viewport(0, 0, size[0], size[1]); - const c = this._clearColor; - gl.clearColor(c[0], c[1], c[2], c[3]); + //const c = this._clearColor; + //gl.clearColor(c[0], c[1], c[2], c[3]); - this._target.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, true, false); + //this._target.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, true, false); this._program.bind(); diff --git a/website/examples.pug b/website/examples.pug index 34cd8b3b..b42441fd 100644 --- a/website/examples.pug +++ b/website/examples.pug @@ -22,12 +22,12 @@ block content .col-lg-9.col-md-9.col-sm-8 .embed-responsive.embed-responsive-16by9 - iframe(name = 'example' src = `/examples/${examples[7].target}.html` + iframe(name = 'example' src = `/examples/${examples[0].target}.html` frameborder = 0 marginwidth = 0 marginheight = 0 allow-scripts allowfullscreen).embed-responsive-item script. - $('a.example-link:eq(6)').addClass('active'); + $('a.example-link:eq(0)').addClass('active'); $('a.example-link').click(function() { $('a.example-link').removeClass('active'); $(this).addClass('active');