UNPKG

@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
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