AEC Aesthetics (#2225)

* Addede a bunch of abstract hdris as well as the option to cycle cle though them at runtime

* Added an extended PMREMGenerator that allows for the rotation of the hdri image.Now our IBL sources are finally straight not tilded 90 deg anymore. Added artificial shininess to all objects configurable in real time from the sandbox.

* Added color grading operations to our standard shader and material. Updated the sandbox to reflect the required AEC Aesthetics properties
This commit is contained in:
Alexandru Popovici
2024-04-23 09:56:05 +03:00
committed by GitHub
parent c64c15a55c
commit 1010c9d23a
15 changed files with 322 additions and 36 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+156 -25
View File
@@ -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', {
+4 -2
View File
@@ -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'
+5 -1
View File
@@ -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 {
+9 -4
View File
@@ -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
+1 -2
View File
@@ -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()
@@ -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
}
}
@@ -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 <common>
#include <packing>
@@ -137,7 +193,14 @@ void main() {
#endif
#include <output_fragment>
#include <tonemapping_fragment>
// #include <tonemapping_fragment> // 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 <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
@@ -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 <common>
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)
}
}