playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
140 lines (139 loc) • 3.68 kB
JavaScript
import {
ADDRESS_CLAMP_TO_EDGE,
FILTER_LINEAR,
FILTER_NEAREST,
FUNC_LESS,
PIXELFORMAT_R32F,
PIXELFORMAT_R16F,
pixelFormatInfo,
TEXHINT_SHADOWMAP
} from "../../platform/graphics/constants.js";
import { RenderTarget } from "../../platform/graphics/render-target.js";
import { Texture } from "../../platform/graphics/texture.js";
import {
LIGHTTYPE_OMNI,
SHADOW_VSM_32F,
SHADOW_PCSS_32F,
shadowTypeInfo
} 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;
}
// creates a shadow map which is used by the light texture atlas for clustered lighting
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,
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
};