Atlaslog
Atlaslog / three.js / Tutorial / Custom shaders
three.js · Tutorial

Custom shaders

Quick answer

This tutorial shows when material parameters are not enough and a custom GLSL shader is required. It contrasts ShaderMaterial, which injects projectionMatrix, modelViewMatrix and normalMatrix, with RawShaderMaterial, then writes a height-based color ramp and a time-driven vertex displacement using uniforms. It covers varying declarations, transparency with depthWrite and linear color values. Readers can shade terrain by elevation and animate vertices without rebuilding geometry.

Last verified 2026-11-03 · three.js 0.160.0 · Cite this page
Key points
Example
const vertexShader = `
  varying vec3 vNormal;
  varying float vHeight;

  void main() {
    vNormal = normalize(normalMatrix * normal);
    vHeight = position.y;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`;

const fragmentShader = `
  uniform vec3 lowColor;
  uniform vec3 highColor;
  varying vec3 vNormal;
  varying float vHeight;

  void main() {
    float t = clamp((vHeight - 20.0) / 60.0, 0.0, 1.0);
    vec3 base = mix(lowColor, highColor, t);
    float light = 0.45 + 0.55 * max(dot(vNormal, vec3(0.0, 1.0, 0.0)), 0.0);
    gl_FragColor = vec4(base * light, 1.0);
  }
`;

const terrainMaterial = new THREE.ShaderMaterial({
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  uniforms: {
    lowColor: { value: new THREE.Color('#1f5fd0') },
    highColor: { value: new THREE.Color('#d0453a') }
  },
  side: THREE.DoubleSide
});
const shaderMaterial = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0 },
    uScale: { value: 0.15 }
  },
  vertexShader: `
    uniform float uTime;
    uniform float uScale;
    varying float vWave;

    void main() {
      vWave = sin(position.x * uScale + uTime) * 0.5 + 0.5;
      vec3 displaced = position;
      displaced.y += vWave * 2.0;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(displaced, 1.0);
    }
  `,
  fragmentShader: `
    varying float vWave;

    void main() {
      gl_FragColor = vec4(0.12, 0.40, 0.85, 0.6 + vWave * 0.4);
    }
  `,
  transparent: true,
  depthWrite: false
});

const clock = new THREE.Clock();

renderer.setAnimationLoop(function () {
  shaderMaterial.uniforms.uTime.value = clock.getElapsedTime();
  renderer.render(scene, camera);
});
Notes
Runnable examplesChinese originalAll Tutorials
English edition written for Atlaslog. Code is taken from the original Chinese page and runs unchanged.
Cite this page

Atlaslog (2026). Custom shaders. https://www.jingweizhi.com/en/three/tutorials/custom-shader.html (accessed 2026-11-03).