| @@ -95,6 +95,7 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [DepthBufferPlugin](#depthbufferplugin) - Pre-rendering of depth buffer | |||
| - [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal buffer | |||
| - [GBufferPlugin](#gbufferplugin) - Pre-rendering of depth-normal and flags buffers in a single pass | |||
| - [SSAOPlugin](#ssaoplugin) - Add SSAO(Screen Space Ambient Occlusion) for physical materials. | |||
| - [CanvasSnapshotPlugin](#canvassnapshotplugin) - Add support for taking snapshots of the canvas | |||
| - [PickingPlugin](#pickingplugin) - Adds support for selecting objects in the viewer with user interactions and selection widgets | |||
| - [TransformControlsPlugin](#transformcontrolsplugin) - Adds support for moving, rotating and scaling objects in the viewer with interactive widgets | |||
| @@ -2265,6 +2266,39 @@ const normalDepth = gBufferPlugin.normalDepthTexture; | |||
| const gBufferFlags = gBufferPlugin.flagsTexture; | |||
| ``` | |||
| ## SSAOPlugin | |||
| [//]: # (todo: image) | |||
| [Example](https://threepipe.org/examples/#ssao-plugin/) — | |||
| [Source Code](./src/plugins/pipeline/SSAOPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/SSAOPlugin.html) | |||
| SSAO Plugin adds support for [Screen Space Ambient Occlusion](https://en.wikipedia.org/wiki/Screen_space_ambient_occlusion) to the viewer. Simply add the plugin to the viewer to use it. | |||
| This is done by adding a pre-render pass to the render manager which renders SSAO to a custom render target. SSAOPlugin depends on [GBufferPlugin](#gbufferplugin), and is automatically added if not already. | |||
| This render target is then used by all PhysicalMaterial(s) in the scene during the main RenderPass to get the AO data. SSAO can also be disabled from the UI of the material. | |||
| Note: Use with [ProgressivePlugin](#progressiveplugin) and `TemporalAAPlugin` for best results. | |||
| ```typescript | |||
| import {ThreeViewer, SSAOPlugin} from 'threepipe' | |||
| const viewer = new ThreeViewer({...}) | |||
| const ssaoPlugin = viewer.addPluginSync(new SSAOPlugin()) | |||
| // get the buffer. | |||
| console.log(ssaoPlugin.target); | |||
| // disable ssao for a material in the scene | |||
| material.userData.ssaoDisabled = true | |||
| ``` | |||
| > In the target/buffer - The ssao data is in the `r` channel to remain compatible with ORM. `gba` contains the depth in vec3(xyz) format. | |||
| > Note that its `ssaoDisabled`, so setting it to `true` will disable the effect. | |||
| ## CanvasSnapshotPlugin | |||
| [//]: # (todo: image) | |||
| @@ -326,6 +326,7 @@ | |||
| <li><a href="./depth-buffer-plugin/">Depth Buffer Plugin </a></li> | |||
| <li><a href="./normal-buffer-plugin/">Normal Buffer Plugin </a></li> | |||
| <li><a href="./gbuffer-plugin/">GBuffer Plugin <br/>(NormalDepth+Flags) </a></li> | |||
| <li><a href="./ssao-plugin/">SSAO Plugin </a></li> | |||
| <li><a href="./virtual-cameras-plugin/">Virtual Cameras Plugin </a></li> | |||
| <li><a href="./virtual-camera/">Virtual Camera (Animated) </a></li> | |||
| </ul> | |||
| @@ -0,0 +1,36 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>SSAO Plugin</title> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,42 @@ | |||
| import { | |||
| _testFinish, | |||
| ProgressivePlugin, | |||
| RenderTargetPreviewPlugin, | |||
| SSAOPlugin, | |||
| ThreeViewer, | |||
| UnsignedByteType, | |||
| } from 'threepipe' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| plugins: [ProgressivePlugin], | |||
| }) | |||
| async function init() { | |||
| const ssaoPlugin = viewer.addPluginSync(new SSAOPlugin(UnsignedByteType, 1)) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| await viewer.load('https://threejs.org/examples/models/gltf/kira.glb', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| const ssaoTarget = ssaoPlugin.target | |||
| if (!ssaoTarget) { | |||
| throw new Error('ssaoPlugin.target returned undefined') | |||
| } | |||
| // to render ssao buffer to screen, uncomment this line: | |||
| // viewer.renderManager.screenPass.overrideReadBuffer = ssaoTarget | |||
| const targetPreview = await viewer.addPlugin(RenderTargetPreviewPlugin) | |||
| targetPreview.addTarget(()=>ssaoTarget, 'ssao', false, true, true, (s)=>`${s} = vec4(${s}.r);`) | |||
| const ui = viewer.addPluginSync(TweakpaneUiPlugin, true) | |||
| ui.setupPluginUi(SSAOPlugin) | |||
| } | |||
| init().finally(_testFinish) | |||
| @@ -79,10 +79,6 @@ export interface IMaterialUserData extends IImportResultUserData{ | |||
| // todo: move these to respective plugins | |||
| /** | |||
| * For SSAOPlugin | |||
| */ | |||
| ssaoDisabled?: boolean | |||
| /** | |||
| * For SSCSPlugin | |||
| */ | |||
| @@ -14,6 +14,7 @@ export type {ProgressivePluginEventTypes, ProgressivePluginTarget} from './pipel | |||
| export type {GBufferPluginEventTypes, GBufferPluginPass, GBufferUpdater, GBufferUpdaterContext} from './pipeline/GBufferPlugin' | |||
| export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | |||
| export type {NormalBufferPluginEventTypes, NormalBufferPluginPass, NormalBufferPluginTarget} from './pipeline/NormalBufferPlugin' | |||
| export {SSAOPlugin, SSAOPluginPass, type SSAOPluginEventTypes, type SSAOPluginTarget} from './pipeline/SSAOPlugin' | |||
| // ui | |||
| export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' | |||
| @@ -137,6 +137,7 @@ export class GBufferPlugin | |||
| ['GBUFFER_HAS_FLAGS']: ()=>this.flagsTexture ? 1 : undefined, | |||
| // ['HAS_FLAGS_BUFFER']: ()=>this.flagsTexture ? 1 : undefined, | |||
| ['HAS_GBUFFER']: ()=>this.isPrimaryGBuffer && this.normalDepthTexture ? 1 : undefined, | |||
| // LINEAR_DEPTH: 1, // to tell that the depth is linear. todo; see SSAOPlugin. also add support in DepthBufferPlugin? | |||
| }, | |||
| priority: 100, | |||
| isCompatible: () => true, | |||
| @@ -0,0 +1,326 @@ | |||
| import {Matrix4, Texture, TextureDataType, UnsignedByteType, Vector2, Vector3, Vector4, WebGLRenderTarget} from 'three' | |||
| import {ExtendedShaderPass, IPassID, IPipelinePass} from '../../postprocessing' | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | |||
| import {uiConfig, uiFolderContainer, uiImage, uiSlider} from 'uiconfig.js' | |||
| import {ICamera, IMaterial, IRenderManager, IScene, IWebGLRenderer, PhysicalMaterial} from '../../core' | |||
| import {getOrCall, glsl, onChange2, serialize, ValOrFunc} from 'ts-browser-helpers' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {shaderReplaceString, shaderUtils} from '../../utils' | |||
| import {getTexelDecoding, matDefine} from '../../three' | |||
| import ssaoPass from './shaders/SSAOPlugin.pass.glsl' | |||
| import ssaoPatch from './shaders/SSAOPlugin.patch.glsl' | |||
| import {uiConfigMaterialExtension} from '../../materials/MaterialExtender' | |||
| import {GBufferPlugin} from './GBufferPlugin' | |||
| export type SSAOPluginEventTypes = '' | |||
| export type SSAOPluginTarget = WebGLRenderTarget | |||
| /** | |||
| * SSAO Plugin | |||
| * | |||
| * Adds a post-render pass to blend the last frame with the current frame. | |||
| * This can be used to create a progressive rendering effect which is useful for progressive shadows, gi, denoising, baking, anti-aliasing, and many other effects. | |||
| * @category Plugins | |||
| */ | |||
| @uiFolderContainer('SSAO Plugin') | |||
| export class SSAOPlugin | |||
| extends PipelinePassPlugin<SSAOPluginPass, 'ssao', SSAOPluginEventTypes> { | |||
| readonly passId = 'ssao' | |||
| public static readonly PluginType = 'SSAOPlugin' | |||
| dependencies = [GBufferPlugin] | |||
| target?: SSAOPluginTarget | |||
| @uiImage('SSAO Buffer' /* {readOnly: true}*/) texture?: Texture | |||
| @uiConfig() protected _pass?: SSAOPluginPass | |||
| // @onChange2(SSAOPlugin.prototype._createTarget) | |||
| // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | |||
| readonly bufferType: TextureDataType // cannot be changed after creation (for now) | |||
| // @onChange2(SSAOPlugin.prototype._createTarget) | |||
| // @uiSlider('Buffer Size Multiplier', [0.25, 2.0], 0.25) | |||
| readonly sizeMultiplier: number // cannot be changed after creation (for now) | |||
| constructor( | |||
| bufferType: TextureDataType = UnsignedByteType, | |||
| sizeMultiplier = 1, | |||
| enabled = true, | |||
| ) { | |||
| super() | |||
| this.enabled = enabled | |||
| this.bufferType = bufferType | |||
| this.sizeMultiplier = sizeMultiplier | |||
| } | |||
| protected _createTarget(recreate = true) { | |||
| if (!this._viewer) return | |||
| if (recreate) this._disposeTarget() | |||
| if (!this.target) | |||
| this.target = this._viewer.renderManager.createTarget<SSAOPluginTarget>( | |||
| { | |||
| depthBuffer: false, | |||
| type: this.bufferType, | |||
| sizeMultiplier: this.sizeMultiplier, | |||
| // magFilter: NearestFilter, | |||
| // minFilter: NearestFilter, | |||
| // generateMipmaps: false, | |||
| // encoding: LinearEncoding, | |||
| }) | |||
| this.texture = this.target.texture | |||
| this.texture.name = 'ssaoBuffer' | |||
| if (this._pass) this._pass.target = this.target | |||
| } | |||
| protected _disposeTarget() { | |||
| if (!this._viewer) return | |||
| if (this.target) { | |||
| this._viewer.renderManager.disposeTarget(this.target) | |||
| this.target = undefined | |||
| } | |||
| this.texture = undefined | |||
| } | |||
| private _gbufferUnpackExtension = undefined as MaterialExtension|undefined | |||
| private _gbufferUnpackExtensionChanged = ()=>{ | |||
| if (!this._pass || !this._viewer) throw new Error('SSAOPlugin: pass/viewer not created yet') | |||
| const newExtension = this._viewer.renderManager.gbufferUnpackExtension | |||
| if (this._gbufferUnpackExtension === newExtension) return | |||
| if (this._gbufferUnpackExtension) this._pass.material.unregisterMaterialExtensions([this._gbufferUnpackExtension]) | |||
| this._gbufferUnpackExtension = newExtension | |||
| if (this._gbufferUnpackExtension) this._pass.material.registerMaterialExtensions([this._gbufferUnpackExtension]) | |||
| else this._viewer.console.warn('SSAOPlugin: GBuffer unpack extension removed') | |||
| } | |||
| protected _createPass() { | |||
| if (!this._viewer) throw new Error('SSAOPlugin: viewer not set') | |||
| if (!this._viewer.renderManager.gbufferTarget || !this._viewer.renderManager.gbufferUnpackExtension) | |||
| throw new Error('SSAOPlugin: GBuffer target not created. GBufferPlugin or DepthBufferPlugin is required.') | |||
| this._createTarget(true) | |||
| const pass = new SSAOPluginPass(this.passId, this.target) | |||
| pass.before = ['render'] | |||
| pass.after = ['gbuffer', 'depth'] | |||
| pass.required = ['render'] // gbuffer required check above. | |||
| return pass | |||
| } | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| this._gbufferUnpackExtensionChanged() | |||
| viewer.renderManager.addEventListener('gbufferUnpackExtensionChanged', this._gbufferUnpackExtensionChanged) | |||
| } | |||
| onRemove(viewer: ThreeViewer): void { | |||
| this._disposeTarget() | |||
| return super.onRemove(viewer) | |||
| } | |||
| /** | |||
| * @deprecated use {@link target} instead | |||
| */ | |||
| get aoTarget() { | |||
| console.warn('SSAOPlugin: aoTarget is deprecated, use target instead') | |||
| return this.target | |||
| } | |||
| } | |||
| @uiFolderContainer('SSAO Pass') | |||
| export class SSAOPluginPass extends ExtendedShaderPass implements IPipelinePass { | |||
| before = ['screen'] | |||
| after = ['render'] | |||
| required = ['render'] | |||
| // todo bilateralPass | |||
| // @serialize() readonly bilateralPass: BilateralFilterPass | |||
| // todo old deserialize | |||
| // @serialize() readonly parameters: SSAOParams = { | |||
| // intensity: 0.25, | |||
| // occlusionWorldRadius: 1, | |||
| // bias: 0.001, | |||
| // falloff: 1.3, | |||
| // } | |||
| @serialize() | |||
| @uiSlider('Intensity', [0, 4], 0.01) | |||
| @onChange2(SSAOPluginPass.prototype.setDirty) | |||
| intensity = 0.25 | |||
| @serialize() | |||
| @uiSlider('Occlusion World Radius', [0.1, 8], 0.01) | |||
| @onChange2(SSAOPluginPass.prototype.setDirty) | |||
| occlusionWorldRadius = 1 | |||
| @serialize() | |||
| @uiSlider('Bias', [0.00001, 0.01], 0.00001) | |||
| @onChange2(SSAOPluginPass.prototype.setDirty) | |||
| bias = 0.001 | |||
| @serialize() | |||
| @uiSlider('Falloff', [0.01, 3], 0.01) | |||
| @onChange2(SSAOPluginPass.prototype.setDirty) | |||
| falloff = 1.3 | |||
| @serialize() | |||
| @uiSlider('Num Samples', [1, 11], 1) | |||
| @matDefine('NUM_SAMPLES', undefined, undefined, SSAOPluginPass.prototype.setDirty) | |||
| numSamples = 8 | |||
| // todo after bilateralPass is implemented | |||
| // @bindToValue({obj: 'bilateralPass', key: 'enabled', onChange: 'setDirty'}) | |||
| // smoothEnabled = true | |||
| // todo after bilateralPass is implemented | |||
| // @bindToValue({obj: 'bilateralPass', key: 'enabled', onChange: 'setDirty'}) | |||
| // smoothEdgeSharpness = true | |||
| constructor(public readonly passId: IPassID, public target?: ValOrFunc<WebGLRenderTarget|undefined>) { | |||
| super({ | |||
| defines: { | |||
| LINEAR_DEPTH: 1, // todo set from unpack extension | |||
| NUM_SAMPLES: 11, | |||
| NUM_SPIRAL_TURNS: 3, | |||
| PERSPECTIVE_CAMERA: 1, // set in PerspectiveCamera2 | |||
| }, | |||
| uniforms: { | |||
| tLastThis: {value: null}, | |||
| screenSize: {value: new Vector2(0, 0)}, // set in ExtendedRenderMaterial | |||
| saoData: {value: new Vector4()}, | |||
| frameCount: {value: 0}, // set in RenderManager | |||
| cameraNearFar: {value: new Vector2(0.1, 1000)}, // set in PerspectiveCamera2 | |||
| projection: {value: new Matrix4()}, // set in PerspectiveCamera2 | |||
| saoBiasEpsilon: {value: new Vector3(1, 1, 1)}, | |||
| }, | |||
| vertexShader: shaderUtils.defaultVertex, | |||
| fragmentShader: ssaoPass, | |||
| }, 'tDiffuse') | |||
| this.needsSwap = false | |||
| this.clear = true | |||
| // this.bilateralPass = new BilateralFilterPass(this._target as any, gBufferUnpack, 'rrrr') | |||
| // this._multiplyPass = new GenericBlendTexturePass(this._target.texture as any, 'c = vec4((1.0-b.r) * a.xyz, a.a);') | |||
| } | |||
| render(renderer: IWebGLRenderer, writeBuffer: WebGLRenderTarget, readBuffer: WebGLRenderTarget, deltaTime: number, maskActive: boolean) { | |||
| if (!this.enabled) return | |||
| const target = getOrCall(this.target) | |||
| if (!target) { | |||
| console.warn('SSAOPluginPass: target not defined') | |||
| return | |||
| } | |||
| this._updateParameters() | |||
| if (!this.material.defines.HAS_GBUFFER) { | |||
| console.warn('SSAOPluginPass: DepthNormalBuffer required for ssao') | |||
| } | |||
| renderer.renderManager.blit(writeBuffer, { | |||
| source: target.texture, | |||
| }) | |||
| this.uniforms.tLastThis.value = writeBuffer.texture | |||
| super.render(renderer, target, readBuffer, deltaTime, maskActive) | |||
| // todo | |||
| // if (this.smoothEnabled) { | |||
| // this.bilateralPass.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) | |||
| // } | |||
| } | |||
| private _updateParameters() { | |||
| // const projectionScale = 1 / (Math.tan(DEG2RAD * (camera as any).fov / 2) * 2); | |||
| const saoData = this.material.uniforms.saoData.value | |||
| // saoData.x = projectionScale; | |||
| saoData.y = this.intensity | |||
| saoData.z = this.occlusionWorldRadius | |||
| // saoData.w = this.accIndex_++; | |||
| const saoBiasEpsilon = this.material.uniforms.saoBiasEpsilon.value | |||
| saoBiasEpsilon.x = this.bias | |||
| saoBiasEpsilon.y = 0.001 | |||
| saoBiasEpsilon.z = this.falloff | |||
| // this.material.uniforms.size.value.set(this._target.texture.image?.width, this._target.texture.image?.height) | |||
| } | |||
| beforeRender(_: IScene, camera: ICamera, renderManager: IRenderManager) { | |||
| if (!this.enabled) return | |||
| this.updateShaderProperties([camera, renderManager]) | |||
| } | |||
| readonly materialExtension: MaterialExtension = { | |||
| extraUniforms: { | |||
| tSSAOMap: ()=>({value: getOrCall(this.target)?.texture ?? null}), | |||
| }, | |||
| shaderExtender: (shader, _material, _renderer) => { | |||
| if (!shader.defines.SSAO_ENABLED) return | |||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#include <aomap_fragment>', ssaoPatch) | |||
| }, | |||
| onObjectRender: (_object, material, renderer: any) => { | |||
| const opaque = !material.transparent && (!material.transmission || material.transmission < 0.001) | |||
| const x: any = this.enabled && opaque && | |||
| renderer.userData.screenSpaceRendering !== false && | |||
| !material.userData?.ssaoDisabled ? 1 : 0 | |||
| if (material.defines!.SSAO_ENABLED !== x) { | |||
| material.defines!.SSAO_ENABLED = x | |||
| material.needsUpdate = true | |||
| } | |||
| }, | |||
| parsFragmentSnippet: (renderer)=>glsl` | |||
| uniform sampler2D tSSAOMap; | |||
| ${getTexelDecoding('tSSAOMap', getOrCall(this.target)?.texture, renderer!.capabilities.isWebGL2)} | |||
| #include <simpleCameraHelpers> | |||
| `, | |||
| computeCacheKey: () => { | |||
| return this.enabled ? '1' : '0' + getOrCall(this.target)?.texture?.colorSpace | |||
| }, | |||
| uuid: SSAOPlugin.PluginType, | |||
| ...uiConfigMaterialExtension(this._getUiConfig, SSAOPlugin.PluginType), | |||
| isCompatible: material => { | |||
| return (material as PhysicalMaterial).isPhysicalMaterial | |||
| }, | |||
| } | |||
| /** | |||
| * Returns a uiConfig to toggle SSAO on a material. | |||
| * This uiConfig is added to each material by extension | |||
| * @param material | |||
| * @private | |||
| */ | |||
| protected _getUiConfig(material: IMaterial) { | |||
| return { | |||
| type: 'folder', | |||
| label: 'SSAO', | |||
| children: [ | |||
| { | |||
| type: 'checkbox', | |||
| label: 'Enabled', | |||
| get value() { | |||
| return !(material.userData.ssaoDisabled ?? false) | |||
| }, | |||
| set value(v) { | |||
| if (v === !(material.userData.ssaoDisabled ?? false)) return | |||
| material.userData.ssaoDisabled = !v | |||
| material.setDirty() | |||
| }, | |||
| onChange: this.setDirty, | |||
| }, | |||
| ], | |||
| } | |||
| } | |||
| } | |||
| declare module '../../core/IMaterial' { | |||
| interface IMaterialUserData { | |||
| /** | |||
| * Disable SSAOPlugin for this material. | |||
| */ | |||
| ssaoDisabled?: boolean | |||
| } | |||
| } | |||
| @@ -0,0 +1,166 @@ | |||
| #include <randomHelpers> | |||
| #include <common> | |||
| #include <packing> | |||
| #define THREE_PACKING_INCLUDED | |||
| #include <cameraHelpers> | |||
| varying vec2 vUv; | |||
| uniform sampler2D tLastThis; | |||
| uniform float frameCount; | |||
| uniform vec4 saoData; | |||
| uniform vec3 saoBiasEpsilon; | |||
| uniform vec2 screenSize; | |||
| const float INV_NUM_SAMPLES = 1.0 / float(NUM_SAMPLES); | |||
| //int getSelectionBit(in int number) { | |||
| // #ifdef WebGL2Context | |||
| // return (number/4) % 2; | |||
| // #else | |||
| // return int(mod(floor(float(number)/4.), 2.)); | |||
| // #endif | |||
| //} | |||
| vec3 packFloatToRGB(const in float x) { | |||
| const vec3 code = vec3(1.0, 255.0, 65025.0); | |||
| vec3 pack = vec3(code * x); | |||
| pack.gb = fract(pack.gb); | |||
| pack.rg -= pack.gb * (1.0 / 256.0); | |||
| return pack; | |||
| } | |||
| vec3 getPositionFromOffset(const in vec2 uv, | |||
| const in vec2 offset, | |||
| const in float screenSpaceRadius) { | |||
| vec2 uvOffset = uv + floor(screenSpaceRadius * offset) / screenSize; | |||
| #if defined(HAS_DEPTH_BUFFER) || defined(HAS_NORMAL_DEPTH_BUFFER) | |||
| float d = getDepth(uvOffset); | |||
| #else | |||
| float d = 0.5; | |||
| #endif | |||
| #if LINEAR_DEPTH == 0 | |||
| float centerViewZ = viewZFromNDCZ(d); | |||
| return screenToView(uvOffset, centerViewZ); | |||
| #else | |||
| d = mix(-cameraNearFar.x, -cameraNearFar.y, d); | |||
| return screenToView(uvOffset, d); | |||
| #endif | |||
| } | |||
| float getOcclusion(const in vec2 uv, | |||
| const in int id, | |||
| const in float randomAngle, | |||
| const in float occlusionSphereRadius, | |||
| const in vec3 centerPosition, | |||
| const in vec3 centerNormal) { | |||
| float screenSpaceRadius = (float(id) + mod(randomAngle, 1.) + 0.5) * INV_NUM_SAMPLES; | |||
| float angle = screenSpaceRadius * (float(NUM_SPIRAL_TURNS) * 6.28) + randomAngle; | |||
| screenSpaceRadius = (screenSpaceRadius * occlusionSphereRadius); | |||
| vec2 offset = vec2(cos(angle), sin(angle)); | |||
| vec3 samplePosition = getPositionFromOffset(uv, offset, screenSpaceRadius); | |||
| vec3 direction = samplePosition - centerPosition; | |||
| float d2 = dot(direction, direction); | |||
| float ao = max((dot(centerNormal, direction) + centerPosition.z * saoBiasEpsilon.x) / (saoBiasEpsilon.z * d2 + saoBiasEpsilon.y), 0.0); | |||
| return ao; | |||
| } | |||
| void main() { | |||
| // initial values | |||
| float centerDepth = 0.5; | |||
| vec3 centerNormal = vec3(0, 1, 0); | |||
| #ifdef HAS_NORMAL_DEPTH_BUFFER | |||
| getDepthNormal(vUv, centerDepth, centerNormal); | |||
| #else | |||
| #ifdef HAS_DEPTH_BUFFER | |||
| centerDepth = getDepth(vUv); | |||
| #endif | |||
| // todo - add support for NormalBufferPlugin | |||
| // #ifdef HAS_NORMAL_BUFFER | |||
| // centerDepth = getDepth(vUv); | |||
| // #endif | |||
| #endif | |||
| // if (centerDepth >= (1.0 - EPSILON)) { | |||
| // discard; | |||
| // } | |||
| #if LINEAR_DEPTH == 0 | |||
| float centerViewZ = viewZFromNDCZ(centerDepth); | |||
| #else | |||
| float centerViewZ = mix(-cameraNearFar.x, -cameraNearFar.y, centerDepth); | |||
| #endif | |||
| vec3 centerPosition = screenToView(vUv, centerViewZ); | |||
| float occlusionSphereScreenRadius = 200. * saoData.z / (-centerPosition.z); | |||
| // if (occlusionSphereScreenRadius < 1.) { | |||
| // discard; | |||
| // } | |||
| float randomAngle = 6.2 * random3(vec3(vUv, frameCount * 0.1)); | |||
| float sum = 0.0; | |||
| sum += getOcclusion(vUv, 0, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #if NUM_SAMPLES > 1 | |||
| sum += getOcclusion(vUv, 1, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 2 | |||
| sum += getOcclusion(vUv, 2, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 3 | |||
| sum += getOcclusion(vUv, 3, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 4 | |||
| sum += getOcclusion(vUv, 4, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 5 | |||
| sum += getOcclusion(vUv, 5, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 6 | |||
| sum += getOcclusion(vUv, 6, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 7 | |||
| sum += getOcclusion(vUv, 7, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 8 | |||
| sum += getOcclusion(vUv, 8, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 9 | |||
| sum += getOcclusion(vUv, 9, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| #if NUM_SAMPLES > 10 | |||
| sum += getOcclusion(vUv, 10, randomAngle, occlusionSphereScreenRadius, centerPosition, centerNormal); | |||
| #endif | |||
| float aoValue = sum * saoData.y * INV_NUM_SAMPLES; | |||
| // bool disableAO = getSelectionBit(getGBufferFlags(vUv).a) > 0 ? true : false; | |||
| aoValue = 1. - clamp(aoValue, 0., 1.); | |||
| // so that depth can also be sampled with ssao if required? | |||
| gl_FragColor.gba = packFloatToRGB(centerDepth); | |||
| // vec4 lastAO = texture2D( tLastThis, vUv ); | |||
| // gl_FragColor.r = (vec4(aoValue)).r;// + (lastAO.r) * frameCount)/(frameCount+1.); | |||
| gl_FragColor.r = aoValue;// + (lastAO.r) * frameCount)/(frameCount+1.); | |||
| // gl_FragColor.r = aoValue; | |||
| // gl_FragColor = vec4(centerDepth); | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| #ifndef USE_TRANSMISSION | |||
| #if defined(SSAO_ENABLED) && SSAO_ENABLED > 0 | |||
| // reads channel R, compatible with a combined OcclusionRoughnessMetallic (RGB) texture | |||
| float ambientOcclusion = tSSAOMapTexelToLinear ( texture2D( tSSAOMap, viewToScreen(vViewPosition.xyz).xy )).r; //todo: check encoding for tSSAOMap | |||
| reflectedLight.indirectDiffuse *= ambientOcclusion; | |||
| #if defined( USE_ENVMAP ) | |||
| float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) ); | |||
| reflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness ); | |||
| #endif | |||
| #else | |||
| #include <aomap_fragment> | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,6 @@ | |||
| varying vec2 vUv; | |||
| void main() { | |||
| vUv = uv; | |||
| gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); | |||
| } | |||