Source: material/lambert-material.js

import { ShaderProgram } from '../shader/shader-program.js';
import {
    DirectionalLightMaterial,
    POSITION_ATTRIBUTE_LOCATION,
    NORMAL_ATTRIBUTE_LOCATION,
    FINAL_MATRIX_UNIFORM_NAME,
    WORLD_INVERSE_TRANSPOSE_MATRIX_UNIFORM_NAME,
    COLOR_UNIFORM_NAME,
    LIGHT_DIRECTION_UNIFORM_NAME,
    AMBIENT_STRENGTH_UNIFORM_NAME,
    DIRECTIONAL_STRENGTH_UNIFORM_NAME,
    LIGHTING_ENABLED_UNIFORM_NAME,
    OPACITY_UNIFORM_NAME
} from './directional-light-material.js';

/**
 * GLSL vertex shader source code.
 *
 * Requires:
 * - position attribute at location 0
 * - normal attribute at location 3
 *
 * @type {string}
 */
const VERTEX_SHADER_SOURCE = `#version 300 es
precision mediump float;
layout(location = ${POSITION_ATTRIBUTE_LOCATION}) in vec3 a_position;
layout(location = ${NORMAL_ATTRIBUTE_LOCATION}) in vec3 a_normal;
uniform mat4 ${FINAL_MATRIX_UNIFORM_NAME};
uniform mat4 ${WORLD_INVERSE_TRANSPOSE_MATRIX_UNIFORM_NAME};
out vec3 v_normal;

void main() {
    gl_Position = ${FINAL_MATRIX_UNIFORM_NAME} * vec4(a_position, 1.0);
    v_normal    = (${WORLD_INVERSE_TRANSPOSE_MATRIX_UNIFORM_NAME} * vec4(a_normal, 0.0)).xyz;
}
`;

/**
 * GLSL fragment shader source code.
 *
 * Implements Lambert diffuse lighting: `ambient + diffuse`.
 *
 * @type {string}
 */
const FRAGMENT_SHADER_SOURCE = `#version 300 es
precision mediump float;
in vec3 v_normal;
uniform vec3  ${COLOR_UNIFORM_NAME};
uniform vec3  ${LIGHT_DIRECTION_UNIFORM_NAME};
uniform float ${AMBIENT_STRENGTH_UNIFORM_NAME};
uniform float ${DIRECTIONAL_STRENGTH_UNIFORM_NAME};
uniform float ${LIGHTING_ENABLED_UNIFORM_NAME};
uniform float ${OPACITY_UNIFORM_NAME};
out vec4 outColor;

void main() {
    vec3 surface_normal = normalize(v_normal);

    if (!gl_FrontFacing) {
        surface_normal = -surface_normal;
    }

    vec3 light_direction    = normalize(${LIGHT_DIRECTION_UNIFORM_NAME});
    float diffuse_intensity = max(dot(surface_normal, light_direction), 0.0) * ${DIRECTIONAL_STRENGTH_UNIFORM_NAME};
    float lit_intensity     = clamp(${AMBIENT_STRENGTH_UNIFORM_NAME} + diffuse_intensity, 0.0, 1.0);
    float light_intensity   = mix(1.0, lit_intensity, ${LIGHTING_ENABLED_UNIFORM_NAME});
    outColor                = vec4(${COLOR_UNIFORM_NAME} * light_intensity, ${OPACITY_UNIFORM_NAME});
}
`;

/**
 * Options used by `LambertMaterial`.
 *
 * @typedef {Object} LambertMaterialOptions
 * @property {Float32Array | number[]} [color]          - Diffuse RGB color [red, green, blue] in [0..1] range.
 * @property {Float32Array | number[]} [lightDirection] - Directional light direction (world space), normalized internally.
 * @property {number} [ambientStrength]                 - Ambient term multiplier.
 */

/**
 * Simple Lambert (diffuse) material with one directional light.
 *
 * This material expects geometry to provide normals at attribute location 3.
 */
export class LambertMaterial extends DirectionalLightMaterial {

    /**
     * Creates a new `LambertMaterial`.
     *
     * @param {WebGL2RenderingContext} webglContext - WebGL2 rendering context used to compile shaders.
     * @param {LambertMaterialOptions} [options]    - Material options.
     */
    constructor(webglContext, options = {}) {
        if (options === null || typeof options !== 'object' || Array.isArray(options)) {
            throw new TypeError('`LambertMaterial` expects an options object (plain object).');
        }

        const shaderProgram = new ShaderProgram(webglContext, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
        super(webglContext, shaderProgram, options, { ownsShaderProgram: true });
    }
}