| @@ -94,7 +94,7 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [ProgressivePlugin](#progressiveplugin) - Post-render pass to blend the last frame with the current frame | |||
| - [DepthBufferPlugin](#depthbufferplugin) - Pre-rendering of depth buffer | |||
| - [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal buffer | |||
| - [GBufferPlugin](#depthnormalbufferplugin) - Pre-rendering of depth and normal buffers in a single pass buffer | |||
| - [GBufferPlugin](#gbufferplugin) - Pre-rendering of depth-normal and flags buffers in a single pass | |||
| - [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 | |||
| - [GLTFAnimationPlugin](#gltfanimationplugin) - Add support for playing and seeking gltf animations | |||
| @@ -2212,7 +2212,7 @@ The depth values are based on camera near far values, which are controlled autom | |||
| Normal Buffer Plugin adds a pre-render pass to the render manager and renders a normal buffer to a target. The render target can be accessed by other plugins throughout the rendering pipeline to create effects like SSAO, SSR, etc. | |||
| Note: Use [`DepthNormalBufferPlugin`](#DepthNormalBufferPlugin) if using both `DepthBufferPlugin` and `NormalBufferPlugin` to render both depth and normal buffers in a single pass. | |||
| Note: Use [`GBufferPlugin`](#GBufferPlugin) if using both `DepthBufferPlugin` and `NormalBufferPlugin` to render both depth and normal buffers in a single pass. | |||
| ```typescript | |||
| import {ThreeViewer, NormalBufferPlugin} from 'threepipe' | |||
| @@ -2229,7 +2229,25 @@ const normalTarget = normalPlugin.target; | |||
| ## GBufferPlugin | |||
| todo | |||
| [//]: # (todo: image) | |||
| [Example](https://threepipe.org/examples/#gbuffer-plugin/) — | |||
| [Source Code](./src/plugins/pipeline/GBufferPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/GBufferPlugin.html) | |||
| GBuffer Plugin adds a pre-render pass to the render manager and renders depth+normals to a target and some customizable flags to another. The multiple render target and textures can be accessed by other plugins throughout the rendering pipeline to create effects like SSAO, SSR, etc. | |||
| ```typescript | |||
| import {ThreeViewer, GBufferPlugin} from 'threepipe' | |||
| const viewer = new ThreeViewer({...}) | |||
| const gBufferPlugin = viewer.addPluginSync(new GBufferPlugin()) | |||
| const gBuffer = gBufferPlugin.target; | |||
| const normalDepth = gBufferPlugin.normalDepthTexture; | |||
| const gBufferFlags = gBufferPlugin.flagsTexture; | |||
| ``` | |||
| ## PickingPlugin | |||
| @@ -0,0 +1,35 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>GBuffer 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" | |||
| } | |||
| } | |||
| </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,80 @@ | |||
| import { | |||
| _testFinish, | |||
| downloadBlob, | |||
| GBufferPlugin, | |||
| HalfFloatType, | |||
| IObject3D, | |||
| RenderTargetPreviewPlugin, | |||
| ThreeViewer, | |||
| } from 'threepipe' | |||
| import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js' | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| }) | |||
| async function init() { | |||
| const gbufferPlugin = viewer.addPluginSync(new GBufferPlugin(HalfFloatType)) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| const model = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/kira.glb', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| let id = 1 | |||
| model?.traverse((o) => { | |||
| o.materials?.forEach(m=>{ | |||
| if (!m.userData.gBufferData) m.userData.gBufferData = {} | |||
| if (!m.userData.gBufferData.materialId) m.userData.gBufferData.materialId = id += 10 | |||
| }) | |||
| }) | |||
| // Disable automatic near/far plane calculation based on scene bounding box | |||
| viewer.scene.mainCamera.userData.autoNearFar = false | |||
| viewer.scene.mainCamera.userData.minNearPlane = 1 | |||
| viewer.scene.mainCamera.userData.maxFarPlane = 10 | |||
| viewer.scene.refreshScene() | |||
| const gbufferTarget = gbufferPlugin.target | |||
| if (!gbufferTarget) { | |||
| throw new Error('gbufferPlugin.target returned undefined') | |||
| } | |||
| // to render depth buffer to screen, uncomment this line: | |||
| // viewer.renderManager.screenPass.overrideReadBuffer = gbufferTarget | |||
| const getNormalDepth = ()=>({texture: gbufferPlugin.normalDepthTexture}) | |||
| const getFlags = ()=>({texture: gbufferPlugin.flagsTexture}) | |||
| const targetPreview = await viewer.addPlugin(RenderTargetPreviewPlugin) | |||
| targetPreview.addTarget(getNormalDepth, 'normalDepth') | |||
| targetPreview.addTarget(getFlags, 'gBufferFlags') | |||
| const screenPass = viewer.renderManager.screenPass | |||
| createSimpleButtons({ | |||
| ['Toggle Normal+Depth']: () => { | |||
| const rt = getNormalDepth() | |||
| screenPass.overrideReadBuffer = screenPass.overrideReadBuffer?.texture === rt.texture ? null : rt | |||
| viewer.setDirty() | |||
| }, | |||
| ['Toggle Gbuffer Flags']: () => { | |||
| const rt = getFlags() | |||
| screenPass.overrideReadBuffer = screenPass.overrideReadBuffer?.texture === rt.texture ? null : rt | |||
| 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) | |||
| @@ -228,9 +228,10 @@ | |||
| <h2 class="category">Rendering</h2> | |||
| <ul> | |||
| <li><a href="./progressive-plugin/">Progressive Plugin </a></li> | |||
| <li><a href="./custom-pipeline/">Custom Pipeline specification </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="./custom-pipeline/">Custom Pipeline specification </a></li> | |||
| <li><a href="./gbuffer-plugin/">GBuffer Plugin <br/>(NormalDepth+Flags) </a></li> | |||
| <li><a href="./virtual-cameras-plugin/">Virtual Cameras Plugin </a></li> | |||
| <li><a href="./virtual-camera/">Virtual Camera (Animated) </a></li> | |||
| </ul> | |||
| @@ -11,6 +11,7 @@ import { | |||
| FragmentClippingExtensionPlugin, | |||
| FrameFadePlugin, | |||
| FullScreenPlugin, | |||
| GBufferPlugin, | |||
| GLTFAnimationPlugin, | |||
| HalfFloatType, | |||
| HDRiGroundPlugin, | |||
| @@ -64,7 +65,7 @@ async function init() { | |||
| new ProgressivePlugin(), | |||
| GLTFAnimationPlugin, | |||
| PickingPlugin, | |||
| TransformControlsPlugin, | |||
| new TransformControlsPlugin(false), | |||
| EditorViewWidgetPlugin, | |||
| CameraViewPlugin, | |||
| ViewerUiConfigPlugin, | |||
| @@ -74,7 +75,8 @@ async function init() { | |||
| CustomBumpMapPlugin, | |||
| VirtualCamerasPlugin, | |||
| // new SceneUiConfigPlugin(), // this is already in ViewerUiPlugin | |||
| new DepthBufferPlugin(HalfFloatType, true, true), | |||
| new GBufferPlugin(HalfFloatType, true, true, true), | |||
| new DepthBufferPlugin(HalfFloatType, false, false), | |||
| new NormalBufferPlugin(HalfFloatType, false), | |||
| new RenderTargetPreviewPlugin(false), | |||
| new FrameFadePlugin(), | |||
| @@ -97,13 +99,15 @@ async function init() { | |||
| ]) | |||
| const rt = viewer.getOrAddPluginSync(RenderTargetPreviewPlugin) | |||
| rt.addTarget({texture: viewer.getPlugin(GBufferPlugin)?.normalDepthTexture}, 'normalDepth') | |||
| rt.addTarget({texture: viewer.getPlugin(GBufferPlugin)?.flagsTexture}, 'gBufferFlags') | |||
| rt.addTarget(viewer.getPlugin(DepthBufferPlugin)?.target, 'depth', false, false, false) | |||
| rt.addTarget(viewer.getPlugin(NormalBufferPlugin)?.target, 'normal', false, true, false) | |||
| editor.loadPlugins({ | |||
| ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin, TweakpaneUiPlugin], | |||
| ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin], | |||
| ['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin], | |||
| ['GBuffer']: [GBufferPlugin, DepthBufferPlugin, NormalBufferPlugin], | |||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | |||
| ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], | |||
| ['Extras']: [HDRiGroundPlugin, Rhino3dmLoadPlugin, ClearcoatTintPlugin, FragmentClippingExtensionPlugin, NoiseBumpMaterialPlugin, CustomBumpMapPlugin, VirtualCamerasPlugin], | |||
| @@ -1,20 +1,20 @@ | |||
| { | |||
| "name": "threepipe", | |||
| "version": "0.0.20-dev.1", | |||
| "version": "0.0.20", | |||
| "lockfileVersion": 2, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "threepipe", | |||
| "version": "0.0.20-dev.1", | |||
| "version": "0.0.20", | |||
| "license": "Apache-2.0", | |||
| "dependencies": { | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1019/package.tgz", | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1020/package.tgz", | |||
| "@types/webxr": "^0.5.1", | |||
| "@types/wicg-file-system-access": "^2020.9.5", | |||
| "stats.js": "^0.17.0", | |||
| "ts-browser-helpers": "^0.9.0", | |||
| "uiconfig.js": "^0.0.9" | |||
| "ts-browser-helpers": "^0.12.0", | |||
| "uiconfig.js": "^0.0.12" | |||
| }, | |||
| "devDependencies": { | |||
| "@rollup/plugin-commonjs": "^25.0.0", | |||
| @@ -41,7 +41,7 @@ | |||
| "rollup-plugin-glsl": "^1.3.0", | |||
| "rollup-plugin-license": "^3.0.1", | |||
| "rollup-plugin-postcss": "^4.0.2", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2019/package.tgz", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2020/package.tgz", | |||
| "tslib": "^2.5.0", | |||
| "typedoc": "^0.25.6", | |||
| "typescript": "^5.3.3", | |||
| @@ -1393,9 +1393,9 @@ | |||
| "dev": true | |||
| }, | |||
| "node_modules/@types/three": { | |||
| "version": "0.152.1019", | |||
| "resolved": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1019/package.tgz", | |||
| "integrity": "sha512-blLZGZVAkpv7HpbUkQZhU8bsVfEKzpSa1qHoey3MkgW+FNiaxFqf2V16tK7etcEF7YK46czMvDGMGc/oOvBoKg==", | |||
| "version": "0.152.1020", | |||
| "resolved": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1020/package.tgz", | |||
| "integrity": "sha512-Xnfma6acJylMQEBYiEOBAUifspGy2ao1UQ2ecYDKAFMwC04fDhTnO2LELluk3j/o/8qaYadOW4OcjvMKLOxTdg==", | |||
| "dependencies": { | |||
| "@tweenjs/tween.js": "~18.6.4", | |||
| "fflate": "~0.6.9", | |||
| @@ -10614,9 +10614,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/three": { | |||
| "version": "0.152.2019", | |||
| "resolved": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2019/package.tgz", | |||
| "integrity": "sha512-aszR8Ae48vf9pRqmFzvC6IG7o1yJcEyOsuEEkQL2z9mlTqOHl342cQe9ob8Bp2gN6QJGsUnea/sI0z7j+Xao0g==", | |||
| "version": "0.152.2020", | |||
| "resolved": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2020/package.tgz", | |||
| "integrity": "sha512-XpB018kSE/FZG5xmh6XG22lXTS9bParloATQ952tZaY3p7PsdiP3skXR49GYiLjG2Lui46wA18aq5aXJ9oabGA==", | |||
| "dev": true, | |||
| "license": "MIT" | |||
| }, | |||
| @@ -10690,9 +10690,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/ts-browser-helpers": { | |||
| "version": "0.9.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.9.0.tgz", | |||
| "integrity": "sha512-8ViPxn9X/K+is+4i8x9H1MB0RmPYFqkrrX2v9EtVZxGbYXLaKmkiL+kfzISUGNOQdzu4E93KjluT9S7cxegs0g==", | |||
| "version": "0.12.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.12.0.tgz", | |||
| "integrity": "sha512-lrXA3VZd9OvheHec+fQ3hVeSyLzQI0Bu3fH20/fQXH7Q4wYhZ2IwererEix/mn3j7Apo/W1XgYS8s8Xu7cCypQ==", | |||
| "dependencies": { | |||
| "@types/wicg-file-system-access": "^2020.9.5" | |||
| } | |||
| @@ -10917,9 +10917,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/uiconfig.js": { | |||
| "version": "0.0.9", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.9.tgz", | |||
| "integrity": "sha512-jB2LIUTBA7gHegrFr62J7xOEkyXmb+h0/5FczD9qzcBjCbb2R8UsdaxOyVoa+9ecfc2NAaeboTzJK8n8ji1cbw==" | |||
| "version": "0.0.12", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.12.tgz", | |||
| "integrity": "sha512-OnEkWuVYHFQRM9s+SYPvDCFG4NbMcR2vOt5JXt6h91P/PbbJVSWeyfo3ka+KqkuXmJe+x2IJUHqAlbv0VgmbhQ==" | |||
| }, | |||
| "node_modules/unbox-primitive": { | |||
| "version": "1.0.2", | |||
| @@ -12609,8 +12609,8 @@ | |||
| "dev": true | |||
| }, | |||
| "@types/three": { | |||
| "version": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1019/package.tgz", | |||
| "integrity": "sha512-blLZGZVAkpv7HpbUkQZhU8bsVfEKzpSa1qHoey3MkgW+FNiaxFqf2V16tK7etcEF7YK46czMvDGMGc/oOvBoKg==", | |||
| "version": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1020/package.tgz", | |||
| "integrity": "sha512-Xnfma6acJylMQEBYiEOBAUifspGy2ao1UQ2ecYDKAFMwC04fDhTnO2LELluk3j/o/8qaYadOW4OcjvMKLOxTdg==", | |||
| "requires": { | |||
| "@tweenjs/tween.js": "~18.6.4", | |||
| "fflate": "~0.6.9", | |||
| @@ -19342,8 +19342,8 @@ | |||
| } | |||
| }, | |||
| "three": { | |||
| "version": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2019/package.tgz", | |||
| "integrity": "sha512-aszR8Ae48vf9pRqmFzvC6IG7o1yJcEyOsuEEkQL2z9mlTqOHl342cQe9ob8Bp2gN6QJGsUnea/sI0z7j+Xao0g==", | |||
| "version": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2020/package.tgz", | |||
| "integrity": "sha512-XpB018kSE/FZG5xmh6XG22lXTS9bParloATQ952tZaY3p7PsdiP3skXR49GYiLjG2Lui46wA18aq5aXJ9oabGA==", | |||
| "dev": true | |||
| }, | |||
| "through": { | |||
| @@ -19396,9 +19396,9 @@ | |||
| "dev": true | |||
| }, | |||
| "ts-browser-helpers": { | |||
| "version": "0.9.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.9.0.tgz", | |||
| "integrity": "sha512-8ViPxn9X/K+is+4i8x9H1MB0RmPYFqkrrX2v9EtVZxGbYXLaKmkiL+kfzISUGNOQdzu4E93KjluT9S7cxegs0g==", | |||
| "version": "0.12.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.12.0.tgz", | |||
| "integrity": "sha512-lrXA3VZd9OvheHec+fQ3hVeSyLzQI0Bu3fH20/fQXH7Q4wYhZ2IwererEix/mn3j7Apo/W1XgYS8s8Xu7cCypQ==", | |||
| "requires": { | |||
| "@types/wicg-file-system-access": "^2020.9.5" | |||
| } | |||
| @@ -19568,9 +19568,9 @@ | |||
| "dev": true | |||
| }, | |||
| "uiconfig.js": { | |||
| "version": "0.0.9", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.9.tgz", | |||
| "integrity": "sha512-jB2LIUTBA7gHegrFr62J7xOEkyXmb+h0/5FczD9qzcBjCbb2R8UsdaxOyVoa+9ecfc2NAaeboTzJK8n8ji1cbw==" | |||
| "version": "0.0.12", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.12.tgz", | |||
| "integrity": "sha512-OnEkWuVYHFQRM9s+SYPvDCFG4NbMcR2vOt5JXt6h91P/PbbJVSWeyfo3ka+KqkuXmJe+x2IJUHqAlbv0VgmbhQ==" | |||
| }, | |||
| "unbox-primitive": { | |||
| "version": "1.0.2", | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "name": "threepipe", | |||
| "version": "0.0.20", | |||
| "version": "0.0.21", | |||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | |||
| "main": "dist/index.js", | |||
| "module": "dist/index.mjs", | |||
| @@ -100,7 +100,7 @@ | |||
| "rollup-plugin-glsl": "^1.3.0", | |||
| "rollup-plugin-license": "^3.0.1", | |||
| "rollup-plugin-postcss": "^4.0.2", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2019/package.tgz", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2020/package.tgz", | |||
| "tslib": "^2.5.0", | |||
| "typedoc": "^0.25.6", | |||
| "typescript": "^5.3.3", | |||
| @@ -109,21 +109,21 @@ | |||
| "vite-plugin-dts": "^3.7.0" | |||
| }, | |||
| "dependencies": { | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1019/package.tgz", | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1020/package.tgz", | |||
| "@types/webxr": "^0.5.1", | |||
| "@types/wicg-file-system-access": "^2020.9.5", | |||
| "stats.js": "^0.17.0", | |||
| "ts-browser-helpers": "^0.9.0", | |||
| "uiconfig.js": "^0.0.9" | |||
| "ts-browser-helpers": "^0.12.0", | |||
| "uiconfig.js": "^0.0.12" | |||
| }, | |||
| "//": { | |||
| "dependencies": { | |||
| "uiconfig.js": "^0.0.9", | |||
| "ts-browser-helpers": "^0.8.0", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2019/package.tgz", | |||
| "three-f": "https://github.com/repalash/three.js-modded/archive/refs/tags/v0.152.2019.tar.gz", | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1019/package.tgz", | |||
| "@types/three-f": "https://github.com/repalash/three-ts-types/archive/refs/tags/v0.152.1019.tar.gz", | |||
| "uiconfig.js": "^0.0.12", | |||
| "ts-browser-helpers": "^0.12.0", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2020/package.tgz", | |||
| "three-f": "https://github.com/repalash/three.js-modded/archive/refs/tags/v0.152.2020.tar.gz", | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1020/package.tgz", | |||
| "@types/three-f": "https://github.com/repalash/three-ts-types/archive/refs/tags/v0.152.1020.tar.gz", | |||
| "@types/three-pkg": "https://gitpkg.now.sh/repalash/three-ts-types/types/three?modded_three" | |||
| }, | |||
| "local_dependencies": { | |||
| @@ -1,12 +1,12 @@ | |||
| { | |||
| "name": "@threepipe/plugin-tweakpane", | |||
| "version": "0.2.0", | |||
| "version": "0.2.1", | |||
| "lockfileVersion": 2, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "@threepipe/plugin-tweakpane", | |||
| "version": "0.2.0", | |||
| "version": "0.2.1", | |||
| "license": "Apache-2.0", | |||
| "dependencies": { | |||
| "threepipe": "file:./../../src/" | |||
| @@ -1,7 +1,7 @@ | |||
| { | |||
| "name": "@threepipe/plugin-tweakpane", | |||
| "description": "Tweakpane UI Plugin for ThreePipe", | |||
| "version": "0.2.0", | |||
| "version": "0.2.1", | |||
| "devDependencies": { | |||
| "tweakpane-image-plugin": "https://github.com/repalash/tweakpane-image-plugin/releases/download/v1.1.404/package.tgz", | |||
| "uiconfig-tweakpane": "^0.0.8" | |||
| @@ -53,7 +53,7 @@ | |||
| ], | |||
| "replace": { | |||
| "dependencies": { | |||
| "threepipe": "^0.0.18" | |||
| "threepipe": "^0.0.21" | |||
| } | |||
| } | |||
| }, | |||
| @@ -52,14 +52,15 @@ function proxyGetValue(cc: any, viewer: ThreeViewer) { | |||
| cc.image.tp_src = imageBitmapToBase64(cc.image, 160) | |||
| } else if (cc.isRenderTargetTexture) { | |||
| if (cc._target) { | |||
| cc.image.tp_src = viewer.renderManager.renderTargetToDataUrl(cc._target) | |||
| setTimeout(()=>cc.image.tp_src && delete cc.image.tp_src, 1000) // clear after 1 second so it refreshes on next render | |||
| // here we are not doing cc.image.tp_src because cc.image can be shared across multiple textures in MRT | |||
| cc.tp_src = viewer.renderManager.renderTargetToDataUrl(cc._target, undefined, undefined, Array.isArray(cc._target.texture) ? cc._target.texture.indexOf(cc) : undefined) | |||
| setTimeout(()=>cc.tp_src && delete cc.tp_src, 1000) // clear after 1 second so it refreshes on next render | |||
| } | |||
| } else { | |||
| cc.image.tp_src = textureToDataUrl(cc, 160, false, 'image/png', 90) // this supports DataTexture also | |||
| } | |||
| if (!cc.image.tp_src) { | |||
| if (!cc.image.tp_src && !cc.tp_src) { | |||
| if (cc.isRenderTargetTexture) cc.image.tp_src = staticData.renderTarImage | |||
| else if (cc.isDataTexture) cc.image.tp_src = staticData.dataTexImage | |||
| } | |||
| @@ -69,6 +70,7 @@ function proxyGetValue(cc: any, viewer: ThreeViewer) { | |||
| ret = ret ? staticData.imageMap[ret] : undefined | |||
| if (!ret) ret = cc.image.tp_src || cc.image.src | |||
| } | |||
| if (cc.tp_src) ret = cc.tp_src | |||
| } else if (typeof cc === 'string') { | |||
| ret = cc | |||
| } else if (cc.domainMin) { // for lut CUBE files. | |||
| @@ -90,7 +92,6 @@ function proxyGetValue(cc: any, viewer: ThreeViewer) { | |||
| cc.image.tp_src_uuid = uuid | |||
| staticData.tempMap[ret] = uuid | |||
| } | |||
| // console.log(ret, cc, tar, key) | |||
| if (typeof ret === 'string') | |||
| ret = staticData.imageMap[ret] ?? ret // Note: this will be a bottleneck if the length of src is too long. | |||
| return ret | |||
| @@ -8,7 +8,8 @@ export class EXRExporter2 extends EXRExporter implements IExportParser { | |||
| const target = <IRenderTarget>obj | |||
| if (target.isWebGLRenderTarget && !target.renderManager) throw new Error('No renderManager on renderTarget') | |||
| if (!target.isWebGLRenderTarget && !(<DataTexture>obj).isDataTexture) throw new Error('Invalid object type') | |||
| if (target.isWebGLMultipleRenderTargets) throw new Error('WebGLMultipleRenderTargets not supported') | |||
| if (target.isWebGLMultipleRenderTargets && options.textureIndex === undefined) | |||
| console.warn('No textureIndex specified for WebGLMultipleRenderTargets') | |||
| const res = target.isWebGLRenderTarget ? | |||
| this.parse(target.renderManager!.webglRenderer, <WebGLRenderTarget>target, options) : | |||
| this.parse(undefined, <DataTexture>obj, options) | |||
| @@ -58,11 +58,30 @@ export interface IMaterialUserData extends IImportResultUserData{ | |||
| inverseAlphaMap?: boolean // only for physical material right now | |||
| /** | |||
| * See {@link GBufferPlugin} | |||
| */ | |||
| gBufferData?: { | |||
| materialId?: number | |||
| /** | |||
| * @default true | |||
| */ | |||
| tonemapEnabled?: boolean | |||
| } | |||
| [key: string]: any | |||
| // legacy, to be removed | |||
| /** | |||
| * @deprecated | |||
| */ | |||
| setDirty?: (options?: IMaterialSetDirtyOptions) => void | |||
| /** | |||
| * @deprecated | |||
| */ | |||
| postTonemap?: boolean | |||
| } | |||
| export interface IMaterial<E extends IMaterialEvent = IMaterialEvent, ET = IMaterialEventTypes> extends Material<E, ET>, IJSONSerializable, IDisposable, IUiConfigContainer { | |||
| @@ -125,6 +144,7 @@ export interface IMaterial<E extends IMaterialEvent = IMaterialEvent, ET = IMate | |||
| lightMap?: ITexture | null | |||
| normalMap?: ITexture | null | |||
| bumpMap?: ITexture | null | |||
| displacementMap?: ITexture | null | |||
| aoMapIntensity?: number | |||
| lightMapIntensity?: number | |||
| roughnessMap?: ITexture | null | |||
| @@ -101,7 +101,7 @@ export class LineMaterial2 extends LineMaterial<IMaterialEvent, LineMaterial2Eve | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: false, refreshUi: true}) | |||
| }, | |||
| children: [ | |||
| ...generateUiConfig(this), | |||
| ...generateUiConfig(this) || [], | |||
| iMaterialUI.blending(this), | |||
| iMaterialUI.polygonOffset(this), | |||
| ...iMaterialUI.misc(this), | |||
| @@ -18,7 +18,7 @@ import {ITexture} from '../ITexture' | |||
| import {AddObjectOptions, IScene, ISceneEvent, ISceneEventTypes, ISceneSetDirtyOptions, IWidget} from '../IScene' | |||
| import {iObjectCommons} from './iObjectCommons' | |||
| import {RootSceneImportResult} from '../../assetmanager' | |||
| import {uiColor, uiConfig, uiFolderContainer, uiImage, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {uiButton, uiColor, uiConfig, uiFolderContainer, uiImage, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {IGeometry} from '../IGeometry' | |||
| @uiFolderContainer('Root Scene') | |||
| @@ -249,9 +249,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| obj.userData.autoScaled = true // mark as auto-scaled, so that autoScale is not called again when file is reloaded. | |||
| } | |||
| if (centerGeometries && !obj.userData.geometriesCentered) { | |||
| const geoms = new Set<IGeometry>() | |||
| obj.traverse((o)=> o.geometry && geoms.add(o.geometry)) | |||
| geoms.forEach(g=>g.center(undefined, centerGeometriesKeepPosition)) | |||
| this.centerAllGeometries(centerGeometriesKeepPosition, obj) | |||
| obj.userData.geometriesCentered = true | |||
| } else { | |||
| obj.userData.geometriesCentered = true // mark as centered, so that geometry center is not called again when file is reloaded. | |||
| @@ -262,6 +260,13 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| this.setDirty({refreshScene: true}) | |||
| } | |||
| @uiButton() | |||
| centerAllGeometries(keepPosition = true, obj?: IObject3D) { | |||
| const geoms = new Set<IGeometry>() | |||
| ;(obj ?? this.modelRoot).traverse((o) => o.geometry && geoms.add(o.geometry)) | |||
| geoms.forEach(g => g.center(undefined, keepPosition)) | |||
| } | |||
| clearSceneModels(dispose = false, setDirty = true): void { | |||
| if (dispose) return this.disposeSceneModels(setDirty) | |||
| this.modelRoot.clear() | |||
| @@ -1,8 +1,9 @@ | |||
| import {IPassID, IPipelinePass} from '../../postprocessing' | |||
| import {AViewerPluginSync, ISerializedConfig, ThreeViewer} from '../../viewer' | |||
| import {onChange, serialize, wrapThisFunction} from 'ts-browser-helpers' | |||
| import {SerializationMetaType} from '../../utils' | |||
| import {onChange, serialize} from 'ts-browser-helpers' | |||
| import {SerializationMetaType, wrapThisFunction2} from '../../utils' | |||
| import {uiToggle} from 'uiconfig.js' | |||
| import {ICamera, IRenderManager, IScene} from '../../core' | |||
| export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extends IPassID, TEvent extends string, TViewer extends ThreeViewer=ThreeViewer> extends AViewerPluginSync<TEvent, TViewer> { | |||
| abstract passId: TPassId | |||
| @@ -20,8 +21,10 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| /** | |||
| * This function is called every frame before composer render, if this pass is being used in the pipeline | |||
| * @param _ | |||
| * @param _1 | |||
| * @param _2 | |||
| */ | |||
| protected _beforeRender(): boolean { | |||
| protected _beforeRender(_?: IScene, _1?: ICamera, _2?: IRenderManager): boolean { | |||
| if (!this._pass) return false | |||
| this._pass.enabled = !this.isDisabled() | |||
| return this._pass.enabled | |||
| @@ -36,7 +39,7 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| this._pass = this._createPass() | |||
| this._pass.onDirty?.push(viewer.setDirty) | |||
| this._pass.beforeRender = wrapThisFunction(this._beforeRender, this._pass.beforeRender) | |||
| this._pass.beforeRender = wrapThisFunction2(this._beforeRender, this._pass.beforeRender) | |||
| viewer.renderManager.registerPass(this._pass) | |||
| } | |||
| @@ -4,10 +4,12 @@ export {BaseImporterPlugin} from './base/BaseImporterPlugin' | |||
| // pipeline | |||
| export {ProgressivePlugin} from './pipeline/ProgressivePlugin' | |||
| export {GBufferPlugin, GBufferMaterial, DepthNormalMaterial} from './pipeline/GBufferPlugin' | |||
| export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | |||
| export {NormalBufferPlugin} from './pipeline/NormalBufferPlugin' | |||
| export {FrameFadePlugin, type FrameFadePluginEventTypes} from './pipeline/FrameFadePlugin' | |||
| export type {ProgressivePluginEventTypes, ProgressivePluginTarget} from './pipeline/ProgressivePlugin' | |||
| 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' | |||
| @@ -1,4 +1,4 @@ | |||
| import {uiConfig, uiPanelContainer, uiToggle} from 'uiconfig.js' | |||
| import {uiButton, uiConfig, uiPanelContainer, uiToggle} from 'uiconfig.js' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {OrbitControls3, TransformControls2} from '../../three' | |||
| import {PickingPlugin} from './PickingPlugin' | |||
| @@ -33,10 +33,11 @@ export class TransformControlsPlugin extends AViewerPluginSync<''> { | |||
| this._viewer.setDirty() | |||
| } | |||
| constructor() { | |||
| constructor(enabled: boolean) { | |||
| super() | |||
| TransformControls.ObjectConstructors.MeshBasicMaterial = UnlitMaterial as any | |||
| TransformControls.ObjectConstructors.LineBasicMaterial = UnlitLineMaterial as any | |||
| this.enabled = enabled | |||
| } | |||
| toJSON: any = undefined | |||
| @@ -103,5 +104,9 @@ export class TransformControlsPlugin extends AViewerPluginSync<''> { | |||
| this.transformControls.camera = this._viewer.scene.mainCamera | |||
| } | |||
| @uiButton('Center All Meshes') | |||
| centerAllMeshes() { | |||
| this._viewer?.scene.centerAllGeometries(true) | |||
| } | |||
| } | |||
| @@ -142,9 +142,9 @@ export class DepthBufferPlugin | |||
| this._createTarget(true) | |||
| if (!this.target) throw new Error('DepthBufferPlugin: target not created') | |||
| this.material.userData.isGBufferMaterial = true | |||
| const pass = new GBufferRenderPass('depth', this.target, this.material, new Color(0, 0, 0), 1) | |||
| const pass = new GBufferRenderPass(this.passId, this.target, this.material, new Color(0, 0, 0), 1) | |||
| const preprocessMaterial = pass.preprocessMaterial | |||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, m.userData.renderToDepth) | |||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, m.userData.renderToDepth) // if renderToDepth is undefined then renderToGbuffer is taken internally | |||
| pass.before = ['render'] | |||
| pass.after = [] | |||
| pass.required = ['render'] | |||
| @@ -0,0 +1,431 @@ | |||
| import { | |||
| BufferGeometry, | |||
| Camera, | |||
| Color, | |||
| DoubleSide, | |||
| GLSL1, | |||
| GLSL3, | |||
| IUniform, | |||
| NoBlending, | |||
| NormalMapTypes, | |||
| Object3D, | |||
| Scene, | |||
| ShaderMaterialParameters, | |||
| TangentSpaceNormalMap, | |||
| Texture, | |||
| TextureDataType, | |||
| UniformsLib, | |||
| UniformsUtils, | |||
| UnsignedByteType, | |||
| Vector2, | |||
| Vector4, | |||
| WebGLMultipleRenderTargets, | |||
| WebGLRenderer, | |||
| WebGLRenderTarget, | |||
| } from 'three' | |||
| import {GBufferRenderPass} from '../../postprocessing' | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {MaterialExtension, updateMaterialDefines} from '../../materials' | |||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | |||
| import {uiFolderContainer, uiImage} from 'uiconfig.js' | |||
| import {shaderReplaceString} from '../../utils' | |||
| import GBufferUnpack from './shaders/GBufferPlugin.unpack.glsl' | |||
| import GBufferMatVert from './shaders/GBufferPlugin.mat.vert.glsl' | |||
| import GBufferMatFrag from './shaders/GBufferPlugin.mat.frag.glsl' | |||
| import { | |||
| ICamera, | |||
| IMaterial, | |||
| IMaterialParameters, | |||
| IRenderManager, | |||
| IScene, | |||
| ITexture, | |||
| PhysicalMaterial, | |||
| ShaderMaterial2, | |||
| } from '../../core' | |||
| export type GBufferPluginEventTypes = '' | |||
| type GBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | |||
| // export type GBufferPluginTarget = WebGLRenderTarget | |||
| export type GBufferPluginPass = GBufferRenderPass<'gbuffer', GBufferPluginTarget> | |||
| export interface GBufferUpdaterContext { | |||
| material: IMaterial, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D | |||
| } | |||
| export interface GBufferUpdater { | |||
| updateGBufferFlags: (data: Vector4, context: GBufferUpdaterContext) => void | |||
| } | |||
| /** | |||
| * G-Buffer Plugin | |||
| * | |||
| * Adds a pre-render pass to render the g-buffer(depth+normal+flags) to render target(s) that can be used as gbuffer and for postprocessing. | |||
| * @category Plugins | |||
| */ | |||
| @uiFolderContainer('G-Buffer Plugin') | |||
| export class GBufferPlugin | |||
| extends PipelinePassPlugin<GBufferPluginPass, 'gbuffer', GBufferPluginEventTypes> { | |||
| readonly passId = 'gbuffer' | |||
| public static readonly PluginType = 'GBuffer' | |||
| target?: GBufferPluginTarget | |||
| // @uiConfig(/* {readOnly: true}*/) // todo: fix bug in uiconfig or tpImageGenerator because of which 0 index is not showing in the UI, when we uncomment this | |||
| textures: Texture[] = [] | |||
| @uiImage(/* {readOnly: true}*/) | |||
| get normalDepthTexture(): ITexture|undefined { | |||
| return this.textures[0] | |||
| } | |||
| @uiImage(/* {readOnly: true}*/) | |||
| get flagsTexture(): ITexture|undefined { | |||
| return this.textures[1] | |||
| } | |||
| // @uiConfig() // not supported in this material yet | |||
| material?: GBufferMaterial | |||
| // @onChange(GBufferPlugin.prototype._depthPackingChanged) | |||
| // @uiDropdown('Depth Packing', threeConstMappings.DepthPackingStrategies.uiConfig) packing: DepthPackingStrategies | |||
| // @onChange2(GBufferPlugin.prototype._createTargetAndMaterial) | |||
| // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | |||
| readonly bufferType: TextureDataType // cannot be changed after creation (for now) | |||
| // @uiToggle() | |||
| // @onChange2(GBufferPlugin.prototype._createTargetAndMaterial) | |||
| readonly isPrimaryGBuffer: boolean // cannot be changed after creation (for now) | |||
| // protected _depthPackingChanged() { | |||
| // this.material.depthPacking = this.depthPacking | |||
| // this.material.needsUpdate = true | |||
| // if (this.unpackExtension && this.unpackExtension.extraDefines) { | |||
| // this.unpackExtension.extraDefines.DEPTH_PACKING = this.depthPacking | |||
| // this.unpackExtension.setDirty?.() | |||
| // } | |||
| // this.setDirty() | |||
| // } | |||
| unpackExtension: MaterialExtension = { | |||
| shaderExtender: (shader)=>{ | |||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | |||
| '#include <packing>', | |||
| '\n' + GBufferUnpack + '\n', {append: true}) | |||
| }, | |||
| extraUniforms: { | |||
| tNormalDepth: ()=>({value: this.normalDepthTexture}), | |||
| tGBufferFlags: ()=>({value: this.flagsTexture}), | |||
| }, | |||
| extraDefines: { | |||
| // ['GBUFFER_PACKING']: BasicDepthPacking, | |||
| ['HAS_NORMAL_DEPTH_BUFFER']: ()=>this.normalDepthTexture ? 1 : undefined, | |||
| // ['HAS_FLAGS_BUFFER']: ()=>this.flagsTexture ? 1 : undefined, | |||
| ['GBUFFER_HAS_FLAGS']: ()=>this.flagsTexture ? 1 : undefined, | |||
| ['HAS_GBUFFER']: ()=>this.isPrimaryGBuffer && this.normalDepthTexture ? 1 : undefined, | |||
| }, | |||
| priority: 100, | |||
| isCompatible: () => true, | |||
| } | |||
| private _isPrimaryGBufferSet = false | |||
| protected _createTargetAndMaterial(recreateTarget = true) { | |||
| if (!this._viewer) return | |||
| if (recreateTarget) this._disposeTarget() | |||
| const useMultiple = this._viewer?.renderManager.isWebGL2 && this.renderFlagsBuffer | |||
| if (!this.target) { | |||
| this.target = this._viewer.renderManager.createTarget<GBufferPluginTarget>( | |||
| { | |||
| depthBuffer: true, | |||
| samples: this._viewer.renderManager.composerTarget.samples || 0, | |||
| type: this.bufferType, | |||
| textureCount: useMultiple ? 2 : 1, | |||
| // magFilter: NearestFilter, | |||
| // minFilter: NearestFilter, | |||
| // generateMipmaps: false, | |||
| // encoding: LinearEncoding, | |||
| }) | |||
| if (Array.isArray(this.target.texture)) { | |||
| this.target.texture[0].name = 'gbufferDepthNormal' | |||
| this.target.texture[1].name = 'gbufferFlags' | |||
| this.textures = this.target.texture | |||
| } else { | |||
| this.target.texture.name = 'gbufferDepthNormal' | |||
| this.textures.push(this.target.texture) | |||
| } | |||
| } | |||
| if (!this.material) { | |||
| this.material = new GBufferMaterial(useMultiple, { | |||
| blending: NoBlending, | |||
| transparent: true, | |||
| }) | |||
| } | |||
| if (this._pass) this._pass.target = this.target | |||
| if (this.isPrimaryGBuffer) { | |||
| this._viewer.renderManager.gbufferTarget = this.target | |||
| this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]) | |||
| this._isPrimaryGBufferSet = true | |||
| } | |||
| } | |||
| protected _disposeTarget() { | |||
| if (!this._viewer) return | |||
| if (this.target) { | |||
| this._viewer.renderManager.disposeTarget(this.target) | |||
| this.target = undefined | |||
| } | |||
| this.textures = [] | |||
| if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it. | |||
| this._viewer.renderManager.gbufferTarget = undefined | |||
| // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo | |||
| this._isPrimaryGBufferSet = false | |||
| } | |||
| } | |||
| protected _createPass() { | |||
| this._createTargetAndMaterial(true) | |||
| if (!this.target) throw new Error('GBufferPlugin: target not created') | |||
| if (!this.material) throw new Error('GBufferPlugin: material not created') | |||
| this.material.userData.isGBufferMaterial = true | |||
| const pass = new GBufferRenderPass(this.passId, this.target, this.material, new Color(1, 1, 1), 1) | |||
| const preprocessMaterial = pass.preprocessMaterial | |||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, m.userData.renderToDepth) // if renderToDepth is undefined then renderToGbuffer is taken internally | |||
| pass.before = ['render'] | |||
| pass.after = [] | |||
| pass.required = ['render'] | |||
| return pass | |||
| } | |||
| protected _beforeRender(scene: IScene, camera: ICamera, renderManager: IRenderManager): boolean { | |||
| if (!super._beforeRender(scene, camera, renderManager) || !this.material) return false | |||
| camera.updateShaderProperties(this.material) | |||
| return true | |||
| } | |||
| constructor( | |||
| bufferType: TextureDataType = UnsignedByteType, | |||
| isPrimaryGBuffer = true, | |||
| enabled = true, | |||
| public renderFlagsBuffer: boolean = true | |||
| // packing: DepthPackingStrategies = BasicDepthPacking, | |||
| ) { | |||
| super() | |||
| this.enabled = enabled | |||
| this.bufferType = bufferType | |||
| this.isPrimaryGBuffer = isPrimaryGBuffer | |||
| // this.depthPacking = depthPacking | |||
| } | |||
| registerGBufferUpdater(key: string, updater: GBufferUpdater['updateGBufferFlags']): void { | |||
| if (this.material) this.material.flagUpdaters.set(key, updater) | |||
| } | |||
| unregisterGBufferUpdater(key: string): void { | |||
| if (this.material) this.material.flagUpdaters.delete(key) | |||
| } | |||
| onRemove(viewer: ThreeViewer): void { | |||
| this._disposeTarget() | |||
| this.material?.dispose() | |||
| this.material = undefined | |||
| return super.onRemove(viewer) | |||
| } | |||
| /** | |||
| * @deprecated use {@link normalDepthTexture} instead | |||
| */ | |||
| getDepthNormal() { | |||
| return this.textures.length > 0 ? this.textures[0] : undefined | |||
| } | |||
| /** | |||
| * @deprecated use {@link flagsTexture} instead | |||
| */ | |||
| getFlagsTexture() { | |||
| return this.textures.length > 1 ? this.textures[1] : undefined | |||
| } | |||
| /** | |||
| * @deprecated use {@link target} instead | |||
| */ | |||
| getTarget() { | |||
| return this.target | |||
| } | |||
| /** | |||
| * @deprecated use {@link unpackExtension} instead | |||
| */ | |||
| getUnpackSnippet(): string { | |||
| return GBufferUnpack | |||
| } | |||
| /** | |||
| * @deprecated use {@link unpackExtension} instead, it adds the same uniforms and defines | |||
| * @param material | |||
| */ | |||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}, needsUpdate?: boolean}): this { | |||
| if (material.uniforms.tNormalDepth) material.uniforms.tNormalDepth.value = this.normalDepthTexture ?? undefined | |||
| else this._viewer?.console.warn('BaseRenderer: no uniform: tNormalDepth') | |||
| if (material.uniforms.tGBufferFlags) { | |||
| material.uniforms.tGBufferFlags.value = this.flagsTexture ?? undefined | |||
| const t = material.uniforms.tGBufferFlags.value ? 1 : 0 | |||
| if (t !== material.defines.GBUFFER_HAS_FLAGS) { | |||
| material.defines.GBUFFER_HAS_FLAGS = t | |||
| material.needsUpdate = true | |||
| } | |||
| } | |||
| return this | |||
| } | |||
| } | |||
| /** | |||
| * Renders DepthNormal to a texture and flags to another | |||
| */ | |||
| export class GBufferMaterial extends ShaderMaterial2 { | |||
| constructor(multipleRT = true, parameters?: ShaderMaterialParameters & IMaterialParameters) { | |||
| super({ | |||
| vertexShader: GBufferMatVert, | |||
| fragmentShader: GBufferMatFrag, | |||
| uniforms: UniformsUtils.merge([ | |||
| UniformsLib.common, | |||
| UniformsLib.bumpmap, | |||
| UniformsLib.normalmap, | |||
| UniformsLib.displacementmap, | |||
| { | |||
| cameraNearFar: {value: new Vector2(0.1, 1000)}, // this has to be set from outside | |||
| flags: {value: new Vector4(255, 255, 255, 255)}, | |||
| }, | |||
| ]), | |||
| defines: { | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| IS_GLSL3: multipleRT ? '1' : '0', | |||
| }, | |||
| glslVersion: multipleRT ? GLSL3 : GLSL1, | |||
| ...parameters, | |||
| }) | |||
| this.reset() | |||
| } | |||
| flagUpdaters: Map<string, GBufferUpdater['updateGBufferFlags']> = new Map() | |||
| normalMapType: NormalMapTypes = TangentSpaceNormalMap | |||
| flatShading = false | |||
| onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D) { | |||
| super.onBeforeRender(renderer, scene, camera, geometry, object) | |||
| let material = (object as any).material as IMaterial & Partial<PhysicalMaterial> | |||
| if (Array.isArray(material)) { // todo: add support for multi materials. | |||
| material = material[0] | |||
| } | |||
| if (!material) return | |||
| const setMap = (key: keyof IMaterial)=>{ | |||
| const map = material[key] | |||
| if (!map) return | |||
| this.uniforms[key].value = map | |||
| if (!this.uniforms[key + 'Transform']) console.error('GBufferMaterial: ' + key + 'Transform is not defined in uniform') | |||
| else renderer.materials.refreshTransformUniform(map, this.uniforms[key + 'Transform']) | |||
| } | |||
| setMap('map') | |||
| if (material.side !== undefined) this.side = material.side ?? DoubleSide | |||
| setMap('alphaMap') | |||
| if (material.alphaTest !== undefined) this.alphaTest = material.alphaTest < 1e-4 ? 1e-4 : material.alphaTest | |||
| setMap('bumpMap') | |||
| if (material.bumpScale !== undefined) this.uniforms.bumpScale.value = material.bumpScale | |||
| setMap('normalMap') | |||
| if (material.normalScale !== undefined) this.uniforms.normalScale.value.copy(material.normalScale) | |||
| if (material.normalMapType !== undefined) this.normalMapType = material.normalMapType | |||
| if (material.flatShading !== undefined) this.flatShading = material.flatShading | |||
| setMap('displacementMap') | |||
| if (material.displacementScale !== undefined) this.uniforms.displacementScale.value = material.displacementScale | |||
| if (material.displacementBias !== undefined) this.uniforms.displacementBias.value = material.displacementBias | |||
| if (material.wireframe !== undefined) this.wireframe = material.wireframe | |||
| if (material.wireframeLinewidth !== undefined) this.wireframeLinewidth = material.wireframeLinewidth | |||
| /* | |||
| GBuffer Flags has the following data | |||
| 1st Rendertarget has Depth and Normal buffers | |||
| 2nd Render Target:: | |||
| x : Empty | |||
| y : first 3 bits lut index, second 5 bits bevel radius | |||
| z : material id (userData.gBufferData?.materialId, userData.matId) | |||
| w : this field is for setting bits - lutEnable-0, tonemap-1, bloom-2 | |||
| */ | |||
| this.uniforms.flags.value.set(255, 255, 255, 255) | |||
| const materialId = material.userData.gBufferData?.materialId ?? material.userData.matId // matId for backward compatibility | |||
| this.uniforms.flags.value.z = materialId || 0 | |||
| this.flagUpdaters.forEach((updater)=> updater(this.uniforms.flags.value, {material, renderer, scene, camera, geometry, object})) | |||
| this.uniforms.flags.value.x /= 255 | |||
| this.uniforms.flags.value.y /= 255 | |||
| this.uniforms.flags.value.z /= 255 | |||
| this.uniforms.flags.value.w /= 255 | |||
| this.uniformsNeedUpdate = true | |||
| updateMaterialDefines({ | |||
| ['USE_ALPHAMAP']: this.uniforms.alphaMap.value ? 1 : undefined, | |||
| ['ALPHAMAP_UV']: this.uniforms.alphaMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js | |||
| ['USE_DISPLACEMENTMAP']: this.uniforms.displacementMap.value ? 1 : undefined, | |||
| ['DISPLACEMENTMAP_UV']: this.uniforms.displacementMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js | |||
| ['ALPHA_I_RGBA_PACKING']: material.userData.ALPHA_I_RGBA_PACKING ? 1 : undefined, | |||
| ['FORCED_LINEAR_DEPTH']: material.userData.forcedLinearDepth ?? undefined, | |||
| }, material) | |||
| // this.transparent = true | |||
| this.needsUpdate = true | |||
| } | |||
| onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D) { | |||
| super.onAfterRender(renderer, scene, camera, geometry, object) | |||
| this.reset() | |||
| } | |||
| reset() { | |||
| this.uniforms.map.value = null | |||
| this.side = DoubleSide | |||
| this.uniforms.alphaMap.value = null | |||
| this.alphaTest = 0.001 | |||
| this.uniforms.bumpMap.value = null | |||
| this.uniforms.bumpScale.value = 1 | |||
| this.uniforms.normalMap.value = null | |||
| this.uniforms.normalScale.value.set(1, 1) | |||
| this.normalMapType = TangentSpaceNormalMap | |||
| this.flatShading = false | |||
| this.uniforms.displacementMap.value = null | |||
| this.uniforms.displacementScale.value = 1 | |||
| this.uniforms.displacementBias.value = 0 | |||
| this.uniforms.flags.value.set(255, 255, 255, 255) | |||
| this.wireframe = false | |||
| this.wireframeLinewidth = 1 | |||
| } | |||
| } | |||
| /** | |||
| * @deprecated use GBufferMaterial instead | |||
| */ | |||
| export class DepthNormalMaterial extends GBufferMaterial { | |||
| constructor(multipleRT: boolean, parameters?: ShaderMaterialParameters & IMaterialParameters) { | |||
| super(multipleRT, parameters) | |||
| console.warn('DepthNormalMaterial is deprecated, use GBufferMaterial instead') | |||
| } | |||
| } | |||
| @@ -83,7 +83,7 @@ export class NormalBufferPlugin | |||
| this._createTarget(true) | |||
| if (!this.target) throw new Error('NormalBufferPlugin: target not created') | |||
| this.material.userData.isGBufferMaterial = true | |||
| const pass = new GBufferRenderPass('normal', this.target, this.material, new Color(0, 0, 0), 1) | |||
| const pass = new GBufferRenderPass(this.passId, this.target, this.material, new Color(0, 0, 0), 1) | |||
| const preprocessMaterial = pass.preprocessMaterial | |||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, true) | |||
| pass.before = ['render'] | |||
| @@ -6,6 +6,7 @@ import {uiFolderContainer, uiImage, uiInput} from 'uiconfig.js' | |||
| import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../../core' | |||
| import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass' | |||
| import {getOrCall, serialize, ValOrFunc} from 'ts-browser-helpers' | |||
| import {IShaderPropertiesUpdater} from '../../materials' | |||
| export type ProgressivePluginEventTypes = '' | |||
| export type ProgressivePluginTarget = WebGLRenderTarget | |||
| @@ -19,7 +20,7 @@ export type ProgressivePluginTarget = WebGLRenderTarget | |||
| */ | |||
| @uiFolderContainer('Progressive Plugin') | |||
| export class ProgressivePlugin | |||
| extends PipelinePassPlugin<ProgressiveBlendPass, 'progressive', ProgressivePluginEventTypes> { | |||
| extends PipelinePassPlugin<ProgressiveBlendPass, 'progressive', ProgressivePluginEventTypes> implements IShaderPropertiesUpdater { | |||
| readonly passId = 'progressive' | |||
| public static readonly PluginType = 'ProgressivePlugin' | |||
| @@ -0,0 +1,119 @@ | |||
| // Similar to meshnormal.glsl.js in three.js, check for ref | |||
| #define DEPTH_NORMAL | |||
| //uniform float opacity; | |||
| #if IS_GLSL3 > 0 | |||
| in vec3 vViewPosition; | |||
| #else | |||
| varying vec3 vViewPosition; | |||
| #endif | |||
| #ifdef USE_ALPHAMAP | |||
| #define USE_UV | |||
| #include <packing> | |||
| #endif | |||
| #if IS_GLSL3 > 0 | |||
| #ifndef gl_FragColor // webgl2 with glsl3 | |||
| layout(location = 0) out vec4 gDepthNormal; | |||
| layout(location = 1) out vec4 gFlags; | |||
| #endif | |||
| #endif | |||
| #include <uv_pars_fragment> | |||
| #include <normal_pars_fragment> | |||
| #include <map_pars_fragment> | |||
| #include <alphamap_pars_fragment> | |||
| #include <alphatest_pars_fragment> | |||
| #include <bumpmap_pars_fragment> | |||
| #include <normalmap_pars_fragment> | |||
| #include <logdepthbuf_pars_fragment> | |||
| #include <clipping_planes_pars_fragment> | |||
| uniform vec2 cameraNearFar; | |||
| uniform vec4 flags; | |||
| //vec2 pack16(float value) { | |||
| // float sMax = 65535.0; | |||
| // int v = int(clamp(value, 0.0, 1.0)*sMax+0.5); | |||
| // int digit0 = v/256; | |||
| // int digit1 = v-digit0*256; | |||
| // return vec2(float(digit0)/255.0, float(digit1)/255.0); | |||
| //} | |||
| vec2 pack16(float value){ | |||
| float f = clamp(value, 0.0, 1.0)*255.0; | |||
| float digitLow = fract(f); | |||
| float digitHigh = floor(f)/255.0; | |||
| return vec2(digitHigh, digitLow); | |||
| } | |||
| //float unpack16(vec2 value){ | |||
| // return value.x+value.y/255.0; | |||
| //} | |||
| vec2 packNormal(vec3 n){ | |||
| float p = sqrt(n.z*8.0+8.0); | |||
| return vec2(n.xy/p + 0.5); | |||
| } | |||
| float linstep(float edge0, float edge1, float value) { | |||
| return clamp((value-edge0)/(edge1-edge0), 0.0, 1.0); | |||
| } | |||
| void main() { | |||
| #glMarker mainStart | |||
| #include <clipping_planes_fragment> | |||
| vec4 diffuseColor = vec4( 1.0 ); | |||
| #include <map_fragment> | |||
| //#/include <alphamap_fragment> // changed for ALPHA_I_RGBA_PACKING | |||
| #ifdef USE_ALPHAMAP | |||
| float alphaMapValue = | |||
| #ifdef ALPHA_I_RGBA_PACKING | |||
| 1. - unpackRGBAToDepth( texture2D( alphaMap, vAlphaMapUv ) ); | |||
| #else | |||
| texture2D( alphaMap, vAlphaMapUv ).g; | |||
| #endif | |||
| #if defined(INVERSE_ALPHAMAP) && INVERSE_ALPHAMAP >= 1 | |||
| diffuseColor.a *= 1.0 - alphaMapValue; | |||
| #else | |||
| diffuseColor.a *= alphaMapValue; | |||
| #endif | |||
| #endif | |||
| #include <alphatest_fragment> | |||
| #include <logdepthbuf_fragment> | |||
| #include <normal_fragment_begin> | |||
| #include <normal_fragment_maps> | |||
| #ifdef FORCED_LINEAR_DEPTH | |||
| float linearZ = float(FORCED_LINEAR_DEPTH); | |||
| #else | |||
| float linearZ = linstep(-cameraNearFar.x, -cameraNearFar.y, -vViewPosition.z); | |||
| #endif | |||
| vec2 packedZ = pack16(pow(linearZ, 0.5)); | |||
| vec2 packedNormal = packNormal(normal); | |||
| #if IS_GLSL3 > 0 | |||
| #ifndef gl_FragColor // webgl2 with glsl3 | |||
| gDepthNormal = vec4(packedZ.x, packedZ.y, packedNormal.x, packedNormal.y); | |||
| gFlags = flags; | |||
| #else | |||
| gl_FragColor = vec4(packedZ.x, packedZ.y, packedNormal.x, packedNormal.y); | |||
| #endif | |||
| #else | |||
| gl_FragColor = vec4(packedZ.x, packedZ.y, packedNormal.x, packedNormal.y); | |||
| #endif | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| // Pretty much the same as meshnormal.glsl.js in three.js with minor changes. | |||
| #define DEPTH_NORMAL | |||
| #if IS_GLSL3 > 0 | |||
| out vec3 vViewPosition; | |||
| #else | |||
| varying vec3 vViewPosition; | |||
| #endif | |||
| #ifdef USE_ALPHAMAP | |||
| #define USE_UV // see todo in GBufferMaterialOverride updateMaterialDefines | |||
| #endif | |||
| //#/include <common> | |||
| #include <uv_pars_vertex> | |||
| #include <displacementmap_pars_vertex> | |||
| #include <normal_pars_vertex> | |||
| #include <morphtarget_pars_vertex> | |||
| #include <skinning_pars_vertex> | |||
| #include <logdepthbuf_pars_vertex> | |||
| #include <clipping_planes_pars_vertex> | |||
| void main() { | |||
| #include <uv_vertex> | |||
| #include <beginnormal_vertex> | |||
| #include <morphnormal_vertex> | |||
| #include <skinbase_vertex> | |||
| #include <skinnormal_vertex> | |||
| #include <defaultnormal_vertex> | |||
| #include <normal_vertex> | |||
| #include <begin_vertex> | |||
| #include <morphtarget_vertex> | |||
| #include <skinning_vertex> | |||
| #include <displacementmap_vertex> | |||
| #include <project_vertex> | |||
| #include <logdepthbuf_vertex> | |||
| #include <clipping_planes_vertex> | |||
| vViewPosition = - mvPosition.xyz; | |||
| } | |||
| @@ -0,0 +1,77 @@ | |||
| #ifndef UNPACK_GBUFFER_SNIPPET | |||
| #define UNPACK_GBUFFER_SNIPPET | |||
| //precision highp usampler2D; | |||
| uniform sampler2D tNormalDepth; | |||
| //float unpack16(vec2 value) { | |||
| // return ( | |||
| // value.x*0.996108949416342426275150501169264316558837890625 + | |||
| // value.y*0.00389105058365758760263730664519243873655796051025390625 | |||
| // ); | |||
| //} | |||
| float unpack16(vec2 value){ | |||
| return value.x+value.y/255.0; | |||
| } | |||
| vec3 unpackNormal(vec2 enc) { | |||
| vec2 fenc = enc*4.0-2.0; | |||
| float f = dot(fenc, fenc); | |||
| float g = sqrt(1.0-f/4.0); | |||
| return vec3(fenc*g, 1.0-f/2.0); | |||
| } | |||
| float unpackDepth(vec2 uncodedDepth) { | |||
| float x = unpack16(uncodedDepth.xy); | |||
| return x*x; | |||
| } | |||
| #define getDepth(uv) unpackDepth(texture2D(tNormalDepth, uv).xy) | |||
| void getDepthNormal(const in vec2 uv, out float depth, out vec3 normal){ | |||
| vec4 uncodedDepth = texture2D(tNormalDepth, uv); | |||
| depth = unpackDepth(uncodedDepth.xy); | |||
| normal = unpackNormal(uncodedDepth.zw); | |||
| } | |||
| vec3 getViewNormal(const in vec2 uv ) { | |||
| // #if DEPTH_NORMAL_TEXTURE == 1 | |||
| return unpackNormal( texture2D( tNormalDepth, uv ).zw ); | |||
| // #else | |||
| // return unpackRGBToNormal( texture2D( tNormal, uv ).xyz ); | |||
| // #endif | |||
| } | |||
| #if defined(GBUFFER_HAS_FLAGS) && GBUFFER_HAS_FLAGS == 1 | |||
| uniform sampler2D tGBufferFlags; | |||
| #endif | |||
| ivec4 getGBufferFlags(const in vec2 uv){ | |||
| #if defined(GBUFFER_HAS_FLAGS) && GBUFFER_HAS_FLAGS == 1 | |||
| return ivec4(texture2D(tGBufferFlags, uv) * 255.); | |||
| #else | |||
| return ivec4(1); | |||
| #endif | |||
| } | |||
| //#if DEPTH_NORMAL_TEXTURE == 1 | |||
| //uniform sampler2D tNormalDepth; | |||
| //#else | |||
| //uniform sampler2D tNormal; | |||
| //#endif | |||
| //float decodeDepth( const in vec2 uv ) { | |||
| // vec4 uncodedDepth; | |||
| // #if DEPTH_PACKING_MODE == 2 | |||
| // uncodedDepth = texture2D( tNormalDepth, uv ); | |||
| // #else | |||
| // uncodedDepth = texture2D( tDepth, uv ); | |||
| // #endif | |||
| // | |||
| // #if DEPTH_PACKING_MODE == 0 | |||
| // return uncodedDepth.x; | |||
| // #elif DEPTH_PACKING_MODE == 1 | |||
| // #if LINEAR_DEPTH == 1 | |||
| // return pow2(unpackRGBAToDepth(uncodedDepth)); | |||
| // #else | |||
| // return unpackRGBAToDepth( uncodedDepth ); | |||
| // #endif | |||
| // #else | |||
| // return pow2(unpack16(uncodedDepth.xy)); | |||
| // #endif | |||
| //} | |||
| #endif | |||
| @@ -4,11 +4,7 @@ import {MaterialExtension} from '../../materials' | |||
| import {Shader, Vector4, WebGLRenderer} from 'three' | |||
| import {IMaterial} from '../../core' | |||
| import {shaderReplaceString} from '../../utils' | |||
| // todo move | |||
| export interface GBufferUpdater { | |||
| updateGBufferFlags: (material: IMaterial, data: Vector4) => void | |||
| } | |||
| import {GBufferPlugin, GBufferUpdater, GBufferUpdaterContext} from '../pipeline/GBufferPlugin' | |||
| /** | |||
| * Base Screen Pass Extension Plugin | |||
| @@ -84,26 +80,21 @@ export abstract class AScreenPassExtensionPlugin<T extends string> extends AView | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| // viewer.getPlugin(GBufferPlugin)?.registerGBufferUpdater(this.updateGBufferFlags) // todo | |||
| viewer.getPlugin(GBufferPlugin)?.registerGBufferUpdater(this.constructor.PluginType, this.updateGBufferFlags) | |||
| viewer.renderManager.screenPass.material.registerMaterialExtensions([this]) | |||
| } | |||
| onRemove(viewer: ThreeViewer) { | |||
| // viewer.getPlugin(GBufferPlugin)?.unregisterGBufferUpdater(this.updateGBufferFlags) | |||
| viewer.getPlugin(GBufferPlugin)?.unregisterGBufferUpdater(this.constructor.PluginType) | |||
| viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this]) | |||
| super.onRemove(viewer) | |||
| } | |||
| // updateGBufferFlags(material: IMaterial, data: Vector4): void { | |||
| // const x = material?.userData.postTonemap === false ? 0 : 1 | |||
| // data.w = updateBit(data.w, 1, x) // 2nd Bit | |||
| // } | |||
| // for typescript | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| __setDirty?: () => void | |||
| updateGBufferFlags(_: IMaterial, _1: Vector4): void { | |||
| updateGBufferFlags(_: Vector4, _1: GBufferUpdaterContext): void { | |||
| return | |||
| } | |||
| @@ -20,6 +20,7 @@ import Uncharted2ToneMappingShader from './shaders/Uncharted2ToneMapping.glsl' | |||
| import TonemapShader from './shaders/TonemapPlugin.pars.glsl' | |||
| import TonemapShaderPatch from './shaders/TonemapPlugin.patch.glsl' | |||
| import {AScreenPassExtensionPlugin} from './AScreenPassExtensionPlugin' | |||
| import {GBufferUpdaterContext} from '../pipeline/GBufferPlugin' | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| export const Uncharted2Tonemapping: ToneMapping = CustomToneMapping | |||
| @@ -131,10 +132,11 @@ export class TonemapPlugin extends AScreenPassExtensionPlugin<''> { | |||
| return super.fromJSON(data, meta) | |||
| } | |||
| updateGBufferFlags(material: IMaterial, data: Vector4): void { | |||
| const x = material?.userData.postTonemap === false ? 0 : 1 | |||
| // TODO: add gBufferData or just tonemapEnabled to the scene material UI with an extension | |||
| updateGBufferFlags(data: Vector4, c: GBufferUpdaterContext): void { | |||
| const x = (c.material.userData.gBufferData?.tonemapEnabled ?? c.material?.userData.postTonemap) === false ? 0 : 1 | |||
| data.w = updateBit(data.w, 1, x) // 2nd Bit | |||
| super.updateGBufferFlags(material, data) | |||
| super.updateGBufferFlags(data, c) | |||
| } | |||
| static { | |||
| @@ -64,24 +64,6 @@ export class GeometryUVPreviewPlugin<TEvent extends string> extends AViewerPlugi | |||
| target.uvCanvas.style.height = '100%' | |||
| } | |||
| if (target.uvCanvas && target.uvCanvas.parentElement !== target.div) target.div.appendChild(target.uvCanvas) | |||
| // const rect = target.div.getBoundingClientRect() | |||
| // const canvasRect = this._viewer.canvas.getBoundingClientRect() | |||
| // rect.x = rect.x - canvasRect.x | |||
| // rect.y = canvasRect.height + canvasRect.y - rect.y - rect.height | |||
| // if (Array.isArray(tex)) { | |||
| // // todo support multi target | |||
| // this._viewer.console.warn('Multi target preview not supported yet') | |||
| // continue | |||
| // } | |||
| // const outputColorSpace = this._viewer.renderManager.webglRenderer.outputColorSpace | |||
| // if (!target.originalColorSpace) this._viewer.renderManager.webglRenderer.outputColorSpace = SRGBColorSpace | |||
| // this._viewer.renderManager.blit(null, { | |||
| // source: tex, | |||
| // clear: !target.transparent, | |||
| // respectColorSpace: !target.originalColorSpace, | |||
| // viewport: new Vector4(rect.x, rect.y, rect.width, rect.height), | |||
| // }) | |||
| // this._viewer.renderManager.webglRenderer.outputColorSpace = outputColorSpace | |||
| } | |||
| } | |||
| @@ -1,11 +1,20 @@ | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {IRenderTarget} from '../../rendering' | |||
| import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers' | |||
| import {createDiv, createStyles, getOrCall, onChange, ValOrArr, ValOrFunc} from 'ts-browser-helpers' | |||
| import {SRGBColorSpace, Vector4, WebGLRenderTarget} from 'three' | |||
| import styles from './RenderTargetPreviewPlugin.css?inline' | |||
| import {CustomContextMenu} from '../../utils' | |||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | |||
| import {ITexture} from '../../core' | |||
| export interface RenderTargetBlock { | |||
| target: ValOrFunc<IRenderTarget|{texture?: ValOrArr<ITexture>}|undefined> | |||
| name: string | |||
| visible: boolean | |||
| transparent: boolean | |||
| originalColorSpace: boolean | |||
| div: HTMLDivElement | |||
| } | |||
| @uiFolderContainer('Render Target Preview Plugin') | |||
| export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPluginSync<TEvent> { | |||
| static readonly PluginType = 'RenderTargetPreviewPlugin' | |||
| @@ -22,14 +31,7 @@ export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPlu | |||
| this.enabled = enabled | |||
| } | |||
| targetBlocks: { | |||
| target: ValOrFunc<IRenderTarget|undefined> | |||
| name: string | |||
| visible: boolean | |||
| transparent: boolean | |||
| originalColorSpace: boolean | |||
| div: HTMLDivElement | |||
| }[] = [] | |||
| targetBlocks: RenderTargetBlock[] = [] | |||
| onAdded(viewer: ThreeViewer): void { | |||
| super.onAdded(viewer) | |||
| @@ -50,36 +52,36 @@ export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPlu | |||
| private _postRender = () => { | |||
| if (!this._viewer) return | |||
| for (const target of this.targetBlocks) { | |||
| if (!target.visible) continue | |||
| const rt = getOrCall(target.target) | |||
| for (const targetBlock of this.targetBlocks) { | |||
| if (!targetBlock.visible) continue | |||
| const rt = getOrCall(targetBlock.target) | |||
| if (!rt) { | |||
| // todo draw white or pink | |||
| continue | |||
| } | |||
| const rect = target.div.getBoundingClientRect() | |||
| const tex = rt.texture | |||
| const rect = targetBlock.div.getBoundingClientRect() | |||
| let tex = rt.texture | |||
| const canvasRect = this._viewer.canvas.getBoundingClientRect() | |||
| rect.x = rect.x - canvasRect.x | |||
| rect.y = canvasRect.height + canvasRect.y - rect.y - rect.height | |||
| if (Array.isArray(tex)) { | |||
| // todo support multi target | |||
| this._viewer.console.warn('Multi target preview not supported yet') | |||
| continue | |||
| this._viewer.console.warn('Multi target preview not supported yet, rendering just the first one') | |||
| tex = tex[0] | |||
| } | |||
| const outputColorSpace = this._viewer.renderManager.webglRenderer.outputColorSpace | |||
| if (!target.originalColorSpace) this._viewer.renderManager.webglRenderer.outputColorSpace = SRGBColorSpace | |||
| if (!targetBlock.originalColorSpace) this._viewer.renderManager.webglRenderer.outputColorSpace = SRGBColorSpace | |||
| this._viewer.renderManager.blit(null, { | |||
| source: tex, | |||
| clear: !target.transparent, | |||
| respectColorSpace: !target.originalColorSpace, | |||
| clear: !targetBlock.transparent, | |||
| respectColorSpace: !targetBlock.originalColorSpace, | |||
| viewport: new Vector4(rect.x, rect.y, rect.width, rect.height), | |||
| }) | |||
| this._viewer.renderManager.webglRenderer.outputColorSpace = outputColorSpace | |||
| } | |||
| } | |||
| addTarget(target: ValOrFunc<IRenderTarget|undefined>, name: string, transparent = false, originalColorSpace = false, visible = true): this { | |||
| addTarget(target: RenderTargetBlock['target'], name: string, transparent = false, originalColorSpace = false, visible = true): this { | |||
| if (!target) return this | |||
| const div = document.createElement('div') | |||
| const targetDef = {target, name, transparent, div, originalColorSpace, visible} | |||
| @@ -109,7 +111,7 @@ export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPlu | |||
| return this | |||
| } | |||
| removeTarget(target: ValOrFunc<IRenderTarget|undefined>): this { | |||
| removeTarget(target: RenderTargetBlock['target']): this { | |||
| const index = this.targetBlocks.findIndex(t => t.target === target) | |||
| if (index >= 0) { | |||
| const t = this.targetBlocks[index] | |||
| @@ -119,7 +121,7 @@ export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPlu | |||
| this.refreshUi() | |||
| return this | |||
| } | |||
| downloadTarget(target1: ValOrFunc<IRenderTarget|undefined>): this { | |||
| downloadTarget(target1: RenderTargetBlock['target']): this { | |||
| if (!this._viewer) return this | |||
| const target = getOrCall(target1) | |||
| if (!target) return this | |||
| @@ -52,7 +52,6 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| this._transparentTarget = undefined | |||
| } | |||
| constructor(renderManager: ViewerRenderManager, overrideMaterial?: Material, clearColor = new Color(0, 0, 0), clearAlpha = 0) { | |||
| super(undefined, undefined, overrideMaterial, clearColor, clearAlpha) | |||
| this.renderManager = renderManager | |||
| @@ -10,7 +10,7 @@ export class ExtendedShaderPass extends ShaderPass implements IPass { | |||
| public static readonly DEFAULT_TEX_ID = 'tDiffuse' | |||
| material!: ShaderMaterial2 | |||
| overrideReadBuffer: WebGLRenderTarget|null = null | |||
| overrideReadBuffer: {texture?: WebGLRenderTarget['texture']}|null = null | |||
| readonly isExtendedShaderPass = true | |||
| // private _textureIDs: string[] | |||
| @@ -29,7 +29,7 @@ export class ExtendedShaderPass extends ShaderPass implements IPass { | |||
| renderer.renderWithModes({ | |||
| backgroundRender: false, | |||
| }, ()=>{ | |||
| super.render(renderer, writeBuffer || null, this.overrideReadBuffer || readBuffer, deltaTime, maskActive) | |||
| super.render(renderer, writeBuffer || null, (this.overrideReadBuffer as WebGLRenderTarget) || readBuffer, deltaTime, maskActive) | |||
| }) | |||
| } | |||
| @@ -31,7 +31,7 @@ export class ScreenPass extends ExtendedShaderPass implements IPipelinePass<'scr | |||
| super( | |||
| (<any>shader)?.fragmentShader || (<ShaderMaterial2>shader)?.isShaderMaterial ? <ShaderMaterialParameters|ShaderMaterial2>shader : | |||
| makeScreenShader(shader), | |||
| ...textureID.length ? textureID : ['tDiffuse', 'tTransparent', 'tGBuffer']) | |||
| ...textureID.length ? textureID : ['tDiffuse', 'tTransparent']) | |||
| this.material.addEventListener('materialUpdate', this.setDirty) | |||
| } | |||
| @@ -11,6 +11,7 @@ import { | |||
| Texture, | |||
| Vector2, | |||
| Vector4, | |||
| WebGLMultipleRenderTargets, | |||
| WebGLRenderer, | |||
| WebGLRenderTarget, | |||
| WebGLRenderTargetOptions, | |||
| @@ -504,41 +505,45 @@ export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRen | |||
| * @param target | |||
| * @param mimeType | |||
| * @param quality | |||
| * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) | |||
| */ | |||
| renderTargetToDataUrl(target: WebGLRenderTarget, mimeType = 'image/png', quality = 90): string { | |||
| renderTargetToDataUrl(target: WebGLMultipleRenderTargets|WebGLRenderTarget, mimeType = 'image/png', quality = 90, textureIndex = 0): string { | |||
| const canvas = document.createElement('canvas') | |||
| canvas.width = target.width | |||
| canvas.height = target.height | |||
| const ctx = canvas.getContext('2d') | |||
| if (!ctx) throw new Error('Unable to get 2d context') | |||
| const imageData = ctx.createImageData(target.width, target.height, {colorSpace: ['display-p3', 'srgb'].includes(target.texture.colorSpace) ? <PredefinedColorSpace>target.texture.colorSpace : undefined}) | |||
| if (target.texture.type === HalfFloatType || target.texture.type === FloatType) { | |||
| const buffer = this.renderTargetToBuffer(target) | |||
| textureDataToImageData({data: buffer, width: target.width, height: target.height}, target.texture.colorSpace, imageData) // this handles converting to srgb | |||
| const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture | |||
| const imageData = ctx.createImageData(target.width, target.height, {colorSpace: ['display-p3', 'srgb'].includes(texture.colorSpace) ? <PredefinedColorSpace>texture.colorSpace : undefined}) | |||
| if (texture.type === HalfFloatType || texture.type === FloatType) { | |||
| const buffer = this.renderTargetToBuffer(target, textureIndex) | |||
| textureDataToImageData({data: buffer, width: target.width, height: target.height}, texture.colorSpace, imageData) // this handles converting to srgb | |||
| } else { | |||
| // todo: handle rgbm to srgb conversion? | |||
| this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, imageData.data) | |||
| this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, imageData.data, undefined, textureIndex) | |||
| } | |||
| ctx.putImageData(imageData, 0, 0) | |||
| const string = (target.texture.flipY ? canvas : canvasFlipY(canvas)).toDataURL(mimeType, quality) // intentionally inverted ternary | |||
| const string = (texture.flipY ? canvas : canvasFlipY(canvas)).toDataURL(mimeType, quality) // intentionally inverted ternary | |||
| canvas.remove() | |||
| return string | |||
| } | |||
| /** | |||
| * Rend pixels from a render target into a new Uint8Array|Uint16Array|Float32Array buffer | |||
| * @param target | |||
| * @param target - render target to read from | |||
| * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) | |||
| */ | |||
| renderTargetToBuffer(target: WebGLRenderTarget): Uint8Array|Uint16Array|Float32Array { | |||
| renderTargetToBuffer(target: WebGLMultipleRenderTargets|WebGLRenderTarget, textureIndex = 0): Uint8Array|Uint16Array|Float32Array { | |||
| const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture | |||
| const buffer = | |||
| target.texture.type === HalfFloatType ? | |||
| texture.type === HalfFloatType ? | |||
| new Uint16Array(target.width * target.height * 4) : | |||
| target.texture.type === FloatType ? | |||
| texture.type === FloatType ? | |||
| new Float32Array(target.width * target.height * 4) : | |||
| new Uint8Array(target.width * target.height * 4) | |||
| this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, buffer) | |||
| this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, buffer, undefined, textureIndex) | |||
| return buffer | |||
| } | |||
| @@ -547,17 +552,19 @@ export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRen | |||
| * @param target - render target to export | |||
| * @param mimeType - mime type to use. | |||
| * If auto (default), then it will be picked based on the render target type. | |||
| * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) | |||
| */ | |||
| exportRenderTarget(target: WebGLRenderTarget, mimeType = 'auto'): BlobExt { | |||
| exportRenderTarget(target: WebGLMultipleRenderTargets|WebGLRenderTarget, mimeType = 'auto', textureIndex = 0): BlobExt { | |||
| const hdrFormats = ['image/x-exr'] | |||
| let hdr = target.texture.type === HalfFloatType || target.texture.type === FloatType | |||
| const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture | |||
| let hdr = texture.type === HalfFloatType || texture.type === FloatType | |||
| if (mimeType === 'auto') { | |||
| mimeType = hdr ? 'image/x-exr' : 'image/png' | |||
| } | |||
| if (!hdrFormats.includes(mimeType)) hdr = false | |||
| let buffer: ArrayBufferLike | |||
| if (!hdr) { | |||
| const url = this.renderTargetToDataUrl(target, mimeType === 'auto' ? undefined : mimeType) | |||
| const url = this.renderTargetToDataUrl(target, mimeType === 'auto' ? undefined : mimeType, 90, textureIndex) | |||
| buffer = base64ToArrayBuffer(url.split(',')[1]) | |||
| mimeType = url.split(';')[0].split(':')[1] | |||
| } else { | |||
| @@ -566,7 +573,7 @@ export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRen | |||
| mimeType = 'image/x-exr' | |||
| } | |||
| const exporter = new EXRExporter2() | |||
| buffer = exporter.parse(this._renderer, target) | |||
| buffer = exporter.parse(this._renderer, target, {textureIndex}) | |||
| } | |||
| const b = new Blob([buffer], {type: mimeType}) as BlobExt | |||
| b.ext = mimeType === 'image/x-exr' ? 'exr' : mimeType.split('/')[1] | |||
| @@ -2,12 +2,14 @@ export type {IEvent, IEventDispatcher} from 'ts-browser-helpers' | |||
| export type {ImageCanvasOptions} from 'ts-browser-helpers' | |||
| export type {AnyFunction, AnyOptions, Class, IDisposable, IJSONSerializable, PartialPick, PartialRecord, StringKeyOf, Fof, ValOrFunc, ValOrArr, ValOrFuncOp, ValOrArrOp} from 'ts-browser-helpers' | |||
| export type {Serializer} from 'ts-browser-helpers' | |||
| export {PointerDragHelper} from 'ts-browser-helpers' | |||
| export {Damper} from 'ts-browser-helpers' | |||
| export {SimpleEventDispatcher} from 'ts-browser-helpers' | |||
| export {createCanvasElement, createDiv, createImage, createStyles, createScriptFromURL} from 'ts-browser-helpers' | |||
| export {createCanvasElement, createDiv, createImage, createStyles, createScriptFromURL, cloneScriptTag, setInnerHTMLWithScripts} from 'ts-browser-helpers' | |||
| export {TYPED_ARRAYS, arrayBufferToBase64, base64ToArrayBuffer, getTypedArray} from 'ts-browser-helpers' | |||
| export {escapeRegExp, getFilenameFromPath, parseFileExtension, replaceAll, toTitleCase, longestCommonPrefix} from 'ts-browser-helpers' | |||
| export {escapeRegExp, getFilenameFromPath, parseFileExtension, replaceAll, toTitleCase, longestCommonPrefix, toCamelCase, safeReplaceString} from 'ts-browser-helpers' | |||
| export {prettyScrollbar} from 'ts-browser-helpers' | |||
| export {blobToDataURL, downloadBlob, downloadFile, uploadFile, mobileAndTabletCheck} from 'ts-browser-helpers' | |||
| export {LinearToSRGB, SRGBToLinear, colorToDataUrl} from 'ts-browser-helpers' | |||
| @@ -15,9 +17,9 @@ export {onChange, onChange2, onChange3, onChangeDispatchEvent, serialize, serial | |||
| export {aesGcmDecrypt, aesGcmEncrypt} from 'ts-browser-helpers' | |||
| export {verifyPermission, writeFile, getFileHandle, getNewFileHandle, readFile} from 'ts-browser-helpers' | |||
| export {embedUrlRefs, htmlToCanvas, htmlToPng, htmlToSvg} from 'ts-browser-helpers' | |||
| export {imageToCanvas, imageBitmapToBase64, imageUrlToImageData, imageDataToCanvas, isWebpExportSupported, canvasFlipY} from 'ts-browser-helpers' | |||
| export {absMax, clearBit, updateBit} from 'ts-browser-helpers' | |||
| export {includesAll, wrapThisFunction, findLastIndex} from 'ts-browser-helpers' | |||
| export {imageToCanvas, imageBitmapToBase64, imageUrlToImageData, imageDataToCanvas, canvasFlipY, isWebpExportSupported, imageBitmapToBlob, imageBitmapToCanvas, blobToImage} from 'ts-browser-helpers' | |||
| export {absMax, clearBit, updateBit, uuidV4} from 'ts-browser-helpers' | |||
| export {includesAll, wrapThisFunction, wrapThisFunction2, findLastIndex} from 'ts-browser-helpers' | |||
| export {copyProps, getOrCall, getPropertyDescriptor, isPropertyWritable, safeSetProperty} from 'ts-browser-helpers' | |||
| export {deepAccessObject, getKeyByValue, objectHasOwn, objectMap2, objectMap} from 'ts-browser-helpers' | |||
| export {makeColorSvg, makeTextSvg, makeColorSvgCircle, svgToCanvas, svgToPng} from 'ts-browser-helpers' | |||
| @@ -15,7 +15,7 @@ export function shaderReplaceString(shader: string, str: string, newStr: string, | |||
| append = false, | |||
| } = {}) { | |||
| // todo: use safeReplaceString from ts-browser-helpers | |||
| if (warnEnabled) { | |||
| if (warnEnabled /* && ThreeViewer.ViewerDebugging */) { | |||
| if (!shader.includes(str)) { | |||
| console.error(`${str} not found in shader`) | |||
| return shader | |||
| @@ -31,7 +31,7 @@ export abstract class AViewerPlugin<T extends string = string, TViewer extends T | |||
| toJSON(meta?: SerializationMetaType): ISerializedConfig { | |||
| const data: any = ThreeSerialization.Serialize(this, meta, true) | |||
| data.type = (this as any).constructor.PluginType | |||
| data.type = this.constructor.PluginType | |||
| data.assetType = 'config' | |||
| this.dispatchEvent({type: 'serialize', data}) | |||
| return data | |||
| @@ -297,6 +297,12 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| private _tempVec: Vector3 = new Vector3() | |||
| private _tempQuat: Quaternion = new Quaternion() | |||
| /** | |||
| * If any of the viewers are in debug mode, this will be true. | |||
| * This is required for debugging/logging in some cases. | |||
| */ | |||
| public static ViewerDebugging = false // todo use in shaderReplaceString | |||
| /** | |||
| * Create a viewer instance for using the webgi viewer SDK. | |||
| * @param options - {@link ThreeViewerOptions} | |||
| @@ -304,6 +310,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| constructor({debug = false, ...options}: ThreeViewerOptions) { | |||
| super() | |||
| this.debug = debug | |||
| if (debug) ThreeViewer.ViewerDebugging = true | |||
| this._canvas = options.canvas || createCanvasElement() | |||
| let container = options.container | |||
| if (container && !options.canvas) container.appendChild(this._canvas) | |||
| @@ -52,7 +52,7 @@ export class ViewerRenderManager extends RenderManager { | |||
| } | |||
| /** | |||
| * Reference to the gbuffer target, if it exists. This can be set by plugins like {@link DepthBufferPlugin} | |||
| * Reference to the gbuffer target, if it exists. This can be set by plugins like {@link DepthBufferPlugin}, {@link GBufferPlugin} | |||
| */ | |||
| gbufferTarget: IRenderTarget | undefined | |||
| @@ -1 +1 @@ | |||
| export const VERSION = '0.0.20' | |||
| export const VERSION = '0.0.21' | |||