Florian Ludewig
Florian Ludewig

Reputation: 6002

How to Create a Multi-Colored Noise Shader

I need a shader that takes 3 input colors and generates noise as seen below. It is easy to achieve in Blender with the help of the "Noise Texture" and the "Color Ramp" nodes.

I've found this gist, which might solve my problem. But I wasn't able to configure the colors. And also the noise looks a lot "sharper" than the result in Blender.

Will I need to write my own shader for this or is there a simpler way to achieve this effect with ThreeJS?

Upvotes: 1

Views: 2197

Answers (1)

prisoner849
prisoner849

Reputation: 17596

Basically, you need just two things:

  1. Noise function in shader
  2. A gradiental texture.

I chose FBM for noise and used .onBeforeCompile() to change a built-in material (Standard):

body{
  overflow: hidden;
  margin: 0;
}
<canvas id="cnvsGradient" width="300" height="50" style="position: absolute; margin: 10px; border: 1px solid aqua"/>

<script>
  // https://github.com/yiwenl/glsl-fbm/blob/master/3d.glsl
  const fbm = `
  #define NUM_OCTAVES 5

  float mod289(float 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 perm(vec4 x){return mod289(((x * 34.0) + 1.0) * x);}

  float noise(vec3 p){
      vec3 a = floor(p);
      vec3 d = p - a;
      d = d * d * (3.0 - 2.0 * d);

      vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0);
      vec4 k1 = perm(b.xyxy);
      vec4 k2 = perm(k1.xyxy + b.zzww);

      vec4 c = k2 + a.zzzz;
      vec4 k3 = perm(c);
      vec4 k4 = perm(c + 1.0);

      vec4 o1 = fract(k3 * (1.0 / 41.0));
      vec4 o2 = fract(k4 * (1.0 / 41.0));

      vec4 o3 = o2 * d.z + o1 * (1.0 - d.z);
      vec2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x);

      return o4.y * d.y + o4.x * (1.0 - d.y);
  }


  float fbm(vec3 x) {
    float v = 0.0;
    float a = 0.5;
    vec3 shift = vec3(100);
    for (int i = 0; i < NUM_OCTAVES; ++i) {
      v += a * noise(x);
      x = x * 2.0 + shift;
      a *= 0.5;
    }
    return v;
  }
  `;
</script>
<script type="module">
console.clear();

import * as THREE from "https://cdn.skypack.dev/[email protected]";
import {OrbitControls} from "https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js";

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);
renderer.setClearColor(0x444444);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);

let light = new THREE.DirectionalLight(0xffffff);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));

let g = new THREE.CylinderBufferGeometry(2, 1, 5, 6, 64);

let pos = g.attributes.position;
let v = new THREE.Vector3();
let axis = new THREE.Vector3(0, 1, 0);
for(let i = 0; i < pos.count; i++){
  v.fromBufferAttribute(pos, i);
  let ratio = (v.y - (-2.5)) / 5;
  v.applyAxisAngle(axis, THREE.MathUtils.degToRad(60) * ratio);
  pos.setXYZ(i, v.x, v.y, v.z);
}
g.computeVertexNormals();

let uniforms = {
  tex: {
    value: setGradient()
  }
}
let m = new THREE.MeshStandardMaterial({
  metalness: 0.25,
  roughness: 0.75,
  onBeforeCompile: shader => {
    shader.uniforms.tex = uniforms.tex;
    shader.vertexShader = `
      varying vec3 vPos;
      ${shader.vertexShader}
    `.replace(
      `#include <begin_vertex>`,
      `#include <begin_vertex>
      //vPos = (modelMatrix * vec4(position, 1.0)).xyz;
      vPos = vec3(position);
      `
    );
    //console.log(shader.vertexShader);
    shader.fragmentShader = `
      uniform sampler2D tex;
      varying vec3 vPos;
      ${fbm}
      ${shader.fragmentShader}      
    `.replace(
      `vec4 diffuseColor = vec4( diffuse, opacity );`,
      `
      float d = fbm(vPos * 0.5);
      for(int i = 0; i < 4; i++){
        d = fbm(vPos * (float(i) + 1.) * d);
      }

      vec3 col = texture(tex, vec2(d, 0.5)).rgb;
      vec4 diffuseColor = vec4( col, opacity );`
    );
    //console.log(shader.fragmentShader);
  }
});
let o = new THREE.Mesh(g, m);
scene.add(o);

window.addEventListener( 'resize', onWindowResize, false );

renderer.setAnimationLoop(() => {
  o.rotation.y += 0.01;
  renderer.render(scene, camera);
});

function setGradient(){
  
  let canvas = document.getElementById('cnvsGradient');
  let ctx = canvas.getContext('2d');

  let gradient = ctx.createLinearGradient(0,0, 300,0);

  gradient.addColorStop(0.15, 'yellow');
  gradient.addColorStop(.5, 'red');
  gradient.addColorStop(0.85, 'blue');

  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  return new THREE.CanvasTexture(canvas);

}

function onWindowResize() {

  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize( innerWidth, innerHeight );

}
</script>

Upvotes: 7

Related Questions