| viewer.scene.mainCamera.userData.maxFarPlane = 10 | viewer.scene.mainCamera.userData.maxFarPlane = 10 | ||||
| viewer.scene.refreshScene() | viewer.scene.refreshScene() | ||||
| const depthTarget = depthPlugin.getTarget() | |||||
| const depthTarget = depthPlugin.target | |||||
| if (!depthTarget) { | if (!depthTarget) { | ||||
| throw new Error('depthPlugin.getTarget() returned undefined') | |||||
| throw new Error('depthPlugin.target returned undefined') | |||||
| } | } | ||||
| // to render depth buffer to screen, uncomment this line: | // to render depth buffer to screen, uncomment this line: | ||||
| targetPreview.addTarget(()=>depthTarget, 'depth') | targetPreview.addTarget(()=>depthTarget, 'depth') | ||||
| createSimpleButtons({ | createSimpleButtons({ | ||||
| ['Toggle depth rendering']: () => { | |||||
| ['Toggle Depth rendering']: () => { | |||||
| viewer.renderManager.screenPass.overrideReadBuffer = | viewer.renderManager.screenPass.overrideReadBuffer = | ||||
| viewer.renderManager.screenPass.overrideReadBuffer ? null : depthTarget | viewer.renderManager.screenPass.overrideReadBuffer ? null : depthTarget | ||||
| viewer.setDirty() | viewer.setDirty() |
| <h2 class="category">Plugins</h2> | <h2 class="category">Plugins</h2> | ||||
| <ul> | <ul> | ||||
| <li><a href="./depth-buffer-plugin/">Depth Buffer Plugin </a></li> | <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="./render-target-preview/">Render Target Preview </a></li> | <li><a href="./render-target-preview/">Render Target Preview </a></li> | ||||
| <li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) </a></li> | <li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) </a></li> | ||||
| </ul> | </ul> |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Depth Buffer Plugin</title> | |||||
| <!-- 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" | |||||
| } | |||||
| } | |||||
| </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> |
| import { | |||||
| _testFinish, | |||||
| downloadBlob, | |||||
| HalfFloatType, | |||||
| NoColorSpace, | |||||
| NormalBufferPlugin, | |||||
| RenderTargetPreviewPlugin, | |||||
| SRGBColorSpace, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js' | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| }) | |||||
| async function init() { | |||||
| const normalPlugin = viewer.addPluginSync(new NormalBufferPlugin(HalfFloatType)) | |||||
| 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 normalTarget = normalPlugin.target | |||||
| if (!normalTarget) { | |||||
| throw new Error('normalPlugin.target returned undefined') | |||||
| } | |||||
| viewer.renderManager.screenPass.overrideReadBuffer = normalTarget | |||||
| viewer.renderManager.screenPass.outputColorSpace = NoColorSpace // default is SRGBColorSpace | |||||
| const targetPreview = await viewer.addPlugin(RenderTargetPreviewPlugin) | |||||
| targetPreview.addTarget(()=>normalTarget, 'normal') | |||||
| createSimpleButtons({ | |||||
| ['Toggle Normal rendering']: () => { | |||||
| const state = !!viewer.renderManager.screenPass.overrideReadBuffer | |||||
| viewer.renderManager.screenPass.overrideReadBuffer = state ? null : normalTarget | |||||
| viewer.renderManager.screenPass.outputColorSpace = state ? SRGBColorSpace : NoColorSpace | |||||
| viewer.setDirty() | |||||
| }, | |||||
| ['Download snapshot']: async(btn: HTMLButtonElement) => { | |||||
| btn.disabled = true | |||||
| const blob = await viewer.getScreenshotBlob({mimeType: 'image/png'}) | |||||
| if (blob) downloadBlob(blob, 'file.png') | |||||
| else console.error('Unable to get screenshot') | |||||
| btn.disabled = false | |||||
| }, | |||||
| }) | |||||
| } | |||||
| init().then(_testFinish) |
| import {_testFinish, DepthBufferPlugin, HalfFloatType, RenderTargetPreviewPlugin, ThreeViewer} from 'threepipe' | |||||
| import { | |||||
| _testFinish, | |||||
| DepthBufferPlugin, | |||||
| HalfFloatType, | |||||
| NormalBufferPlugin, | |||||
| RenderTargetPreviewPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| async function init() { | async function init() { | ||||
| const depth = viewer.addPluginSync(new DepthBufferPlugin(HalfFloatType, true)) | const depth = viewer.addPluginSync(new DepthBufferPlugin(HalfFloatType, true)) | ||||
| const normal = viewer.addPluginSync(new NormalBufferPlugin(HalfFloatType)) | |||||
| const targetPreview = viewer.addPluginSync(RenderTargetPreviewPlugin) | const targetPreview = viewer.addPluginSync(RenderTargetPreviewPlugin) | ||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | ||||
| viewer.renderManager.autoBuildPipeline = false | viewer.renderManager.autoBuildPipeline = false | ||||
| viewer.renderManager.pipeline = ['depth', 'render', 'screen'] | viewer.renderManager.pipeline = ['depth', 'render', 'screen'] | ||||
| targetPreview.addTarget(()=>depth.getTarget(), 'depth', false, true) | |||||
| targetPreview.addTarget(()=>depth.target, 'depth', false, true) | |||||
| targetPreview.addTarget(()=>normal.target, 'normal', false, true) | |||||
| targetPreview.addTarget(()=>viewer.renderManager.composerTarget, 'composer-1', false, false) | targetPreview.addTarget(()=>viewer.renderManager.composerTarget, 'composer-1', false, false) | ||||
| targetPreview.addTarget(()=>viewer.renderManager.composerTarget2, 'composer-2', false, false) | targetPreview.addTarget(()=>viewer.renderManager.composerTarget2, 'composer-2', false, false) | ||||
| export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | ||||
| export {NormalBufferPlugin} from './pipeline/NormalBufferPlugin' | |||||
| export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | ||||
| export {PipelinePassPlugin} from './base/PipelinePassPlugin' | export {PipelinePassPlugin} from './base/PipelinePassPlugin' | ||||
| export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' | export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' |
| readonly passId = 'depth' | readonly passId = 'depth' | ||||
| public static readonly PluginType = 'DepthBufferPlugin' | public static readonly PluginType = 'DepthBufferPlugin' | ||||
| private _depthTarget?: DepthBufferPluginTarget | |||||
| private _depthTexture?: Texture | |||||
| target?: DepthBufferPluginTarget | |||||
| texture?: Texture | |||||
| readonly material: MeshDepthMaterial = new MeshDepthMaterial({ | readonly material: MeshDepthMaterial = new MeshDepthMaterial({ | ||||
| depthPacking: BasicDepthPacking, | depthPacking: BasicDepthPacking, | ||||
| blending: NoBlending, | blending: NoBlending, | ||||
| // private _gbufferPass?: IFilter<GBufferRenderPass<WebGLMultipleRenderTargets> | // private _gbufferPass?: IFilter<GBufferRenderPass<WebGLMultipleRenderTargets> | ||||
| createPass(v: ThreeViewer) { | createPass(v: ThreeViewer) { | ||||
| const target = v.renderManager.createTarget<DepthBufferPluginTarget>( | |||||
| if (!this.target) this.target = v.renderManager.createTarget<DepthBufferPluginTarget>( | |||||
| { | { | ||||
| depthBuffer: true, | depthBuffer: true, | ||||
| samples: v.renderManager.composerTarget.samples || 0, | samples: v.renderManager.composerTarget.samples || 0, | ||||
| // generateMipmaps: false, | // generateMipmaps: false, | ||||
| // encoding: LinearEncoding, | // encoding: LinearEncoding, | ||||
| }) | }) | ||||
| target.texture.name = 'depthBuffer' | |||||
| this._depthTexture = target.texture | |||||
| this._depthTarget = target | |||||
| this.texture = this.target.texture | |||||
| this.texture.name = 'depthBuffer' | |||||
| if (this.isPrimaryGBuffer) v.renderManager.gbufferTarget = target | |||||
| if (this.isPrimaryGBuffer) v.renderManager.gbufferTarget = this.target | |||||
| this.material.userData.isGBufferMaterial = true | this.material.userData.isGBufferMaterial = true | ||||
| const pass = new GBufferRenderPass('depth', target, this.material, new Color(0, 0, 0), 1) | |||||
| const pass = new GBufferRenderPass('depth', this.target, this.material, new Color(0, 0, 0), 1) | |||||
| const preprocessMaterial = pass.preprocessMaterial | |||||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, m.userData.renderToDepth) | |||||
| pass.before = ['render'] | pass.before = ['render'] | ||||
| pass.after = [] | pass.after = [] | ||||
| pass.required = ['render'] | pass.required = ['render'] | ||||
| } | } | ||||
| onRemove(viewer: ThreeViewer): void { | onRemove(viewer: ThreeViewer): void { | ||||
| if (this._depthTarget) { | |||||
| viewer.renderManager.disposeTarget(this._depthTarget) | |||||
| this._depthTarget = undefined | |||||
| if (this.target) { | |||||
| viewer.renderManager.disposeTarget(this.target) | |||||
| this.target = undefined | |||||
| } | } | ||||
| return super.onRemove(viewer) | return super.onRemove(viewer) | ||||
| } | } | ||||
| getTarget() { | |||||
| return this._depthTarget | |||||
| } | |||||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | ||||
| if (material.uniforms.tDepth) material.uniforms.tDepth.value = this._depthTexture ?? undefined | |||||
| if (material.uniforms.tDepth) material.uniforms.tDepth.value = this.texture ?? undefined | |||||
| else this._viewer?.console.warn('BaseRenderer: no uniform: tDepth') | else this._viewer?.console.warn('BaseRenderer: no uniform: tDepth') | ||||
| return this | return this | ||||
| } | } |
| import { | |||||
| Color, | |||||
| HalfFloatType, | |||||
| IUniform, | |||||
| LinearSRGBColorSpace, | |||||
| MeshNormalMaterial, | |||||
| NearestFilter, | |||||
| NoBlending, | |||||
| Texture, | |||||
| TextureDataType, | |||||
| WebGLRenderTarget, | |||||
| } from 'three' | |||||
| import {GBufferRenderPass} from '../../postprocessing' | |||||
| import {ThreeViewer} from '../../viewer' | |||||
| import {IShaderPropertiesUpdater} from '../../materials' | |||||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | |||||
| export type NormalBufferPluginEventTypes = '' | |||||
| // type NormalBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | |||||
| export type NormalBufferPluginTarget = WebGLRenderTarget | |||||
| export type NormalBufferPluginPass = GBufferRenderPass<'normal', NormalBufferPluginTarget> | |||||
| export class NormalBufferPlugin | |||||
| extends PipelinePassPlugin<NormalBufferPluginPass, 'normal', NormalBufferPluginEventTypes> | |||||
| implements IShaderPropertiesUpdater { | |||||
| readonly passId = 'normal' | |||||
| public static readonly PluginType = 'NormalBufferPlugin' | |||||
| target?: NormalBufferPluginTarget | |||||
| texture?: Texture | |||||
| readonly material: MeshNormalMaterial = new MeshNormalMaterial({ | |||||
| blending: NoBlending, | |||||
| }) | |||||
| // private _gbufferPass?: IFilter<GBufferRenderPass<WebGLMultipleRenderTargets> | |||||
| createPass(v: ThreeViewer) { | |||||
| if (!this.target) this.target = v.renderManager.createTarget<NormalBufferPluginTarget>( | |||||
| { | |||||
| depthBuffer: true, | |||||
| // samples: v.renderManager.composerTarget.samples || 0, | |||||
| samples: 0, | |||||
| type: this.bufferType, | |||||
| magFilter: NearestFilter, | |||||
| minFilter: NearestFilter, | |||||
| generateMipmaps: false, | |||||
| colorSpace: LinearSRGBColorSpace, | |||||
| }) | |||||
| this.texture = this.target.texture | |||||
| this.texture.name = 'normalBuffer' | |||||
| this.material.userData.isGBufferMaterial = true | |||||
| const pass = new GBufferRenderPass('normal', this.target, this.material, new Color(0, 0, 0), 1) | |||||
| const preprocessMaterial = pass.preprocessMaterial | |||||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, true) | |||||
| pass.before = ['render'] | |||||
| pass.after = [] | |||||
| pass.required = ['render'] | |||||
| return pass | |||||
| } | |||||
| constructor( | |||||
| public readonly bufferType: TextureDataType = HalfFloatType, | |||||
| ) { | |||||
| super() | |||||
| } | |||||
| onRemove(viewer: ThreeViewer): void { | |||||
| if (this.target) { | |||||
| viewer.renderManager.disposeTarget(this.target) | |||||
| this.target = undefined | |||||
| } | |||||
| return super.onRemove(viewer) | |||||
| } | |||||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | |||||
| if (material.uniforms.tNormal) material.uniforms.tNormal.value = this.texture ?? undefined | |||||
| else this._viewer?.console.warn('BaseRenderer: no uniform: tNormal') | |||||
| return this | |||||
| } | |||||
| } | |||||
| private _transparentMats = new Set<IMaterial>() | private _transparentMats = new Set<IMaterial>() | ||||
| private _transmissiveMats = new Set<[IMaterial, number]>() | private _transmissiveMats = new Set<[IMaterial, number]>() | ||||
| preprocessMaterial = (material: IMaterial, renderToGBuffer?: boolean) => { | |||||
| renderToGBuffer = renderToGBuffer ?? material.userData.renderToGBuffer | |||||
| if ( | |||||
| material.transparent && renderToGBuffer || // transparent and render to gbuffer | |||||
| !material.transparent && !material.transmission && renderToGBuffer === false // opaque and dont render to gbuffer | |||||
| ) { | |||||
| this._transparentMats.add(material) | |||||
| material.transparent = !material.transparent | |||||
| // material.needsUpdate = true | |||||
| } | |||||
| if ( | |||||
| material.transmission && | |||||
| Math.abs(material.transmission || 0) > 0 && renderToGBuffer // transmission and render to gbuffer | |||||
| ) { | |||||
| this._transmissiveMats.add([material, material.transmission]) | |||||
| material.transmission = 0 | |||||
| // material.needsUpdate = true | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * Renders to {@link target} | * Renders to {@link target} | ||||
| * @param renderer | * @param renderer | ||||
| const activeCubeFace = renderer.getActiveCubeFace() | const activeCubeFace = renderer.getActiveCubeFace() | ||||
| const activeMipLevel = renderer.getActiveMipmapLevel() | const activeMipLevel = renderer.getActiveMipmapLevel() | ||||
| const preprocessMaterial = (material: IMaterial) => { | |||||
| const renderToGBuffer = material.userData.renderToGBuffer === undefined ? material.userData.renderToDepth : material.userData.renderToGBuffer | |||||
| if ( | |||||
| material.transparent && material.userData.renderToDepth || // transparent and render to gbuffer | |||||
| !material.transparent && !material.transmission && renderToGBuffer === false // opaque and dont render to gbuffer | |||||
| ) { | |||||
| this._transparentMats.add(material) | |||||
| material.transparent = !material.transparent | |||||
| // material.needsUpdate = true | |||||
| } | |||||
| if ( | |||||
| material.transmission && | |||||
| Math.abs(material.transmission || 0) > 0 && renderToGBuffer // transmission and render to gbuffer | |||||
| ) { | |||||
| this._transmissiveMats.add([material, material.transmission]) | |||||
| material.transmission = 0 | |||||
| // material.needsUpdate = true | |||||
| } | |||||
| } | |||||
| this.scene.traverse(({material}) => { | this.scene.traverse(({material}) => { | ||||
| if (!material) return | if (!material) return | ||||
| if (Array.isArray(material)) material.forEach(preprocessMaterial) | |||||
| else preprocessMaterial(material) | |||||
| if (Array.isArray(material)) material.forEach((m)=>this.preprocessMaterial(m)) | |||||
| else this.preprocessMaterial(material) | |||||
| }) | }) | ||||
| // todo; copy double sided, check with post processing | // todo; copy double sided, check with post processing |