@animech-public/playcanvas
Version:
PlayCanvas WebGL game engine
127 lines (124 loc) • 4.12 kB
JavaScript
import { PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA16F, PIXELFORMAT_DEPTH, PIXELFORMAT_R32F, PIXELFORMAT_RGBA8, FILTER_NEAREST, FILTER_LINEAR, TEXHINT_SHADOWMAP, 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 { SHADOW_VSM32, SHADOW_VSM16, SHADOW_PCF5, SHADOW_PCF1, SHADOW_PCF3, SHADOW_PCSS, LIGHTTYPE_OMNI } 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 getShadowFormat(device, shadowType) {
if (shadowType === SHADOW_VSM32) {
return PIXELFORMAT_RGBA32F;
} else if (shadowType === SHADOW_VSM16) {
return PIXELFORMAT_RGBA16F;
} else if (shadowType === SHADOW_PCF5) {
return PIXELFORMAT_DEPTH;
} else if ((shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3) && device.supportsDepthShadow) {
return PIXELFORMAT_DEPTH;
} else if (shadowType === SHADOW_PCSS && !device.isWebGL1) {
return PIXELFORMAT_R32F;
}
return PIXELFORMAT_RGBA8;
}
static getShadowFiltering(device, shadowType) {
if ((shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3 || shadowType === SHADOW_PCSS) && !device.supportsDepthShadow) {
return FILTER_NEAREST;
} else if (shadowType === SHADOW_VSM32) {
return device.extTextureFloatLinear ? FILTER_LINEAR : FILTER_NEAREST;
} else if (shadowType === SHADOW_VSM16) {
return device.extTextureHalfFloatLinear ? FILTER_LINEAR : FILTER_NEAREST;
}
return FILTER_LINEAR;
}
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 format = this.getShadowFormat(device, shadowType);
const filter = this.getShadowFiltering(device, shadowType);
const texture = new Texture(device, {
profilerHint: TEXHINT_SHADOWMAP,
format: format,
width: size,
height: size,
mipmaps: false,
minFilter: filter,
magFilter: filter,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE,
name: 'ShadowMap2D'
});
let target = null;
if (shadowType === SHADOW_PCF5 || (shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3) && device.supportsDepthShadow) {
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 format = shadowType === SHADOW_PCSS && !device.isWebGL1 ? PIXELFORMAT_R32F : PIXELFORMAT_RGBA8;
const cubemap = new Texture(device, {
profilerHint: TEXHINT_SHADOWMAP,
format: format,
width: size,
height: size,
cubemap: true,
mipmaps: false,
minFilter: FILTER_NEAREST,
magFilter: FILTER_NEAREST,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE,
name: 'ShadowMapCube'
});
const targets = [];
for (let i = 0; i < 6; i++) {
const target = new RenderTarget({
colorBuffer: cubemap,
face: i,
depth: true
});
targets.push(target);
}
return new ShadowMap(cubemap, targets);
}
}
export { ShadowMap };