playcanvas
Version:
PlayCanvas WebGL game engine
178 lines (175 loc) • 7.75 kB
JavaScript
import { Vec3 } from '../../core/math/vec3.js';
import { SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL, PIXELFORMAT_RGBA32F, FILTER_NEAREST, TEXTURETYPE_DEFAULT, ADDRESS_CLAMP_TO_EDGE } from '../../platform/graphics/constants.js';
import { FloatPacking } from '../../core/math/float-packing.js';
import { LIGHTTYPE_SPOT, LIGHTSHAPE_PUNCTUAL, LIGHT_COLOR_DIVIDER, LIGHTSHAPE_SPHERE, LIGHTSHAPE_DISK, LIGHTSHAPE_RECT } from '../constants.js';
import { Texture } from '../../platform/graphics/texture.js';
import { LightCamera } from '../renderer/light-camera.js';
import { ShaderChunks } from '../shader-lib/shader-chunks.js';
const tempVec3 = new Vec3();
const tempAreaLightSizes = new Float32Array(6);
const areaHalfAxisWidth = new Vec3(-0.5, 0, 0);
const areaHalfAxisHeight = new Vec3(0, 0, 0.5);
const TextureIndexFloat = {
POSITION_RANGE: 0,
DIRECTION_FLAGS: 1,
COLOR_ANGLES_BIAS: 2,
PROJ_MAT_0: 3,
ATLAS_VIEWPORT: 3,
PROJ_MAT_1: 4,
PROJ_MAT_2: 5,
PROJ_MAT_3: 6,
AREA_DATA_WIDTH: 7,
AREA_DATA_HEIGHT: 8,
COUNT: 9
};
const enums = {
'LIGHTSHAPE_PUNCTUAL': `${LIGHTSHAPE_PUNCTUAL}u`,
'LIGHTSHAPE_RECT': `${LIGHTSHAPE_RECT}u`,
'LIGHTSHAPE_DISK': `${LIGHTSHAPE_DISK}u`,
'LIGHTSHAPE_SPHERE': `${LIGHTSHAPE_SPHERE}u`,
'LIGHT_COLOR_DIVIDER': `${LIGHT_COLOR_DIVIDER}.0`
};
const buildShaderDefines = (object, prefix)=>{
return Object.keys(object).map((key)=>`#define {${prefix}${key}} ${object[key]}`).join('\n');
};
const lightBufferDefines = `\n
${buildShaderDefines(TextureIndexFloat, 'CLUSTER_TEXTURE_')}
${buildShaderDefines(enums, '')}
`;
class LightsBuffer {
constructor(device){
this.areaLightsEnabled = false;
this.device = device;
ShaderChunks.get(device, SHADERLANGUAGE_GLSL).set('lightBufferDefinesPS', lightBufferDefines);
ShaderChunks.get(device, SHADERLANGUAGE_WGSL).set('lightBufferDefinesPS', lightBufferDefines);
this.cookiesEnabled = false;
this.shadowsEnabled = false;
this.areaLightsEnabled = false;
this.maxLights = 255;
const pixelsPerLightFloat = TextureIndexFloat.COUNT;
this.lightsFloat = new Float32Array(4 * pixelsPerLightFloat * this.maxLights);
this.lightsUint = new Uint32Array(this.lightsFloat.buffer);
this.lightsTexture = this.createTexture(this.device, pixelsPerLightFloat, this.maxLights, PIXELFORMAT_RGBA32F, 'LightsTexture');
this._lightsTextureId = this.device.scope.resolve('lightsTexture');
this.invMaxColorValue = 0;
this.invMaxAttenuation = 0;
this.boundsMin = new Vec3();
this.boundsDelta = new Vec3();
}
destroy() {
this.lightsTexture?.destroy();
this.lightsTexture = null;
}
createTexture(device, width, height, format, name) {
const tex = new Texture(device, {
name: name,
width: width,
height: height,
mipmaps: false,
format: format,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE,
type: TEXTURETYPE_DEFAULT,
magFilter: FILTER_NEAREST,
minFilter: FILTER_NEAREST,
anisotropy: 1
});
return tex;
}
setBounds(min, delta) {
this.boundsMin.copy(min);
this.boundsDelta.copy(delta);
}
uploadTextures() {
this.lightsTexture.lock().set(this.lightsFloat);
this.lightsTexture.unlock();
}
updateUniforms() {
this._lightsTextureId.setValue(this.lightsTexture);
}
getSpotDirection(direction, spot) {
const mat = spot._node.getWorldTransform();
mat.getY(direction).mulScalar(-1);
direction.normalize();
}
getLightAreaSizes(light) {
const mat = light._node.getWorldTransform();
mat.transformVector(areaHalfAxisWidth, tempVec3);
tempAreaLightSizes[0] = tempVec3.x;
tempAreaLightSizes[1] = tempVec3.y;
tempAreaLightSizes[2] = tempVec3.z;
mat.transformVector(areaHalfAxisHeight, tempVec3);
tempAreaLightSizes[3] = tempVec3.x;
tempAreaLightSizes[4] = tempVec3.y;
tempAreaLightSizes[5] = tempVec3.z;
return tempAreaLightSizes;
}
addLightData(light, lightIndex) {
const isSpot = light._type === LIGHTTYPE_SPOT;
const hasAtlasViewport = light.atlasViewportAllocated;
const isCookie = this.cookiesEnabled && !!light._cookie && hasAtlasViewport;
const isArea = this.areaLightsEnabled && light.shape !== LIGHTSHAPE_PUNCTUAL;
const castShadows = this.shadowsEnabled && light.castShadows && hasAtlasViewport;
const pos = light._node.getPosition();
let lightProjectionMatrix = null;
let atlasViewport = null;
if (isSpot) {
if (castShadows) {
const lightRenderData = light.getRenderData(null, 0);
lightProjectionMatrix = lightRenderData.shadowMatrix;
} else if (isCookie) {
lightProjectionMatrix = LightCamera.evalSpotCookieMatrix(light);
}
} else {
if (castShadows || isCookie) {
atlasViewport = light.atlasViewport;
}
}
const dataFloat = this.lightsFloat;
const dataUint = this.lightsUint;
const dataFloatStart = lightIndex * this.lightsTexture.width * 4;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 0] = pos.x;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 1] = pos.y;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 2] = pos.z;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.POSITION_RANGE + 3] = light.attenuationEnd;
const clusteredData = light.clusteredData;
dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 0] = clusteredData[0];
dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 1] = clusteredData[1];
dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 2] = clusteredData[2];
if (light.castShadows) {
const lightRenderData = light.getRenderData(null, 0);
const biases = light._getUniformBiasValues(lightRenderData);
const biasHalf = FloatPacking.float2Half(biases.bias);
const normalBiasHalf = FloatPacking.float2Half(biases.normalBias);
dataUint[dataFloatStart + 4 * TextureIndexFloat.COLOR_ANGLES_BIAS + 3] = biasHalf | normalBiasHalf << 16;
}
if (isSpot) {
this.getSpotDirection(tempVec3, light);
dataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 0] = tempVec3.x;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 1] = tempVec3.y;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 2] = tempVec3.z;
}
dataUint[dataFloatStart + 4 * TextureIndexFloat.DIRECTION_FLAGS + 3] = light.getClusteredFlags(castShadows, isCookie);
if (lightProjectionMatrix) {
const matData = lightProjectionMatrix.data;
for(let m = 0; m < 16; m++){
dataFloat[dataFloatStart + 4 * TextureIndexFloat.PROJ_MAT_0 + m] = matData[m];
}
}
if (atlasViewport) {
dataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 0] = atlasViewport.x;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 1] = atlasViewport.y;
dataFloat[dataFloatStart + 4 * TextureIndexFloat.ATLAS_VIEWPORT + 2] = atlasViewport.z / 3;
}
if (isArea) {
const areaSizes = this.getLightAreaSizes(light);
dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 0] = areaSizes[0];
dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 1] = areaSizes[1];
dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_WIDTH + 2] = areaSizes[2];
dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 0] = areaSizes[3];
dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 1] = areaSizes[4];
dataFloat[dataFloatStart + 4 * TextureIndexFloat.AREA_DATA_HEIGHT + 2] = areaSizes[5];
}
}
}
export { LightsBuffer };