Browse Source

multiple lights, per object materials

- multiple directional lights
- per-object materials (roughness & metalness)
- fixed reflectivity to 0.5
- update PhysicalMaterial from three.js to r134
Alexander Rose 3 years ago
parent
commit
b5ccdfdd53

+ 12 - 14
src/apps/docking-viewer/viewport.tsx

@@ -26,7 +26,6 @@ function shinyStyle(plugin: PluginContext) {
     return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
         renderer: {
             ...plugin.canvas3d!.props.renderer,
-            style: { name: 'plastic', params: {} },
         },
         postprocessing: {
             ...plugin.canvas3d!.props.postprocessing,
@@ -40,7 +39,6 @@ function occlusionStyle(plugin: PluginContext) {
     return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
         renderer: {
             ...plugin.canvas3d!.props.renderer,
-            style: { name: 'flat', params: {} }
         },
         postprocessing: {
             ...plugin.canvas3d!.props.postprocessing,
@@ -94,8 +92,8 @@ export const StructurePreset = StructureRepresentationPresetProvider({
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
-            polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, roughness: 0.2 }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
         };
 
         await update.commit({ revertOnError: true });
@@ -121,8 +119,8 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
-            polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
         };
 
         await update.commit({ revertOnError: true });
@@ -149,8 +147,8 @@ const SurfacePreset = StructureRepresentationPresetProvider({
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
-            polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, roughness: 0.2, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
         };
 
         await update.commit({ revertOnError: true });
@@ -177,8 +175,8 @@ const PocketPreset = StructureRepresentationPresetProvider({
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
-            surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, roughness: 0.2, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
         };
 
         await update.commit({ revertOnError: true });
@@ -206,10 +204,10 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
-            ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
-            interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
-            label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, roughness: 0.2, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
+            interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, roughness: 0.2 }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
+            label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, roughness: 0.2, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
         };
 
         await update.commit({ revertOnError: true });

+ 2 - 4
src/extensions/geo-export/controls.ts

@@ -4,7 +4,6 @@
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  */
 
-import { getStyle } from '../../mol-gl/renderer';
 import { Box3D } from '../../mol-math/geometry';
 import { PluginComponent } from '../../mol-plugin-state/component';
 import { PluginContext } from '../../mol-plugin/context';
@@ -46,13 +45,12 @@ export class GeometryControls extends PluginComponent {
                 const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
                 const filename = this.getFilename();
 
-                const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
                 const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
                 const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
                 let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
                 switch (this.behaviors.params.value.format) {
                     case 'glb':
-                        renderObjectExporter = new GlbExporter(style, boundingBox);
+                        renderObjectExporter = new GlbExporter(boundingBox);
                         break;
                     case 'obj':
                         renderObjectExporter = new ObjExporter(filename, boundingBox);
@@ -61,7 +59,7 @@ export class GeometryControls extends PluginComponent {
                         renderObjectExporter = new StlExporter(boundingBox);
                         break;
                     case 'usdz':
-                        renderObjectExporter = new UsdzExporter(style, boundingBox, boundingSphere.radius);
+                        renderObjectExporter = new UsdzExporter(boundingBox, boundingSphere.radius);
                         break;
                     default: throw new Error('Unsupported format.');
                 }

+ 25 - 10
src/extensions/geo-export/glb-exporter.ts

@@ -2,10 +2,10 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { BaseValues } from '../../mol-gl/renderable/schema';
-import { Style } from '../../mol-gl/renderer';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
 import { Box3D } from '../../mol-math/geometry';
@@ -38,6 +38,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
     readonly fileExtension = 'glb';
     private nodes: Record<string, any>[] = [];
     private meshes: Record<string, any>[] = [];
+    private materials: Record<string, any>[] = [];
+    private materialMap = new Map<string, number>();
     private accessors: Record<string, any>[] = [];
     private bufferViews: Record<string, any>[] = [];
     private binaryBuffer: ArrayBuffer[] = [];
@@ -157,6 +159,21 @@ export class GlbExporter extends MeshExporter<GlbData> {
         return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
     }
 
+    private addMaterial(metalness: number, roughness: number) {
+        const hash = `${metalness}|${roughness}`;
+        if (!this.materialMap.has(hash)) {
+            this.materialMap.set(hash, this.materials.length);
+            this.materials.push({
+                pbrMetallicRoughness: {
+                    baseColorFactor: [1, 1, 1, 1],
+                    metallicFactor: metalness,
+                    roughnessFactor: roughness
+                }
+            });
+        }
+        return this.materialMap.get(hash)!;
+    }
+
     protected async addMeshWithColors(input: AddMeshInput) {
         const { mesh, values, isGeoTexture, webgl, ctx } = input;
 
@@ -166,6 +183,10 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const dTransparency = values.dTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
+        const metalness = values.uMetalness.ref.value;
+        const roughness = values.uRoughness.ref.value;
+
+        const material = this.addMaterial(metalness, roughness);
 
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
@@ -214,7 +235,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
                             COLOR_0: colorAccessorIndex!
                         },
                         indices: indexAccessorIndex,
-                        material: 0
+                        material
                     }]
                 });
             }
@@ -248,13 +269,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
             }],
             bufferViews: this.bufferViews,
             accessors: this.accessors,
-            materials: [{
-                pbrMetallicRoughness: {
-                    baseColorFactor: [1, 1, 1, 1],
-                    metallicFactor: this.style.metalness,
-                    roughnessFactor: this.style.roughness
-                }
-            }]
+            materials: this.materials
         };
 
         const createChunk = (chunkType: number, data: ArrayBuffer[], byteLength: number, padChar: number): [ArrayBuffer[], number] => {
@@ -303,7 +318,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
         return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
     }
 
-    constructor(private style: Style, boundingBox: Box3D) {
+    constructor(boundingBox: Box3D) {
         super();
         const tmpV = Vec3();
         Vec3.add(tmpV, boundingBox.min, boundingBox.max);

+ 15 - 15
src/extensions/geo-export/usdz-exporter.ts

@@ -2,9 +2,9 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Style } from '../../mol-gl/renderer';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { Box3D } from '../../mol-math/geometry';
 import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
@@ -31,17 +31,16 @@ export class UsdzExporter extends MeshExporter<UsdzData> {
     readonly fileExtension = 'usdz';
     private meshes: string[] = [];
     private materials: string[] = [];
-    private materialSet = new Set<number>();
+    private materialMap = new Map<string, number>();
     private centerTransform: Mat4;
 
-    private static getMaterialKey(color: Color, alpha: number) {
-        return color * 256 + Math.round(alpha * 255);
-    }
+    private addMaterial(color: Color, alpha: number, metalness: number, roughness: number): number {
+        const hash = `${color}|${alpha}|${metalness}|${roughness}`;
+        if (this.materialMap.has(hash)) return this.materialMap.get(hash)!;
+
+        const materialKey = this.materialMap.size;
+        this.materialMap.set(hash, materialKey);
 
-    private addMaterial(color: Color, alpha: number) {
-        const materialKey = UsdzExporter.getMaterialKey(color, alpha);
-        if (this.materialSet.has(materialKey)) return;
-        this.materialSet.add(materialKey);
         const [r, g, b] = Color.toRgbNormalized(color);
         this.materials.push(`
 def Material "material${materialKey}"
@@ -52,12 +51,13 @@ def Material "material${materialKey}"
         uniform token info:id = "UsdPreviewSurface"
         color3f inputs:diffuseColor = (${r},${g},${b})
         float inputs:opacity = ${alpha}
-        float inputs:metallic = ${this.style.metalness}
-        float inputs:roughness = ${this.style.roughness}
+        float inputs:metallic = ${metalness}
+        float inputs:roughness = ${roughness}
         token outputs:surface
     }
 }
 `);
+        return materialKey;
     }
 
     protected async addMeshWithColors(input: AddMeshInput) {
@@ -75,6 +75,8 @@ def Material "material${materialKey}"
         const tTransparency = values.tTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
+        const metalness = values.uMetalness.ref.value;
+        const roughness = values.uRoughness.ref.value;
 
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
@@ -141,9 +143,7 @@ def Material "material${materialKey}"
                     alpha *= 1 - transparency;
                 }
 
-                this.addMaterial(color, alpha);
-
-                const materialKey = UsdzExporter.getMaterialKey(color, alpha);
+                const materialKey = this.addMaterial(color, alpha, metalness, roughness);
                 let faceIndices = faceIndicesByMaterial.get(materialKey);
                 if (faceIndices === undefined) {
                     faceIndices = [];
@@ -215,7 +215,7 @@ def Mesh "mesh${this.meshes.length}"
         return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
     }
 
-    constructor(private style: Style, boundingBox: Box3D, radius: number) {
+    constructor(boundingBox: Box3D, radius: number) {
         super();
         const t = Mat4();
         // scale the model so that it fits within 1 meter

+ 15 - 7
src/mol-geo/geometry/base.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -34,18 +34,21 @@ export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames);
 //
 
 export namespace BaseGeometry {
-    export const Params = {
-        alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
-        quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
-    };
-    export type Params = typeof Params
-
+    export const MaterialCategory: PD.Info = { category: 'Material' };
     export const ShadingCategory: PD.Info = { category: 'Shading' };
     export const CustomQualityParamInfo: PD.Info = {
         category: 'Custom Quality',
         hideIf: (params: PD.Values<Params>) => typeof params.quality !== 'undefined' && params.quality !== 'custom'
     };
 
+    export const Params = {
+        alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
+        quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
+        metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }, MaterialCategory),
+        roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }, MaterialCategory),
+    };
+    export type Params = typeof Params
+
     export type Counts = { drawCount: number, vertexCount: number, groupCount: number, instanceCount: number }
 
     export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {
@@ -65,11 +68,16 @@ export namespace BaseGeometry {
             uVertexCount: ValueCell.create(counts.vertexCount),
             uGroupCount: ValueCell.create(counts.groupCount),
             drawCount: ValueCell.create(counts.drawCount),
+            uMetalness: ValueCell.create(props.metalness),
+            uRoughness: ValueCell.create(props.roughness),
+            dLightCount: ValueCell.create(1),
         };
     }
 
     export function updateValues(values: BaseValues, props: PD.Values<Params>) {
         ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render
+        ValueCell.updateIfChanged(values.uMetalness, props.metalness);
+        ValueCell.updateIfChanged(values.uRoughness, props.roughness);
     }
 
     export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {

+ 5 - 0
src/mol-gl/_spec/renderer.spec.ts

@@ -73,6 +73,9 @@ function createPoints() {
         uGroupCount: ValueCell.create(3),
         uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere.ref.value)),
 
+        uMetalness: ValueCell.create(0.0),
+        uRoughness: ValueCell.create(1.0),
+
         alpha: ValueCell.create(1.0),
         drawCount: ValueCell.create(3),
         instanceCount: ValueCell.create(1),
@@ -86,6 +89,8 @@ function createPoints() {
         uSizeFactor: ValueCell.create(1),
         dPointSizeAttenuation: ValueCell.create(true),
         dPointStyle: ValueCell.create('square'),
+
+        dLightCount: ValueCell.create(1),
     };
     const state: RenderableState = {
         disposed: false,

+ 12 - 9
src/mol-gl/renderable/schema.ts

@@ -141,17 +141,10 @@ export const GlobalUniformSchema = {
     uClipObjectRotation: UniformSpec('v4[]'),
     uClipObjectScale: UniformSpec('v3[]'),
 
-    uLightDirection: UniformSpec('v3'),
-    uLightColor: UniformSpec('v3'),
+    uLightDirection: UniformSpec('v3[]'),
+    uLightColor: UniformSpec('v3[]'),
     uAmbientColor: UniformSpec('v3'),
 
-    // all the following could in principle be per object
-    // as a kind of 'material' parameter set
-    // would need to test performance implications
-    uMetalness: UniformSpec('f'),
-    uRoughness: UniformSpec('f'),
-    uReflectivity: UniformSpec('f'),
-
     uPickingAlphaThreshold: UniformSpec('f'),
 
     uInteriorDarkening: UniformSpec('f'),
@@ -249,12 +242,22 @@ export const ClippingSchema = {
 export type ClippingSchema = typeof ClippingSchema
 export type ClippingValues = Values<ClippingSchema>
 
+export const MaterialSchema = {
+    uMetalness: UniformSpec('f'),
+    uRoughness: UniformSpec('f'),
+} as const;
+export type MaterialSchema = typeof MaterialSchema
+export type MaterialValues = Values<MaterialSchema>
+
 export const BaseSchema = {
     ...ColorSchema,
     ...MarkerSchema,
     ...OverpaintSchema,
     ...TransparencySchema,
     ...ClippingSchema,
+    ...MaterialSchema,
+
+    dLightCount: DefineSpec('number'),
 
     aInstance: AttributeSpec('float32', 1, 1),
     /**

+ 48 - 101
src/mol-gl/renderer.ts

@@ -70,7 +70,6 @@ interface Renderer {
 export const RendererParams = {
     backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
 
-    // the following are general 'material' parameters
     pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
 
     interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
@@ -85,25 +84,19 @@ export const RendererParams = {
 
     xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
 
-    lightInclination: PD.Numeric(180, { min: 0, max: 180, step: 1 }),
-    lightAzimuth: PD.Numeric(0, { min: 0, max: 360, step: 1 }),
-    lightColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
+    light: PD.ObjectList({
+        inclination: PD.Numeric(180, { min: 0, max: 180, step: 1 }),
+        azimuth: PD.Numeric(0, { min: 0, max: 360, step: 1 }),
+        color: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
+        intensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
+    }, o => Color.toHexString(o.color), { defaultValue: [{
+        inclination: 180,
+        azimuth: 0,
+        color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
+        intensity: 0.6
+    }] }),
     ambientColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
-
-    style: PD.MappedStatic('matte', {
-        custom: PD.Group({
-            lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
-            ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
-            metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
-            roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
-            reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
-        }, { isExpanded: true }),
-        flat: PD.Group({}),
-        matte: PD.Group({}),
-        glossy: PD.Group({}),
-        metallic: PD.Group({}),
-        plastic: PD.Group({}),
-    }, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
+    ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
 
     clip: PD.Group({
         variant: PD.Select('instance', PD.arrayToOptions<Clipping.Variant>(['instance', 'pixel'])),
@@ -121,44 +114,27 @@ export const RendererParams = {
 };
 export type RendererProps = PD.Values<typeof RendererParams>
 
-export type Style = {
-    lightIntensity: number
-    ambientIntensity: number
-    metalness: number
-    roughness: number
-    reflectivity: number
+type Light = {
+    count: number
+    direction: number[]
+    color: number[]
 }
 
-export function getStyle(props: RendererProps['style']): Style {
-    switch (props.name) {
-        case 'custom':
-            return props.params as Style;
-        case 'flat':
-            return {
-                lightIntensity: 0, ambientIntensity: 1,
-                metalness: 0, roughness: 0.4, reflectivity: 0.5
-            };
-        case 'matte':
-            return {
-                lightIntensity: 0.7, ambientIntensity: 0.3,
-                metalness: 0, roughness: 1, reflectivity: 0.5
-            };
-        case 'glossy':
-            return {
-                lightIntensity: 0.7, ambientIntensity: 0.3,
-                metalness: 0, roughness: 0.4, reflectivity: 0.5
-            };
-        case 'metallic':
-            return {
-                lightIntensity: 0.7, ambientIntensity: 0.7,
-                metalness: 0.6, roughness: 0.6, reflectivity: 0.5
-            };
-        case 'plastic':
-            return {
-                lightIntensity: 0.7, ambientIntensity: 0.3,
-                metalness: 0, roughness: 0.2, reflectivity: 0.5
-            };
+const tmpDir = Vec3();
+const tmpColor = Vec3();
+function getLight(props: RendererProps['light'], light?: Light): Light {
+    const { direction, color } = light || {
+        direction: (new Array(5 * 3)).fill(0),
+        color: (new Array(5 * 3)).fill(0),
+    };
+    for (let i = 0, il = props.length; i < il; ++i) {
+        const p = props[i];
+        Vec3.directionFromSpherical(tmpDir, degToRad(p.inclination), degToRad(p.azimuth), 1);
+        Vec3.toArray(tmpDir, direction, i * 3);
+        Vec3.scale(tmpColor, Color.toVec3Normalized(tmpColor, p.color), p.intensity);
+        Vec3.toArray(tmpColor, color, i * 3);
     }
+    return { count: props.length, direction, color };
 }
 
 type Clip = {
@@ -200,7 +176,7 @@ namespace Renderer {
     export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
         const { gl, state, stats, extensions: { fragDepth } } = ctx;
         const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
-        const style = getStyle(p.style);
+        const light = getLight(p.light);
         const clip = getClip(p.clip);
 
         const viewport = Viewport();
@@ -225,13 +201,8 @@ namespace Renderer {
         const cameraDir = Vec3();
         const viewOffset = Vec2();
 
-        const lightDirection = Vec3();
-        Vec3.directionFromSpherical(lightDirection, degToRad(p.lightInclination), degToRad(p.lightAzimuth), 1);
-
-        const lightColor = Color.toVec3Normalized(Vec3(), p.lightColor);
-        Vec3.scale(lightColor, lightColor, style.lightIntensity);
-        const ambientColor = Color.toVec3Normalized(Vec3(), p.ambientColor);
-        Vec3.scale(ambientColor, ambientColor, style.ambientIntensity);
+        const ambientColor = Vec3();
+        Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
 
         const globalUniforms: GlobalUniformValues = {
             uModel: ValueCell.create(Mat4.identity()),
@@ -270,15 +241,10 @@ namespace Renderer {
             uClipObjectRotation: ValueCell.create(clip.objects.rotation),
             uClipObjectScale: ValueCell.create(clip.objects.scale),
 
-            uLightDirection: ValueCell.create(lightDirection),
-            uLightColor: ValueCell.create(lightColor),
+            uLightDirection: ValueCell.create(light.direction),
+            uLightColor: ValueCell.create(light.color),
             uAmbientColor: ValueCell.create(ambientColor),
 
-            // the following 3 are general 'material' uniforms
-            uMetalness: ValueCell.create(style.metalness),
-            uRoughness: ValueCell.create(style.roughness),
-            uReflectivity: ValueCell.create(style.reflectivity),
-
             uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
 
             uInteriorDarkening: ValueCell.create(p.interiorDarkening),
@@ -318,6 +284,10 @@ namespace Renderer {
                     definesNeedUpdate = true;
                 }
             }
+            if (r.values.dLightCount.ref.value !== light.count) {
+                ValueCell.update(r.values.dLightCount, light.count);
+                definesNeedUpdate = true;
+            }
             if (definesNeedUpdate) r.update();
 
             const program = r.getProgram(variant);
@@ -696,44 +666,21 @@ namespace Renderer {
                     ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
                 }
 
-                if (props.lightInclination !== undefined && props.lightInclination !== p.lightInclination) {
-                    p.lightInclination = props.lightInclination;
-                    Vec3.directionFromSpherical(lightDirection, degToRad(p.lightInclination), degToRad(p.lightAzimuth), 1);
-                    ValueCell.update(globalUniforms.uLightDirection, lightDirection);
-                }
-                if (props.lightAzimuth !== undefined && props.lightAzimuth !== p.lightAzimuth) {
-                    p.lightAzimuth = props.lightAzimuth;
-                    Vec3.directionFromSpherical(lightDirection, degToRad(p.lightInclination), degToRad(p.lightAzimuth), 1);
-                    ValueCell.update(globalUniforms.uLightDirection, lightDirection);
-                }
-
-                if (props.lightColor !== undefined && props.lightColor !== p.lightColor) {
-                    p.lightColor = props.lightColor;
-                    Color.toVec3Normalized(lightColor, p.lightColor);
-                    Vec3.scale(lightColor, lightColor, style.lightIntensity);
-                    ValueCell.update(globalUniforms.uLightColor, lightColor);
+                if (props.light !== undefined && !deepEqual(props.light, p.light)) {
+                    p.light = props.light;
+                    Object.assign(light, getLight(props.light, light));
+                    ValueCell.update(globalUniforms.uLightDirection, light.direction);
+                    ValueCell.update(globalUniforms.uLightColor, light.color);
                 }
                 if (props.ambientColor !== undefined && props.ambientColor !== p.ambientColor) {
                     p.ambientColor = props.ambientColor;
-                    Color.toVec3Normalized(ambientColor, p.ambientColor);
-                    Vec3.scale(ambientColor, ambientColor, style.ambientIntensity);
+                    Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
                     ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
                 }
-
-                if (props.style !== undefined) {
-                    p.style = props.style;
-                    Object.assign(style, getStyle(props.style));
-
-                    Color.toVec3Normalized(lightColor, p.lightColor);
-                    Vec3.scale(lightColor, lightColor, style.lightIntensity);
-                    ValueCell.update(globalUniforms.uLightColor, lightColor);
-                    Color.toVec3Normalized(ambientColor, p.ambientColor);
-                    Vec3.scale(ambientColor, ambientColor, style.ambientIntensity);
+                if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
+                    p.ambientIntensity = props.ambientIntensity;
+                    Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
                     ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
-
-                    ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness);
-                    ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness);
-                    ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity);
                 }
 
                 if (props.clip !== undefined && !deepEqual(props.clip, p.clip)) {

+ 1 - 0
src/mol-gl/shader-code.ts

@@ -134,6 +134,7 @@ function loopReplacer(match: string, start: string, end: string, snippet: string
 }
 
 function replaceCounts(str: string, defines: ShaderDefines) {
+    if (defines.dLightCount) str = str.replace(/dLightCount/g, `${defines.dLightCount.ref.value}`);
     if (defines.dClipObjectCount) str = str.replace(/dClipObjectCount/g, `${defines.dClipObjectCount.ref.value}`);
     return str;
 }

+ 26 - 13
src/mol-gl/shader/chunks/apply-light-color.glsl.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  *
  * adapted from three.js (https://github.com/mrdoob/three.js/)
- * which under the MIT License, Copyright © 2010-2019 three.js authors
+ * which under the MIT License, Copyright © 2010-2021 three.js authors
  */
 
 export const apply_light_color = `
@@ -14,21 +14,25 @@ export const apply_light_color = `
 // - vec3 normal
 // - float uMetalness
 // - float uRoughness
-// - float uReflectivity
-// - float uLightIntensity
-// - float uAmbientIntensity
+// - vec3 uLightColor
+// - vec3 uAmbientColor
 
 // outputs
 // - sets gl_FragColor
 
 vec4 color = material;
 
-ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0));
+ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
 
 PhysicalMaterial physicalMaterial;
 physicalMaterial.diffuseColor = color.rgb * (1.0 - uMetalness);
-physicalMaterial.specularRoughness = clamp(uRoughness, 0.04, 1.0);
-physicalMaterial.specularColor = mix(vec3(0.16 * pow2(uReflectivity)), color.rgb, uMetalness);
+vec3 dxy = max(abs(dFdx(normal)), abs(dFdy(normal)));
+float geometryRoughness = max(max(dxy.x, dxy.y), dxy.z);
+physicalMaterial.roughness = max(uRoughness, 0.0525);
+physicalMaterial.roughness += geometryRoughness;
+physicalMaterial.roughness = min(physicalMaterial.roughness, 1.0);
+physicalMaterial.specularColor = mix(vec3( 0.04 ), color.rgb, uMetalness);
+physicalMaterial.specularF90 = 1.0;
 
 GeometricContext geometry;
 geometry.position = -vViewPosition;
@@ -36,15 +40,24 @@ geometry.normal = normal;
 geometry.viewDir = normalize(vViewPosition);
 
 IncidentLight directLight;
-directLight.direction = uLightDirection;
-directLight.color = uLightColor;
+#pragma unroll_loop_start
+for (int i = 0; i < dLightCount; ++i) {
+    directLight.direction = normalize(uLightDirection[i]);
+    directLight.color = uLightColor[i] * PI; // * PI for punctual light
+    RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
+}
+#pragma unroll_loop_end
 
-RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
-
-vec3 irradiance = uAmbientColor * PI;
+vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
 RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
 
-vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular;
+// indirect specular only metals
+vec3 radiance = uAmbientColor * uMetalness;
+vec3 iblIrradiance = uAmbientColor * uMetalness;
+vec3 clearcoatRadiance = vec3(0.0);
+RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
+
+vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
 
 gl_FragColor = vec4(outgoingLight, color.a);
 

+ 62 - 36
src/mol-gl/shader/chunks/light-frag-params.glsl.ts

@@ -4,12 +4,12 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  *
  * adapted from three.js (https://github.com/mrdoob/three.js/)
- * which under the MIT License, Copyright © 2010-2019 three.js authors
+ * which under the MIT License, Copyright © 2010-2021 three.js authors
  */
 
 export const light_frag_params = `
-uniform vec3 uLightDirection;
-uniform vec3 uLightColor;
+uniform vec3 uLightDirection[dLightCount];
+uniform vec3 uLightColor[dLightCount];
 uniform vec3 uAmbientColor;
 
 uniform float uReflectivity;
@@ -18,8 +18,9 @@ uniform float uRoughness;
 
 struct PhysicalMaterial {
     vec3 diffuseColor;
-    float specularRoughness;
+    float roughness;
     vec3 specularColor;
+    float specularF90;
 };
 
 struct IncidentLight {
@@ -31,6 +32,7 @@ struct ReflectedLight {
     vec3 directDiffuse;
     vec3 directSpecular;
     vec3 indirectDiffuse;
+    vec3 indirectSpecular;
 };
 
 struct GeometricContext {
@@ -39,20 +41,23 @@ struct GeometricContext {
     vec3 viewDir;
 };
 
-vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {
+vec3 BRDF_Lambert(const in vec3 diffuseColor) {
+    return RECIPROCAL_PI * diffuseColor;
+}
+
+vec3 F_Schlick(const in vec3 f0, const in float f90, const in float dotVH) {
     // Original approximation by Christophe Schlick '94
-    // float fresnel = pow( 1.0 - dotLH, 5.0 );
+    // float fresnel = pow( 1.0 - dotVH, 5.0 );
     // Optimized variant (presented by Epic at SIGGRAPH '13)
     // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
-    float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
-    return (1.0 - specularColor) * fresnel + specularColor;
+    float fresnel = exp2((-5.55473 * dotVH - 6.98316) * dotVH);
+    return f0 * (1.0 - fresnel) + (f90 * fresnel);
 }
 
 // Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
 // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
-float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
+float V_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
     float a2 = pow2(alpha);
-    // dotNL and dotNV are explicitly swapped. This is not a mistake.
     float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));
     float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));
     return 0.5 / max(gv + gl, EPSILON);
@@ -67,47 +72,68 @@ float D_GGX(const in float alpha, const in float dotNH) {
     return RECIPROCAL_PI * a2 / pow2(denom);
 }
 
-vec3 BRDF_Diffuse_Lambert(const in vec3 diffuseColor) {
-    return RECIPROCAL_PI * diffuseColor;
-}
-
-// GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
-vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
+// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
+vec3 BRDF_GGX(const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in float f90, const in float roughness) {
     float alpha = pow2(roughness); // UE4's roughness
-    vec3 halfDir = normalize(incidentLight.direction + geometry.viewDir);
-
-    float dotNL = saturate(dot(geometry.normal, incidentLight.direction));
-    float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
-    float dotNH = saturate(dot(geometry.normal, halfDir));
-    float dotLH = saturate(dot(incidentLight.direction, halfDir));
-
-    vec3 F = F_Schlick(specularColor, dotLH);
-    float G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV);
+    vec3 halfDir = normalize( lightDir + viewDir);
+    float dotNL = saturate(dot(normal, lightDir));
+    float dotNV = saturate(dot(normal, viewDir));
+    float dotNH = saturate(dot(normal, halfDir));
+    float dotVH = saturate(dot(viewDir, halfDir));
+    vec3 F = F_Schlick(f0, f90, dotVH);
+    float V = V_GGX_SmithCorrelated(alpha, dotNL, dotNV);
     float D = D_GGX(alpha, dotNH);
-    return F * (G * D);
+    return F * (V * D);
 }
 
-// ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
-vec3 BRDF_Specular_GGX_Environment(const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
-    float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
+// Analytical approximation of the DFG LUT, one half of the
+// split-sum approximation used in indirect specular lighting.
+// via 'environmentBRDF' from "Physically Based Shading on Mobile"
+// https://www.unrealengine.com/blog/physically-based-shading-on-mobile
+vec2 DFGApprox(const in vec3 normal, const in vec3 viewDir, const in float roughness) {
+    float dotNV = saturate(dot(normal, viewDir));
     const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
     const vec4 c1 = vec4(1, 0.0425, 1.04, -0.04);
     vec4 r = roughness * c0 + c1;
     float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;
-    vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
-    return specularColor * AB.x + AB.y;
+    vec2 fab = vec2(-1.04, 1.04) * a004 + r.zw;
+    return fab;
+}
+
+// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
+// Approximates multiscattering in order to preserve energy.
+// http://www.jcgt.org/published/0008/01/03/
+void computeMultiscattering(const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter) {
+    vec2 fab = DFGApprox(normal, viewDir, roughness);
+    vec3 FssEss = specularColor * fab.x + specularF90 * fab.y;
+    float Ess = fab.x + fab.y;
+    float Ems = 1.0 - Ess;
+    vec3 Favg = specularColor + (1.0 - specularColor) * 0.047619; // 1/21
+    vec3 Fms = FssEss * Favg / (1.0 - Ems * Favg);
+    singleScatter += FssEss;
+    multiScatter += Fms * Ems;
 }
 
 void RE_Direct_Physical(const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
     float dotNL = saturate(dot(geometry.normal, directLight.direction));
     vec3 irradiance = dotNL * directLight.color;
-    irradiance *= PI; // punctual light
-
-    reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX(directLight, geometry, material.specularColor, material.specularRoughness);
-    reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
+    reflectedLight.directSpecular += irradiance * BRDF_GGX(directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness);
+    reflectedLight.directDiffuse += irradiance * BRDF_Lambert(material.diffuseColor);
 }
 
 void RE_IndirectDiffuse_Physical(const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
-    reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
+    reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert(material.diffuseColor);
+}
+
+void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
+    // Both indirect specular and indirect diffuse light accumulate here
+    vec3 singleScattering = vec3(0.0);
+    vec3 multiScattering = vec3(0.0);
+    vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;
+    computeMultiscattering(geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering);
+    vec3 diffuse = material.diffuseColor * (1.0 - ( singleScattering + multiScattering));
+    reflectedLight.indirectSpecular += radiance * singleScattering;
+    reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;
+    reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;
 }
 `;

+ 9 - 16
src/mol-plugin-ui/viewport/simple-settings.tsx

@@ -55,12 +55,9 @@ const SimpleSettingsParams = {
         color: PD.Color(Color(0xFCFBF9), { label: 'Background', description: 'Custom background color' }),
         transparent: PD.Boolean(false)
     }, { pivot: 'color' }),
-    lighting: PD.Group({
-        renderStyle: Canvas3DParams.renderer.params.style,
-        occlusion: Canvas3DParams.postprocessing.params.occlusion,
-        outline: Canvas3DParams.postprocessing.params.outline,
-        fog: Canvas3DParams.cameraFog,
-    }, { pivot: 'renderStyle' }),
+    occlusion: Canvas3DParams.postprocessing.params.occlusion,
+    outline: Canvas3DParams.postprocessing.params.outline,
+    fog: Canvas3DParams.cameraFog,
     clipping: PD.Group<any>({
         ...Canvas3DParams.cameraClipping.params,
         ...(Canvas3DParams.renderer.params.clip as any).params as any
@@ -104,12 +101,9 @@ const SimpleSettingsMapping = ParamMapping({
                 color: renderer.backgroundColor,
                 transparent: canvas.transparentBackground
             },
-            lighting: {
-                renderStyle: renderer.style,
-                occlusion: canvas.postprocessing.occlusion,
-                outline: canvas.postprocessing.outline,
-                fog: canvas.cameraFog
-            },
+            occlusion: canvas.postprocessing.occlusion,
+            outline: canvas.postprocessing.outline,
+            fog: canvas.cameraFog,
             clipping: {
                 ...canvas.cameraClipping,
                 ...canvas.renderer.clip
@@ -123,10 +117,9 @@ const SimpleSettingsMapping = ParamMapping({
         canvas.camera = s.camera;
         canvas.transparentBackground = s.background.transparent;
         canvas.renderer.backgroundColor = s.background.color;
-        canvas.renderer.style = s.lighting.renderStyle;
-        canvas.postprocessing.occlusion = s.lighting.occlusion;
-        canvas.postprocessing.outline = s.lighting.outline;
-        canvas.cameraFog = s.lighting.fog;
+        canvas.postprocessing.occlusion = s.occlusion;
+        canvas.postprocessing.outline = s.outline;
+        canvas.cameraFog = s.fog;
         canvas.cameraClipping = {
             radius: s.clipping.radius,
             far: s.clipping.far,