Search code examples
javascriptgraphicsthree.jsshaderfragment-shader

How to manipulate the background color of a scene in three.js with shaders?


I'd like to change the background color of a simple three.js scene, like the one you change with

renderer.setClearColor(/*insert color here*/)

or

scene.background = new THREE.Color(/*insert color here*/)

with fragment shaders, but including glsl code or ShaderMaterial inside the brackets doesnt work. How can I do it?


Solution

  • Here is an approach with WebGLRenderTarget.texture as scene.background:

    let scene = new THREE.Scene();
    let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
    camera.position.set(0, 0, 10);
    let renderer = new THREE.WebGLRenderer();
    renderer.setSize(innerWidth, innerHeight);
    document.body.appendChild(renderer.domElement);
    window.addEventListener("resize", event => {
      camera.aspect = innerWidth / innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(innerWidth, innerHeight);
      gu.resolution.set(innerWidth, innerHeight);
    });
    
    let gu = {
      time: {value: 0},
      resolution: {value: new THREE.Vector2(innerWidth, innerHeight)}
    }
    
    let light = new THREE.DirectionalLight(0xffffff, 0.8);
    light.position.setScalar(1);
    scene.add(light, new THREE.AmbientLight(0xffffff, 0.2));
    
    let ico = new THREE.Mesh(new THREE.IcosahedronGeometry(3, 0), new THREE.MeshLambertMaterial({color: 0xff4488}));
    scene.add(ico);
    
    // <background stuff>
    let backScene = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), new THREE.MeshBasicMaterial({
      color: 0x004488,
      onBeforeCompile: shader =>  {
      shader.uniforms.time = gu.time;
      shader.uniforms.resolution = gu.resolution;
      shader.vertexShader = `
        varying vec2 vUv;
      ` + shader.vertexShader;
      shader.vertexShader = shader.vertexShader.replace(
        `#include <uv_vertex>`,
        `vUv = uv;`
      );
      shader.fragmentShader = `
        ${noise}
        uniform float time;
        uniform vec2 resolution;
        varying vec2 vUv;
      ` + shader.fragmentShader;
      shader.fragmentShader = shader.fragmentShader.replace(
        `vec4 diffuseColor = vec4( diffuse, opacity );`,
        `
          vec2 tUv = vUv * 5.0;
          tUv.x *= resolution.x / resolution.y;
          float t = time * 0.5;
          tUv -= vec2(t * 0.25, t);
          float n1 = snoise(vec3(tUv, time * 0.125));
          n1 = (n1 + 1.0) * 0.5;
          
          float n2 = snoise(vec3(n1 * 10., 1, 1));
          n2 = sin(((n2 + 1.0) * 0.5) * 3.1415926 * 2.);
          
          float effect = smoothstep(0.1, 0.125, n1) * (1. - smoothstep(0.375, 0.4, n2));
          float coef = sin(n2 * 3.141526 * 0.5) * 0.125;
          float e = effect - abs(coef);
          e = n1 > 0.25 && n1 < 0.75? e * e : pow(e, 16.);
    
          vec4 diffuseColor = vec4(diffuse * e, opacity);
        `
      );
    }
    }));
    let backCamera = new THREE.Camera();
    let backRT = new THREE.WebGLRenderTarget(innerWidth, innerHeight);
    scene.background = backRT.texture; // assign to background
    // </background stuff>
    
    let clock = new THREE.Clock();
    
    renderer.setAnimationLoop( _ => {
      let t = clock.getElapsedTime();
      gu.time.value = t;
      
      ico.rotation.x = t * 0.31;
      ico.rotation.y = t * 0.27;
      
      renderer.setRenderTarget(backRT); // set render target
      renderer.render(backScene, backCamera); //render background
      
      renderer.setRenderTarget(null); // remove render target, render to default buffer
      renderer.render(scene, camera); // render scene
      
      renderer.render(scene, camera);
    })
    body{
      overflow: hidden;
      margin: 0;
    }
    <script src="https://threejs.org/build/three.min.js"></script>
    <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
    <script>
      var noise = `
      //
    // Description : Array and textureless GLSL 2D/3D/4D simplex 
    //               noise functions.
    //      Author : Ian McEwan, Ashima Arts.
    //  Maintainer : stegu
    //     Lastmod : 20110822 (ijm)
    //     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
    //               Distributed under the MIT License. See LICENSE file.
    //               https://github.com/ashima/webgl-noise
    //               https://github.com/stegu/webgl-noise
    // 
    
    vec3 mod289(vec3 x) {
      return x - floor(x * (1.0 / 289.0)) * 289.0;
    }
    
    vec4 mod289(vec4 x) {
      return x - floor(x * (1.0 / 289.0)) * 289.0;
    }
    
    vec4 permute(vec4 x) {
         return mod289(((x*34.0)+1.0)*x);
    }
    
    vec4 taylorInvSqrt(vec4 r)
    {
      return 1.79284291400159 - 0.85373472095314 * r;
    }
    
    float snoise(vec3 v)
      { 
      const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
      const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
    
    // First corner
      vec3 i  = floor(v + dot(v, C.yyy) );
      vec3 x0 =   v - i + dot(i, C.xxx) ;
    
    // Other corners
      vec3 g = step(x0.yzx, x0.xyz);
      vec3 l = 1.0 - g;
      vec3 i1 = min( g.xyz, l.zxy );
      vec3 i2 = max( g.xyz, l.zxy );
    
      //   x0 = x0 - 0.0 + 0.0 * C.xxx;
      //   x1 = x0 - i1  + 1.0 * C.xxx;
      //   x2 = x0 - i2  + 2.0 * C.xxx;
      //   x3 = x0 - 1.0 + 3.0 * C.xxx;
      vec3 x1 = x0 - i1 + C.xxx;
      vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
      vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y
    
    // Permutations
      i = mod289(i); 
      vec4 p = permute( permute( permute( 
                 i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
               + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
               + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
    
    // Gradients: 7x7 points over a square, mapped onto an octahedron.
    // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
      float n_ = 0.142857142857; // 1.0/7.0
      vec3  ns = n_ * D.wyz - D.xzx;
    
      vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)
    
      vec4 x_ = floor(j * ns.z);
      vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)
    
      vec4 x = x_ *ns.x + ns.yyyy;
      vec4 y = y_ *ns.x + ns.yyyy;
      vec4 h = 1.0 - abs(x) - abs(y);
    
      vec4 b0 = vec4( x.xy, y.xy );
      vec4 b1 = vec4( x.zw, y.zw );
    
      //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
      //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
      vec4 s0 = floor(b0)*2.0 + 1.0;
      vec4 s1 = floor(b1)*2.0 + 1.0;
      vec4 sh = -step(h, vec4(0.0));
    
      vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
      vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
    
      vec3 p0 = vec3(a0.xy,h.x);
      vec3 p1 = vec3(a0.zw,h.y);
      vec3 p2 = vec3(a1.xy,h.z);
      vec3 p3 = vec3(a1.zw,h.w);
    
    //Normalise gradients
      vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
      p0 *= norm.x;
      p1 *= norm.y;
      p2 *= norm.z;
      p3 *= norm.w;
    
    // Mix final noise value
      vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
      m = m * m;
      return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
                                    dot(p2,x2), dot(p3,x3) ) );
    }
    `;
    </script>