Skip to content
Snippets Groups Projects
Commit b5ccdfdd authored by Alexander Rose's avatar Alexander Rose
Browse files

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
parent d0c0d8e7
Branches
No related tags found
No related merge requests found
......@@ -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 });
......
......@@ -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.');
}
......
......@@ -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);
......
......@@ -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
......
/**
* 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 {
......
......@@ -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,
......
......@@ -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),
/**
......
......@@ -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' }),
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)) {
......
......@@ -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;
}
......
......@@ -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
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);
......
......@@ -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;
}
`;
\ No newline at end of file
......@@ -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' }),
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
},
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,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment