Need help writing a "true skybox" TSL shader node

I have here a JSFiddle that renders a simple 3D environment: a small flat plane geometry for the “ground”, placed within a box to act as the “skybox”. The skybox is textured with an equirectangular sky texture, and is rotating to assist with troubleshooting.

I was wondering if any of you could write or share with me a TSL shader node to render the equirectangular sky texture as a “true skybox”, where its parallax creates the illusion that it is infinitely far away.

Note:

  1. I must use an equirectangular image
  2. I do not want to use the scene.background property
  3. I do not want to simply create a giant textured sphere that follows the camera (although this works)

The relevant section of code for the TSL node is here (see the “what goes here…” comment):

const material = new THREE.MeshBasicMaterial({
  side: THREE.DoubleSide,
})
const texture = await getSkyboxTexture(textureLoader)
material.colorNode = THREE.Fn(() => {
  // what goes here to create a "true skybox"?
  return THREE.texture(texture, THREE.uv(), null)
})()

Here is the JSFiddle:

One step closer. Three lines are modified (look for the cat):

material.colorNode = THREE.Fn(() => {
      return THREE.texture(
           texture,
           THREE.equirectUV(THREE.positionLocal.normalize()), // ^..^
           null)
})()

const mesh = new THREE.Mesh(geometry, material)
mesh.position.copy( camera.position ); // ^..^
texture.minFilter = THREE.NearestFilter; // ^..^

1 Like

Thanks for the help! That is closer to what I am hoping to achieve but not quite 100%:

  1. Is there a way to adjust for the camera position in the TSL node so I don’t need to call mesh.position.copy(camera.position)? I intend to use this material in an FPS-style game and don’t want to update the skybox mesh position on every frame.
  2. Can the corners of the skybox should be adjusted for in the TSL node so that the final rendered skybox appears to be a giant sphere and not a box? At the moment, even if we were able to achieve item #1 listed above, the skybox would not look right because the skybox corners are not being accounted for.

For context on what I am trying to do, if you are familiar with the Source Engine (Half Life 2, Portal, etc) and using Hammer Editor, there is a special material called “skybox” that you can paint meshes with that are specifically designed to render a skybox texture. I’m basically trying to replicate that exactly.

Screenshot of the “skybox” texture in the Source Engine/Hammer Editor:

Why not just place the cube at the camera this is basically free.

1 Like

Check this JSFiddle out. I’m so close–I have the skybox working almost perfectly (infinite parallax and spherical projection). The only thing wrong with it is that its rotation appears to be stuck to the camera rotation.

What do I need to change to “unstick” the rotation from the camera?

material.colorNode = THREE.Fn(() => {
  const dir = THREE.positionViewDirection.negate();
  const uv = THREE.equirectUV(dir);
  return THREE.texture(texture, uv, null)
})()

This is really trivial to do with just a box. If you render it first just have it not write to depth, and place it onto the camera. No shaders are needed. Why not put

box.position.copy(camera.position) in an update, like onBeforeRender.

Figured it out. See code:

const texture = await getSkyboxTexture(textureLoader)
material.colorNode = THREE.Fn(() => {
  function rotateVector(vector, axis, angle) {
    const vxp = axis.cross(vector)
    const vxvxp = axis.cross(vxp)
    const a = angle.sin().mul(vxp)
    const b = angle.cos().oneMinus().mul(vxvxp)
    return vector.add(a).add(b)
  }
  const q = new THREE.Quaternion()
  const e = new THREE.Euler()
  const cameraWorldRotation = THREE.uniform("vec3").onRenderUpdate(() => {
    camera.getWorldQuaternion(q)
    return e.setFromQuaternion(q)
  })
  let dir = THREE.positionViewDirection.negate()
  dir = rotateVector(dir, THREE.vec3(0, 0, 1), cameraWorldRotation.z)
  dir = rotateVector(dir, THREE.vec3(0, 1, 0), cameraWorldRotation.y)
  dir = rotateVector(dir, THREE.vec3(1, 0, 0), cameraWorldRotation.x)
  return THREE.texture(texture, THREE.equirectUV(dir), null)
})()

skybox2