UNPKG

@luma.gl/shadertools

Version:

Shader module system for luma.gl

200 lines 6.81 kB
// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { log } from '@luma.gl/core'; import { lightingUniformsGLSL } from "./lighting-glsl.js"; import { lightingUniformsWGSL } from "./lighting-wgsl.js"; import { normalizeByteColor3, resolveUseByteColors } from "../../../lib/color/normalize-byte-colors.js"; /** Max number of supported lights (in addition to ambient light */ const MAX_LIGHTS = 5; const LIGHT_UNIFORM_TYPE = { color: 'vec3<f32>', position: 'vec3<f32>', direction: 'vec3<f32>', attenuation: 'vec3<f32>', coneCos: 'vec2<f32>' }; /** * Portable lighting shader module shared by the Phong, Gouraud, and PBR material modules. * * The public JavaScript API accepts `lights: Light[]`, while the uniform buffer packs * non-ambient lights into a fixed-size trailing array for portability across WebGL2 and WebGPU. */ export const lighting = { props: {}, uniforms: {}, name: 'lighting', defines: { // MAX_LIGHTS }, uniformTypes: { enabled: 'i32', directionalLightCount: 'i32', pointLightCount: 'i32', spotLightCount: 'i32', ambientColor: 'vec3<f32>', lights: [LIGHT_UNIFORM_TYPE, MAX_LIGHTS] }, defaultUniforms: createDefaultLightingUniforms(), bindingLayout: [{ name: 'lighting', group: 2 }], firstBindingSlot: 0, source: lightingUniformsWGSL, vs: lightingUniformsGLSL, fs: lightingUniformsGLSL, getUniforms }; function getUniforms(props, _prevUniforms = {}) { // Copy props so we can modify props = props ? { ...props } : props; // TODO legacy if (!props) { return createDefaultLightingUniforms(); } // Support for array of lights. Type of light is detected by type field if (props.lights) { props = { ...props, ...extractLightTypes(props.lights), lights: undefined }; } // Specify lights separately const { useByteColors, ambientLight, pointLights, spotLights, directionalLights } = props || {}; const hasLights = ambientLight || (pointLights && pointLights.length > 0) || (spotLights && spotLights.length > 0) || (directionalLights && directionalLights.length > 0); // TODO - this may not be the correct decision if (!hasLights) { return { ...createDefaultLightingUniforms(), enabled: 0 }; } const uniforms = { ...createDefaultLightingUniforms(), ...getLightSourceUniforms({ useByteColors, ambientLight, pointLights, spotLights, directionalLights }) }; if (props.enabled !== undefined) { uniforms.enabled = props.enabled ? 1 : 0; } return uniforms; } function getLightSourceUniforms({ useByteColors, ambientLight, pointLights = [], spotLights = [], directionalLights = [] }) { const lights = createDefaultLightUniforms(); let currentLight = 0; let pointLightCount = 0; let spotLightCount = 0; let directionalLightCount = 0; for (const pointLight of pointLights) { if (currentLight >= MAX_LIGHTS) { break; } lights[currentLight] = { ...lights[currentLight], color: convertColor(pointLight, useByteColors), position: pointLight.position, attenuation: pointLight.attenuation || [1, 0, 0] }; currentLight++; pointLightCount++; } for (const spotLight of spotLights) { if (currentLight >= MAX_LIGHTS) { break; } lights[currentLight] = { ...lights[currentLight], color: convertColor(spotLight, useByteColors), position: spotLight.position, direction: spotLight.direction, attenuation: spotLight.attenuation || [1, 0, 0], coneCos: getSpotConeCos(spotLight) }; currentLight++; spotLightCount++; } for (const directionalLight of directionalLights) { if (currentLight >= MAX_LIGHTS) { break; } lights[currentLight] = { ...lights[currentLight], color: convertColor(directionalLight, useByteColors), direction: directionalLight.direction }; currentLight++; directionalLightCount++; } if (pointLights.length + spotLights.length + directionalLights.length > MAX_LIGHTS) { log.warn(`MAX_LIGHTS exceeded, truncating to ${MAX_LIGHTS}`)(); } return { ambientColor: convertColor(ambientLight, useByteColors), directionalLightCount, pointLightCount, spotLightCount, lights }; } function extractLightTypes(lights) { const lightSources = { pointLights: [], spotLights: [], directionalLights: [] }; for (const light of lights || []) { switch (light.type) { case 'ambient': // Note: Only uses last ambient light // TODO - add ambient light sources on CPU? lightSources.ambientLight = light; break; case 'directional': lightSources.directionalLights?.push(light); break; case 'point': lightSources.pointLights?.push(light); break; case 'spot': lightSources.spotLights?.push(light); break; default: // eslint-disable-next-line // console.warn(light.type); } } return lightSources; } /** Take color 0-255 and intensity as input and output 0.0-1.0 range */ function convertColor(colorDef = {}, useByteColors) { const { color = [0, 0, 0], intensity = 1.0 } = colorDef; const normalizedColor = normalizeByteColor3(color, resolveUseByteColors(useByteColors, true)); return normalizedColor.map(component => component * intensity); } function createDefaultLightingUniforms() { return { enabled: 1, directionalLightCount: 0, pointLightCount: 0, spotLightCount: 0, ambientColor: [0.1, 0.1, 0.1], lights: createDefaultLightUniforms() }; } function createDefaultLightUniforms() { return Array.from({ length: MAX_LIGHTS }, () => createDefaultLightUniform()); } function createDefaultLightUniform() { return { color: [1, 1, 1], position: [1, 1, 2], direction: [1, 1, 1], attenuation: [1, 0, 0], coneCos: [1, 0] }; } function getSpotConeCos(spotLight) { const innerConeAngle = spotLight.innerConeAngle ?? 0; const outerConeAngle = spotLight.outerConeAngle ?? Math.PI / 4; return [Math.cos(innerConeAngle), Math.cos(outerConeAngle)]; } //# sourceMappingURL=lighting.js.map