How to load KTX2 files for a cubemap

Hi, I’m using three.js and next.js for a project and i want to load a ktx2 files to use them as a cubemap, in the console i can see that they are loaded but it doesn’t seems to show, the cubemap go from black to dark gray and that is it, here is my code:

“use client”;
import React, { useEffect, useRef } from “react”;
import * as THREE from “three”;
import gsap from “gsap”;
import { KTX2Loader } from “…/node_modules/three/examples/jsm/loaders/KTX2Loader”;

const MainCanvas = () => {
const mount = useRef(null);

const init = () => {
const loadingBar = document.querySelector(“.loading-bar”);

const loadingManager = new THREE.LoadingManager(
  () => {
    setTimeout(() => {
      window.scrollTo(0, 0);


      gsap.to(camera.position, {
        duration: 1,
        delay: 1,
        z: 128,
        y: 20,
      });
    }, 50);
  },
  (itemUrl, itemsLoaded, itemsTotal) => {
    const progressRatio = itemsLoaded / itemsTotal;
    loadingBar.style.transform = "scaleX(" + progressRatio + ")";
  }
);

// Scene
const scene = new THREE.Scene();
// scene.background = envMap;

// Objects
const Group = new THREE.Group();
scene.add(Group);

const LambertMaterial = new THREE.MeshLambertMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});

const plane = new THREE.Mesh(
  new THREE.PlaneGeometry(
    window.innerWidth * 1.25,
    window.innerHeight * 1.25,
    window.innerWidth / 2,
    window.innerHeight / 2
  ),
  LambertMaterial
);
Group.add(plane);
plane.rotation.x = -Math.PI / 2 - 0.2;
plane.position.y = -25;
plane.position.z = -60;

const donutGeometry = new THREE.BufferGeometry(0.3, 0.2, 20, 45);
const dodecahedronGeometry = new THREE.OctahedronGeometry(0.4, 0);
const sphereBufferGeometry = new THREE.OctahedronGeometry(0.7, 7);

let obj;
for (let i = 0; i < 125; i++) {
  switch (Math.floor(Math.random() * 3)) {
    case 0:
      obj = new THREE.Mesh(donutGeometry, LambertMaterial);
      break;
    case 1:
      obj = new THREE.Mesh(dodecahedronGeometry, LambertMaterial);
      break;
    case 2:
      obj = new THREE.Mesh(sphereBufferGeometry, LambertMaterial);
      break;
    default:
      obj = new THREE.Mesh(dodecahedronGeometry, LambertMaterial);
      break;
  }

  obj.position.set(
    (Math.random() - 0.5) * 200,
    (Math.random() - 0.5) * 10 + 5,
    (Math.random() - 0.5) * 200
  );

  obj.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0);

  scene.add(obj);
}

// Sizes
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};

// Lights
const initLights = () => {
  const r = 30;
  const y = 10;
  const lightDistance = 500;

  const light1 = new THREE.PointLight(0x0e09dc, 0.4, lightDistance);
  light1.position.set(0, y, r - 60);
  scene.add(light1);

  const light2 = new THREE.PointLight(0x1cd1e1, 0.4, lightDistance);
  light2.position.set(0, -y, -r - 60);
  Group.add(light2);

  const light3 = new THREE.PointLight(0x18c02c, 0.4, lightDistance);
  light3.position.set(r, y, -60);
  scene.add(light3);

  const light4 = new THREE.PointLight(0xee3bcf, 0.4, lightDistance);
  light4.position.set(-r, y, -60);
  Group.add(light4);
};
initLights();

// Camera
const camera = new THREE.PerspectiveCamera(
  80,
  sizes.width / sizes.height,
  0.1,
  400
);
camera.position.set(0, 75, 325);
Group.add(camera);



// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

mount.current.appendChild(renderer.domElement);

const ktx2Loader = new KTX2Loader(loadingManager)
  .setTranscoderPath("/basis/")
  .detectSupport(renderer);

const cubemapFaces = [
  "px.ktx2",
  "nx.ktx2",
  "py.ktx2",
  "ny.ktx2",
  "pz.ktx2",
  "nz.ktx2",
];

const envMapPromises = cubemapFaces.map((face) => {
  return new Promise((resolve, reject) => {
    ktx2Loader.load(
      `/ktx/${face}`,
      (texture) => {
        resolve(texture);
      },
      undefined,
      (error) => {
        reject(error);
      }
    );
  });
});

let envMap = Promise.all(envMapPromises)
  .then((textures) => {
    console.log(textures);
    textures.minFilter = THREE.NearestMipmapNearestFilter;
    const envMap = new THREE.CubeTexture(textures);
    console.log(envMap);
    // Use envMap in your scene
    scene.background = envMap;
    return envMap;
  })
  .catch((error) => {
    console.error("Error loading KTX2 cubemap:", error);
  });
if (envMap) {
  scene.background = envMap;
}

// Update Resizes
const handleResize = () => {
  // Update sizes
  sizes.width = window.innerWidth;
  sizes.height = window.innerHeight;

  // Update camera
  if (sizes.width > 600) {
    camera.aspect = sizes.width / sizes.height;
    camera.updateProjectionMatrix();

    // Update
    renderer.setSize(sizes.width, sizes.height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  }
};
window.addEventListener("resize", handleResize);

const clock = new THREE.Clock();


const tick = () => {
  const elapsedTime = clock.getElapsedTime();


  // Render
  renderer.render(scene, camera);

  // Call tick again on the next frame
  requestAnimationFrame(tick);
};

tick();

};

useEffect(() => {
init();
}, ); // empty dependency array ensures that the effect runs once on mount

return

;
};

export default MainCanvas;

  1. Please format the code properly (the </> button in the post editor.)
  1. Instead of using relative import paths, consider using just three/examples/jsm/loaders/KTX2Loader - it’s easier and more manageable over time.
  2. empty dependency array ensures that the effect runs once on mount - keep in mind, this is not true for react dev mode, this effect will run twice, be sure to take that into account.
  3. Your code snippet doesn’t show any actual loading of KTX texture?
1 Like