Implement lightmap-based rendering for performant lighting illusion
Replace emissive-only rendering with diffuse + lightmap system to achieve realistic lighting appearance without dynamic light overhead. - Create LightmapGenerator class with canvas-based radial gradient generation - Generate one lightmap per color (16 total) using top-left directional light simulation - Cache lightmaps in static Map for reuse across all instances - Preload all lightmaps at toolbox initialization for instant availability - Update buildColor() to use diffuseColor + lightmapTexture instead of emissiveColor - Update buildMissingMaterial() to use lightmap-based rendering - Enable lighting calculations (disableLighting = false) to apply lightmaps Lightmap details: - 512x512 resolution RGBA textures - Radial gradient: center (color × 1.5), mid (base color), edge (color × 0.3) - Simulates top-left key light with smooth falloff - Total memory: ~16 MB for all lightmaps - Zero per-frame performance cost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
100c5e612c
commit
3f02fc7ea5
@ -18,6 +18,7 @@ import log from "loglevel";
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
import {xyztovec} from "./vectorConversion";
|
||||
import {AnimatedLineTexture} from "../../util/animatedLineTexture";
|
||||
import {LightmapGenerator} from "../../util/lightmapGenerator";
|
||||
|
||||
export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh {
|
||||
const logger = log.getLogger('buildMeshFromDiagramEntity');
|
||||
@ -190,11 +191,13 @@ export function buildMissingMaterial(name: string, scene: Scene, color: string):
|
||||
if (existingMaterial) {
|
||||
return (existingMaterial as StandardMaterial);
|
||||
}
|
||||
const colorObj = Color3.FromHexString(color);
|
||||
const newMaterial = new StandardMaterial(name, scene);
|
||||
newMaterial.id = name;
|
||||
newMaterial.emissiveColor = Color3.FromHexString(color);
|
||||
newMaterial.disableLighting = true;
|
||||
// newMaterial.diffuseColor = Color3.FromHexString(color);
|
||||
newMaterial.diffuseColor = colorObj;
|
||||
newMaterial.disableLighting = false;
|
||||
newMaterial.lightmapTexture = LightmapGenerator.generateLightmapForColor(colorObj, scene);
|
||||
newMaterial.useLightmapAsShadowmap = false;
|
||||
newMaterial.alpha = 1;
|
||||
return newMaterial;
|
||||
}
|
||||
@ -11,21 +11,17 @@ import {
|
||||
import {enumKeys} from "../../util/functions/enumKeys";
|
||||
import {ToolType} from "../types/toolType";
|
||||
import {buildTool} from "./buildTool";
|
||||
import {LightmapGenerator} from "../../util/lightmapGenerator";
|
||||
|
||||
export async function buildColor(color: Color3, scene: Scene, parent: TransformNode, index: number, toolMap: Map<string, AbstractMesh>): Promise<Node> {
|
||||
const width = .1;
|
||||
const height = .1;
|
||||
const material = new StandardMaterial("material-" + color.toHexString(), scene);
|
||||
material.emissiveColor = color;
|
||||
material.diffuseColor = color;
|
||||
material.disableLighting = true;
|
||||
// material.diffuseColor = color;
|
||||
// material.ambientColor = color;
|
||||
//material.roughness = 1;
|
||||
material.disableLighting = false;
|
||||
material.lightmapTexture = LightmapGenerator.generateLightmapForColor(color, scene);
|
||||
material.useLightmapAsShadowmap = false;
|
||||
material.specularPower = 64;
|
||||
// material.ambientColor = color;
|
||||
//material.roughness = .1;
|
||||
//material.maxSimultaneousLights = 2;
|
||||
|
||||
const colorBoxMesh = MeshBuilder.CreatePlane("toolbox-color-" + color.toHexString(), {
|
||||
width: width,
|
||||
|
||||
@ -4,6 +4,7 @@ import log from "loglevel";
|
||||
import {Handle} from "../objects/handle";
|
||||
import {DefaultScene} from "../defaultScene";
|
||||
import {Button} from "../objects/Button";
|
||||
import {LightmapGenerator} from "../util/lightmapGenerator";
|
||||
|
||||
const colors: string[] = [
|
||||
"#222222", "#8b4513", "#006400", "#778899",
|
||||
@ -27,6 +28,10 @@ export class Toolbox {
|
||||
this._handle = new Handle(this._toolboxBaseNode, 'Toolbox');
|
||||
this._toolboxBaseNode.position.y = .2;
|
||||
this._toolboxBaseNode.scaling = new Vector3(0.6, 0.6, 0.6);
|
||||
|
||||
// Preload lightmaps for all toolbox colors for better first-render performance
|
||||
LightmapGenerator.preloadLightmaps(colors, this._scene);
|
||||
|
||||
this.buildToolbox().then(() => {
|
||||
readyObservable.notifyObservers(true);
|
||||
this._logger.info('Toolbox built');
|
||||
|
||||
125
src/util/lightmapGenerator.ts
Normal file
125
src/util/lightmapGenerator.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import {Color3, DynamicTexture, Scene} from "@babylonjs/core";
|
||||
import {DefaultScene} from "../defaultScene";
|
||||
|
||||
export class LightmapGenerator {
|
||||
private static lightmapCache: Map<string, DynamicTexture> = new Map();
|
||||
private static readonly DEFAULT_RESOLUTION = 512;
|
||||
|
||||
/**
|
||||
* Generates or retrieves cached lightmap for a given color
|
||||
* @param color The base color for the lightmap
|
||||
* @param scene The BabylonJS scene
|
||||
* @param resolution Texture resolution (default: 512)
|
||||
* @returns DynamicTexture with baked lighting
|
||||
*/
|
||||
public static generateLightmapForColor(
|
||||
color: Color3,
|
||||
scene: Scene,
|
||||
resolution: number = LightmapGenerator.DEFAULT_RESOLUTION
|
||||
): DynamicTexture {
|
||||
const colorKey = color.toHexString();
|
||||
|
||||
// Return cached lightmap if available
|
||||
if (this.lightmapCache.has(colorKey)) {
|
||||
return this.lightmapCache.get(colorKey)!;
|
||||
}
|
||||
|
||||
// Create new lightmap
|
||||
const lightmap = this.createLightmap(color, scene, resolution);
|
||||
this.lightmapCache.set(colorKey, lightmap);
|
||||
return lightmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-generates lightmaps for all specified colors
|
||||
* Call during initialization for better first-render performance
|
||||
* @param colors Array of hex color strings
|
||||
* @param scene The BabylonJS scene
|
||||
*/
|
||||
public static preloadLightmaps(colors: string[], scene: Scene): void {
|
||||
colors.forEach(colorHex => {
|
||||
const color = Color3.FromHexString(colorHex);
|
||||
this.generateLightmapForColor(color, scene);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lightmap texture with simulated lighting
|
||||
* Uses radial gradient to simulate top-left directional light
|
||||
* @param color Base color
|
||||
* @param scene BabylonJS scene
|
||||
* @param resolution Texture size
|
||||
* @returns DynamicTexture with baked lighting gradient
|
||||
*/
|
||||
private static createLightmap(
|
||||
color: Color3,
|
||||
scene: Scene,
|
||||
resolution: number
|
||||
): DynamicTexture {
|
||||
const texture = new DynamicTexture(
|
||||
`lightmap-${color.toHexString()}`,
|
||||
resolution,
|
||||
scene,
|
||||
false // generateMipMaps
|
||||
);
|
||||
|
||||
const ctx = texture.getContext();
|
||||
const canvas = ctx.canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
|
||||
// Create radial gradient simulating directional light from top-left
|
||||
// Offset the gradient center to create directional effect
|
||||
const centerX = resolution * 0.4; // Offset left
|
||||
const centerY = resolution * 0.4; // Offset up
|
||||
const radius = resolution * 0.8; // Larger radius for smoother falloff
|
||||
|
||||
const gradient = canvas.createRadialGradient(
|
||||
centerX, centerY, 0,
|
||||
centerX, centerY, radius
|
||||
);
|
||||
|
||||
// Calculate lit and shadow colors
|
||||
// Lit area: 1.5x brighter than base color (clamped to 1.0)
|
||||
const litColor = new Color3(
|
||||
Math.min(color.r * 1.5, 1.0),
|
||||
Math.min(color.g * 1.5, 1.0),
|
||||
Math.min(color.b * 1.5, 1.0)
|
||||
);
|
||||
|
||||
// Shadow area: 0.3x darker than base color
|
||||
const shadowColor = color.scale(0.3);
|
||||
|
||||
// Mid-tone: base color unchanged
|
||||
const midColor = color;
|
||||
|
||||
// Build gradient with multiple stops for smoother transition
|
||||
gradient.addColorStop(0, litColor.toHexString()); // Center: bright
|
||||
gradient.addColorStop(0.5, midColor.toHexString()); // Mid: base color
|
||||
gradient.addColorStop(1.0, shadowColor.toHexString()); // Edge: dark
|
||||
|
||||
// Fill canvas with gradient
|
||||
canvas.fillStyle = gradient;
|
||||
canvas.fillRect(0, 0, resolution, resolution);
|
||||
|
||||
// Update texture with canvas content
|
||||
texture.update();
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the lightmap cache
|
||||
* Useful for memory management or when regenerating lightmaps
|
||||
*/
|
||||
public static clearCache(): void {
|
||||
this.lightmapCache.forEach(texture => texture.dispose());
|
||||
this.lightmapCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current cache size
|
||||
* @returns Number of cached lightmaps
|
||||
*/
|
||||
public static getCacheSize(): number {
|
||||
return this.lightmapCache.size;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user