Lightmap uses wrong uv in loaded gltf model

Hello, I created a second UV set in my model from blender. but when I load the model lightmap uses the first uv.

import * as THREE from 'three';

import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import { ACESFilmicToneMapping, CineonToneMapping, HalfFloatType, LinearEncoding, LinearFilter, NearestFilter, NoToneMapping, ReinhardToneMapping, RGBAFormat, sRGBEncoding, WebGLCubeRenderTarget } from 'three'

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 750);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 0);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.antialias = true;
renderer.toneMappingExposure = 1;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

let lightProbe;

lightProbe = new THREE.LightProbe();

let cubeTexture;

const genCubeUrls = function (prefix, postfix) {

	return [
		prefix + 'posx' + postfix, prefix + 'negx' + postfix,
		prefix + 'posy' + postfix, prefix + 'negy' + postfix,
		prefix + 'posz' + postfix, prefix + 'negz' + postfix


const urls = genCubeUrls('textures/cubeMaps/', '.jpg');

new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {

	scene.environment = cubeTexture;



const controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', renderer);
controls.minDistance = 0.1;
controls.maxDistance = 75;
controls.enablePan = true;
controls.minPolarAngle = 0;
controls.maxPolarAngle = Math.PI * 0.5;

const loader = new GLTFLoader();

// Optional: Provide a DRACOLoader instance to decode compressed mesh data

var model;
// Load a glTF resource
	// resource URL
	// called when the resource is loaded
	function (gltf) {

		var lightMap = new THREE.TextureLoader().load('Lightmap.png');

		lightMap.flipY = false;

		gltf.animations; // Array<THREE.AnimationClip>
		gltf.scene; // THREE.Group
		model = gltf.scene;
		gltf.scenes; // Array<THREE.Group>
		gltf.cameras; // Array<THREE.Camera>
		gltf.asset; // Object

		gltf.receiveShadow = true;

		gltf.scene.traverse(function (child) {
			if (child.isMesh) {
				child.castShadow = true;
				child.receiveShadow = true;
				child.material.lightMap = lightMap;


	// called while loading is progressing
	function (xhr) {

		//console.log((xhr.loaded / * 100) + '% loaded');

	// called when loading has errors
	function (error) {



Where might the problem be?

Which version of three are you using?

here. I think it’s the latest version

What exactly is a second set of UVs ? If im not mistaken each model has its own UV. If by chance you duplicated you model and used the same UV. Its possible something carried over. For that go to Blender, select your object, then in the menu above, select object - relations - make local or make single user. If you have one model, and indeed somehow have 2 different UVs for the same model, then you can try to manipulate the UV properties in detail. console.log(yourgltfmodel), then the UV properties are located gltf.children.geometry.uv. Just remember the UVs use x and y coordinates, or rather u and v coordinates.

Each mesh may have 0–4 sets of UVs. To use the 2nd UV set for a lightmap, try: = 1; // 0, 1, 2, 3

This customization is supported only in three.js r151+.


thanks. it solved problem.

Thanks, ì’ve been struggling upon these for days! Anyway, i think that there is something broken in general. The documentation says that ao maps and light maps loaded from gltf are automatically mapped on the second uv set but that is not true, even the three js editor seems to be broken.

Forcing the use of the right channel through solves it perfectly, thanks again

Hm, which documentation is this?

Previously GLTFLoader generated a second UV set (a copy of the first) for models containing AO maps, because AO maps in three.js required it. GLTFLoader has never done anything particular for lightmaps, because glTF does not support them.

Because three.js can now use an AO map with whichever UV set it’s actually supposed to use, GLTFLoader does nothing custom here anymore, and all textures default to .channel = 0 unless otherwise specified in the glTF file.

Assuming that the imported GLTF in three.js uses the meshStandardMaterial, in the documentation there is something misleading or missing. This is the link:

The documentation explains that ambient occlusion/light map need a seccond uv set. If i import a gltf model with 2 uv sets and if i don’t specify which one to use, the first set is picked by default instead of the second. It’s the same in the three.js editor webapp, i can use lightmaps and AO maps just with the first uv set and the second is totally ignored.

Anyway, with your solution everything works fine

The lines that say “{aoMap,lightMap} requires a second set of UVs” are out of date, this is no longer true. We should fix this in the documentation. Because the second UV set is no longer required, neither GLTFLoader nor the editor are forcing your model to use them, that part is correct.

Ok good to know! In your opinion, willl be changed also the three.js editor so that is possible to choose which uv set to use for AO/light maps?
I’m asking this because should be really useful the usage of three.js editor for scenes setups, basically i’m trying to build a bridge from blender to three.js passing through the editor to prepare the scene (mostly shading and lighting) without coding. Does this make sense to you?

Makes sense to me – it would probably make sense to include this for all texture types, since aoMap and lightMap are no different anymore. It’d probably be best to open a feature request on GitHub, to make sure others are OK with the idea and it fits into the UI.