-
-
Notifications
You must be signed in to change notification settings - Fork 36k
Description
Description
I’ve attached a html containing a minimal reproduction.
The issue occurs in two cases:
Geometry without vertex normals but material with flatShading = true
Normally this renders fine, since flatShading handles the shading without requiring vertex normals.
In progressiveLightMap, however, the material gets replaced with the internal uvMat, which has flatShading = false.
As a result, the object renders completely black.

Geometry without vertex normals and material without flatShading
In this case, the code does not raise any error or warning.
This leads to silent failures and incorrect results during rendering.
Reproduction steps
1.Copy the provided HTML code into a local file (e.g. index.html).
2.Download the model from Horse.glb.
3.Open index.html in a browser.
4.Use the Choose File button to select the downloaded Horse.glb.
5.Click Load Model.
Code
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - progressive lightmap accumulation</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
/>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
#upload-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 10px;
text-align: center;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
#upload-container.hidden {
display: none;
}
#file-input {
display: none;
}
#file-select-btn {
margin: 10px 0;
padding: 10px 20px;
border: 2px dashed #ccc;
border-radius: 5px;
background: #f9f9f9;
cursor: pointer;
display: inline-block;
transition: background-color 0.3s;
}
#file-select-btn:hover {
background: #e9e9e9;
}
#file-name {
margin: 10px 0;
font-style: italic;
color: #666;
}
#upload-btn {
background: #007cff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
#upload-btn:hover {
background: #0056b3;
}
#upload-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
#loading {
display: none;
margin-top: 10px;
color: #666;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>
- Progressive Lightmaps by
<a href="https://github.com/zalo" target="_blank" rel="noopener">zalo</a
><br />
[Inspired by
<a
href="http://madebyevan.com/shaders/lightmap/"
target="_blank"
rel="noopener"
>evanw's Lightmap Generation</a
>]
</div>
<!-- File Upload Interface -->
<div id="upload-container">
<h3>Upload 3D Model File</h3>
<p>Supported formats: .glb, .gltf</p>
<input type="file" id="file-input" accept=".glb,.gltf" />
<div id="file-select-btn">Choose File</div>
<div id="file-name">No file selected</div>
<button id="upload-btn" disabled>Load Model</button>
<div id="loading">Loading model...</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { TransformControls } from "three/addons/controls/TransformControls.js";
import { ProgressiveLightMap } from "three/addons/misc/ProgressiveLightMap.js";
// ShadowMap + LightMap Res and Number of Directional Lights
const shadowMapRes = 512,
lightMapRes = 1024,
lightCount = 8;
let camera,
scene,
renderer,
controls,
object = new THREE.Mesh(),
lightOrigin = null,
progressiveSurfacemap;
const dirLights = [],
lightmapObjects = [];
const params = {
Enable: true,
"Blur Edges": true,
"Blend Window": 200,
"Light Radius": 50,
"Ambient Weight": 0.5,
"Debug Lightmap": true,
};
// File upload related elements
const uploadContainer = document.getElementById("upload-container");
const fileInput = document.getElementById("file-input");
const uploadBtn = document.getElementById("upload-btn");
const loadingDiv = document.getElementById("loading");
init();
setupFileUpload();
/**
* Initialize Three.js scene
*/
function init() {
// renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// camera
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
1,
1000
);
camera.position.set(0, 100, 200);
camera.name = "Camera";
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xebebeb);
scene.fog = new THREE.Fog(0x949494, 1000, 3000);
// progressive lightmap
progressiveSurfacemap = new ProgressiveLightMap(renderer, lightMapRes);
// directional lighting "origin"
lightOrigin = new THREE.Group();
lightOrigin.position.set(60, 150, 100);
scene.add(lightOrigin);
// create 8 directional lights to speed up the convergence
for (let l = 0; l < lightCount; l++) {
const dirLight = new THREE.DirectionalLight(
0xffffff,
Math.PI / lightCount
);
dirLight.name = "Dir. Light " + l;
dirLight.position.set(200, 200, 200);
dirLight.castShadow = true;
dirLight.shadow.camera.near = 100;
dirLight.shadow.camera.far = 5000;
dirLight.shadow.camera.right = 150;
dirLight.shadow.camera.left = -150;
dirLight.shadow.camera.top = 150;
dirLight.shadow.camera.bottom = -150;
dirLight.shadow.mapSize.width = shadowMapRes;
dirLight.shadow.mapSize.height = shadowMapRes;
lightmapObjects.push(dirLight);
dirLights.push(dirLight);
}
// ground
const groundMesh = new THREE.Mesh(
new THREE.PlaneGeometry(600, 600),
new THREE.MeshPhongMaterial({ color: 0xffffff, depthWrite: true })
);
groundMesh.position.y = -0.1;
groundMesh.rotation.x = -Math.PI / 2;
groundMesh.name = "Ground Mesh";
groundMesh.material.color = new THREE.Color(0xebebeb);
scene.add(groundMesh);
lightmapObjects.push(groundMesh);
// controls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.05;
controls.screenSpacePanning = true;
controls.minDistance = 100;
controls.maxDistance = 500;
controls.maxPolarAngle = Math.PI / 1.5;
controls.target.set(0, 100, 0);
window.addEventListener("resize", onWindowResize);
}
/**
* Setup file upload functionality
*/
function setupFileUpload() {
const fileSelectBtn = document.getElementById("file-select-btn");
const fileName = document.getElementById("file-name");
// Custom file select button click event
fileSelectBtn.addEventListener("click", function () {
fileInput.click();
});
// File selection event
fileInput.addEventListener("change", function (event) {
const file = event.target.files[0];
if (file) {
fileName.textContent = file.name;
uploadBtn.disabled = false;
} else {
fileName.textContent = "No file selected";
uploadBtn.disabled = true;
}
});
// Upload button click event
uploadBtn.addEventListener("click", function () {
const file = fileInput.files[0];
if (file) {
loadModel(file);
}
});
}
/**
* Load 3D model file
* @param {File} file - User selected file
*/
function loadModel(file) {
// Show loading state
loadingDiv.style.display = "block";
uploadBtn.disabled = true;
const loader = new GLTFLoader();
const url = URL.createObjectURL(file);
loader.load(
url,
function (gltf) {
// Clean up previous model
if (object && object.parent) {
scene.remove(object);
}
// Clean up old models from lightmapObjects
for (let i = lightmapObjects.length - 1; i >= 0; i--) {
if (lightmapObjects[i].name === "Loaded Mesh") {
lightmapObjects.splice(i, 1);
}
}
object = gltf.scene.children[0] || gltf.scene;
object.position.set(0, 50, 0);
object.traverse(function (child) {
if (child.isMesh) {
child.name = "Loaded Mesh";
lightmapObjects.push(child);
} else {
child.layers.disableAll(); // Disable Rendering for this
}
});
progressiveSurfacemap.addObjectsToLightMap(lightmapObjects);
scene.add(object);
// Hide upload interface and create GUI
uploadContainer.classList.add("hidden");
createGUI();
// Release object URL
URL.revokeObjectURL(url);
},
function (progress) {
// Loading progress can be handled here if needed
},
function (error) {
alert(
"Model loading failed. Please check if the file format is correct."
);
loadingDiv.style.display = "none";
uploadBtn.disabled = false;
URL.revokeObjectURL(url);
}
);
}
/**
* Create GUI control panel
*/
function createGUI() {
const gui = new GUI({ title: "Accumulation Settings" });
gui.add(params, "Enable");
gui.add(params, "Blur Edges");
gui.add(params, "Blend Window", 1, 500).step(1);
gui.add(params, "Light Radius", 0, 200).step(10);
gui.add(params, "Ambient Weight", 0, 1).step(0.1);
gui.add(params, "Debug Lightmap");
}
/**
* Handle window resize
*/
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
/**
* Animation loop function
*/
function animate() {
// Update the inertia on the orbit controls
controls.update();
// Accumulate Surface Maps
if (params["Enable"]) {
progressiveSurfacemap.update(
camera,
params["Blend Window"],
params["Blur Edges"]
);
if (!progressiveSurfacemap.firstUpdate) {
progressiveSurfacemap.showDebugLightmap(params["Debug Lightmap"]);
}
}
// Manually Update the Directional Lights
for (let l = 0; l < dirLights.length; l++) {
if (Math.random() > params["Ambient Weight"]) {
dirLights[l].position.set(
lightOrigin.position.x + Math.random() * params["Light Radius"],
lightOrigin.position.y + Math.random() * params["Light Radius"],
lightOrigin.position.z + Math.random() * params["Light Radius"]
);
} else {
const lambda = Math.acos(2 * Math.random() - 1) - 3.14159 / 2.0;
const phi = 2 * 3.14159 * Math.random();
dirLights[l].position.set(
Math.cos(lambda) * Math.cos(phi) * 300 + object.position.x,
Math.abs(Math.cos(lambda) * Math.sin(phi) * 300) +
object.position.y +
20,
Math.sin(lambda) * 300 + object.position.z
);
}
}
renderer.render(scene, camera);
}
</script>
</body>
</html>
Live example
none
Version
@0.160.0/
Device
No response
Browser
No response
OS
No response