@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
221 lines (162 loc) • 6.38 kB
JavaScript
import {
Color,
LinearFilter,
NearestFilter,
RGBAFormat,
Scene,
ShaderMaterial,
Vector2,
VSMShadowMap,
WebGLRenderTarget
} from "three";
import Signal from "../../../core/events/signal/Signal.js";
import { fragment as vsm_frag, vertex as vsm_vert } from "./vsm.glsl.js";
import { renderScreenSpace } from "../render/utils/renderScreenSpace.js";
const shadowMaterialVertical = new ShaderMaterial({
uniforms: {
shadow_pass: { value: null },
resolution: { value: new Vector2() },
radius: { value: 4.0 },
samples: { value: 8.0 }
},
defines: {
VSM_SAMPLES: 8, // in 'defines' since r134
},
vertexShader: vsm_vert,
fragmentShader: vsm_frag
});
const shadowMaterialHorizontal = shadowMaterialVertical.clone();
shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
const scratch_color = new Color();
export class ShadowMapRenderer {
constructor() {
/**
*
* @type {WebGLRenderer|null}
* @private
*/
this.__renderer = null;
/**
*
* @type {Signal}
*/
this.onBeforeRender = new Signal();
}
/**
*
* @param {THREE.WebGLRenderer} renderer
* @param {LightShadow} shadow
* @param {Scene} scene
* @param {Light} light
*/
update(renderer, shadow, scene, light) {
//
const shadow_camera = shadow.camera;
// this.__scene = scene;
this.__renderer = renderer;
this.__ensure_map(shadow, renderer);
// draw scene from perspective of shadow camera into the render texture
const __rt = renderer.getRenderTarget();
const __acf = renderer.getActiveCubeFace();
const __aml = renderer.getActiveMipmapLevel();
const __scissor = renderer.getScissorTest();
renderer.getClearColor(scratch_color);
const __clear_alpha = renderer.getClearAlpha();
const __autoClearColor = renderer.autoClearColor;
const __autoClearDepth = renderer.autoClearDepth;
const __autoClearStencil = renderer.autoClearStencil;
const __sortObjects = renderer.sortObjects;
const target = shadow.map;
renderer.setRenderTarget(target);
renderer.setClearColor(0xFFFFFF, 1);
renderer.setScissorTest(false);
renderer.autoClearColor = false;
renderer.autoClearDepth = false;
renderer.autoClearStencil = false;
// disable sorting, unnecessary since fragment cost is very low and sorting is expensive for large number of objects
renderer.sortObjects = false;
renderer.clear(true, true, false);
shadow.updateMatrices(light, 0);
this.onBeforeRender.send3(renderer, shadow_camera, scene);
if (target.width <= 0 || target.height <= 0) {
// no point in rendering
} else {
// do the draw
renderer.render(scene, shadow_camera);
if (renderer.shadowMap.type === VSMShadowMap) {
this.__do_vsm_pass(shadow);
}
}
// restore state
renderer.setRenderTarget(__rt, __acf, __aml);
renderer.setScissorTest(__scissor);
renderer.setClearColor(scratch_color, __clear_alpha);
renderer.autoClearColor = __autoClearColor;
renderer.autoClearDepth = __autoClearDepth;
renderer.autoClearStencil = __autoClearStencil;
renderer.sortObjects = __sortObjects;
// increment frame index to signal to the renderer that geometries might need to be updated
renderer.info.render.frame++;
}
/**
*
* @param {LightShadow} shadow
* @private
*/
__do_vsm_pass(shadow) {
const _renderer = this.__renderer;
if (shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples) {
shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples;
shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples;
shadowMaterialVertical.needsUpdate = true;
shadowMaterialHorizontal.needsUpdate = true;
}
// vertical pass
const vertical = shadowMaterialVertical.uniforms;
vertical.shadow_pass.value = shadow.map.texture;
vertical.resolution.value = shadow.mapSize;
vertical.radius.value = shadow.radius;
vertical.samples.value = shadow.blurSamples;
_renderer.setRenderTarget(shadow.mapPass);
_renderer.clear();
renderScreenSpace(_renderer, shadowMaterialVertical);
// horizontal pass
const horizontal = shadowMaterialHorizontal.uniforms;
horizontal.shadow_pass.value = shadow.mapPass.texture;
horizontal.resolution.value = shadow.mapSize;
horizontal.radius.value = shadow.radius;
horizontal.samples.value = shadow.blurSamples;
_renderer.setRenderTarget(shadow.map);
_renderer.clear();
renderScreenSpace(_renderer, shadowMaterialHorizontal);
}
/**
*
* @param {LightShadow} shadow
* @param {THREE.WebGLRenderer} renderer
* @private
*/
__ensure_map(shadow, renderer) {
const _shadowMapSize = shadow.mapSize;
if (shadow.map === null) {
const pars = {
format: RGBAFormat,
scissorTest: false,
stencilBuffer: false
};
if (!shadow.isPointLightShadow && renderer.shadowMap.type === VSMShadowMap) {
pars.minFilter = LinearFilter;
pars.magFilter = LinearFilter;
shadow.mapPass = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, Object.assign({
depthBuffer: false
}, pars));
} else {
pars.minFilter = NearestFilter;
pars.magFilter = NearestFilter;
}
shadow.map = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars);
shadow.map.texture.name = '.shadowMap';
shadow.camera.updateProjectionMatrix();
}
}
}