LoginSignup
8
8

More than 5 years have passed since last update.

three.jsで背景をフラグメントシェーダーでつくる

Posted at

three.jsで以下のように背景をフラグメントシェーダーで作る方法です。

https://aadebdeb.github.io/study-three.js/background-shader.html
background-shader4.gif

背景とするオブジェクトは通常のオブジェクトと同じようにTHREE.Meshで作成します。このときに大事なことはマテリアルのdepthTestプロパティをfalseにして深度テストをしないようにすることと、メッシュのrenderOrderプロパティの値を他のメッシュよりも小さくして最初に背景がレンダリングされるようにすることです。これにより、無限遠方に背景をレンダリングした状態で他のオブジェクトのレンダリングを開始することができます。
renderOrderはデフォルトで0が設定されるので、他のオブジェクトのrenderOrderを変更しない場合は-1を指定すればいいでしょう(参考)。

js
const scene = new THREE.Scene();
const background = new THREE.Mesh(
  new THREE.PlaneBufferGeometry(2.0, 2.0),
  new THREE.RawShaderMaterial({
    uniforms: {
      resolution: {value: new THREE.Vector2(window.innerWidth, window.innerHeight)},
      time: {value: 0.0}
    },
    vertexShader: document.getElementById('vertexShader').textContent,
    fragmentShader: document.getElementById('fragmentShader').textContent,
    depthTest: false, // no depth test
  })
);
background.renderOrder = -1; // rendering first
scene.add(background);

背景に利用する頂点シェーダーは単純にattribute変数で渡ってくるpositiongl_Positionに渡しているだけです。背景の平面の大きさを2x2にしているため、左下が(-1, -1, 0)、右上が(1, 1, 0)となりクリッピング空間に一致します。そのため画面全体を覆うようにポリゴンをレンダリングすることができます。

vertex_shader
precision highp float;

attribute vec3 position;

void main() {
  gl_Position = vec4(position, 1.0);
}

フラグメントシェーダーは好きなように書けばいいと思います。

fragment_shader
precision highp float;

uniform vec2 resolution;

void main() {
  gl_FragColor = vec4(gl_FragCoord.xy / resolution, 0.0, 1.0);
}

最初に表示したgifアニメの全体のコードは以下のようになっています。
フラグメントシェーダーはThe Book of Shadersを参考にしました。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Background Shader</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="./js/three.js"></script>
    <script src="./js/controls/OrbitControls.js"></script>
    <script src="./js/stats.js"></script>
    <script id="vertexShader" type="x-shader/x-vertex">
      precision highp float;

      attribute vec3 position;

      void main() {
        gl_Position = vec4(position, 1.0);
      }
    </script>
    <script id="fragmentShader" type="x-shader/x-fragment">
      // based on the sample of  "The Book of Shaders"
      // https://thebookofshaders.com/13/

      precision highp float;

      uniform vec2 resolution;
      uniform float time;

      float random(in vec2 st) {
        return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
      }

      float noise(in vec2 st) {
        vec2 i = floor(st);
        vec2 f = fract(st);

        float a = random(i);
        float b = random(i + vec2(1.0, 0.0));
        float c = random(i + vec2(0.0, 1.0));
        float d = random(i + vec2(1.0, 1.0));

        vec2 u = f * f * (3.0 - 2.0 * f);

        return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
      }

      #define NUM_OCTAVES 5

      float fbm(in vec2 st) {
        float v = 0.0;
        float a = 0.5;
        vec2 shift = vec2(100.0);
        mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));
        for (int i = 0; i < NUM_OCTAVES; i++) {
          v += a * noise(st);
          st = rot * st * 2.0 + shift;
          a *= 0.5;
        }
        return v;
      }

      void main() {
        vec2 st = gl_FragCoord.xy / resolution.xy * 2.0;
        vec3 color = vec3(0.0);
        float time = 0.5 * time;

        vec2 q = vec2(0.0);
        q.x = fbm(st + time);
        q.y = fbm(st + vec2(1.0));

        vec2 r = vec2(0.0);
        r.x = fbm(st + 1.0 * q + vec2(1.7, 9.2) + 0.15 * time);
        r.y = fbm(st + 1.0 * q + vec2(8.3, 2.8) + 0.126 * time);

        float f = fbm(st + r);

        color = vec3(0.75);

        gl_FragColor = vec4((f*f*f+.6*f*f+.5*f)*color,1.);
      }
    </script>
    <script>
      const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
      camera.position.z = 500;
      const controls = new THREE.OrbitControls(camera);

      const scene = new THREE.Scene();

      const background = new THREE.Mesh(
        new THREE.PlaneBufferGeometry(2.0, 2.0),
        new THREE.RawShaderMaterial({
          uniforms: {
            resolution: {value: new THREE.Vector2(window.innerWidth, window.innerHeight)},
            time: {value: 0.0}
          },
          vertexShader: document.getElementById('vertexShader').textContent,
          fragmentShader: document.getElementById('fragmentShader').textContent,
          depthTest: false,
        })
      );
      background.renderOrder = -1;
      scene.add(background);

      function createMesh(color) {
        return new THREE.Mesh(
          new THREE.TetrahedronBufferGeometry(80.0),
          new THREE.MeshStandardMaterial({
            color: color,
            roughness: 0.3,
            metalness: 0.7,
          })
        );
      }
      const mesh1 = createMesh(0xff9966);
      mesh1.position.set(-150, 0.0, 0.0);
      scene.add(mesh1);
      const mesh2 = createMesh(0x66ff99);
      scene.add(mesh2);
      const mesh3 = createMesh(0x9966ff);
      mesh3.position.set(150.0, 0.0, 0.0);
      scene.add(mesh3);

      scene.add(new THREE.AmbientLight(0xcccccc));
      const mainLight = new THREE.DirectionalLight(0xffffff, 4.0);
      mainLight.position.set(1.0, 1.0, 1.0);
      scene.add(mainLight);
      const sideLight = new THREE.DirectionalLight(0xffffff, 2.0);
      sideLight.position.set(-1.0, 0.0, -0.5);
      scene.add(sideLight);

      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      const stats = new Stats();
      document.body.appendChild(stats.dom);

      const clock = new THREE.Clock();

      animate();
      function animate() {
        requestAnimationFrame(animate);
        stats.update();
        controls.update();
        const time = clock.getElapsedTime();
        background.material.uniforms.time.value = time;
        mesh1.rotation.set(time * 0.42, -time * 0.65, 0.0);
        mesh2.rotation.set(-time * 0.39, 0.0, time * 0.78);
        mesh3.rotation.set(0.0, time * 0.54, -time * 0.49);
        renderer.render(scene, camera);
      }

    </script>
  </body>
</html>
8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8