playcanvas
Version:
PlayCanvas WebGL game engine
127 lines (124 loc) • 3.87 kB
JavaScript
import { PIXELFORMAT_R32F, PIXELFORMAT_R16F, pixelFormatInfo, FILTER_LINEAR, FILTER_NEAREST, ADDRESS_CLAMP_TO_EDGE, FUNC_LESS } from '../../platform/graphics/constants.js';
import { RenderTarget } from '../../platform/graphics/render-target.js';
import { Texture } from '../../platform/graphics/texture.js';
import { LIGHTTYPE_OMNI, shadowTypeInfo, SHADOW_VSM_32F, SHADOW_PCSS_32F } from '../constants.js';
class ShadowMap {
constructor(texture, targets){
this.texture = texture;
this.cached = false;
this.renderTargets = targets;
}
destroy() {
if (this.texture) {
this.texture.destroy();
this.texture = null;
}
const targets = this.renderTargets;
for(let i = 0; i < targets.length; i++){
targets[i].destroy();
}
this.renderTargets.length = 0;
}
static create(device, light) {
let shadowMap = null;
if (light._type === LIGHTTYPE_OMNI) {
shadowMap = this.createCubemap(device, light._shadowResolution, light._shadowType);
} else {
shadowMap = this.create2dMap(device, light._shadowResolution, light._shadowType);
}
return shadowMap;
}
static createAtlas(device, resolution, shadowType) {
const shadowMap = this.create2dMap(device, resolution, shadowType);
const targets = shadowMap.renderTargets;
const rt = targets[0];
for(let i = 0; i < 5; i++){
targets.push(rt);
}
return shadowMap;
}
static create2dMap(device, size, shadowType) {
const shadowInfo = shadowTypeInfo.get(shadowType);
let format = shadowInfo.format;
if (format === PIXELFORMAT_R32F && !device.textureFloatRenderable && device.textureHalfFloatRenderable) {
format = PIXELFORMAT_R16F;
}
const formatName = pixelFormatInfo.get(format)?.name;
let filter = FILTER_LINEAR;
if (shadowType === SHADOW_VSM_32F) {
filter = device.extTextureFloatLinear ? FILTER_LINEAR : FILTER_NEAREST;
}
if (shadowType === SHADOW_PCSS_32F) {
filter = FILTER_NEAREST;
}
const texture = new Texture(device, {
format: format,
width: size,
height: size,
mipmaps: false,
minFilter: filter,
magFilter: filter,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE,
name: `ShadowMap2D_${formatName}`
});
let target = null;
if (shadowInfo?.pcf) {
texture.compareOnRead = true;
texture.compareFunc = FUNC_LESS;
target = new RenderTarget({
depthBuffer: texture
});
} else {
target = new RenderTarget({
colorBuffer: texture,
depth: true
});
}
if (device.isWebGPU) {
target.flipY = true;
}
return new ShadowMap(texture, [
target
]);
}
static createCubemap(device, size, shadowType) {
const shadowInfo = shadowTypeInfo.get(shadowType);
const formatName = pixelFormatInfo.get(shadowInfo.format)?.name;
const isPcss = shadowType === SHADOW_PCSS_32F;
const filter = isPcss ? FILTER_NEAREST : FILTER_LINEAR;
const cubemap = new Texture(device, {
format: shadowInfo?.format,
width: size,
height: size,
cubemap: true,
mipmaps: false,
minFilter: filter,
magFilter: filter,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE,
name: `ShadowMapCube_${formatName}`
});
if (!isPcss) {
cubemap.compareOnRead = true;
cubemap.compareFunc = FUNC_LESS;
}
const targets = [];
for(let i = 0; i < 6; i++){
if (isPcss) {
targets.push(new RenderTarget({
colorBuffer: cubemap,
face: i,
depth: true
}));
} else {
targets.push(new RenderTarget({
depthBuffer: cubemap,
face: i
}));
}
}
return new ShadowMap(cubemap, targets);
}
}
export { ShadowMap };