Add Chromium-only Blender WebEngine parity work
This commit is contained in:
130
web/app/src/three-adapter/pbr.ts
Normal file
130
web/app/src/three-adapter/pbr.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
ACESFilmicToneMapping,
|
||||
Color,
|
||||
DirectionalLight,
|
||||
DoubleSide,
|
||||
MeshPhysicalMaterial,
|
||||
Object3D,
|
||||
PCFShadowMap,
|
||||
PointLight,
|
||||
RectAreaLight,
|
||||
SRGBColorSpace,
|
||||
SpotLight,
|
||||
Vector3,
|
||||
type Light,
|
||||
type WebGLRenderer,
|
||||
} from "../vendor/three/three.module.js";
|
||||
import type { LightIR, MaterialIR, SceneNodeIR } from "../../../protocol/scene-ir";
|
||||
|
||||
export const PBR_PROFILE = "physical-v1";
|
||||
export const PBR_TONE_MAPPING = "aces";
|
||||
export const PBR_SHADOW_PROFILE = "pcf-1024";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function configurePBRRenderer(renderer: WebGLRenderer, exposure = 0): void {
|
||||
renderer.outputColorSpace = SRGBColorSpace;
|
||||
renderer.toneMapping = ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 2 ** clamp(exposure, -8, 8, 0);
|
||||
renderer.shadowMap.enabled = true;
|
||||
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);
|
||||
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),
|
||||
// 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),
|
||||
transmission,
|
||||
emissive: new Color().setRGB(emission[0], emission[1], emission[2]),
|
||||
emissiveIntensity: clamp(definition?.emissionStrength, 0, 1_000_000, 1),
|
||||
opacity: alpha,
|
||||
transparent: alpha < 0.999,
|
||||
depthWrite: alpha >= 0.999,
|
||||
vertexColors: true,
|
||||
side: DoubleSide,
|
||||
});
|
||||
material.userData.baseEmissive = material.emissive.getHex();
|
||||
material.userData.baseEmissiveIntensity = material.emissiveIntensity;
|
||||
material.userData.pbrProfile = PBR_PROFILE;
|
||||
return material;
|
||||
}
|
||||
|
||||
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;
|
||||
material.emissive.set(selected ? 0x4a1f08 : baseEmissive);
|
||||
material.emissiveIntensity = selected ? Math.max(0.65, baseIntensity) : baseIntensity;
|
||||
}
|
||||
|
||||
export function blenderLightIntensity(definition: LightIR): number {
|
||||
return Math.max(0, definition.energy) * 2 ** clamp(definition.exposure, -20, 20, 0) / 10;
|
||||
}
|
||||
|
||||
export function createPBRLight(definition: LightIR): Light {
|
||||
const color = new Color().setRGB(...definition.color);
|
||||
const intensity = blenderLightIntensity(definition);
|
||||
const light = definition.lightType === 1 ? new DirectionalLight(color, intensity) :
|
||||
definition.lightType === 2 ? new SpotLight(color, intensity, 0, definition.spotAngle, definition.spotBlend, 2) :
|
||||
definition.lightType === 4 ? new RectAreaLight(color, intensity, definition.areaSize, definition.areaSizeY) :
|
||||
new PointLight(color, intensity, 0, 2);
|
||||
light.userData.blenderCastsShadowDefinition = definition.castsShadow ?? true;
|
||||
light.userData.blenderExposure = definition.exposure ?? 0;
|
||||
light.userData.blenderTemperature = definition.temperature ?? 6500;
|
||||
light.userData.blenderUsesTemperature = definition.useTemperature ?? false;
|
||||
return light;
|
||||
}
|
||||
|
||||
export function configurePBRLight(light: Light, node: SceneNodeIR, parent: Object3D): 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]);
|
||||
if (light instanceof PointLight) {
|
||||
light.distance = 0;
|
||||
light.decay = 2;
|
||||
}
|
||||
light.userData.blenderCastsShadow = definitionCastsShadow(light);
|
||||
if ((light instanceof DirectionalLight || light instanceof SpotLight) && light.userData.blenderCastsShadow) {
|
||||
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.bias = -0.0005;
|
||||
light.shadow.normalBias = 0.03;
|
||||
light.shadow.camera.near = 0.05;
|
||||
light.shadow.camera.far = 100;
|
||||
if (light instanceof DirectionalLight) {
|
||||
light.shadow.camera.left = -20;
|
||||
light.shadow.camera.right = 20;
|
||||
light.shadow.camera.top = 20;
|
||||
light.shadow.camera.bottom = -20;
|
||||
}
|
||||
parent.add(target);
|
||||
}
|
||||
else if (light instanceof PointLight && light.userData.blenderCastsShadow) {
|
||||
light.castShadow = true;
|
||||
light.shadow.mapSize.set(1024, 1024);
|
||||
light.shadow.bias = -0.0005;
|
||||
light.shadow.normalBias = 0.03;
|
||||
light.shadow.camera.near = 0.05;
|
||||
light.shadow.camera.far = 100;
|
||||
}
|
||||
}
|
||||
|
||||
function definitionCastsShadow(light: Light): boolean {
|
||||
return typeof light.userData.blenderCastsShadowDefinition === "boolean" ?
|
||||
light.userData.blenderCastsShadowDefinition : true;
|
||||
}
|
||||
Reference in New Issue
Block a user