| // for shader prop updater | // for shader prop updater | ||||
| private _positionWorld = new Vector3() | private _positionWorld = new Vector3() | ||||
| /** | |||||
| * See also cameraHelpers.glsl | |||||
| * @param material | |||||
| */ | |||||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | ||||
| material.uniforms.cameraPositionWorld?.value?.copy(this._positionWorld) | material.uniforms.cameraPositionWorld?.value?.copy(this._positionWorld) | ||||
| material.uniforms.cameraNearFar?.value?.set(this.near, this.far) | material.uniforms.cameraNearFar?.value?.set(this.near, this.far) |
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | import {AViewerPluginSync, ThreeViewer} from '../../viewer' | ||||
| import {IRenderTarget} from '../../rendering' | import {IRenderTarget} from '../../rendering' | ||||
| import {createDiv, createStyles, getOrCall, onChange, ValOrArr, ValOrFunc} from 'ts-browser-helpers' | import {createDiv, createStyles, getOrCall, onChange, ValOrArr, ValOrFunc} from 'ts-browser-helpers' | ||||
| import {SRGBColorSpace, Vector4, WebGLRenderTarget} from 'three' | |||||
| import {ShaderMaterial, 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' | import {ITexture} from '../../core' | ||||
| import {ExtendedCopyPass} from '../../postprocessing' | |||||
| export interface RenderTargetBlock { | export interface RenderTargetBlock { | ||||
| target: ValOrFunc<IRenderTarget|{texture?: ValOrArr<ITexture>}|undefined> | target: ValOrFunc<IRenderTarget|{texture?: ValOrArr<ITexture>}|undefined> | ||||
| transparent: boolean | transparent: boolean | ||||
| originalColorSpace: boolean | originalColorSpace: boolean | ||||
| div: HTMLDivElement | div: HTMLDivElement | ||||
| material?: ShaderMaterial // see ExtendedCopyPass | |||||
| } | } | ||||
| @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> { | ||||
| clear: !targetBlock.transparent, | clear: !targetBlock.transparent, | ||||
| respectColorSpace: !targetBlock.originalColorSpace, | 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), | ||||
| material: targetBlock.material, | |||||
| }) | }) | ||||
| this._viewer.renderManager.webglRenderer.outputColorSpace = outputColorSpace | this._viewer.renderManager.webglRenderer.outputColorSpace = outputColorSpace | ||||
| } | } | ||||
| } | } | ||||
| addTarget(target: RenderTargetBlock['target'], name: string, transparent = false, originalColorSpace = false, visible = true): this { | |||||
| /** | |||||
| * | |||||
| * @param target - render target or a function that returns a render target | |||||
| * @param name - name of the target | |||||
| * @param transparent - if true, the target will be rendered with transparency | |||||
| * @param originalColorSpace - if true, the target will be rendered in its original color space | |||||
| * @param visible - initial visibility | |||||
| * @param material - snippet for {@link ExtendedCopyPass} or a custom {@link ExtendedShaderMaterial} or three.js ShaderMaterial. Example to read just the red channel `(s)=>s + ' = vec4(' + s + '.r);'` | |||||
| */ | |||||
| addTarget(target: RenderTargetBlock['target'], name: string, transparent = false, originalColorSpace = false, visible = true, material?: ValOrFunc<string, [string]> | ShaderMaterial): 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: RenderTargetBlock = {target, name, transparent, div, originalColorSpace, visible} | |||||
| if (material) targetDef.material = (material as ShaderMaterial)?.isMaterial ? material as ShaderMaterial : new ExtendedCopyPass(material as any).material | |||||
| div.classList.add('RenderTargetPreviewPluginTarget') | div.classList.add('RenderTargetPreviewPluginTarget') | ||||
| if (!targetDef.visible) div.classList.add('RenderTargetPreviewPluginCollapsed') | if (!targetDef.visible) div.classList.add('RenderTargetPreviewPluginCollapsed') | ||||
| const header = document.createElement('div') | const header = document.createElement('div') | ||||
| if (!canvas) return this | if (!canvas) return this | ||||
| const blob = this._viewer.renderManager.exportRenderTarget(target as WebGLRenderTarget) | const blob = this._viewer.renderManager.exportRenderTarget(target as WebGLRenderTarget) | ||||
| const url = URL.createObjectURL(blob) | const url = URL.createObjectURL(blob) | ||||
| // todo use file transfer or viewer downloadBlob | |||||
| const link = document.createElement('a') | const link = document.createElement('a') | ||||
| document.body.appendChild(link) | document.body.appendChild(link) | ||||
| link.style.display = 'none' | link.style.display = 'none' |
| import {UniformsUtils} from 'three' | import {UniformsUtils} from 'three' | ||||
| import {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | import {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | ||||
| import {glsl} from 'ts-browser-helpers' | |||||
| import {getOrCall, glsl, ValOrFunc} from 'ts-browser-helpers' | |||||
| import {ExtendedShaderPass} from './ExtendedShaderPass' | import {ExtendedShaderPass} from './ExtendedShaderPass' | ||||
| export class ExtendedCopyPass extends ExtendedShaderPass { | export class ExtendedCopyPass extends ExtendedShaderPass { | ||||
| constructor() { | |||||
| constructor(snippet?: ValOrFunc<string, [string]>, respectColorSpace = true) { | |||||
| super({ | super({ | ||||
| uniforms: UniformsUtils.clone(CopyShader.uniforms), | uniforms: UniformsUtils.clone(CopyShader.uniforms), | ||||
| vertexShader: CopyShader.vertexShader, | vertexShader: CopyShader.vertexShader, | ||||
| #include <alphatest_pars_fragment> | #include <alphatest_pars_fragment> | ||||
| varying vec2 vUv; | varying vec2 vUv; | ||||
| void main() { | void main() { | ||||
| vec4 diffuseColor = tDiffuseTexelToLinear(texture2D(tDiffuse, vUv)) * opacity; | |||||
| ${respectColorSpace ? | |||||
| 'vec4 diffuseColor = tDiffuseTexelToLinear(texture2D(tDiffuse, vUv)) * opacity;' : | |||||
| 'vec4 diffuseColor = texture2D(tDiffuse, vUv) * opacity;'} | |||||
| #include <alphatest_fragment> | #include <alphatest_fragment> | ||||
| ${snippet ? getOrCall(snippet, 'diffuseColor') : ''} | |||||
| #ifdef OPAQUE | #ifdef OPAQUE | ||||
| diffuseColor.a = 1.0; | diffuseColor.a = 1.0; | ||||
| #endif | #endif | ||||
| gl_FragColor = diffuseColor; | gl_FragColor = diffuseColor; | ||||
| #include <encodings_fragment> | |||||
| ${respectColorSpace ? '#include <encodings_fragment>' : ''} | |||||
| } | } | ||||
| `, | `, | ||||
| }, 'tDiffuse') | }, 'tDiffuse') |
| }) | }) | ||||
| } | } | ||||
| /** | |||||
| * to be called from beforeRender or onObjectRender or similar. | |||||
| * @param updater | |||||
| */ | |||||
| updateShaderProperties(updater?: (IShaderPropertiesUpdater|undefined) | (IShaderPropertiesUpdater|undefined)[]) { | updateShaderProperties(updater?: (IShaderPropertiesUpdater|undefined) | (IShaderPropertiesUpdater|undefined)[]) { | ||||
| if (!updater) return | if (!updater) return | ||||
| if (!Array.isArray(updater)) updater = [updater] | if (!Array.isArray(updater)) updater = [updater] | ||||
| } | } | ||||
| setDirty() { | setDirty() { | ||||
| this.material.needsUpdate = true // do this when material defines etc are changed | |||||
| this.onDirty.forEach(v=>v()) | this.onDirty.forEach(v=>v()) | ||||
| } | } | ||||
| import {IDisposable, ValOrFunc} from 'ts-browser-helpers' | import {IDisposable, ValOrFunc} from 'ts-browser-helpers' | ||||
| import {IUniform} from 'three' | import {IUniform} from 'three' | ||||
| import {Pass} from 'three/examples/jsm/postprocessing/Pass.js' | import {Pass} from 'three/examples/jsm/postprocessing/Pass.js' | ||||
| import {IShaderPropertiesUpdater, MaterialExtension} from '../materials' | |||||
| import {MaterialExtension} from '../materials' | |||||
| import {ICamera, IRenderManager, IScene} from '../core' | import {ICamera, IRenderManager, IScene} from '../core' | ||||
| export type IPassID = 'render' | 'screen' | string | export type IPassID = 'render' | 'screen' | string | ||||
| export interface IPass<Tid extends IPassID = IPassID> extends Pass, IDisposable { | export interface IPass<Tid extends IPassID = IPassID> extends Pass, IDisposable { | ||||
| uniforms?: {[name: string]: IUniform} | uniforms?: {[name: string]: IUniform} | ||||
| updateShaderProperties?: (updater?: (IShaderPropertiesUpdater|undefined) | (IShaderPropertiesUpdater|undefined)[])=>void | |||||
| // todo? | |||||
| // updateShaderProperties?: (updater?: (IShaderPropertiesUpdater|undefined) | (IShaderPropertiesUpdater|undefined)[])=>void | |||||
| materialExtension?: MaterialExtension | materialExtension?: MaterialExtension | ||||
| /** | |||||
| * Checked by {@link RenderManager} to determine whether to render this frame. A frame is rendered if any pass is dirty. | |||||
| * This can be set by the plugin/pass to indicate when to continue rendering. See {@link ProgressivePlugin}. | |||||
| * This is different from {@link setDirty} which is implementation specific to the pass/plugin. It generally calls onDirty and set the viewer dirty. | |||||
| */ | |||||
| dirty?: ValOrFunc<boolean> // isDirty (optional) | dirty?: ValOrFunc<boolean> // isDirty (optional) | ||||
| /** | |||||
| * Set the pass as dirty. This is implementation specific to the pass/plugin. It generally calls all {@link onDirty} and set the viewer dirty. | |||||
| */ | |||||
| setDirty?(): void | setDirty?(): void | ||||
| onDirty?: (()=>void)[]; | onDirty?: (()=>void)[]; | ||||
| /** | |||||
| * Unique id for the pass. Used to determine the order of passes in the pipeline. | |||||
| */ | |||||
| passId?: Tid; | passId?: Tid; | ||||
| } | } | ||||
| import simpleCameraHelpers from './shaders/simpleCameraHelpers.glsl' | import simpleCameraHelpers from './shaders/simpleCameraHelpers.glsl' | ||||
| import cameraHelpers from './shaders/cameraHelpers.glsl' | |||||
| import randomHelpers from './shaders/randomHelpers.glsl' | import randomHelpers from './shaders/randomHelpers.glsl' | ||||
| import defaultVertex from './shaders/defaultVertex.glsl' | |||||
| import voronoiNoise from './shaders/voronoiNoise.glsl' | import voronoiNoise from './shaders/voronoiNoise.glsl' | ||||
| export const shaderUtils = { | export const shaderUtils = { | ||||
| simpleCameraHelpers, randomHelpers, voronoiNoise, | |||||
| simpleCameraHelpers, cameraHelpers, randomHelpers, defaultVertex, voronoiNoise, | |||||
| } | } |
| #ifndef BASIC_CAMERA_HELPERS | |||||
| #define BASIC_CAMERA_HELPERS | |||||
| // See also PerspectiveCamera2 | |||||
| uniform mat4 projection; | |||||
| uniform vec2 cameraNearFar; | |||||
| uniform vec3 cameraPositionWorld; | |||||
| #ifndef THREE_PACKING_INCLUDED | |||||
| #define THREE_PACKING_INCLUDED | |||||
| #include <packing> | |||||
| #endif | |||||
| float linstep(float edge0, float edge1, float value) { | |||||
| return clamp((value-edge0)/(edge1-edge0), 0.0, 1.0); | |||||
| } | |||||
| float depthToViewZ(const in float depth){ | |||||
| return (depth > 0.999) ? -cameraNearFar.y * 1000.0 : -mix(cameraNearFar.x, cameraNearFar.y, depth); | |||||
| } | |||||
| float viewZToDepth(const in float viewZ){ | |||||
| return linstep(-cameraNearFar.x, -cameraNearFar.y, viewZ); | |||||
| } | |||||
| vec4 viewToScreen3(const in vec3 pos) { | |||||
| vec4 projected = projection * vec4(pos, 1.0); | |||||
| projected.z = pos.z; | |||||
| // w is -viewZ | |||||
| projected.w = 1./projected.w; | |||||
| projected.xyz *= projected.w; | |||||
| projected.xy = 0.5 + 0.5 * projected.xy; | |||||
| return projected; | |||||
| } | |||||
| vec3 screenToView(const in vec2 uv, const in float viewZ) { | |||||
| vec2 uv_ = 2. * uv - 1.; | |||||
| float xe = -(uv_.x + projection[2][0]) * viewZ / projection[0][0]; | |||||
| float ye = -(uv_.y + projection[2][1]) * viewZ / projection[1][1]; | |||||
| return vec3(xe, ye, viewZ); | |||||
| } | |||||
| float viewZFromNDCZ(const in float depth) { | |||||
| #if PERSPECTIVE_CAMERA == 1 | |||||
| return perspectiveDepthToViewZ(depth, cameraNearFar.x, cameraNearFar.y); | |||||
| #else | |||||
| return orthographicDepthToViewZ(depth, cameraNearFar.x, cameraNearFar.y); | |||||
| #endif | |||||
| } | |||||
| #endif |
| vec4 projected = projectionMatrix * vec4(pos, 1.0); | vec4 projected = projectionMatrix * vec4(pos, 1.0); | ||||
| return vec3(0.5 + 0.5 * projected.xy / projected.w, projected.w); | return vec3(0.5 + 0.5 * projected.xy / projected.w, projected.w); | ||||
| } | } | ||||
| vec3 screenToView(const in vec2 uv, const in float viewZ) { | |||||
| vec2 uv_ = 2. * uv - 1.; | |||||
| float xe = -(uv_.x + projectionMatrix[2][0]) * viewZ / projectionMatrix[0][0]; | |||||
| float ye = -(uv_.y + projectionMatrix[2][1]) * viewZ / projectionMatrix[1][1]; | |||||
| return vec3(xe, ye, viewZ); | |||||
| } | |||||
| #endif | #endif |