Starfield texture looks atrocious in scene

My starfield texture was taken from NASA. Dimensions: 8192 × 4096, Color space: RGB, Alpha channel: No.

However, the texture in my scene looks completely washed out and inaccurate to the original. In fact, even the Earth texture is wacky, but I don’t mind it.

I have done a very similar demo (same textures) in react-three-fiber with its default settings, and it looked great. So this probably means it has to do with the renderer settings and not the image itself.

I tried setting alpha to true, outputColorSpace to THREE.SRGBColorSpace, toneMapping to THREE.ACESFilmicToneMapping, THREE.ColorManagement.enabled to true, toneMappingExposure to 1.0,

but none of them made a difference (slight but not better). Here is a picture of the scene with these settings:

And the react-three-fiber results:

JS code:


import * as THREE from "three";

const CAMERA_PARAMETERS = {
  fov: 45,
  near: 0.1,
  far: 2000,
  position: { x: 0, y: 0, z: 0 },
  lookAt: { x: 0, y: 0, z: 1000 },
};

const EARTH_PARAMETERS = {
  radius: 50,
  widthSegments: 32,
  heightSegments: 32,
  texturePath: "/images/earth.jpeg",
  position: { x: 0, y: 0, z: 1000 },
  rotation: { y: 0.001 },
};

const SUNLIGHT_PARAMETERS = {
  color: 0xffffff,
  intensity: 1,
  position: { x: 0, y: 0, z: -500 },
};

const STARFIELD_PARAMETERS = {
  radius: 1000,
  widthSegments: 64,
  heightSegments: 64,
  texturePath: "/images/starfield.jpeg",
};

function createStarfield(scene, loader) {
  const starfieldGeometry = new THREE.SphereGeometry(
    STARFIELD_PARAMETERS.radius,
    STARFIELD_PARAMETERS.widthSegments,
    STARFIELD_PARAMETERS.heightSegments
  );

  const starfieldTexture = loader.load(STARFIELD_PARAMETERS.texturePath);
  const starfieldMaterial = new THREE.MeshBasicMaterial({
    map: starfieldTexture,
    side: THREE.BackSide,
  });

  const starfield = new THREE.Mesh(starfieldGeometry, starfieldMaterial);

  scene.add(starfield);
}

function createEarth(scene, loader) {
  const earthGeometry = new THREE.SphereGeometry(
    EARTH_PARAMETERS.radius,
    EARTH_PARAMETERS.widthSegments,
    EARTH_PARAMETERS.heightSegments
  );

  const earthMaterial = new THREE.MeshPhongMaterial();
  earthMaterial.map = loader.load(EARTH_PARAMETERS.texturePath);

  const earth = new THREE.Mesh(earthGeometry, earthMaterial);

  earth.position.set(
    EARTH_PARAMETERS.position.x,
    EARTH_PARAMETERS.position.y,
    EARTH_PARAMETERS.position.z
  );

  earth.name = "earth";
  scene.add(earth);
}

function createSunlight(scene) {
  const sunlight = new THREE.DirectionalLight(
    SUNLIGHT_PARAMETERS.color,
    SUNLIGHT_PARAMETERS.intensity
  );
  sunlight.position.set(
    SUNLIGHT_PARAMETERS.position.x,
    SUNLIGHT_PARAMETERS.position.y,
    SUNLIGHT_PARAMETERS.position.z
  );

  sunlight.name = "sunlight";
  scene.add(sunlight);
}

function rotateEarth(scene) {
  scene.getObjectByName("earth").rotation.y += 0.001;
}

function setupCamera() {
  const camera = new THREE.PerspectiveCamera(
    CAMERA_PARAMETERS.fov,
    window.innerWidth / window.innerHeight,
    CAMERA_PARAMETERS.near,
    CAMERA_PARAMETERS.far
  );

  camera.position.set(
    CAMERA_PARAMETERS.position.x,
    CAMERA_PARAMETERS.position.y,
    CAMERA_PARAMETERS.position.z
  );

  camera.lookAt(
    CAMERA_PARAMETERS.lookAt.x,
    CAMERA_PARAMETERS.lookAt.y,
    CAMERA_PARAMETERS.lookAt.z
  );

  return camera;
}

function setupRenderer() {
  const canvas = document.querySelector(".webgl");
  const renderer = new THREE.WebGLRenderer({
    canvas,
    antialias: true,
  });

  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  return renderer;
}

function animate(renderer, scene, camera) {
  requestAnimationFrame(() => animate(renderer, scene, camera));

  rotateEarth(scene);

  renderer.render(scene, camera);
}

function main() {
  const scene = new THREE.Scene();
  const loader = new THREE.TextureLoader();

  createEarth(scene, loader);
  createSunlight(scene);
  createStarfield(scene, loader);

  const camera = setupCamera();
  const renderer = setupRenderer();

  animate(renderer, scene, camera);
}

main();

React code (relevant parts):


function Earth() {
  const earthTexture = useLoader(TextureLoader, earthMap);
  const earthRef = useRef<Mesh>(null!);

  useFrame(() => {
    earthRef.current.rotation.y += 0.001;
  });

  return (
    <mesh ref={earthRef} name="earth">
      <sphereGeometry args={[50, 32, 32]} />
      <meshPhongMaterial map={earthTexture} />
    </mesh>
  );
}

function Skybox() {
  const texture = useLoader(TextureLoader, starfieldTexture);
  return (
    <mesh>
      <sphereGeometry attach="geometry" args={[5000, 24, 24]} />
      <meshBasicMaterial attach="material" map={texture} side={BackSide} />
    </mesh>
  );
}

function Scene() {
  return (
    <Canvas camera={{ near: 1, far: 10000 }}>
      <directionalLight
        position={[1000, 0, 2000]}
        color={"0x404040"}
        intensity={3}
      />
      <Earth />
      <Asteroid />
      <Skybox />
      <CameraControls />
    </Canvas>
  );
}

function App() {
  return (
    <div className="App h-screen w-screen overflow-hidden">
      <Scene></Scene>
    </div>
  );
}

export default App;

try toneMapped:false on the star field material

No difference sadly.

Have you set the textures colorSpace in conjunction with the renderer? Try one of the following…

starfieldTexture.colorSpace = THREE.SRGBColorSpace

Or

starfieldTexture.colorSpace = THREE.LinearSRGBColorSpace

You can also check out all of the texture constants

1 Like

in that case just use fiber. :smile:

seriously though, color spaces are hard to figure out. like @Lawrence3DPK said, it’s probably starfieldTexture.colorSpace = THREE.SRGBColorSpace i couldn’t think of much else.

2 Likes

Drastic improvement, thanks. I will have to go explore the texture constants later too. For anyone else with the same issue, I used

starfieldTexture.colorSpace = THREE.SRGBColorSpace;

and

renderer.toneMapping = THREE.ACESFilmicToneMapping;

results:

1 Like