UNPKG

@luma.gl/shadertools

Version:

Shader module system for luma.gl

258 lines (214 loc) 7.14 kB
// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {log} from '@luma.gl/core'; import {ShaderModule} from '../../../lib/shader-module/shader-module'; import {lightingUniformsGLSL} from './lighting-uniforms-glsl'; import {lightingUniformsWGSL} from './lighting-uniforms-wgsl'; import type {NumberArray3} from '@math.gl/core'; /** Max number of supported lights (in addition to ambient light */ const MAX_LIGHTS = 3; /** Whether to divide */ const COLOR_FACTOR = 255.0; /** Shader type field for lights */ // eslint-disable-next-line no-shadow export enum LIGHT_TYPE { POINT = 0, DIRECTIONAL = 1 } /** Lighting helper types */ export type Light = AmbientLight | PointLight | DirectionalLight; export type AmbientLight = { type: 'ambient'; color?: Readonly<NumberArray3>; intensity?: number; }; export type PointLight = { type: 'point'; position: Readonly<NumberArray3>; color?: Readonly<NumberArray3>; intensity?: number; attenuation?: Readonly<NumberArray3>; }; export type DirectionalLight = { type: 'directional'; direction: Readonly<NumberArray3>; color?: Readonly<NumberArray3>; intensity?: number; }; export type LightingProps = { enabled?: boolean; lights?: Light[]; /** @deprecated */ ambientLight?: AmbientLight; /** @deprecated */ pointLights?: PointLight[]; /** @deprecated */ directionalLights?: DirectionalLight[]; }; export type LightingUniforms = { enabled: number; ambientLightColor: Readonly<NumberArray3>; directionalLightCount: number; pointLightCount: number; lightType: number; // []; lightColor0: Readonly<NumberArray3>; lightPosition0: Readonly<NumberArray3>; lightDirection0: Readonly<NumberArray3>; lightAttenuation0: Readonly<NumberArray3>; lightColor1: Readonly<NumberArray3>; lightPosition1: Readonly<NumberArray3>; lightDirection1: Readonly<NumberArray3>; lightAttenuation1: Readonly<NumberArray3>; lightColor2: Readonly<NumberArray3>; lightPosition2: Readonly<NumberArray3>; lightDirection2: Readonly<NumberArray3>; lightAttenuation2: Readonly<NumberArray3>; }; /** UBO ready lighting module */ export const lighting = { props: {} as LightingProps, uniforms: {} as LightingUniforms, name: 'lighting', defines: { MAX_LIGHTS }, uniformTypes: { enabled: 'i32', lightType: 'i32', directionalLightCount: 'i32', pointLightCount: 'i32', ambientLightColor: 'vec3<f32>', // TODO define as arrays once we have appropriate uniformTypes lightColor0: 'vec3<f32>', lightPosition0: 'vec3<f32>', // TODO - could combine direction and attenuation lightDirection0: 'vec3<f32>', lightAttenuation0: 'vec3<f32>', lightColor1: 'vec3<f32>', lightPosition1: 'vec3<f32>', lightDirection1: 'vec3<f32>', lightAttenuation1: 'vec3<f32>', lightColor2: 'vec3<f32>', lightPosition2: 'vec3<f32>', lightDirection2: 'vec3<f32>', lightAttenuation2: 'vec3<f32>' }, defaultUniforms: { enabled: 1, lightType: LIGHT_TYPE.POINT, directionalLightCount: 0, pointLightCount: 0, ambientLightColor: [0.1, 0.1, 0.1], lightColor0: [1, 1, 1], lightPosition0: [1, 1, 2], // TODO - could combine direction and attenuation lightDirection0: [1, 1, 1], lightAttenuation0: [1, 0, 0], lightColor1: [1, 1, 1], lightPosition1: [1, 1, 2], lightDirection1: [1, 1, 1], lightAttenuation1: [1, 0, 0], lightColor2: [1, 1, 1], lightPosition2: [1, 1, 2], lightDirection2: [1, 1, 1], lightAttenuation2: [1, 0, 0] }, source: lightingUniformsWGSL, vs: lightingUniformsGLSL, fs: lightingUniformsGLSL, getUniforms } as const satisfies ShaderModule<LightingProps, LightingUniforms, {}>; function getUniforms( props?: LightingProps, prevUniforms: Partial<LightingUniforms> = {} ): LightingUniforms { // Copy props so we can modify props = props ? {...props} : props; // TODO legacy if (!props) { return {...lighting.defaultUniforms}; } // 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 {ambientLight, pointLights, directionalLights} = props || {}; const hasLights = ambientLight || (pointLights && pointLights.length > 0) || (directionalLights && directionalLights.length > 0); // TODO - this may not be the correct decision if (!hasLights) { return {...lighting.defaultUniforms, enabled: 0}; } const uniforms = { ...lighting.defaultUniforms, ...prevUniforms, ...getLightSourceUniforms({ambientLight, pointLights, directionalLights}) }; if (props.enabled !== undefined) { uniforms.enabled = props.enabled ? 1 : 0; } return uniforms; } function getLightSourceUniforms({ ambientLight, pointLights = [], directionalLights = [] }: LightingProps): Partial<LightingUniforms> { const lightSourceUniforms: Partial<LightingUniforms> = {}; lightSourceUniforms.ambientLightColor = convertColor(ambientLight); let currentLight: 0 | 1 | 2 = 0; for (const pointLight of pointLights) { lightSourceUniforms.lightType = LIGHT_TYPE.POINT; const i = currentLight as 0 | 1 | 2; lightSourceUniforms[`lightColor${i}`] = convertColor(pointLight); lightSourceUniforms[`lightPosition${i}`] = pointLight.position; lightSourceUniforms[`lightAttenuation${i}`] = pointLight.attenuation || [1, 0, 0]; currentLight++; } for (const directionalLight of directionalLights) { lightSourceUniforms.lightType = LIGHT_TYPE.DIRECTIONAL; const i = currentLight as 0 | 1 | 2; lightSourceUniforms[`lightColor${i}`] = convertColor(directionalLight); lightSourceUniforms[`lightDirection${i}`] = directionalLight.direction; currentLight++; } if (currentLight > MAX_LIGHTS) { log.warn('MAX_LIGHTS exceeded')(); } lightSourceUniforms.directionalLightCount = directionalLights.length; lightSourceUniforms.pointLightCount = pointLights.length; return lightSourceUniforms; } function extractLightTypes(lights: Light[]): LightingProps { const lightSources: LightingProps = {pointLights: [], 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; 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: {color?: Readonly<NumberArray3>; intensity?: number} = {} ): NumberArray3 { const {color = [0, 0, 0], intensity = 1.0} = colorDef; return color.map(component => (component * intensity) / COLOR_FACTOR) as NumberArray3; }