| - [ProgressivePlugin](#progressiveplugin) - Post-render pass to blend the last frame with the current frame | - [ProgressivePlugin](#progressiveplugin) - Post-render pass to blend the last frame with the current frame | ||||
| - [DepthBufferPlugin](#depthbufferplugin) - Pre-rendering of depth buffer | - [DepthBufferPlugin](#depthbufferplugin) - Pre-rendering of depth buffer | ||||
| - [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal 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 | - [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 | - [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 | - [GLTFAnimationPlugin](#gltfanimationplugin) - Add support for playing and seeking gltf animations | ||||
| 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. | 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 | ```typescript | ||||
| import {ThreeViewer, NormalBufferPlugin} from 'threepipe' | import {ThreeViewer, NormalBufferPlugin} from 'threepipe' | ||||
| ## GBufferPlugin | ## 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 | ## PickingPlugin | ||||
| <!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> |
| 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) |
| <h2 class="category">Rendering</h2> | <h2 class="category">Rendering</h2> | ||||
| <ul> | <ul> | ||||
| <li><a href="./progressive-plugin/">Progressive Plugin </a></li> | <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="./depth-buffer-plugin/">Depth Buffer Plugin </a></li> | ||||
| <li><a href="./normal-buffer-plugin/">Normal 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-cameras-plugin/">Virtual Cameras Plugin </a></li> | ||||
| <li><a href="./virtual-camera/">Virtual Camera (Animated) </a></li> | <li><a href="./virtual-camera/">Virtual Camera (Animated) </a></li> | ||||
| </ul> | </ul> |
| FragmentClippingExtensionPlugin, | FragmentClippingExtensionPlugin, | ||||
| FrameFadePlugin, | FrameFadePlugin, | ||||
| FullScreenPlugin, | FullScreenPlugin, | ||||
| GBufferPlugin, | |||||
| GLTFAnimationPlugin, | GLTFAnimationPlugin, | ||||
| HalfFloatType, | HalfFloatType, | ||||
| HDRiGroundPlugin, | HDRiGroundPlugin, | ||||
| new ProgressivePlugin(), | new ProgressivePlugin(), | ||||
| GLTFAnimationPlugin, | GLTFAnimationPlugin, | ||||
| PickingPlugin, | PickingPlugin, | ||||
| TransformControlsPlugin, | |||||
| new TransformControlsPlugin(false), | |||||
| EditorViewWidgetPlugin, | EditorViewWidgetPlugin, | ||||
| CameraViewPlugin, | CameraViewPlugin, | ||||
| ViewerUiConfigPlugin, | ViewerUiConfigPlugin, | ||||
| CustomBumpMapPlugin, | CustomBumpMapPlugin, | ||||
| VirtualCamerasPlugin, | VirtualCamerasPlugin, | ||||
| // new SceneUiConfigPlugin(), // this is already in ViewerUiPlugin | // 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 NormalBufferPlugin(HalfFloatType, false), | ||||
| new RenderTargetPreviewPlugin(false), | new RenderTargetPreviewPlugin(false), | ||||
| new FrameFadePlugin(), | new FrameFadePlugin(), | ||||
| ]) | ]) | ||||
| const rt = viewer.getOrAddPluginSync(RenderTargetPreviewPlugin) | 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(DepthBufferPlugin)?.target, 'depth', false, false, false) | ||||
| rt.addTarget(viewer.getPlugin(NormalBufferPlugin)?.target, 'normal', false, true, false) | rt.addTarget(viewer.getPlugin(NormalBufferPlugin)?.target, 'normal', false, true, false) | ||||
| editor.loadPlugins({ | editor.loadPlugins({ | ||||
| ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin, TweakpaneUiPlugin], | ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin, TweakpaneUiPlugin], | ||||
| ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin], | ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin], | ||||
| ['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin], | |||||
| ['GBuffer']: [GBufferPlugin, DepthBufferPlugin, NormalBufferPlugin], | |||||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | ||||
| ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], | ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], | ||||
| ['Extras']: [HDRiGroundPlugin, Rhino3dmLoadPlugin, ClearcoatTintPlugin, FragmentClippingExtensionPlugin, NoiseBumpMaterialPlugin, CustomBumpMapPlugin, VirtualCamerasPlugin], | ['Extras']: [HDRiGroundPlugin, Rhino3dmLoadPlugin, ClearcoatTintPlugin, FragmentClippingExtensionPlugin, NoiseBumpMaterialPlugin, CustomBumpMapPlugin, VirtualCamerasPlugin], |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.20-dev.1", | |||||
| "version": "0.0.20", | |||||
| "lockfileVersion": 2, | "lockfileVersion": 2, | ||||
| "requires": true, | "requires": true, | ||||
| "packages": { | "packages": { | ||||
| "": { | "": { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.20-dev.1", | |||||
| "version": "0.0.20", | |||||
| "license": "Apache-2.0", | "license": "Apache-2.0", | ||||
| "dependencies": { | "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/webxr": "^0.5.1", | ||||
| "@types/wicg-file-system-access": "^2020.9.5", | "@types/wicg-file-system-access": "^2020.9.5", | ||||
| "stats.js": "^0.17.0", | "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": { | "devDependencies": { | ||||
| "@rollup/plugin-commonjs": "^25.0.0", | "@rollup/plugin-commonjs": "^25.0.0", | ||||
| "rollup-plugin-glsl": "^1.3.0", | "rollup-plugin-glsl": "^1.3.0", | ||||
| "rollup-plugin-license": "^3.0.1", | "rollup-plugin-license": "^3.0.1", | ||||
| "rollup-plugin-postcss": "^4.0.2", | "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", | "tslib": "^2.5.0", | ||||
| "typedoc": "^0.25.6", | "typedoc": "^0.25.6", | ||||
| "typescript": "^5.3.3", | "typescript": "^5.3.3", | ||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "node_modules/@types/three": { | "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": { | "dependencies": { | ||||
| "@tweenjs/tween.js": "~18.6.4", | "@tweenjs/tween.js": "~18.6.4", | ||||
| "fflate": "~0.6.9", | "fflate": "~0.6.9", | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/three": { | "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, | "dev": true, | ||||
| "license": "MIT" | "license": "MIT" | ||||
| }, | }, | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/ts-browser-helpers": { | "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": { | "dependencies": { | ||||
| "@types/wicg-file-system-access": "^2020.9.5" | "@types/wicg-file-system-access": "^2020.9.5" | ||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/uiconfig.js": { | "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": { | "node_modules/unbox-primitive": { | ||||
| "version": "1.0.2", | "version": "1.0.2", | ||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "@types/three": { | "@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": { | "requires": { | ||||
| "@tweenjs/tween.js": "~18.6.4", | "@tweenjs/tween.js": "~18.6.4", | ||||
| "fflate": "~0.6.9", | "fflate": "~0.6.9", | ||||
| } | } | ||||
| }, | }, | ||||
| "three": { | "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 | "dev": true | ||||
| }, | }, | ||||
| "through": { | "through": { | ||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "ts-browser-helpers": { | "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": { | "requires": { | ||||
| "@types/wicg-file-system-access": "^2020.9.5" | "@types/wicg-file-system-access": "^2020.9.5" | ||||
| } | } | ||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "uiconfig.js": { | "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": { | "unbox-primitive": { | ||||
| "version": "1.0.2", | "version": "1.0.2", |
| { | { | ||||
| "name": "threepipe", | "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.", | "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", | "main": "dist/index.js", | ||||
| "module": "dist/index.mjs", | "module": "dist/index.mjs", | ||||
| "rollup-plugin-glsl": "^1.3.0", | "rollup-plugin-glsl": "^1.3.0", | ||||
| "rollup-plugin-license": "^3.0.1", | "rollup-plugin-license": "^3.0.1", | ||||
| "rollup-plugin-postcss": "^4.0.2", | "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", | "tslib": "^2.5.0", | ||||
| "typedoc": "^0.25.6", | "typedoc": "^0.25.6", | ||||
| "typescript": "^5.3.3", | "typescript": "^5.3.3", | ||||
| "vite-plugin-dts": "^3.7.0" | "vite-plugin-dts": "^3.7.0" | ||||
| }, | }, | ||||
| "dependencies": { | "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/webxr": "^0.5.1", | ||||
| "@types/wicg-file-system-access": "^2020.9.5", | "@types/wicg-file-system-access": "^2020.9.5", | ||||
| "stats.js": "^0.17.0", | "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": { | "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" | "@types/three-pkg": "https://gitpkg.now.sh/repalash/three-ts-types/types/three?modded_three" | ||||
| }, | }, | ||||
| "local_dependencies": { | "local_dependencies": { |
| { | { | ||||
| "name": "@threepipe/plugin-tweakpane", | "name": "@threepipe/plugin-tweakpane", | ||||
| "version": "0.2.0", | |||||
| "version": "0.2.1", | |||||
| "lockfileVersion": 2, | "lockfileVersion": 2, | ||||
| "requires": true, | "requires": true, | ||||
| "packages": { | "packages": { | ||||
| "": { | "": { | ||||
| "name": "@threepipe/plugin-tweakpane", | "name": "@threepipe/plugin-tweakpane", | ||||
| "version": "0.2.0", | |||||
| "version": "0.2.1", | |||||
| "license": "Apache-2.0", | "license": "Apache-2.0", | ||||
| "dependencies": { | "dependencies": { | ||||
| "threepipe": "file:./../../src/" | "threepipe": "file:./../../src/" |
| { | { | ||||
| "name": "@threepipe/plugin-tweakpane", | "name": "@threepipe/plugin-tweakpane", | ||||
| "description": "Tweakpane UI Plugin for ThreePipe", | "description": "Tweakpane UI Plugin for ThreePipe", | ||||
| "version": "0.2.0", | |||||
| "version": "0.2.1", | |||||
| "devDependencies": { | "devDependencies": { | ||||
| "tweakpane-image-plugin": "https://github.com/repalash/tweakpane-image-plugin/releases/download/v1.1.404/package.tgz", | "tweakpane-image-plugin": "https://github.com/repalash/tweakpane-image-plugin/releases/download/v1.1.404/package.tgz", | ||||
| "uiconfig-tweakpane": "^0.0.8" | "uiconfig-tweakpane": "^0.0.8" | ||||
| ], | ], | ||||
| "replace": { | "replace": { | ||||
| "dependencies": { | "dependencies": { | ||||
| "threepipe": "^0.0.18" | |||||
| "threepipe": "^0.0.21" | |||||
| } | } | ||||
| } | } | ||||
| }, | }, |
| cc.image.tp_src = imageBitmapToBase64(cc.image, 160) | cc.image.tp_src = imageBitmapToBase64(cc.image, 160) | ||||
| } else if (cc.isRenderTargetTexture) { | } else if (cc.isRenderTargetTexture) { | ||||
| if (cc._target) { | 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 { | } else { | ||||
| cc.image.tp_src = textureToDataUrl(cc, 160, false, 'image/png', 90) // this supports DataTexture also | 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 | if (cc.isRenderTargetTexture) cc.image.tp_src = staticData.renderTarImage | ||||
| else if (cc.isDataTexture) cc.image.tp_src = staticData.dataTexImage | else if (cc.isDataTexture) cc.image.tp_src = staticData.dataTexImage | ||||
| } | } | ||||
| ret = ret ? staticData.imageMap[ret] : undefined | ret = ret ? staticData.imageMap[ret] : undefined | ||||
| if (!ret) ret = cc.image.tp_src || cc.image.src | if (!ret) ret = cc.image.tp_src || cc.image.src | ||||
| } | } | ||||
| if (cc.tp_src) ret = cc.tp_src | |||||
| } else if (typeof cc === 'string') { | } else if (typeof cc === 'string') { | ||||
| ret = cc | ret = cc | ||||
| } else if (cc.domainMin) { // for lut CUBE files. | } else if (cc.domainMin) { // for lut CUBE files. | ||||
| cc.image.tp_src_uuid = uuid | cc.image.tp_src_uuid = uuid | ||||
| staticData.tempMap[ret] = uuid | staticData.tempMap[ret] = uuid | ||||
| } | } | ||||
| // console.log(ret, cc, tar, key) | |||||
| if (typeof ret === 'string') | if (typeof ret === 'string') | ||||
| ret = staticData.imageMap[ret] ?? ret // Note: this will be a bottleneck if the length of src is too long. | ret = staticData.imageMap[ret] ?? ret // Note: this will be a bottleneck if the length of src is too long. | ||||
| return ret | return ret |
| const target = <IRenderTarget>obj | const target = <IRenderTarget>obj | ||||
| if (target.isWebGLRenderTarget && !target.renderManager) throw new Error('No renderManager on renderTarget') | 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.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 ? | const res = target.isWebGLRenderTarget ? | ||||
| this.parse(target.renderManager!.webglRenderer, <WebGLRenderTarget>target, options) : | this.parse(target.renderManager!.webglRenderer, <WebGLRenderTarget>target, options) : | ||||
| this.parse(undefined, <DataTexture>obj, options) | this.parse(undefined, <DataTexture>obj, options) |
| inverseAlphaMap?: boolean // only for physical material right now | inverseAlphaMap?: boolean // only for physical material right now | ||||
| /** | |||||
| * See {@link GBufferPlugin} | |||||
| */ | |||||
| gBufferData?: { | |||||
| materialId?: number | |||||
| /** | |||||
| * @default true | |||||
| */ | |||||
| tonemapEnabled?: boolean | |||||
| } | |||||
| [key: string]: any | [key: string]: any | ||||
| // legacy, to be removed | // legacy, to be removed | ||||
| /** | |||||
| * @deprecated | |||||
| */ | |||||
| setDirty?: (options?: IMaterialSetDirtyOptions) => void | setDirty?: (options?: IMaterialSetDirtyOptions) => void | ||||
| /** | |||||
| * @deprecated | |||||
| */ | |||||
| postTonemap?: boolean | |||||
| } | } | ||||
| export interface IMaterial<E extends IMaterialEvent = IMaterialEvent, ET = IMaterialEventTypes> extends Material<E, ET>, IJSONSerializable, IDisposable, IUiConfigContainer { | export interface IMaterial<E extends IMaterialEvent = IMaterialEvent, ET = IMaterialEventTypes> extends Material<E, ET>, IJSONSerializable, IDisposable, IUiConfigContainer { | ||||
| lightMap?: ITexture | null | lightMap?: ITexture | null | ||||
| normalMap?: ITexture | null | normalMap?: ITexture | null | ||||
| bumpMap?: ITexture | null | bumpMap?: ITexture | null | ||||
| displacementMap?: ITexture | null | |||||
| aoMapIntensity?: number | aoMapIntensity?: number | ||||
| lightMapIntensity?: number | lightMapIntensity?: number | ||||
| roughnessMap?: ITexture | null | roughnessMap?: ITexture | null |
| this.setDirty({uiChangeEvent: ev, needsUpdate: false, refreshUi: true}) | this.setDirty({uiChangeEvent: ev, needsUpdate: false, refreshUi: true}) | ||||
| }, | }, | ||||
| children: [ | children: [ | ||||
| ...generateUiConfig(this), | |||||
| ...generateUiConfig(this) || [], | |||||
| iMaterialUI.blending(this), | iMaterialUI.blending(this), | ||||
| iMaterialUI.polygonOffset(this), | iMaterialUI.polygonOffset(this), | ||||
| ...iMaterialUI.misc(this), | ...iMaterialUI.misc(this), |
| import {AddObjectOptions, IScene, ISceneEvent, ISceneEventTypes, ISceneSetDirtyOptions, IWidget} from '../IScene' | import {AddObjectOptions, IScene, ISceneEvent, ISceneEventTypes, ISceneSetDirtyOptions, IWidget} from '../IScene' | ||||
| import {iObjectCommons} from './iObjectCommons' | import {iObjectCommons} from './iObjectCommons' | ||||
| import {RootSceneImportResult} from '../../assetmanager' | 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' | import {IGeometry} from '../IGeometry' | ||||
| @uiFolderContainer('Root Scene') | @uiFolderContainer('Root Scene') | ||||
| obj.userData.autoScaled = true // mark as auto-scaled, so that autoScale is not called again when file is reloaded. | obj.userData.autoScaled = true // mark as auto-scaled, so that autoScale is not called again when file is reloaded. | ||||
| } | } | ||||
| if (centerGeometries && !obj.userData.geometriesCentered) { | 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 | obj.userData.geometriesCentered = true | ||||
| } else { | } else { | ||||
| obj.userData.geometriesCentered = true // mark as centered, so that geometry center is not called again when file is reloaded. | obj.userData.geometriesCentered = true // mark as centered, so that geometry center is not called again when file is reloaded. | ||||
| this.setDirty({refreshScene: true}) | 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 { | clearSceneModels(dispose = false, setDirty = true): void { | ||||
| if (dispose) return this.disposeSceneModels(setDirty) | if (dispose) return this.disposeSceneModels(setDirty) | ||||
| this.modelRoot.clear() | this.modelRoot.clear() |
| import {IPassID, IPipelinePass} from '../../postprocessing' | import {IPassID, IPipelinePass} from '../../postprocessing' | ||||
| import {AViewerPluginSync, ISerializedConfig, ThreeViewer} from '../../viewer' | 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 {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> { | export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extends IPassID, TEvent extends string, TViewer extends ThreeViewer=ThreeViewer> extends AViewerPluginSync<TEvent, TViewer> { | ||||
| abstract passId: TPassId | abstract passId: TPassId | ||||
| /** | /** | ||||
| * This function is called every frame before composer render, if this pass is being used in the pipeline | * This function is called every frame before composer render, if this pass is being used in the pipeline | ||||
| * @param _ | * @param _ | ||||
| * @param _1 | |||||
| * @param _2 | |||||
| */ | */ | ||||
| protected _beforeRender(): boolean { | |||||
| protected _beforeRender(_?: IScene, _1?: ICamera, _2?: IRenderManager): boolean { | |||||
| if (!this._pass) return false | if (!this._pass) return false | ||||
| this._pass.enabled = !this.isDisabled() | this._pass.enabled = !this.isDisabled() | ||||
| return this._pass.enabled | return this._pass.enabled | ||||
| this._pass = this._createPass() | this._pass = this._createPass() | ||||
| this._pass.onDirty?.push(viewer.setDirty) | 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) | viewer.renderManager.registerPass(this._pass) | ||||
| } | } | ||||
| // pipeline | // pipeline | ||||
| export {ProgressivePlugin} from './pipeline/ProgressivePlugin' | export {ProgressivePlugin} from './pipeline/ProgressivePlugin' | ||||
| export {GBufferPlugin, GBufferMaterial, DepthNormalMaterial} from './pipeline/GBufferPlugin' | |||||
| export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | ||||
| export {NormalBufferPlugin} from './pipeline/NormalBufferPlugin' | export {NormalBufferPlugin} from './pipeline/NormalBufferPlugin' | ||||
| export {FrameFadePlugin, type FrameFadePluginEventTypes} from './pipeline/FrameFadePlugin' | export {FrameFadePlugin, type FrameFadePluginEventTypes} from './pipeline/FrameFadePlugin' | ||||
| export type {ProgressivePluginEventTypes, ProgressivePluginTarget} from './pipeline/ProgressivePlugin' | 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 {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | ||||
| export type {NormalBufferPluginEventTypes, NormalBufferPluginPass, NormalBufferPluginTarget} from './pipeline/NormalBufferPlugin' | export type {NormalBufferPluginEventTypes, NormalBufferPluginPass, NormalBufferPluginTarget} from './pipeline/NormalBufferPlugin' | ||||
| import {uiConfig, uiPanelContainer, uiToggle} from 'uiconfig.js' | |||||
| import {uiButton, uiConfig, uiPanelContainer, uiToggle} from 'uiconfig.js' | |||||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | import {AViewerPluginSync, ThreeViewer} from '../../viewer' | ||||
| import {OrbitControls3, TransformControls2} from '../../three' | import {OrbitControls3, TransformControls2} from '../../three' | ||||
| import {PickingPlugin} from './PickingPlugin' | import {PickingPlugin} from './PickingPlugin' | ||||
| this._viewer.setDirty() | this._viewer.setDirty() | ||||
| } | } | ||||
| constructor() { | |||||
| constructor(enabled: boolean) { | |||||
| super() | super() | ||||
| TransformControls.ObjectConstructors.MeshBasicMaterial = UnlitMaterial as any | TransformControls.ObjectConstructors.MeshBasicMaterial = UnlitMaterial as any | ||||
| TransformControls.ObjectConstructors.LineBasicMaterial = UnlitLineMaterial as any | TransformControls.ObjectConstructors.LineBasicMaterial = UnlitLineMaterial as any | ||||
| this.enabled = enabled | |||||
| } | } | ||||
| toJSON: any = undefined | toJSON: any = undefined | ||||
| this.transformControls.camera = this._viewer.scene.mainCamera | this.transformControls.camera = this._viewer.scene.mainCamera | ||||
| } | } | ||||
| @uiButton('Center All Meshes') | |||||
| centerAllMeshes() { | |||||
| this._viewer?.scene.centerAllGeometries(true) | |||||
| } | |||||
| } | } |
| this._createTarget(true) | this._createTarget(true) | ||||
| if (!this.target) throw new Error('DepthBufferPlugin: target not created') | if (!this.target) throw new Error('DepthBufferPlugin: target not created') | ||||
| this.material.userData.isGBufferMaterial = true | 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 | 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.before = ['render'] | ||||
| pass.after = [] | pass.after = [] | ||||
| pass.required = ['render'] | pass.required = ['render'] |
| 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') | |||||
| } | |||||
| } |
| this._createTarget(true) | this._createTarget(true) | ||||
| if (!this.target) throw new Error('NormalBufferPlugin: target not created') | if (!this.target) throw new Error('NormalBufferPlugin: target not created') | ||||
| this.material.userData.isGBufferMaterial = true | 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 | const preprocessMaterial = pass.preprocessMaterial | ||||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, true) | pass.preprocessMaterial = (m) => preprocessMaterial(m, true) | ||||
| pass.before = ['render'] | pass.before = ['render'] |
| import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../../core' | import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../../core' | ||||
| import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass' | import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass' | ||||
| import {getOrCall, serialize, ValOrFunc} from 'ts-browser-helpers' | import {getOrCall, serialize, ValOrFunc} from 'ts-browser-helpers' | ||||
| import {IShaderPropertiesUpdater} from '../../materials' | |||||
| export type ProgressivePluginEventTypes = '' | export type ProgressivePluginEventTypes = '' | ||||
| export type ProgressivePluginTarget = WebGLRenderTarget | export type ProgressivePluginTarget = WebGLRenderTarget | ||||
| */ | */ | ||||
| @uiFolderContainer('Progressive Plugin') | @uiFolderContainer('Progressive Plugin') | ||||
| export class ProgressivePlugin | export class ProgressivePlugin | ||||
| extends PipelinePassPlugin<ProgressiveBlendPass, 'progressive', ProgressivePluginEventTypes> { | |||||
| extends PipelinePassPlugin<ProgressiveBlendPass, 'progressive', ProgressivePluginEventTypes> implements IShaderPropertiesUpdater { | |||||
| readonly passId = 'progressive' | readonly passId = 'progressive' | ||||
| public static readonly PluginType = 'ProgressivePlugin' | public static readonly PluginType = 'ProgressivePlugin' |
| // 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 | |||||
| } |
| // 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; | |||||
| } |
| #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 |
| import {Shader, Vector4, WebGLRenderer} from 'three' | import {Shader, Vector4, WebGLRenderer} from 'three' | ||||
| import {IMaterial} from '../../core' | import {IMaterial} from '../../core' | ||||
| import {shaderReplaceString} from '../../utils' | 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 | * Base Screen Pass Extension Plugin | ||||
| onAdded(viewer: ThreeViewer) { | onAdded(viewer: ThreeViewer) { | ||||
| super.onAdded(viewer) | 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]) | viewer.renderManager.screenPass.material.registerMaterialExtensions([this]) | ||||
| } | } | ||||
| onRemove(viewer: ThreeViewer) { | onRemove(viewer: ThreeViewer) { | ||||
| // viewer.getPlugin(GBufferPlugin)?.unregisterGBufferUpdater(this.updateGBufferFlags) | |||||
| viewer.getPlugin(GBufferPlugin)?.unregisterGBufferUpdater(this.constructor.PluginType) | |||||
| viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this]) | viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this]) | ||||
| super.onRemove(viewer) | 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 | // for typescript | ||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | // eslint-disable-next-line @typescript-eslint/naming-convention | ||||
| __setDirty?: () => void | __setDirty?: () => void | ||||
| updateGBufferFlags(_: IMaterial, _1: Vector4): void { | |||||
| updateGBufferFlags(_: Vector4, _1: GBufferUpdaterContext): void { | |||||
| return | return | ||||
| } | } | ||||
| import TonemapShader from './shaders/TonemapPlugin.pars.glsl' | import TonemapShader from './shaders/TonemapPlugin.pars.glsl' | ||||
| import TonemapShaderPatch from './shaders/TonemapPlugin.patch.glsl' | import TonemapShaderPatch from './shaders/TonemapPlugin.patch.glsl' | ||||
| import {AScreenPassExtensionPlugin} from './AScreenPassExtensionPlugin' | import {AScreenPassExtensionPlugin} from './AScreenPassExtensionPlugin' | ||||
| import {GBufferUpdaterContext} from '../pipeline/GBufferPlugin' | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | // eslint-disable-next-line @typescript-eslint/naming-convention | ||||
| export const Uncharted2Tonemapping: ToneMapping = CustomToneMapping | export const Uncharted2Tonemapping: ToneMapping = CustomToneMapping | ||||
| return super.fromJSON(data, meta) | 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 | data.w = updateBit(data.w, 1, x) // 2nd Bit | ||||
| super.updateGBufferFlags(material, data) | |||||
| super.updateGBufferFlags(data, c) | |||||
| } | } | ||||
| static { | static { |
| target.uvCanvas.style.height = '100%' | target.uvCanvas.style.height = '100%' | ||||
| } | } | ||||
| if (target.uvCanvas && target.uvCanvas.parentElement !== target.div) target.div.appendChild(target.uvCanvas) | 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 | |||||
| } | } | ||||
| } | } | ||||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | import {AViewerPluginSync, ThreeViewer} from '../../viewer' | ||||
| import {IRenderTarget} from '../../rendering' | 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 {SRGBColorSpace, Vector4, WebGLRenderTarget} from 'three' | ||||
| import styles from './RenderTargetPreviewPlugin.css?inline' | import styles from './RenderTargetPreviewPlugin.css?inline' | ||||
| import {CustomContextMenu} from '../../utils' | import {CustomContextMenu} from '../../utils' | ||||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | 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') | @uiFolderContainer('Render Target Preview Plugin') | ||||
| export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPluginSync<TEvent> { | export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPluginSync<TEvent> { | ||||
| static readonly PluginType = 'RenderTargetPreviewPlugin' | static readonly PluginType = 'RenderTargetPreviewPlugin' | ||||
| this.enabled = enabled | this.enabled = enabled | ||||
| } | } | ||||
| targetBlocks: { | |||||
| target: ValOrFunc<IRenderTarget|undefined> | |||||
| name: string | |||||
| visible: boolean | |||||
| transparent: boolean | |||||
| originalColorSpace: boolean | |||||
| div: HTMLDivElement | |||||
| }[] = [] | |||||
| targetBlocks: RenderTargetBlock[] = [] | |||||
| onAdded(viewer: ThreeViewer): void { | onAdded(viewer: ThreeViewer): void { | ||||
| super.onAdded(viewer) | super.onAdded(viewer) | ||||
| private _postRender = () => { | private _postRender = () => { | ||||
| if (!this._viewer) return | 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) { | if (!rt) { | ||||
| // todo draw white or pink | // todo draw white or pink | ||||
| continue | 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() | const canvasRect = this._viewer.canvas.getBoundingClientRect() | ||||
| rect.x = rect.x - canvasRect.x | rect.x = rect.x - canvasRect.x | ||||
| rect.y = canvasRect.height + canvasRect.y - rect.y - rect.height | rect.y = canvasRect.height + canvasRect.y - rect.y - rect.height | ||||
| if (Array.isArray(tex)) { | if (Array.isArray(tex)) { | ||||
| // todo support multi target | // 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 | 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, { | this._viewer.renderManager.blit(null, { | ||||
| source: tex, | 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), | viewport: new Vector4(rect.x, rect.y, rect.width, rect.height), | ||||
| }) | }) | ||||
| this._viewer.renderManager.webglRenderer.outputColorSpace = outputColorSpace | 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 | if (!target) return this | ||||
| const div = document.createElement('div') | const div = document.createElement('div') | ||||
| const targetDef = {target, name, transparent, div, originalColorSpace, visible} | const targetDef = {target, name, transparent, div, originalColorSpace, visible} | ||||
| return this | return this | ||||
| } | } | ||||
| removeTarget(target: ValOrFunc<IRenderTarget|undefined>): this { | |||||
| removeTarget(target: RenderTargetBlock['target']): this { | |||||
| const index = this.targetBlocks.findIndex(t => t.target === target) | const index = this.targetBlocks.findIndex(t => t.target === target) | ||||
| if (index >= 0) { | if (index >= 0) { | ||||
| const t = this.targetBlocks[index] | const t = this.targetBlocks[index] | ||||
| this.refreshUi() | this.refreshUi() | ||||
| return this | return this | ||||
| } | } | ||||
| downloadTarget(target1: ValOrFunc<IRenderTarget|undefined>): this { | |||||
| downloadTarget(target1: RenderTargetBlock['target']): this { | |||||
| if (!this._viewer) return this | if (!this._viewer) return this | ||||
| const target = getOrCall(target1) | const target = getOrCall(target1) | ||||
| if (!target) return this | if (!target) return this |
| this._transparentTarget = undefined | this._transparentTarget = undefined | ||||
| } | } | ||||
| constructor(renderManager: ViewerRenderManager, overrideMaterial?: Material, clearColor = new Color(0, 0, 0), clearAlpha = 0) { | constructor(renderManager: ViewerRenderManager, overrideMaterial?: Material, clearColor = new Color(0, 0, 0), clearAlpha = 0) { | ||||
| super(undefined, undefined, overrideMaterial, clearColor, clearAlpha) | super(undefined, undefined, overrideMaterial, clearColor, clearAlpha) | ||||
| this.renderManager = renderManager | this.renderManager = renderManager |
| public static readonly DEFAULT_TEX_ID = 'tDiffuse' | public static readonly DEFAULT_TEX_ID = 'tDiffuse' | ||||
| material!: ShaderMaterial2 | material!: ShaderMaterial2 | ||||
| overrideReadBuffer: WebGLRenderTarget|null = null | |||||
| overrideReadBuffer: {texture?: WebGLRenderTarget['texture']}|null = null | |||||
| readonly isExtendedShaderPass = true | readonly isExtendedShaderPass = true | ||||
| // private _textureIDs: string[] | // private _textureIDs: string[] | ||||
| renderer.renderWithModes({ | renderer.renderWithModes({ | ||||
| backgroundRender: false, | backgroundRender: false, | ||||
| }, ()=>{ | }, ()=>{ | ||||
| super.render(renderer, writeBuffer || null, this.overrideReadBuffer || readBuffer, deltaTime, maskActive) | |||||
| super.render(renderer, writeBuffer || null, (this.overrideReadBuffer as WebGLRenderTarget) || readBuffer, deltaTime, maskActive) | |||||
| }) | }) | ||||
| } | } | ||||
| super( | super( | ||||
| (<any>shader)?.fragmentShader || (<ShaderMaterial2>shader)?.isShaderMaterial ? <ShaderMaterialParameters|ShaderMaterial2>shader : | (<any>shader)?.fragmentShader || (<ShaderMaterial2>shader)?.isShaderMaterial ? <ShaderMaterialParameters|ShaderMaterial2>shader : | ||||
| makeScreenShader(shader), | makeScreenShader(shader), | ||||
| ...textureID.length ? textureID : ['tDiffuse', 'tTransparent', 'tGBuffer']) | |||||
| ...textureID.length ? textureID : ['tDiffuse', 'tTransparent']) | |||||
| this.material.addEventListener('materialUpdate', this.setDirty) | this.material.addEventListener('materialUpdate', this.setDirty) | ||||
| } | } | ||||
| Texture, | Texture, | ||||
| Vector2, | Vector2, | ||||
| Vector4, | Vector4, | ||||
| WebGLMultipleRenderTargets, | |||||
| WebGLRenderer, | WebGLRenderer, | ||||
| WebGLRenderTarget, | WebGLRenderTarget, | ||||
| WebGLRenderTargetOptions, | WebGLRenderTargetOptions, | ||||
| * @param target | * @param target | ||||
| * @param mimeType | * @param mimeType | ||||
| * @param quality | * @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') | const canvas = document.createElement('canvas') | ||||
| canvas.width = target.width | canvas.width = target.width | ||||
| canvas.height = target.height | canvas.height = target.height | ||||
| const ctx = canvas.getContext('2d') | const ctx = canvas.getContext('2d') | ||||
| if (!ctx) throw new Error('Unable to get 2d context') | 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 { | } else { | ||||
| // todo: handle rgbm to srgb conversion? | // 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) | 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() | canvas.remove() | ||||
| return string | return string | ||||
| } | } | ||||
| /** | /** | ||||
| * Rend pixels from a render target into a new Uint8Array|Uint16Array|Float32Array buffer | * 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 = | const buffer = | ||||
| target.texture.type === HalfFloatType ? | |||||
| texture.type === HalfFloatType ? | |||||
| new Uint16Array(target.width * target.height * 4) : | new Uint16Array(target.width * target.height * 4) : | ||||
| target.texture.type === FloatType ? | |||||
| texture.type === FloatType ? | |||||
| new Float32Array(target.width * target.height * 4) : | new Float32Array(target.width * target.height * 4) : | ||||
| new Uint8Array(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 | return buffer | ||||
| } | } | ||||
| * @param target - render target to export | * @param target - render target to export | ||||
| * @param mimeType - mime type to use. | * @param mimeType - mime type to use. | ||||
| * If auto (default), then it will be picked based on the render target type. | * 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'] | 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') { | if (mimeType === 'auto') { | ||||
| mimeType = hdr ? 'image/x-exr' : 'image/png' | mimeType = hdr ? 'image/x-exr' : 'image/png' | ||||
| } | } | ||||
| if (!hdrFormats.includes(mimeType)) hdr = false | if (!hdrFormats.includes(mimeType)) hdr = false | ||||
| let buffer: ArrayBufferLike | let buffer: ArrayBufferLike | ||||
| if (!hdr) { | 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]) | buffer = base64ToArrayBuffer(url.split(',')[1]) | ||||
| mimeType = url.split(';')[0].split(':')[1] | mimeType = url.split(';')[0].split(':')[1] | ||||
| } else { | } else { | ||||
| mimeType = 'image/x-exr' | mimeType = 'image/x-exr' | ||||
| } | } | ||||
| const exporter = new EXRExporter2() | 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 | const b = new Blob([buffer], {type: mimeType}) as BlobExt | ||||
| b.ext = mimeType === 'image/x-exr' ? 'exr' : mimeType.split('/')[1] | b.ext = mimeType === 'image/x-exr' ? 'exr' : mimeType.split('/')[1] |
| export type {ImageCanvasOptions} 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 {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 type {Serializer} from 'ts-browser-helpers' | ||||
| export {PointerDragHelper} from 'ts-browser-helpers' | export {PointerDragHelper} from 'ts-browser-helpers' | ||||
| export {Damper} from 'ts-browser-helpers' | export {Damper} from 'ts-browser-helpers' | ||||
| export {SimpleEventDispatcher} 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 {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 {prettyScrollbar} from 'ts-browser-helpers' | ||||
| export {blobToDataURL, downloadBlob, downloadFile, uploadFile, mobileAndTabletCheck} from 'ts-browser-helpers' | export {blobToDataURL, downloadBlob, downloadFile, uploadFile, mobileAndTabletCheck} from 'ts-browser-helpers' | ||||
| export {LinearToSRGB, SRGBToLinear, colorToDataUrl} from 'ts-browser-helpers' | export {LinearToSRGB, SRGBToLinear, colorToDataUrl} from 'ts-browser-helpers' | ||||
| export {aesGcmDecrypt, aesGcmEncrypt} from 'ts-browser-helpers' | export {aesGcmDecrypt, aesGcmEncrypt} from 'ts-browser-helpers' | ||||
| export {verifyPermission, writeFile, getFileHandle, getNewFileHandle, readFile} from 'ts-browser-helpers' | export {verifyPermission, writeFile, getFileHandle, getNewFileHandle, readFile} from 'ts-browser-helpers' | ||||
| export {embedUrlRefs, htmlToCanvas, htmlToPng, htmlToSvg} 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 {copyProps, getOrCall, getPropertyDescriptor, isPropertyWritable, safeSetProperty} from 'ts-browser-helpers' | ||||
| export {deepAccessObject, getKeyByValue, objectHasOwn, objectMap2, objectMap} from 'ts-browser-helpers' | export {deepAccessObject, getKeyByValue, objectHasOwn, objectMap2, objectMap} from 'ts-browser-helpers' | ||||
| export {makeColorSvg, makeTextSvg, makeColorSvgCircle, svgToCanvas, svgToPng} from 'ts-browser-helpers' | export {makeColorSvg, makeTextSvg, makeColorSvgCircle, svgToCanvas, svgToPng} from 'ts-browser-helpers' |
| append = false, | append = false, | ||||
| } = {}) { | } = {}) { | ||||
| // todo: use safeReplaceString from ts-browser-helpers | // todo: use safeReplaceString from ts-browser-helpers | ||||
| if (warnEnabled) { | |||||
| if (warnEnabled /* && ThreeViewer.ViewerDebugging */) { | |||||
| if (!shader.includes(str)) { | if (!shader.includes(str)) { | ||||
| console.error(`${str} not found in shader`) | console.error(`${str} not found in shader`) | ||||
| return shader | return shader |
| toJSON(meta?: SerializationMetaType): ISerializedConfig { | toJSON(meta?: SerializationMetaType): ISerializedConfig { | ||||
| const data: any = ThreeSerialization.Serialize(this, meta, true) | const data: any = ThreeSerialization.Serialize(this, meta, true) | ||||
| data.type = (this as any).constructor.PluginType | |||||
| data.type = this.constructor.PluginType | |||||
| data.assetType = 'config' | data.assetType = 'config' | ||||
| this.dispatchEvent({type: 'serialize', data}) | this.dispatchEvent({type: 'serialize', data}) | ||||
| return data | return data |
| private _tempVec: Vector3 = new Vector3() | private _tempVec: Vector3 = new Vector3() | ||||
| private _tempQuat: Quaternion = new Quaternion() | 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. | * Create a viewer instance for using the webgi viewer SDK. | ||||
| * @param options - {@link ThreeViewerOptions} | * @param options - {@link ThreeViewerOptions} | ||||
| constructor({debug = false, ...options}: ThreeViewerOptions) { | constructor({debug = false, ...options}: ThreeViewerOptions) { | ||||
| super() | super() | ||||
| this.debug = debug | this.debug = debug | ||||
| if (debug) ThreeViewer.ViewerDebugging = true | |||||
| this._canvas = options.canvas || createCanvasElement() | this._canvas = options.canvas || createCanvasElement() | ||||
| let container = options.container | let container = options.container | ||||
| if (container && !options.canvas) container.appendChild(this._canvas) | if (container && !options.canvas) container.appendChild(this._canvas) |
| } | } | ||||
| /** | /** | ||||
| * 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 | gbufferTarget: IRenderTarget | undefined | ||||
| export const VERSION = '0.0.20' | |||||
| export const VERSION = '0.0.21' |