Advance M8-M11 parity workflows
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
MeshPhysicalMaterial,
|
||||
Object3D,
|
||||
PCFShadowMap,
|
||||
PerspectiveCamera,
|
||||
PointLight,
|
||||
RectAreaLight,
|
||||
SRGBColorSpace,
|
||||
@@ -14,11 +15,14 @@ import {
|
||||
type Light,
|
||||
type WebGLRenderer,
|
||||
} from "../vendor/three/three.module.js";
|
||||
import type { LightIR, MaterialIR, SceneNodeIR } from "../../../protocol/scene-ir";
|
||||
import type { CameraIR, LightIR, MaterialIR, SceneNodeIR } from "../../../protocol/scene-ir";
|
||||
import { compileMaterialGraph, type ShaderCompileContext, type ShaderCompileReport } from "../../../protocol/shader-compiler";
|
||||
import { PBR_RENDER_BUDGETS } from "../../../protocol/render-budget";
|
||||
|
||||
export const PBR_PROFILE = "physical-v1";
|
||||
export const PBR_TONE_MAPPING = "aces";
|
||||
export const PBR_SHADOW_PROFILE = "pcf-1024";
|
||||
export const PBR_SHADOW_MAP_DIMENSION = PBR_RENDER_BUDGETS.THREE_WEBGL2.shadowMapDimension;
|
||||
|
||||
function clamp(value: number | undefined, minimum: number, maximum: number, fallback: number): number {
|
||||
return Number.isFinite(value) ? Math.min(maximum, Math.max(minimum, value as number)) : fallback;
|
||||
@@ -32,23 +36,36 @@ export function configurePBRRenderer(renderer: WebGLRenderer, exposure = 0): voi
|
||||
renderer.shadowMap.type = PCFShadowMap;
|
||||
}
|
||||
|
||||
export function createPBRMaterial(definition?: MaterialIR, active = false): MeshPhysicalMaterial {
|
||||
const baseColor = definition?.baseColor ?? (active ? [0.83, 0.48, 0.29, 1] : [0.55, 0.62, 0.69, 1]);
|
||||
const emission = definition?.emissionColor ?? [0, 0, 0, 1];
|
||||
const alpha = clamp(definition?.alpha ?? baseColor[3], 0, 1, 1);
|
||||
const transmission = clamp(definition?.transmissionWeight, 0, 1, 0);
|
||||
export function configurePBRCamera(camera: PerspectiveCamera, definition: CameraIR): void {
|
||||
const sensor = definition.sensorFit === 2 ? definition.sensorHeightMm : definition.sensorWidthMm;
|
||||
const fov = (2 * Math.atan((sensor / Math.max(0.001, definition.lensMm)) / 2) * 180) / Math.PI;
|
||||
camera.fov = definition.projection === "ORTHOGRAPHIC" ? 45 : fov;
|
||||
camera.near = Math.max(0.0001, definition.near);
|
||||
camera.far = Math.max(camera.near + 0.001, definition.far);
|
||||
camera.filmGauge = sensor;
|
||||
camera.filmOffset = definition.shift[0] * sensor;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
export function createPBRMaterial(definition?: MaterialIR, active = false, shaderContext: ShaderCompileContext = {}): MeshPhysicalMaterial {
|
||||
const compileReport = definition?.nodes?.length ? compileMaterialGraph(definition, shaderContext) : undefined;
|
||||
const compiled = compileReport?.status === "COMPILED" ? compileReport.material : undefined;
|
||||
const baseColor = compiled?.baseColor ?? definition?.baseColor ?? (active ? [0.83, 0.48, 0.29, 1] : [0.55, 0.62, 0.69, 1]);
|
||||
const emission = compiled?.emissionColor ?? definition?.emissionColor ?? [0, 0, 0, 1];
|
||||
const alpha = clamp(compiled?.alpha ?? definition?.alpha ?? baseColor[3], 0, 1, 1);
|
||||
const transmission = clamp(compiled?.transmissionWeight ?? definition?.transmissionWeight, 0, 1, 0);
|
||||
const material = new MeshPhysicalMaterial({
|
||||
color: new Color().setRGB(baseColor[0], baseColor[1], baseColor[2]),
|
||||
roughness: clamp(definition?.roughness, 0, 1, 0.45),
|
||||
metalness: clamp(definition?.metallic, 0, 1, 0.05),
|
||||
ior: clamp(definition?.ior, 1, 2.333, 1.45),
|
||||
roughness: clamp(compiled?.roughness ?? definition?.roughness, 0, 1, 0.45),
|
||||
metalness: clamp(compiled?.metallic ?? definition?.metallic, 0, 1, 0.05),
|
||||
ior: clamp(compiled?.ior ?? definition?.ior, 1, 2.333, 1.45),
|
||||
// Blender's neutral Specular IOR Level is 0.5; Three's neutral multiplier is 1.0.
|
||||
specularIntensity: clamp((definition?.specularIORLevel ?? 0.5) * 2, 0, 1, 1),
|
||||
clearcoat: clamp(definition?.coatWeight, 0, 1, 0),
|
||||
clearcoatRoughness: clamp(definition?.coatRoughness, 0, 1, 0.03),
|
||||
specularIntensity: clamp((compiled?.specularIORLevel ?? definition?.specularIORLevel ?? 0.5) * 2, 0, 1, 1),
|
||||
clearcoat: clamp(compiled?.coatWeight ?? definition?.coatWeight, 0, 1, 0),
|
||||
clearcoatRoughness: clamp(compiled?.coatRoughness ?? definition?.coatRoughness, 0, 1, 0.03),
|
||||
transmission,
|
||||
emissive: new Color().setRGB(emission[0], emission[1], emission[2]),
|
||||
emissiveIntensity: clamp(definition?.emissionStrength, 0, 1_000_000, 1),
|
||||
emissiveIntensity: clamp(compiled?.emissionStrength ?? definition?.emissionStrength, 0, 1_000_000, 1),
|
||||
opacity: alpha,
|
||||
transparent: alpha < 0.999,
|
||||
depthWrite: alpha >= 0.999,
|
||||
@@ -58,9 +75,47 @@ export function createPBRMaterial(definition?: MaterialIR, active = false): Mesh
|
||||
material.userData.baseEmissive = material.emissive.getHex();
|
||||
material.userData.baseEmissiveIntensity = material.emissiveIntensity;
|
||||
material.userData.pbrProfile = PBR_PROFILE;
|
||||
if (compileReport) {
|
||||
material.userData.shaderCompile = compileReport as ShaderCompileReport;
|
||||
material.userData.shaderCompileTextures = compileReport.status === "COMPILED" ? compileReport.textureBindings : [];
|
||||
}
|
||||
return material;
|
||||
}
|
||||
|
||||
export interface PBRMaterialPipelineUpdate {
|
||||
material: MeshPhysicalMaterial;
|
||||
report?: ShaderCompileReport;
|
||||
replaced: boolean;
|
||||
}
|
||||
|
||||
/** Keeps the last compiled material alive when a later graph fails closed. */
|
||||
export class PBRMaterialPipeline {
|
||||
private current: MeshPhysicalMaterial | null = null;
|
||||
|
||||
get material(): MeshPhysicalMaterial | null {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
update(definition?: MaterialIR, active = false, shaderContext: ShaderCompileContext = {}): PBRMaterialPipelineUpdate {
|
||||
const candidate = createPBRMaterial(definition, active, shaderContext);
|
||||
const report = candidate.userData.shaderCompile as ShaderCompileReport | undefined;
|
||||
if (report?.status === "BLOCKED" && this.current) {
|
||||
this.current.userData.shaderCompileFailure = report;
|
||||
candidate.dispose();
|
||||
return { material: this.current, report, replaced: false };
|
||||
}
|
||||
const previous = this.current;
|
||||
this.current = candidate;
|
||||
previous?.dispose();
|
||||
return { material: candidate, report, replaced: previous !== null };
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.current?.dispose();
|
||||
this.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setPBRMaterialSelected(material: MeshPhysicalMaterial, selected: boolean): void {
|
||||
const baseEmissive = typeof material.userData.baseEmissive === "number" ? material.userData.baseEmissive : 0;
|
||||
const baseIntensity = typeof material.userData.baseEmissiveIntensity === "number" ? material.userData.baseEmissiveIntensity : 1;
|
||||
@@ -108,7 +163,12 @@ export function createPBRLight(definition: LightIR): Light {
|
||||
return light;
|
||||
}
|
||||
|
||||
export function configurePBRLight(light: Light, node: SceneNodeIR, parent: Object3D): void {
|
||||
export function configurePBRLight(
|
||||
light: Light,
|
||||
node: SceneNodeIR,
|
||||
parent: Object3D,
|
||||
options: { shadowEnabled?: boolean; shadowMapDimension?: number } = {},
|
||||
): void {
|
||||
const [x, y, z] = node.transform.translation;
|
||||
light.position.set(x, z, -y);
|
||||
light.rotation.set(node.transform.rotationEuler[0], node.transform.rotationEuler[2], -node.transform.rotationEuler[1]);
|
||||
@@ -117,13 +177,15 @@ export function configurePBRLight(light: Light, node: SceneNodeIR, parent: Objec
|
||||
light.decay = 2;
|
||||
}
|
||||
light.userData.blenderCastsShadow = definitionCastsShadow(light);
|
||||
if ((light instanceof DirectionalLight || light instanceof SpotLight) && light.userData.blenderCastsShadow) {
|
||||
const shadowEnabled = options.shadowEnabled ?? light.userData.blenderCastsShadow;
|
||||
light.userData.pbrShadowBudgetBlocked = light.userData.blenderCastsShadow && !shadowEnabled;
|
||||
if ((light instanceof DirectionalLight || light instanceof SpotLight) && shadowEnabled) {
|
||||
const target = new Object3D();
|
||||
const forward = new Vector3(0, -1, 0).applyEuler(light.rotation);
|
||||
target.position.copy(light.position).add(forward);
|
||||
light.target = target;
|
||||
light.castShadow = true;
|
||||
light.shadow.mapSize.set(1024, 1024);
|
||||
light.shadow.mapSize.set(options.shadowMapDimension ?? PBR_SHADOW_MAP_DIMENSION, options.shadowMapDimension ?? PBR_SHADOW_MAP_DIMENSION);
|
||||
light.shadow.bias = -0.0005;
|
||||
light.shadow.normalBias = 0.03;
|
||||
light.shadow.camera.near = 0.05;
|
||||
@@ -136,9 +198,9 @@ export function configurePBRLight(light: Light, node: SceneNodeIR, parent: Objec
|
||||
}
|
||||
parent.add(target);
|
||||
}
|
||||
else if (light instanceof PointLight && light.userData.blenderCastsShadow) {
|
||||
else if (light instanceof PointLight && shadowEnabled) {
|
||||
light.castShadow = true;
|
||||
light.shadow.mapSize.set(1024, 1024);
|
||||
light.shadow.mapSize.set(options.shadowMapDimension ?? PBR_SHADOW_MAP_DIMENSION, options.shadowMapDimension ?? PBR_SHADOW_MAP_DIMENSION);
|
||||
light.shadow.bias = -0.0005;
|
||||
light.shadow.normalBias = 0.03;
|
||||
light.shadow.camera.near = 0.05;
|
||||
|
||||
Reference in New Issue
Block a user