| @@ -383,7 +383,7 @@ | |||
| <li><a href="./depthoffield-plugin/">DepthOfField Plugin </a></li> | |||
| <li><a href="./ssreflection-plugin/">Screen Space Reflection(SSR) Plugin </a></li> | |||
| <li><a href="./temporalaa-plugin/">Temporal Anti-aliasing(TAA) Plugin </a></li> | |||
| <li><a href="./outline-plugin/">Outline(Picking) Plugin </a></li> | |||
| <li><a href="./outline-plugin/">Outline (Picking) Plugin </a></li> | |||
| <li><a href="./ssgi-plugin/">Screen Space Global Illumination(SSGI) Plugin </a></li> | |||
| <li><a href="./ssgi-ssr-plugin/">SSGI + SSR Plugins </a></li> | |||
| <!--<li><a href="./ssrtao-plugin/">Screen Space Ray Traced AO Plugin </a></li>--> | |||
| @@ -498,7 +498,7 @@ | |||
| <li><a href="./custom-bump-map-plugin/">Custom Bump Map Plugin </a></li> | |||
| <li><a href="./parallax-mapping-plugin/">Parallax(Relief) Mapping Plugin </a></li> | |||
| </ul> | |||
| <h2 class="category">Utils</h2> | |||
| <h2 class="category">Utils / Experiments</h2> | |||
| <ul> | |||
| <li><a href="./contact-shadow-ground-plugin/">Contact Shadow Ground Plugin</a></li> | |||
| <li><a href="./hdri-ground-plugin/">HDRi Ground Plugin <br/>(Projected Skybox)</a></li> | |||
| @@ -512,14 +512,13 @@ | |||
| <li><a href="./obj-to-glb/">Convert OBJ to GLB </a></li> | |||
| <li><a href="./3dm-to-glb/">Convert 3DM to GLB </a></li> | |||
| <li><a href="./hdr-to-exr/">Convert HDR to EXR </a></li> | |||
| <li><a href="./fat-lines/">Fat Lines <br/>(Mesh Lines) </a></li> | |||
| <li><a href="./fat-line-spiral/">Line Spiral<br/>(Mesh Lines) </a></li> | |||
| </ul> | |||
| <h2 class="category">Experiments</h2> | |||
| <ul> | |||
| <li><a href="./fat-lines/">Fat Lines (Mesh Lines) </a></li> | |||
| <li><a href="./fat-line-spiral/">Line Spiral (Mesh Lines) </a></li> | |||
| <li><a href="./progressive-hdr-shadows-exp/">Progressive HDR Environment Shadows</a></li> | |||
| <li><a href="./multi-render-uv-clip/">Multi-render UV clipping <br/> (Material Extension) </a></li> | |||
| <li><a href="./svg-geometry-playground/">SVG Geometry Playground </a></li> | |||
| <li><a href="./stencil-clipping-portal/">Stencil Clipping Portal </a></li> | |||
| <li><a href="./stencil-picking-outline/">Stencil Picking Outline </a></li> | |||
| </ul> | |||
| <h2 class="category">Shaders</h2> | |||
| <ul> | |||
| @@ -0,0 +1,37 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Stencil Clipping WebGL</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/global-loading.mjs"></script> | |||
| <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,87 @@ | |||
| import { | |||
| _testFinish, | |||
| _testStart, | |||
| IObject3D, | |||
| LoadingScreenPlugin, | |||
| OrbitControls3, | |||
| PickingPlugin, | |||
| ThreeViewer, | |||
| TransformControlsPlugin, | |||
| } from 'threepipe' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| import * as THREE from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: false, // todo this is not working here, but its working in the other stencil example. | |||
| rgbm: true, | |||
| plugins: [LoadingScreenPlugin, PickingPlugin, TransformControlsPlugin], | |||
| stencil: true, | |||
| }) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| ui.setupPlugins(PickingPlugin, TransformControlsPlugin) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||
| setBackground: true, | |||
| }) | |||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| const model = result?.getObjectByName('node_damagedHelmet_-6514') | |||
| const config = model?.uiConfig | |||
| if (config) ui.appendChild(config) | |||
| const maskMaterial = new THREE.MeshBasicMaterial({ | |||
| color: 0x222222, | |||
| stencilRef: 1, | |||
| depthWrite: false, | |||
| stencilWrite: true, | |||
| depthTest: true, | |||
| stencilFunc: THREE.AlwaysStencilFunc, | |||
| stencilZPass: THREE.ReplaceStencilOp, | |||
| }) | |||
| const maskPlane = new THREE.Mesh(new THREE.PlaneGeometry(4, 4), maskMaterial) | |||
| viewer.scene.addObject(maskPlane) | |||
| const maskCube = new THREE.Mesh(new THREE.BoxGeometry(4, 4, 20), new THREE.MeshBasicMaterial({ | |||
| color: 0xffffff, | |||
| stencilRef: 1, | |||
| depthWrite: false, | |||
| stencilWrite: true, | |||
| depthTest: true, | |||
| stencilFunc: THREE.AlwaysStencilFunc, | |||
| stencilZPass: THREE.ReplaceStencilOp, | |||
| colorWrite:false, | |||
| })) | |||
| maskCube.userData.bboxVisible = false | |||
| maskCube.userData.userSelectable = false | |||
| maskCube.position.set(0, 0, 10) | |||
| viewer.scene.addObject(maskCube) | |||
| maskPlane.addEventListener('objectUpdate', ()=>{ | |||
| maskCube.position.set(maskPlane.position.x, maskPlane.position.y, maskPlane.position.z + 10) | |||
| }) | |||
| const mat = model?.materials?.[0] | |||
| if (mat && model) { | |||
| mat.stencilWrite = true | |||
| mat.stencilRef = 1 | |||
| mat.stencilFunc = THREE.LessEqualStencilFunc | |||
| model.renderOrder = 2 | |||
| model.position.z = -0.4 | |||
| } | |||
| viewer.getPlugin(PickingPlugin)?.setSelectedObject(model) | |||
| const controls = viewer.scene.mainCamera.controls as OrbitControls3 | |||
| controls.minAzimuthAngle = -1.5 | |||
| controls.maxAzimuthAngle = 1.5 | |||
| } | |||
| _testStart() | |||
| init().finally(_testFinish) | |||
| @@ -0,0 +1,37 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Stencil Picking Outline</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/global-loading.mjs"></script> | |||
| <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,143 @@ | |||
| import { | |||
| _testFinish, | |||
| _testStart, | |||
| AlwaysStencilFunc, | |||
| BufferGeometry2, | |||
| DecrementStencilOp, | |||
| EqualStencilFunc, | |||
| IObject3D, | |||
| LoadingScreenPlugin, | |||
| Mesh2, | |||
| PickingPlugin, | |||
| ReplaceStencilOp, | |||
| shaderReplaceString, | |||
| ThreeViewer, | |||
| TransformControlsPlugin, | |||
| UnlitMaterial, | |||
| } from 'threepipe' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| // Custom outline for picking plugin by duplicating the model and using stencil buffer | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| rgbm: true, | |||
| plugins: [LoadingScreenPlugin, PickingPlugin, new TransformControlsPlugin(false)], | |||
| stencil: true, | |||
| }) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| ui.setupPlugins(TransformControlsPlugin, PickingPlugin) | |||
| await Promise.all([ | |||
| viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||
| setBackground: false, | |||
| }), | |||
| viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/kira.glb', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| autoScaleRadius: 6, | |||
| }), | |||
| ]) | |||
| const selectionMaterial = new UnlitMaterial({ | |||
| color: 0xe98a65, | |||
| stencilRef: 1, | |||
| depthWrite: false, | |||
| stencilWrite: true, | |||
| depthTest: true, | |||
| stencilFunc: AlwaysStencilFunc, | |||
| stencilZPass: ReplaceStencilOp, | |||
| colorWrite: true, | |||
| }) | |||
| selectionMaterial.registerMaterialExtensions([{ | |||
| shaderExtender:(shader, _material, _renderer) => { | |||
| shader.vertexShader = shaderReplaceString( | |||
| shader.vertexShader, | |||
| '#include <begin_vertex>', | |||
| '\ntransformed += normal * 0.02;', | |||
| {append: true} | |||
| ) | |||
| }, | |||
| isCompatible: ()=>true, | |||
| computeCacheKey: ()=> 'selectionMaterial', | |||
| }]) | |||
| const selectionMesh = new Mesh2(new BufferGeometry2(), selectionMaterial) | |||
| const selectedObjectUpdateListener = (ev: {object: IObject3D})=>{ | |||
| if (ev.object !== selected) return | |||
| selected.updateMatrixWorld() | |||
| selected.matrixWorld.decompose( | |||
| selectionMesh.position, | |||
| selectionMesh.quaternion, | |||
| selectionMesh.scale, | |||
| ) | |||
| selectionMesh.updateMatrixWorld() | |||
| // const selectionScale = 1.1 | |||
| // const center = new Box3B().expandByObject(selectionMesh).getCenter(new Vector3()).sub(selectionMesh.position) | |||
| // const m = new Matrix4().makeTranslation(new Vector3().copy(center).negate()) | |||
| // .multiply(new Matrix4().makeScale(selectionScale, selectionScale, selectionScale)) | |||
| // .multiply(new Matrix4().makeTranslation(new Vector3().copy(center).multiplyScalar(1 / selectionScale))) | |||
| // selectionMesh.matrix.premultiply(m).decompose(selectionMesh.position, selectionMesh.quaternion, selectionMesh.scale) | |||
| } | |||
| let selected = undefined as IObject3D | undefined | |||
| let lastState = null as any | |||
| viewer.getPlugin(PickingPlugin)!.addEventListener('selectedObjectChanged', ()=>{ | |||
| const model = viewer.getPlugin(PickingPlugin)?.getSelectedObject<IObject3D>() | |||
| const geometry = model?.geometry | |||
| if (selected === model) return | |||
| if (selected) { | |||
| // remove selection mesh from previous selected object | |||
| selected.removeEventListener('objectUpdate', selectedObjectUpdateListener) | |||
| const lastMaterial = selected.materials?.[0] | |||
| if (lastMaterial && lastState) { | |||
| lastMaterial.stencilWrite = lastState.stencilWrite | |||
| lastMaterial.stencilRef = lastState.stencilRef | |||
| lastMaterial.stencilFunc = lastState.stencilFunc | |||
| lastMaterial.stencilZPass = lastState.stencilZPass | |||
| lastMaterial.needsUpdate = true | |||
| selected.renderOrder = lastState.renderOrder | |||
| } | |||
| lastState = null | |||
| } | |||
| const material = model?.materials?.[0] | |||
| if (!model?.isObject3D || !geometry || !material) { // it can also be a selected material | |||
| selectionMesh.geometry = undefined as any | |||
| selectionMesh.removeFromParent() | |||
| return | |||
| } | |||
| if (!geometry) return | |||
| // Set selected object's material to use stencil buffer | |||
| lastState = { | |||
| stencilWrite: material.stencilWrite, | |||
| stencilRef: material.stencilRef, | |||
| stencilFunc: material.stencilFunc, | |||
| stencilZPass: material.stencilZPass, | |||
| renderOrder: model.renderOrder, | |||
| } | |||
| material.stencilWrite = true | |||
| material.stencilRef = 1 | |||
| material.stencilFunc = EqualStencilFunc | |||
| material.stencilZPass = DecrementStencilOp | |||
| material.needsUpdate = true | |||
| model.renderOrder = 2 | |||
| // Set selection mesh to match selected object | |||
| selected = model | |||
| selectionMesh.geometry = geometry | |||
| // add listeners to update selection mesh position when its moved | |||
| selected.addEventListener('objectUpdate', selectedObjectUpdateListener) | |||
| selectedObjectUpdateListener({object: selected}) | |||
| if (!selectionMesh.parent) viewer.scene.addObject(selectionMesh, {addToRoot: true}) // add to root so it is not saved | |||
| }) | |||
| viewer.getPlugin(PickingPlugin)!.widgetEnabled = false | |||
| const chair = viewer.scene.getObjectByName('Node-Mesh003_1') | |||
| viewer.getPlugin(PickingPlugin)!.setSelectedObject(chair, true) | |||
| } | |||
| _testStart() | |||
| init().finally(_testFinish) | |||
| @@ -139,7 +139,7 @@ export class RenderManager<TE extends IRenderManagerEventMap = IRenderManagerEve | |||
| // this._xrPreAnimationLoop = this._xrPreAnimationLoop.bind(this) | |||
| this._renderSize = new Vector2(canvas.clientWidth, canvas.clientHeight) | |||
| this._renderScale = renderScale | |||
| this._renderer = this._initWebGLRenderer(canvas, alpha) | |||
| this._renderer = this._initWebGLRenderer(canvas, alpha, targetOptions?.stencilBuffer ?? false) | |||
| this._context = this._renderer.getContext() | |||
| this._isWebGL2 = this._renderer.capabilities.isWebGL2 | |||
| if (!this._isWebGL2) console.error('RenderManager: WebGL 1 is not officially supported anymore. Some features may not work.') | |||
| @@ -152,7 +152,7 @@ export class RenderManager<TE extends IRenderManagerEventMap = IRenderManagerEve | |||
| // if (animationLoop) this.addEventListener('animationLoop', animationLoop) // todo: from viewer | |||
| } | |||
| protected _initWebGLRenderer(canvas: HTMLCanvasElement, alpha: boolean): IWebGLRenderer<this> { | |||
| protected _initWebGLRenderer(canvas: HTMLCanvasElement, alpha: boolean, stencil: boolean): IWebGLRenderer<this> { | |||
| const renderer = new WebGLRenderer({ | |||
| canvas, | |||
| antialias: false, | |||
| @@ -160,6 +160,7 @@ export class RenderManager<TE extends IRenderManagerEventMap = IRenderManagerEve | |||
| premultipliedAlpha: false, // todo: see this, maybe use this with rgbm mode. | |||
| preserveDrawingBuffer: true, | |||
| powerPreference: RenderManager.POWER_PREFERENCE, | |||
| stencil, | |||
| }) | |||
| // renderer.info.autoReset = false // Not supported by ExtendedRenderPass | |||
| @@ -80,7 +80,14 @@ export interface CreateRenderTargetOptions { | |||
| colorSpace?: ColorSpace | |||
| type?: TextureDataType | |||
| format?: number | |||
| /** | |||
| * @default true | |||
| */ | |||
| depthBuffer?: boolean | |||
| /** | |||
| * @default false | |||
| */ | |||
| stencilBuffer?: boolean | |||
| depthTexture?: boolean | |||
| depthTextureType?: typeof UnsignedShortType | typeof UnsignedInt248Type | typeof UnsignedIntType | typeof FloatType | |||
| depthTextureFormat?: typeof DepthFormat | typeof DepthStencilFormat | |||
| @@ -57,6 +57,7 @@ export abstract class RenderTargetManager<TE extends object = object> extends Ev | |||
| colorSpace = NoColorSpace, | |||
| type = UnsignedByteType, | |||
| format = RGBAFormat, | |||
| stencilBuffer = false, | |||
| depthBuffer = true, | |||
| depthTexture = false, | |||
| depthTextureType = UnsignedIntType, | |||
| @@ -78,7 +79,7 @@ export abstract class RenderTargetManager<TE extends object = object> extends Ev | |||
| height: size.height, | |||
| count: textureCount, | |||
| } : size, | |||
| {samples, colorSpace, type, format, depthBuffer, depthTexture: depthTex}, | |||
| {samples, colorSpace, type, format, depthBuffer, depthTexture: depthTex, stencilBuffer}, | |||
| textureCount > 1 ? WebGLMultipleRenderTargets as any : WebGLRenderTarget) | |||
| this._processNewTarget(target, sizeMultiplier, trackTarget) | |||
| this._setTargetOptions(target, op) | |||
| @@ -165,6 +165,11 @@ export interface ThreeViewerOptions { | |||
| * @default 1 | |||
| */ | |||
| modelRootScale?: number | |||
| /** | |||
| * Enable stencil in renderer and stencilBuffer in composer render targets. | |||
| * @default false | |||
| */ | |||
| stencil?: boolean | |||
| debug?: boolean | |||
| @@ -455,6 +460,7 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| rgbm: options.rgbm ?? options.useRgbm ?? true, | |||
| zPrepass: options.zPrepass ?? options.useGBufferDepth ?? false, | |||
| depthBuffer: !(options.zPrepass ?? options.useGBufferDepth ?? false), | |||
| stencilBuffer: options.stencil, | |||
| screenShader: options.screenShader, | |||
| renderScale: typeof options.renderScale === 'string' ? options.renderScale === 'auto' ? | |||
| Math.min(options.maxRenderScale || 2, window.devicePixelRatio) : parseFloat(options.renderScale) : | |||
| @@ -11,6 +11,7 @@ export interface ViewerRenderManagerOptions extends IRenderManagerOptions { | |||
| rgbm?: boolean, | |||
| msaa?: boolean | number, | |||
| depthBuffer?: boolean, | |||
| stencilBuffer?: boolean, | |||
| zPrepass?: boolean, | |||
| screenShader?: TViewerScreenShader | |||
| maxHDRIntensity?: number | |||
| @@ -38,7 +39,7 @@ export class ViewerRenderManager extends RenderManager<ViewerRenderManagerEventM | |||
| static DEFAULT_MSAA_SAMPLES = 4 | |||
| constructor({rgbm = true, msaa = false, depthBuffer = false, ...options}: ViewerRenderManagerOptions) { | |||
| constructor({rgbm = true, msaa = false, depthBuffer = true, stencilBuffer = false, ...options}: ViewerRenderManagerOptions) { | |||
| super({ | |||
| ...options, | |||
| targetOptions: { | |||
| @@ -47,6 +48,7 @@ export class ViewerRenderManager extends RenderManager<ViewerRenderManagerEventM | |||
| colorSpace: rgbm ? RGBM16ColorSpace : NoColorSpace, | |||
| type: rgbm ? UnsignedByteType : HalfFloatType, | |||
| depthBuffer: depthBuffer, | |||
| stencilBuffer: stencilBuffer, | |||
| generateMipmaps: /* msaa ? true : */false, // todo: hack for now, fix blurTransmissionTarget in ExtendedRenderPass | |||
| minFilter: /* msaa ? LinearMipMapLinearFilter : */LinearFilter, // todo: hack for now, fix blurTransmissionTarget in ExtendedRenderPass | |||
| }, | |||