Search code examples
three.jsglslshadermaterial

Three.js. How do I use a custom material for a scene background rather then color or texture?


The docs for scene say a color or texture can be used for scene.background. I would like to use a ShaderMaterial with my own custom shaders. How can I do this?

Specifically, I want to paint a color ramp behind the foreground elements. Here is the fragment shader:

uniform vec2 uXYPixel;
void main() {
    vec2 xy = vec2(gl_FragCoord.x/uXYPixel.x, gl_FragCoord.y/uXYPixel.y);
    gl_FragColor.rgb = vec3(xy.x, xy.y, 0);
    gl_FragColor.a = 1.0;
}

uXYPixel is a uniform vec2 with the values window.innerWidth, window.innerHeight


Solution

  • You'd need to manually create two render passes. One that renders the background plane with a simple Camera, and the second one that renders the rest of the scene. You can use the most basic Camera class since you won't be using transformation or projection matrices:

    // Set renderer with no autoclear
    var renderer = new THREE.WebGLRenderer();
    renderer.autoClear = false;
    document.body.append(renderer.domElement);
    renderer.setSize(window.innerWidth, window.innerHeight);
    
    // Set up background scene
    var bgScene = new THREE.Scene();
    var bgCam = new THREE.Camera();
    var bgGeom = new THREE.PlaneBufferGeometry(2, 2);
    var bgMat = new THREE.ShaderMaterial({
        // Add shader stuff in here
        // ..
        // Disable depth so it doesn't interfere with foreground scene
        depthTest: false,
        depthWrite: false
    });
    var bgMesh = new THREE.Mesh(bgGeom, bgMat);
    bgScene.add(bgMesh);
    
    // Set up regular scene
    var scene = new THREE.Scene();
    var cam = new THREE.PerspectiveCamera(45, w/h, 1, 100);
    
    function update() {
        // Clear previous frame
        renderer.clear();
    
        // Background render pass
        renderer.render(bgScene, bgCam);
    
        // Foreground render pass
        renderer.render(scene, cam);
    
        requestAnimationFrame(update);
    }
    
    update();
    

    Here you can see a working example.

    Notice that the renderer's autoClear = false attribute makes sure it doesn't clear the buffer in between each render() call; you'll have to clear it manually at the beginning of each frame.