@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
126 lines (103 loc) • 5.72 kB
text/typescript
import { ShaderChunk, Texture, UniformsLib, Vector4 } from "three";
import { Context } from "./engine_setup.js";
import type { SourceIdentifier } from "./engine_types.js";
import { getParam } from "./engine_utils.js";
import { LightmapType } from "./extensions/NEEDLE_lightmaps.js";
const debugLightmap = getParam("debuglightmaps") ? true : false;
export interface ILightDataRegistry {
clear();
registerTexture(sourceId: SourceIdentifier, type: LightmapType, texture: Texture, index?: number);
tryGet(sourceId: SourceIdentifier | undefined, type: LightmapType, index: number): Texture | null;
tryGetLightmap(sourceId: SourceIdentifier | null | undefined, index: number): Texture | null;
tryGetSkybox(sourceId?: SourceIdentifier | null): Texture | null;
tryGetReflection(sourceId?: SourceIdentifier | null): Texture | null;
}
// how do we know which lightmap should be used for which object
// e.g. if we load in 3 gltf objects and all bring their own lightmaps
// we need a good way for e.g. Renderer components to safely get THEIR lightmap
// similarly how do we deal with a gltf bringing its own skybox
// e.g. it contains a camera and a skybox - the camera should be able to access this skybox if necessary
// and reflection data should be accessible as well
export class LightDataRegistry implements ILightDataRegistry {
private _context: Context;
private _lightmaps: Map<SourceIdentifier, Map<LightmapType, Texture[]>> = new Map();
clear() {
this._lightmaps.clear();
}
constructor(context: Context) {
this._context = context;
}
registerTexture(sourceId: SourceIdentifier, type: LightmapType, tex: Texture, index: number) {
if (debugLightmap) console.log("Registering ", LightmapType[type] + " \"" + sourceId + "\"", tex);
if (!this._lightmaps.has(sourceId))
this._lightmaps.set(sourceId, new Map());
const map = this._lightmaps.get(sourceId);
const arr = map?.get(type) ?? [];
if (arr.length < index) arr.length = index + 1;
arr[index] = tex;
map?.set(type, arr);
}
tryGetLightmap(sourceId: SourceIdentifier | null | undefined, index: number = 0): Texture | null {
return this.tryGet(sourceId, LightmapType.Lightmap, index);
}
tryGetSkybox(sourceId?: SourceIdentifier | null): Texture | null {
if (debugLightmap) console.log("[Get Skybox]", sourceId, this._lightmaps)
return this.tryGet(sourceId, LightmapType.Skybox, 0);
}
tryGetReflection(sourceId?: SourceIdentifier | null): Texture | null {
if (debugLightmap) console.log("[Get Reflection]", sourceId, this._lightmaps)
return this.tryGet(sourceId, LightmapType.Reflection, 0);
}
tryGet(sourceId: SourceIdentifier | undefined | null, type: LightmapType, index: number): Texture | null {
if (!sourceId) {
if (debugLightmap) console.warn("Missing source id");
return null;
}
const entry = this._lightmaps.get(sourceId);
if (!entry) {
if (debugLightmap) console.warn(`[Lighting] No ${LightmapType[type]} texture entry for`, sourceId);
return null;
}
const arr = entry.get(type);
if (arr === undefined) {
if (debugLightmap) console.warn(`[Lighting] No ${LightmapType[type]} texture for`, sourceId, "index", index);
return null;
}
if (!arr?.length || arr.length <= index) return null;
return arr[index];
}
}
// all the chunks we can patch
// console.log(ShaderChunk);
// Unity: ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw; ambientOrLightmapUV.zw = 0;
ShaderChunk.lights_fragment_maps = ShaderChunk.lights_fragment_maps.replace("vec4 lightMapTexel = texture2D( lightMap, vLightMapUv );", `
vec2 lUv = vLightMapUv.xy * lightmapScaleOffset.xy + vec2(lightmapScaleOffset.z, (1. - (lightmapScaleOffset.y + lightmapScaleOffset.w)));
vec4 lightMapTexel = texture2D( lightMap, lUv);
// The range of RGBM lightmaps goes from 0 to 34.49 (5^2.2) in linear space, and from 0 to 5 in gamma space.
lightMapTexel.rgb *= lightMapTexel.a * 8.; // no idea where that "8" comes from... heuristically derived
lightMapTexel.a = 1.;
lightMapTexel = conv_sRGBToLinear(lightMapTexel);
`);
ShaderChunk.lightmap_pars_fragment = `
#ifdef USE_LIGHTMAP
uniform sampler2D lightMap;
uniform float lightMapIntensity;
uniform vec4 lightmapScaleOffset;
// took from threejs 05fc79cd52b79e8c3e8dec1e7dca72c5c39983a4
vec4 conv_sRGBToLinear( in vec4 value ) {
return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );
}
#endif
`;
// When objects use Lightmaps in Unity, they already have IBL applied on the Lightmap,
// so they shouldn't receive light probe lighting.
// TODO: this gets difficult if there are additional real-time lightprobes added; we would need to exclude
// exactly those that were active when lighting was baked... that's complicated!
ShaderChunk.lights_fragment_begin = ShaderChunk.lights_fragment_begin.replace(
"irradiance += getLightProbeIrradiance( lightProbe, geometry.normal );", `
#if defined(USE_LIGHTMAP)
irradiance += 0.;
#else
irradiance += getLightProbeIrradiance( lightProbe, geometry.normal );
#endif`);
UniformsLib.lightmap["lightmapScaleOffset"] = { value: new Vector4(1, 1, 0, 0) };