@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.
179 lines (176 loc) • 7.44 kB
JavaScript
import { NEEDLE_progressive } from "@needle-tools/gltf-progressive";
import { Group, Mesh, ShaderMaterial, Vector2, Vector4 } from "three";
import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
import { getParam } from "../engine/engine_utils.js";
const debug = getParam("debuglightmaps");
const $lightmapKey = Symbol("lightmapKey");
/**
* This component is automatically added by the {@link Renderer} component if the object has lightmap uvs AND we have a lightmap.
*
* @category Rendering
* @group Components
*/
export class RendererLightmap {
get lightmap() {
return this.lightmapTexture;
}
set lightmap(tex) {
if (tex !== this.lightmapTexture) {
this.lightmapTexture = tex;
this.applyLightmap();
this.updatePropertyBlockTexture();
if (this.lightmapTexture) {
NEEDLE_progressive.assignTextureLOD(this.lightmapTexture, 0).then((res) => {
if (res?.isTexture) {
this.lightmapTexture = res;
this.updatePropertyBlockTexture();
}
});
}
}
}
lightmapIndex = -1;
lightmapScaleOffset = new Vector4(1, 1, 0, 0);
renderer;
_isApplied = false;
get context() { return this.renderer.context; }
get gameObject() { return this.renderer.gameObject; }
lightmapTexture = null;
constructor(renderer) {
this.renderer = renderer;
}
init(lightmapIndex, lightmapScaleOffset, lightmapTexture) {
console.assert(this.gameObject !== undefined && this.gameObject !== null, "Missing gameobject", this);
this.lightmapIndex = lightmapIndex;
if (this.lightmapIndex < 0)
return;
this.lightmapScaleOffset = lightmapScaleOffset;
this.lightmapTexture = lightmapTexture;
NEEDLE_progressive.assignTextureLOD(lightmapTexture, 0).then((res) => {
if (res?.isTexture) {
this.lightmapTexture = res;
this.updatePropertyBlockTexture();
}
});
if (debug == "show") {
console.log("Lightmap:", this.gameObject.name, lightmapIndex, "\nScaleOffset:", lightmapScaleOffset, "\nTexture:", lightmapTexture);
this.setLightmapDebugMaterial();
}
else if (debug)
console.log("Use debuglightmaps=show to render lightmaps only in the scene.");
this.applyLightmap();
}
updateLightmapUniforms(_material) {
}
/**
* Apply the lightmap to the object using MaterialPropertyBlock instead of cloning materials.
* The lightmap texture and its per-object UV transform are set as overrides via PropertyBlock.
* Three.js reads material.lightMap to determine shader defines and upload uniforms,
* and uses texture.offset/repeat to compute lightMapTransform in the vertex shader.
*/
applyLightmap() {
if (this._isApplied)
return;
if (this.gameObject.type === "Object3D") {
if (debug)
console.warn("Can not add lightmap. Is this object missing a renderer?", this.gameObject.name);
return;
}
const mesh = this.gameObject;
this.ensureLightmapUvs(mesh);
if (this.lightmapIndex >= 0 && this.lightmapTexture) {
this.lightmapTexture.channel = 1;
const so = this.lightmapScaleOffset;
for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
const mat = this.renderer.sharedMaterials[i];
if (!mat)
continue;
// If the material doesn't support lightmaps, skip it
if (mat["lightMap"] === undefined)
continue;
if (debug)
console.log("Setting lightmap on material", mat.name, "for renderer", this.renderer.name);
// Use property block to set lightMap with per-object UV transform.
// The texture transform differentiates cache keys per object and
// PropertyBlock handles save/restore of the shared texture's offset/repeat.
const propertyBlock = MaterialPropertyBlock.get(this.gameObject);
propertyBlock.setOverride("lightMap", this.lightmapTexture, {
offset: new Vector2(so.z, 1 - so.y - so.w),
repeat: new Vector2(so.x, so.y)
});
mat[$lightmapKey] = true;
}
this._isApplied = true;
}
}
/** Update the lightMap override on all property blocks (e.g. after LOD swap) */
updatePropertyBlockTexture() {
if (!this._isApplied || !this.lightmapTexture)
return;
this.lightmapTexture.channel = 1;
const so = this.lightmapScaleOffset;
const propertyBlock = MaterialPropertyBlock.get(this.gameObject);
propertyBlock.setOverride("lightMap", this.lightmapTexture, {
offset: new Vector2(so.z, 1 - so.y - so.w),
repeat: new Vector2(so.x, so.y)
});
}
/**
* Remove the lightmap from the object
*/
onUnset() {
for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
const mat = this.renderer.sharedMaterials[i];
if (mat) {
delete mat[$lightmapKey];
}
}
const block = MaterialPropertyBlock.get(this.gameObject);
if (block) {
block.removeOveride("lightMap");
}
}
ensureLightmapUvs(object) {
if (object instanceof Mesh) {
if (!object.geometry.getAttribute("uv1")) {
object.geometry.setAttribute("uv1", object.geometry.getAttribute("uv"));
}
}
else if (object instanceof Group) {
for (const child of object.children) {
this.ensureLightmapUvs(child);
}
}
}
setLightmapDebugMaterial() {
const so = this.lightmapScaleOffset;
// debug lightmaps
this.gameObject["material"] = new ShaderMaterial({
vertexShader: `
varying vec2 vUv1;
void main()
{
vUv1 = uv1;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`,
fragmentShader: `
uniform sampler2D lightMap;
uniform float lightMapIntensity;
varying vec2 vUv1;
// 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 );
}
void main() {
vec2 lUv = vUv1.xy * vec2(${so.x.toFixed(6)}, ${so.y.toFixed(6)}) + vec2(${so.z.toFixed(6)}, ${(1 - so.y - so.w).toFixed(6)});
vec4 lightMapTexel = texture2D( lightMap, lUv);
gl_FragColor = lightMapTexel;
gl_FragColor.a = 1.;
}
`,
defines: { USE_LIGHTMAP: '' }
});
}
}
//# sourceMappingURL=RendererLightmap.js.map