diff --git a/packages/viewer-sandbox/assets/1.png b/packages/viewer-sandbox/assets/1.png new file mode 100644 index 000000000..5928ab33b Binary files /dev/null and b/packages/viewer-sandbox/assets/1.png differ diff --git a/packages/viewer-sandbox/assets/2.png b/packages/viewer-sandbox/assets/2.png new file mode 100644 index 000000000..03fa2e730 Binary files /dev/null and b/packages/viewer-sandbox/assets/2.png differ diff --git a/packages/viewer-sandbox/assets/3.png b/packages/viewer-sandbox/assets/3.png new file mode 100644 index 000000000..fe5c4ad24 Binary files /dev/null and b/packages/viewer-sandbox/assets/3.png differ diff --git a/packages/viewer-sandbox/assets/4.png b/packages/viewer-sandbox/assets/4.png new file mode 100644 index 000000000..21c5358e2 Binary files /dev/null and b/packages/viewer-sandbox/assets/4.png differ diff --git a/packages/viewer-sandbox/assets/5.png b/packages/viewer-sandbox/assets/5.png new file mode 100644 index 000000000..0bae00833 Binary files /dev/null and b/packages/viewer-sandbox/assets/5.png differ diff --git a/packages/viewer-sandbox/assets/6.png b/packages/viewer-sandbox/assets/6.png new file mode 100644 index 000000000..caa01ef15 Binary files /dev/null and b/packages/viewer-sandbox/assets/6.png differ diff --git a/packages/viewer-sandbox/assets/sample-hdri.png b/packages/viewer-sandbox/assets/sample-hdri.png new file mode 100755 index 000000000..5fdde5995 Binary files /dev/null and b/packages/viewer-sandbox/assets/sample-hdri.png differ diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index fb9ed0ec0..1e6a0838c 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { Box3, SectionTool, TreeNode } from '@speckle/viewer' +import { Box3, SectionTool, SpeckleStandardMaterial, TreeNode } from '@speckle/viewer' import { CanonicalView, DebugViewer, @@ -25,9 +25,18 @@ import { FilteringExtension } from '@speckle/viewer' import { MeasurementsExtension } from '@speckle/viewer' import { CameraController } from '@speckle/viewer' import { UpdateFlags } from '@speckle/viewer' -import { Viewer } from '@speckle/viewer' +import { Viewer, AssetType, Assets } from '@speckle/viewer' +import hdri0 from '../assets/sample-hdri.png' +import hdri1 from '../assets/1.png' +import hdri2 from '../assets/2.png' +import hdri3 from '../assets/3.png' +import hdri4 from '../assets/4.png' +import hdri5 from '../assets/5.png' +import hdri6 from '../assets/6.png' import { Euler, Vector3 } from 'three' +import { GeometryType } from '@speckle/viewer' +import { MeshBatch } from '@speckle/viewer' export default class Sandbox { private viewer: Viewer @@ -50,7 +59,9 @@ export default class Sandbox { worldOrigin: { x: 0, y: 0, z: 0 }, pixelThreshold: 0.5, exposure: 0.5, - tonemapping: 4 //'ACESFilmicToneMapping' + tonemapping: 4, //'ACESFilmicToneMapping', + contrast: 1, + saturation: 1 } public pipelineParams = { @@ -120,6 +131,11 @@ export default class Sandbox { precision: 2 } + public hdriParams = { + id: '/assets/2.png', + minRoughness: 0.5 + } + public constructor( container: HTMLElement, viewer: DebugViewer, @@ -145,8 +161,8 @@ export default class Sandbox { this.properties = [] viewer.on(ViewerEvent.LoadComplete, async (url: string) => { - this.addStreamControls(url) - this.addViewControls() + // this.addStreamControls(url) + // this.addViewControls() this.addBatches() this.properties = await this.viewer.getObjectProperties() this.batchesParams.totalBvhSize = this.getBVHSize() @@ -456,15 +472,6 @@ export default class Sandbox { }) screenshot.on('click', async () => { console.warn(await this.viewer.screenshot()) - // const start = performance.now() - // const nodes = this.viewer.getWorldTree().root.all( - // // eslint-disable-next-line @typescript-eslint/no-explicit-any - // (node: any) => node.model.raw.id === 'c35234a1e8584b159f7e8be59323cd64' - // ) - // console.log(nodes) - // this.viewer.cancelLoad( - // 'https://latest.speckle.dev/streams/97750296c2/objects/c3138e24a866d447eb86b2a8107b2c09' - // ) }) const rotate = this.tabs.pages[0].addButton({ @@ -486,7 +493,7 @@ export default class Sandbox { const canonicalViewsFolder = this.tabs.pages[0].addFolder({ title: 'Canonical Views', - expanded: true + expanded: false }) const sides = ['front', 'back', 'top', 'bottom', 'right', 'left', '3d'] for (let k = 0; k < sides.length; k++) { @@ -500,6 +507,130 @@ export default class Sandbox { .setCameraView(sides[k] as CanonicalView, true) }) } + + const hdriFolder = this.tabs.pages[0].addFolder({ title: 'HDRI', expanded: true }) + + hdriFolder + .addInput(this.hdriParams, 'id', { + label: 'HDRI', + options: { + Default: hdri0, + Mild: hdri1, + Mild2: hdri2, + Sharp: hdri4, + Bright: hdri6 + } + }) + .on('change', async (value) => { + this.viewer.getRenderer().indirectIBL = await Assets.getEnvironment( + { + id: this.hdriParams.id, + src: value.value, + type: AssetType.TEXTURE_EXR + }, + this.viewer.getRenderer().renderer + ) + this.viewer.requestRender() + }) + + hdriFolder + .addInput(this.sceneParams, 'exposure', { + min: 0, + max: 1 + }) + .on('change', () => { + this.viewer.getRenderer().renderer.toneMappingExposure = + this.sceneParams.exposure + this.viewer.requestRender() + }) + hdriFolder + .addInput(this.sceneParams, 'contrast', { + min: 0, + max: 2 + }) + .on('change', () => { + const batches = this.viewer + .getRenderer() + .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[] + batches.forEach((batch: MeshBatch) => { + const materials = batch.materials as SpeckleStandardMaterial[] + materials.forEach((material: SpeckleStandardMaterial) => { + material.userData.contrast.value = this.sceneParams.contrast + material.needsCopy = true + }) + }) + this.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS) + }) + hdriFolder + .addInput(this.sceneParams, 'saturation', { + min: 0, + max: 2 + }) + .on('change', () => { + const batches = this.viewer + .getRenderer() + .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[] + batches.forEach((batch: MeshBatch) => { + const materials = batch.materials as SpeckleStandardMaterial[] + materials.forEach((material: SpeckleStandardMaterial) => { + material.userData.saturation.value = this.sceneParams.saturation + material.needsCopy = true + }) + }) + this.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS) + }) + hdriFolder + .addInput(this.hdriParams, 'minRoughness', { + label: 'Shininess', + min: 0, + max: 1, + step: 0.05 + }) + .on('change', (value) => { + const batches = this.viewer + .getRenderer() + .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[] + batches.forEach((batch: MeshBatch) => { + const materials = batch.materials + materials.forEach((material: SpeckleStandardMaterial) => { + material.roughness = Math.min( + material.userData.originalRoughness, + 1 - this.hdriParams.minRoughness + ) + material.needsCopy = true + }) + }) + this.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS) + }) + } + + public async applyParams() { + this.viewer.getRenderer().indirectIBL = await Assets.getEnvironment( + { + id: 'Mild2', + src: hdri2, + type: AssetType.TEXTURE_EXR + }, + this.viewer.getRenderer().renderer + ) + this.viewer.getRenderer().renderer.toneMappingExposure = this.sceneParams.exposure + const batches = this.viewer + .getRenderer() + .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[] + batches.forEach((batch: MeshBatch) => { + const materials = batch.materials as SpeckleStandardMaterial[] + materials.forEach((material: SpeckleStandardMaterial) => { + material.userData.contrast.value = this.sceneParams.contrast + material.userData.saturation.value = this.sceneParams.saturation + material.userData.originalRoughness = material.roughness + material.roughness = Math.min( + material.userData.originalRoughness, + 1 - this.hdriParams.minRoughness + ) + material.needsCopy = true + }) + this.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS) + }) } makeSceneUI() { @@ -542,16 +673,16 @@ export default class Sandbox { expanded: true }) - postFolder - .addInput(this.sceneParams, 'exposure', { - min: 0, - max: 1 - }) - .on('change', () => { - this.viewer.getRenderer().renderer.toneMappingExposure = - this.sceneParams.exposure - this.viewer.requestRender() - }) + // postFolder + // .addInput(this.sceneParams, 'exposure', { + // min: 0, + // max: 1 + // }) + // .on('change', () => { + // this.viewer.getRenderer().renderer.toneMappingExposure = + // this.sceneParams.exposure + // this.viewer.requestRender() + // }) postFolder .addInput(this.sceneParams, 'tonemapping', { diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 263e233b4..7212906ca 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -111,6 +111,8 @@ const createViewer = async (containerName: string, stream: string) => { sandbox.makeMeasurementsUI() await sandbox.loadUrl(stream) + + await sandbox.applyParams() } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -119,7 +121,7 @@ const getStream = () => { // prettier-ignore // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) - // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' + 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.dev/streams/58b5648c4d/commits/60371ecb2d' // 'Super' heavy revit shit // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' @@ -248,7 +250,7 @@ const getStream = () => { // 'https://latest.speckle.dev/streams/f92e060177/commits/038a587267' // 'https://latest.speckle.dev/streams/3f895e614f/commits/8a3e424997' // 'https://latest.speckle.dev/streams/f92e060177/commits/f51ee777d5' - 'https://latest.speckle.dev/streams/f92e060177/commits/bbd821e3a1' + // 'https://latest.speckle.dev/streams/f92e060177/commits/bbd821e3a1' // Big curves // 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2' // 'https://speckle.xyz/streams/7ce9010d71/commits/afda4ffdf8' diff --git a/packages/viewer/src/index.ts b/packages/viewer/src/index.ts index 1088142ca..f41ec857c 100644 --- a/packages/viewer/src/index.ts +++ b/packages/viewer/src/index.ts @@ -1,5 +1,6 @@ import { Viewer } from './modules/Viewer' import { + AssetType, DefaultLightConfiguration, DefaultViewerParams, IViewer, @@ -66,6 +67,7 @@ import SpeckleStandardMaterial from './modules/materials/SpeckleStandardMaterial import SpeckleTextMaterial from './modules/materials/SpeckleTextMaterial' import { SpeckleText } from './modules/objects/SpeckleText' import { NodeRenderView } from './modules/tree/NodeRenderView' +import { Assets } from './modules/Assets' export { Viewer, @@ -105,7 +107,9 @@ export { SpeckleStandardMaterial, SpeckleTextMaterial, SpeckleText, - NodeRenderView + NodeRenderView, + Assets, + AssetType } export type { diff --git a/packages/viewer/src/modules/Assets.ts b/packages/viewer/src/modules/Assets.ts index d2ad4605e..ac4f483c3 100644 --- a/packages/viewer/src/modules/Assets.ts +++ b/packages/viewer/src/modules/Assets.ts @@ -1,16 +1,18 @@ import { Texture, - PMREMGenerator, WebGLRenderer, TextureLoader, Color, - DataTexture + DataTexture, + Matrix4, + Euler } from 'three' import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js' import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js' import { FontLoader, Font } from 'three/examples/jsm/loaders/FontLoader.js' import { Asset, AssetType } from '../IViewer' import Logger from 'js-logger' +import { RotatablePMREMGenerator } from './objects/RotatablePMREMGenerator' export class Assets { private static _cache: { [name: string]: Texture | Font } = {} @@ -32,8 +34,11 @@ export class Assets { } private static hdriToPMREM(renderer: WebGLRenderer, hdriTex: Texture): Texture { - const generator = new PMREMGenerator(renderer) - generator.compileEquirectangularShader() + const generator = new RotatablePMREMGenerator(renderer) + const mat = new Matrix4().makeRotationFromEuler( + new Euler(-Math.PI * 0.5, 0, -Math.PI * 0.5) + ) + generator.compileProperEquirectShader(mat) const pmremRT = generator.fromEquirectangular(hdriTex) generator.dispose() return pmremRT.texture diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index f0254221a..2cbcbb529 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -41,7 +41,7 @@ export class Viewer extends EventEmitter implements IViewer { /** Viewer components */ protected tree: WorldTree = new WorldTree() protected world: World = new World() - public static Assets: Assets + public static readonly theAssets: Assets = new Assets() public speckleRenderer: SpeckleRenderer protected propertyManager: PropertyManager @@ -143,7 +143,6 @@ export class Viewer extends EventEmitter implements IViewer { this.speckleRenderer.create(this.container) window.addEventListener('resize', this.resize.bind(this), false) - new Assets() this.propertyManager = new PropertyManager() this.frame() diff --git a/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts b/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts index 132a5b26a..647c25348 100644 --- a/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts +++ b/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts @@ -29,7 +29,9 @@ class SpeckleStandardMaterial extends ExtendedMeshStandardMaterial { uShadowViewer_low: new Vector3(), uTransforms: [new Matrix4()], tTransforms: null, - objCount: 1 + objCount: 1, + contrast: 1, + saturation: 1 } } diff --git a/packages/viewer/src/modules/materials/shaders/speckle-standard-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-standard-frag.ts index 1eb17e293..8b5558f8c 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-standard-frag.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-standard-frag.ts @@ -48,6 +48,62 @@ uniform float opacity; #endif varying vec3 vViewPosition; +#define CUSTOM_TONEMAPPING + +#ifdef CUSTOM_TONEMAPPING + + uniform float contrast; + uniform float saturation; + uniform float whitescale; + vec3 EvalLogContrastFunc(vec3 col, float eps, float logMidpoint, float contrastFactor) + { + vec3 x = max(vec3(0.), col); + vec3 logX = log2(x+vec3(eps)); + vec3 adjX = vec3(logMidpoint) + (logX - vec3(logMidpoint)) * contrastFactor; + vec3 ret = max(vec3(0.0), exp2(adjX) - vec3(eps)); + return ret; + } + + vec3 evalSaturation(vec3 rgbVal, float saturationFactor){ + vec3 lumaWeights = vec3(.25,.50,.25); + vec3 grey = vec3(dot(lumaWeights,rgbVal)); + return grey + saturationFactor*(rgbVal-grey); + } + + vec3 evalExposure(vec3 rgbVal, float exposureFactor){ + return rgbVal * exp2(exposureFactor); + } + + vec3 filmicTonemap(vec3 x) { + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + float W = 11.2; + return ((x*(A*x+C*B)+D*E) / (x*(A*x+B)+D*F))- E / F; + } + + + vec3 applyFilmicToneMap( vec3 color) + { + color = 2.0 * filmicTonemap( color); + vec3 whiteScale = 1.0 / filmicTonemap(vec3(11.2)); + color *= whiteScale; + return color; + } + + vec3 postProcess(in vec3 _color, float exposureFactor, float contrastFactor, float saturationFactor){ + vec3 color = _color; + + // color.rgb *= exposureFactor; + color.rgb = evalSaturation(color.rgb, saturationFactor); + color = EvalLogContrastFunc(color, 0.0001, 0.18, contrastFactor); + color.rgb = ACESFilmicToneMapping( color );//applyFilmicToneMap(color.rgb); + return color; + } +#endif #include #include @@ -137,7 +193,14 @@ void main() { #endif #include - #include + // #include // COMMENTED OUT + #ifdef TONE_MAPPING + #ifdef CUSTOM_TONEMAPPING + gl_FragColor.rgb = postProcess(gl_FragColor.rgb, toneMappingExposure, contrast, saturation); + #else + gl_FragColor.rgb = toneMapping( gl_FragColor.rgb ); + #endif + #endif #include #include #include diff --git a/packages/viewer/src/modules/objects/RotatablePMREMGenerator.ts b/packages/viewer/src/modules/objects/RotatablePMREMGenerator.ts new file mode 100644 index 000000000..9a11f1204 --- /dev/null +++ b/packages/viewer/src/modules/objects/RotatablePMREMGenerator.ts @@ -0,0 +1,80 @@ +import { Matrix4, NoBlending, PMREMGenerator, ShaderMaterial } from 'three' + +export class RotatablePMREMGenerator extends PMREMGenerator { + constructor(renderer) { + super(renderer) + } + + public compileProperEquirectShader(rotationMatrix?: Matrix4) { + const fixedEnvFlip = function _getEquirectMaterial() { + return new ShaderMaterial({ + name: 'EquirectangularToCubeUV', + + uniforms: { + envMap: { value: null }, + rotationMatrix: { value: rotationMatrix } + }, + + vertexShader: ` + precision mediump float; + precision mediump int; + attribute float faceIndex; + varying vec3 vOutputDirection; + // RH coordinate system; PMREM face-indexing convention + vec3 getDirection( vec2 uv, float face ) { + uv = 2.0 * uv - 1.0; + vec3 direction = vec3( uv, 1.0 ); + if ( face == 0.0 ) { + direction = direction.zyx; // ( 1, v, u ) pos x + } else if ( face == 1.0 ) { + direction = direction.xzy; + direction.xz *= -1.0; // ( -u, 1, -v ) pos y + } else if ( face == 2.0 ) { + direction.x *= -1.0; // ( -u, v, 1 ) pos z + } else if ( face == 3.0 ) { + direction = direction.zyx; + direction.xz *= -1.0; // ( -1, v, -u ) neg x + } else if ( face == 4.0 ) { + direction = direction.xzy; + direction.xy *= -1.0; // ( -u, -1, v ) neg y + } else if ( face == 5.0 ) { + direction.z *= -1.0; // ( u, v, -1 ) neg z + } + return direction; + } + void main() { + vOutputDirection = getDirection( uv, faceIndex ); + gl_Position = vec4( position, 1.0 ); + } + `, + + fragmentShader: /* glsl */ ` + precision mediump float; + precision mediump int; + varying vec3 vOutputDirection; + uniform sampler2D envMap; + uniform mat4 rotationMatrix; + #include + void main() { + + vec3 outputDirection = normalize( vOutputDirection ); + outputDirection = normalize((rotationMatrix * vec4(vOutputDirection, 0.)).xyz); + vec2 uv = equirectUv( outputDirection ); + gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); + } + `, + + blending: NoBlending, + depthTest: false, + depthWrite: false + }) + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this._equirectMaterial = fixedEnvFlip() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this._compileMaterial(this._equirectMaterial) + } +}