Babylon.js - Custom Shader demo source

Back to demo

main.ts

import { runDemo } from "../shared/demoRunner";
import { createCustomShaderScene } from "./scene";

runDemo({ createScene: createCustomShaderScene });

scene.ts

import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
import type { Engine } from "@babylonjs/core/Engines/engine";
import { PointLight } from "@babylonjs/core/Lights/pointLight";
import { Effect } from "@babylonjs/core/Materials/effect";
import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
import { Color3 } from "@babylonjs/core/Maths/math.color";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { CreateCylinder } from "@babylonjs/core/Meshes/Builders/cylinderBuilder";
import { CreateSphere } from "@babylonjs/core/Meshes/Builders/sphereBuilder";
import { CreateTorus } from "@babylonjs/core/Meshes/Builders/torusBuilder";
import { Scene } from "@babylonjs/core/scene";

Effect.ShadersStore.customCellVertexShader = `
precision highp float;
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
uniform mat4 world;
uniform mat4 worldViewProjection;
varying vec3 vNormalW;
varying vec2 vUV;
void main(void) {
    vec4 worldPosition = world * vec4(position, 1.0);
    vNormalW = normalize(mat3(world) * normal);
    vUV = uv;
    gl_Position = worldViewProjection * vec4(position, 1.0);
}`;

Effect.ShadersStore.customCellFragmentShader = `
precision highp float;
varying vec3 vNormalW;
varying vec2 vUV;
uniform sampler2D textureSampler;
uniform vec3 vLightPosition;
uniform vec3 vLightColor;
void main(void) {
    vec3 normal = normalize(vNormalW);
    float light = max(dot(normal, normalize(vLightPosition)), 0.0);
    float shade = light > 0.95 ? 1.0 : light > 0.5 ? 0.8 : light > 0.2 ? 0.55 : 0.25;
    vec3 tex = texture2D(textureSampler, vUV * 3.0).rgb;
    gl_FragColor = vec4(tex * vLightColor * shade, 1.0);
}`;

export function createCustomShaderScene(engine: Engine, canvas: HTMLCanvasElement): Scene {
    const scene = new Scene(engine);
    const camera = new ArcRotateCamera("Camera", 0, Math.PI / 4, 40, Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    const light = new PointLight("Omni", new Vector3(20, 100, 2), scene);

    const material = new ShaderMaterial(
        "cellShading",
        scene,
        { vertex: "customCell", fragment: "customCell" },
        {
            attributes: ["position", "normal", "uv"],
            uniforms: ["world", "worldViewProjection", "vLightPosition", "vLightColor"],
            samplers: ["textureSampler"],
        }
    );
    material.setTexture("textureSampler", new Texture("/Scenes/Customs/Ground.jpg", scene));
    material.setVector3("vLightPosition", light.position);
    material.setColor3("vLightColor", Color3.White());

    const sphere = CreateSphere("Sphere0", { segments: 32, diameter: 6 }, scene);
    const cylinder = CreateCylinder(
        "Cylinder",
        { height: 5, diameterTop: 3, diameterBottom: 2, tessellation: 32 },
        scene
    );
    const torus = CreateTorus("Torus", { diameter: 6, thickness: 1, tessellation: 32 }, scene);
    sphere.position.x = -10;
    torus.position.x = 10;
    sphere.material = material;
    cylinder.material = material;
    torus.material = material;

    let alpha = 0;
    scene.registerBeforeRender(() => {
        sphere.rotation.set(alpha, alpha, 0);
        cylinder.rotation.set(alpha, alpha, 0);
        torus.rotation.set(alpha, alpha, 0);
        alpha += 0.04 * scene.getAnimationRatio();
    });

    return scene;
}