| @@ -410,6 +410,11 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| // for shader prop updater | |||
| private _positionWorld = new Vector3() | |||
| /** | |||
| * See also cameraHelpers.glsl | |||
| * @param material | |||
| */ | |||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | |||
| material.uniforms.cameraPositionWorld?.value?.copy(this._positionWorld) | |||
| material.uniforms.cameraNearFar?.value?.set(this.near, this.far) | |||
| @@ -1,11 +1,12 @@ | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {IRenderTarget} from '../../rendering' | |||
| 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 {CustomContextMenu} from '../../utils' | |||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | |||
| import {ITexture} from '../../core' | |||
| import {ExtendedCopyPass} from '../../postprocessing' | |||
| export interface RenderTargetBlock { | |||
| target: ValOrFunc<IRenderTarget|{texture?: ValOrArr<ITexture>}|undefined> | |||
| @@ -14,6 +15,7 @@ export interface RenderTargetBlock { | |||
| transparent: boolean | |||
| originalColorSpace: boolean | |||
| div: HTMLDivElement | |||
| material?: ShaderMaterial // see ExtendedCopyPass | |||
| } | |||
| @uiFolderContainer('Render Target Preview Plugin') | |||
| export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPluginSync<TEvent> { | |||
| @@ -76,15 +78,27 @@ export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPlu | |||
| clear: !targetBlock.transparent, | |||
| respectColorSpace: !targetBlock.originalColorSpace, | |||
| viewport: new Vector4(rect.x, rect.y, rect.width, rect.height), | |||
| material: targetBlock.material, | |||
| }) | |||
| 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 | |||
| 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') | |||
| if (!targetDef.visible) div.classList.add('RenderTargetPreviewPluginCollapsed') | |||
| const header = document.createElement('div') | |||
| @@ -136,6 +150,7 @@ export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPlu | |||
| if (!canvas) return this | |||
| const blob = this._viewer.renderManager.exportRenderTarget(target as WebGLRenderTarget) | |||
| const url = URL.createObjectURL(blob) | |||
| // todo use file transfer or viewer downloadBlob | |||
| const link = document.createElement('a') | |||
| document.body.appendChild(link) | |||
| link.style.display = 'none' | |||
| @@ -1,10 +1,10 @@ | |||
| import {UniformsUtils} from 'three' | |||
| 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' | |||
| export class ExtendedCopyPass extends ExtendedShaderPass { | |||
| constructor() { | |||
| constructor(snippet?: ValOrFunc<string, [string]>, respectColorSpace = true) { | |||
| super({ | |||
| uniforms: UniformsUtils.clone(CopyShader.uniforms), | |||
| vertexShader: CopyShader.vertexShader, | |||
| @@ -13,13 +13,16 @@ export class ExtendedCopyPass extends ExtendedShaderPass { | |||
| #include <alphatest_pars_fragment> | |||
| varying vec2 vUv; | |||
| 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> | |||
| ${snippet ? getOrCall(snippet, 'diffuseColor') : ''} | |||
| #ifdef OPAQUE | |||
| diffuseColor.a = 1.0; | |||
| #endif | |||
| gl_FragColor = diffuseColor; | |||
| #include <encodings_fragment> | |||
| ${respectColorSpace ? '#include <encodings_fragment>' : ''} | |||
| } | |||
| `, | |||
| }, 'tDiffuse') | |||
| @@ -33,6 +33,10 @@ export class ExtendedShaderPass extends ShaderPass implements IPass { | |||
| }) | |||
| } | |||
| /** | |||
| * to be called from beforeRender or onObjectRender or similar. | |||
| * @param updater | |||
| */ | |||
| updateShaderProperties(updater?: (IShaderPropertiesUpdater|undefined) | (IShaderPropertiesUpdater|undefined)[]) { | |||
| if (!updater) return | |||
| if (!Array.isArray(updater)) updater = [updater] | |||
| @@ -47,6 +51,7 @@ export class ExtendedShaderPass extends ShaderPass implements IPass { | |||
| } | |||
| setDirty() { | |||
| this.material.needsUpdate = true // do this when material defines etc are changed | |||
| this.onDirty.forEach(v=>v()) | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| import {IDisposable, ValOrFunc} from 'ts-browser-helpers' | |||
| import {IUniform} from 'three' | |||
| import {Pass} from 'three/examples/jsm/postprocessing/Pass.js' | |||
| import {IShaderPropertiesUpdater, MaterialExtension} from '../materials' | |||
| import {MaterialExtension} from '../materials' | |||
| import {ICamera, IRenderManager, IScene} from '../core' | |||
| export type IPassID = 'render' | 'screen' | string | |||
| @@ -9,13 +9,27 @@ export type IPassID = 'render' | 'screen' | string | |||
| export interface IPass<Tid extends IPassID = IPassID> extends Pass, IDisposable { | |||
| uniforms?: {[name: string]: IUniform} | |||
| updateShaderProperties?: (updater?: (IShaderPropertiesUpdater|undefined) | (IShaderPropertiesUpdater|undefined)[])=>void | |||
| // todo? | |||
| // updateShaderProperties?: (updater?: (IShaderPropertiesUpdater|undefined) | (IShaderPropertiesUpdater|undefined)[])=>void | |||
| 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) | |||
| /** | |||
| * 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 | |||
| onDirty?: (()=>void)[]; | |||
| /** | |||
| * Unique id for the pass. Used to determine the order of passes in the pipeline. | |||
| */ | |||
| passId?: Tid; | |||
| } | |||
| @@ -1,7 +1,9 @@ | |||
| import simpleCameraHelpers from './shaders/simpleCameraHelpers.glsl' | |||
| import cameraHelpers from './shaders/cameraHelpers.glsl' | |||
| import randomHelpers from './shaders/randomHelpers.glsl' | |||
| import defaultVertex from './shaders/defaultVertex.glsl' | |||
| import voronoiNoise from './shaders/voronoiNoise.glsl' | |||
| export const shaderUtils = { | |||
| simpleCameraHelpers, randomHelpers, voronoiNoise, | |||
| simpleCameraHelpers, cameraHelpers, randomHelpers, defaultVertex, voronoiNoise, | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| #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 | |||
| @@ -7,4 +7,10 @@ vec3 viewToScreen(const in vec3 pos) { | |||
| vec4 projected = projectionMatrix * vec4(pos, 1.0); | |||
| 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 | |||