Fix glTF Texture flipY Issue in Three.js

This article explains how to resolve the common texture inversion (flipY) issue when loading glTF models in Three.js. You will learn why this coordinate mismatch occurs between WebGL and the glTF standard, and how to apply the correct code configurations to ensure your textures align perfectly on your 3D models.

Understanding the flipY Mismatch

The texture flipping issue arises because of a conflict between the default coordinate systems of WebGL and the glTF format. By default, WebGL (and Three.js Texture objects) expects the UV coordinate origin (0,0) to be at the bottom-left corner of an image. Conversely, the glTF specification defines the UV origin at the top-left corner.

When you use the Three.js GLTFLoader, it automatically configures all embedded and referenced textures inside the glTF file with texture.flipY = false so that they render correctly. However, if you manually load an external texture using TextureLoader and assign it to a glTF material, Three.js defaults to texture.flipY = true, causing the texture to appear upside down on your model.

The Solution: Manually Setting flipY to False

To fix this issue when applying custom or external textures to a glTF model, you must explicitly set the flipY property of your texture to false before assigning it to the material.

Here is the standard implementation using TextureLoader:

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

// Initialize the texture loader
const textureLoader = new THREE.TextureLoader();

// Load the external texture
textureLoader.load('path/to/your/texture.jpg', (texture) => {
    
    // Crucial step: Align the texture coordinate system with glTF standard
    texture.flipY = false;

    // Load the glTF model
    const gltfLoader = new GLTFLoader();
    gltfLoader.load('path/to/your/model.gltf', (gltf) => {
        const model = gltf.scene;

        // Traverse the model to find the mesh and apply the texture
        model.traverse((child) => {
            if (child.isMesh) {
                child.material.map = texture;
                child.material.needsUpdate = true;
            }
        });
    });
});

Working with Compressed Textures

If you are using compressed textures (such as KTX2 basis textures), the flipY property might be read-only or pre-configured during the encoding process. For compressed textures processed via KTX2Loader, the loader automatically handles the layout. Ensure you do not manually change flipY on compressed textures unless you are manually overriding the UV transformation matrix in your custom shaders.