import Base from '../core/Base';
import glenum from '../core/glenum';
import Vector3 from '../math/Vector3';
import BoundingBox from '../math/BoundingBox';
import Frustum from '../math/Frustum';
import Matrix4 from '../math/Matrix4';
import Renderer from '../Renderer';
import Shader from '../Shader';
import Material from '../Material';
import FrameBuffer from '../FrameBuffer';
import Texture from '../Texture';
import Texture2D from '../Texture2D';
import TextureCube from '../TextureCube';
import PerspectiveCamera from '../camera/Perspective';
import OrthoCamera from '../camera/Orthographic';
import Pass from '../compositor/Pass';
import TexturePool from '../compositor/TexturePool';
import mat4 from '../glmatrix/mat4';
var targets = ['px', 'nx', 'py', 'ny', 'pz', 'nz'];
import shadowmapEssl from '../shader/source/shadowmap.glsl.js';
Shader['import'](shadowmapEssl);
function getDepthMaterialUniform(renderable, depthMaterial, symbol) {
if (symbol === 'alphaMap') {
return renderable.material.get('diffuseMap');
}
else if (symbol === 'alphaCutoff') {
if (renderable.material.isDefined('fragment', 'ALPHA_TEST')
&& renderable.material.get('diffuseMap')
) {
var alphaCutoff = renderable.material.get('alphaCutoff');
return alphaCutoff || 0;
}
return 0;
}
else if (symbol === 'uvRepeat') {
return renderable.material.get('uvRepeat');
}
else if (symbol === 'uvOffset') {
return renderable.material.get('uvOffset');
}
else {
return depthMaterial.get(symbol);
}
}
function isDepthMaterialChanged(renderable, prevRenderable) {
var matA = renderable.material;
var matB = prevRenderable.material;
return matA.get('diffuseMap') !== matB.get('diffuseMap')
|| (matA.get('alphaCutoff') || 0) !== (matB.get('alphaCutoff') || 0);
}
/**
* Pass rendering shadow map.
*
* @constructor clay.prePass.ShadowMap
* @extends clay.core.Base
* @example
* var shadowMapPass = new clay.prePass.ShadowMap({
* softShadow: clay.prePass.ShadowMap.VSM
* });
* ...
* animation.on('frame', function (frameTime) {
* shadowMapPass.render(renderer, scene, camera);
* renderer.render(scene, camera);
* });
*/
var ShadowMapPass = Base.extend(function () {
return /** @lends clay.prePass.ShadowMap# */ {
/**
* Soft shadow technique.
* Can be {@link clay.prePass.ShadowMap.PCF} or {@link clay.prePass.ShadowMap.VSM}
* @type {number}
*/
softShadow: ShadowMapPass.PCF,
/**
* Soft shadow blur size
* @type {number}
*/
shadowBlur: 1.0,
lightFrustumBias: 'auto',
kernelPCF: new Float32Array([
1, 0,
1, 1,
-1, 1,
0, 1,
-1, 0,
-1, -1,
1, -1,
0, -1
]),
precision: 'highp',
_lastRenderNotCastShadow: false,
_frameBuffer: new FrameBuffer(),
_textures: {},
_shadowMapNumber: {
'POINT_LIGHT': 0,
'DIRECTIONAL_LIGHT': 0,
'SPOT_LIGHT': 0
},
_depthMaterials: {},
_distanceMaterials: {},
_receivers: [],
_lightsCastShadow: [],
_lightCameras: {},
_lightMaterials: {},
_texturePool: new TexturePool()
};
}, function () {
// Gaussian filter pass for VSM
this._gaussianPassH = new Pass({
fragment: Shader.source('clay.compositor.gaussian_blur')
});
this._gaussianPassV = new Pass({
fragment: Shader.source('clay.compositor.gaussian_blur')
});
this._gaussianPassH.setUniform('blurSize', this.shadowBlur);
this._gaussianPassH.setUniform('blurDir', 0.0);
this._gaussianPassV.setUniform('blurSize', this.shadowBlur);
this._gaussianPassV.setUniform('blurDir', 1.0);
this._outputDepthPass = new Pass({
fragment: Shader.source('clay.sm.debug_depth')
});
}, {
/**
* Render scene to shadow textures
* @param {clay.Renderer} renderer
* @param {clay.Scene} scene
* @param {clay.Camera} sceneCamera
* @param {boolean} [notUpdateScene=false]
* @memberOf clay.prePass.ShadowMap.prototype
*/
render: function (renderer, scene, sceneCamera, notUpdateScene) {
if (!sceneCamera) {
sceneCamera = scene.getMainCamera();
}
this.trigger('beforerender', this, renderer, scene, sceneCamera);
this._renderShadowPass(renderer, scene, sceneCamera, notUpdateScene);
this.trigger('afterrender', this, renderer, scene, sceneCamera);
},
/**
* Debug rendering of shadow textures
* @param {clay.Renderer} renderer
* @param {number} size
* @memberOf clay.prePass.ShadowMap.prototype
*/
renderDebug: function (renderer, size) {
renderer.saveClear();
var viewport = renderer.viewport;
var x = 0, y = 0;
var width = size || viewport.width / 4;
var height = width;
if (this.softShadow === ShadowMapPass.VSM) {
this._outputDepthPass.material.define('fragment', 'USE_VSM');
}
else {
this._outputDepthPass.material.undefine('fragment', 'USE_VSM');
}
for (var name in this._textures) {
var texture = this._textures[name];
renderer.setViewport(x, y, width * texture.width / texture.height, height);
this._outputDepthPass.setUniform('depthMap', texture);
this._outputDepthPass.render(renderer);
x += width * texture.width / texture.height;
}
renderer.setViewport(viewport);
renderer.restoreClear();
},
_updateReceivers: function (renderer, mesh) {
if (mesh.receiveShadow) {
this._receivers.push(mesh);
mesh.material.set('shadowEnabled', 1);
mesh.material.set('pcfKernel', this.kernelPCF);
}
else {
mesh.material.set('shadowEnabled', 0);
}
if (this.softShadow === ShadowMapPass.VSM) {
mesh.material.define('fragment', 'USE_VSM');
mesh.material.undefine('fragment', 'PCF_KERNEL_SIZE');
}
else {
mesh.material.undefine('fragment', 'USE_VSM');
var kernelPCF = this.kernelPCF;
if (kernelPCF && kernelPCF.length) {
mesh.material.define('fragment', 'PCF_KERNEL_SIZE', kernelPCF.length / 2);
}
else {
mesh.material.undefine('fragment', 'PCF_KERNEL_SIZE');
}
}
},
_update: function (renderer, scene) {
var self = this;
scene.traverse(function (renderable) {
if (renderable.isRenderable()) {
self._updateReceivers(renderer, renderable);
}
});
for (var i = 0; i < scene.lights.length; i++) {
var light = scene.lights[i];
if (light.castShadow && !light.invisible) {
this._lightsCastShadow.push(light);
}
}
},
_renderShadowPass: function (renderer, scene, sceneCamera, notUpdateScene) {
// reset
for (var name in this._shadowMapNumber) {
this._shadowMapNumber[name] = 0;
}
this._lightsCastShadow.length = 0;
this._receivers.length = 0;
var _gl = renderer.gl;
if (!notUpdateScene) {
scene.update();
}
if (sceneCamera) {
sceneCamera.update();
}
scene.updateLights();
this._update(renderer, scene);
// Needs to update the receivers again if shadows come from 1 to 0.
if (!this._lightsCastShadow.length && this._lastRenderNotCastShadow) {
return;
}
this._lastRenderNotCastShadow = this._lightsCastShadow === 0;
_gl.enable(_gl.DEPTH_TEST);
_gl.depthMask(true);
_gl.disable(_gl.BLEND);
// Clear with high-z, so the part not rendered will not been shadowed
// TODO
// TODO restore
_gl.clearColor(1.0, 1.0, 1.0, 1.0);
// Shadow uniforms
var spotLightShadowMaps = [];
var spotLightMatrices = [];
var directionalLightShadowMaps = [];
var directionalLightMatrices = [];
var shadowCascadeClips = [];
var pointLightShadowMaps = [];
var dirLightHasCascade;
// Create textures for shadow map
for (var i = 0; i < this._lightsCastShadow.length; i++) {
var light = this._lightsCastShadow[i];
if (light.type === 'DIRECTIONAL_LIGHT') {
if (dirLightHasCascade) {
console.warn('Only one direectional light supported with shadow cascade');
continue;
}
if (light.shadowCascade > 4) {
console.warn('Support at most 4 cascade');
continue;
}
if (light.shadowCascade > 1) {
dirLightHasCascade = light;
}
this.renderDirectionalLightShadow(
renderer,
scene,
sceneCamera,
light,
shadowCascadeClips,
directionalLightMatrices,
directionalLightShadowMaps
);
}
else if (light.type === 'SPOT_LIGHT') {
this.renderSpotLightShadow(
renderer,
scene,
light,
spotLightMatrices,
spotLightShadowMaps
);
}
else if (light.type === 'POINT_LIGHT') {
this.renderPointLightShadow(
renderer,
scene,
light,
pointLightShadowMaps
);
}
this._shadowMapNumber[light.type]++;
}
for (var lightType in this._shadowMapNumber) {
var number = this._shadowMapNumber[lightType];
var key = lightType + '_SHADOWMAP_COUNT';
for (var i = 0; i < this._receivers.length; i++) {
var mesh = this._receivers[i];
var material = mesh.material;
if (material.fragmentDefines[key] !== number) {
if (number > 0) {
material.define('fragment', key, number);
}
else if (material.isDefined('fragment', key)) {
material.undefine('fragment', key);
}
}
}
}
for (var i = 0; i < this._receivers.length; i++) {
var mesh = this._receivers[i];
var material = mesh.material;
if (dirLightHasCascade) {
material.define('fragment', 'SHADOW_CASCADE', dirLightHasCascade.shadowCascade);
}
else {
material.undefine('fragment', 'SHADOW_CASCADE');
}
}
var shadowUniforms = scene.shadowUniforms;
function getSize(texture) {
return texture.height;
}
if (directionalLightShadowMaps.length > 0) {
var directionalLightShadowMapSizes = directionalLightShadowMaps.map(getSize);
shadowUniforms.directionalLightShadowMaps = { value: directionalLightShadowMaps, type: 'tv' };
shadowUniforms.directionalLightMatrices = { value: directionalLightMatrices, type: 'm4v' };
shadowUniforms.directionalLightShadowMapSizes = { value: directionalLightShadowMapSizes, type: '1fv' };
if (dirLightHasCascade) {
var shadowCascadeClipsNear = shadowCascadeClips.slice();
var shadowCascadeClipsFar = shadowCascadeClips.slice();
shadowCascadeClipsNear.pop();
shadowCascadeClipsFar.shift();
// Iterate from far to near
shadowCascadeClipsNear.reverse();
shadowCascadeClipsFar.reverse();
// directionalLightShadowMaps.reverse();
directionalLightMatrices.reverse();
shadowUniforms.shadowCascadeClipsNear = { value: shadowCascadeClipsNear, type: '1fv' };
shadowUniforms.shadowCascadeClipsFar = { value: shadowCascadeClipsFar, type: '1fv' };
}
}
if (spotLightShadowMaps.length > 0) {
var spotLightShadowMapSizes = spotLightShadowMaps.map(getSize);
var shadowUniforms = scene.shadowUniforms;
shadowUniforms.spotLightShadowMaps = { value: spotLightShadowMaps, type: 'tv' };
shadowUniforms.spotLightMatrices = { value: spotLightMatrices, type: 'm4v' };
shadowUniforms.spotLightShadowMapSizes = { value: spotLightShadowMapSizes, type: '1fv' };
}
if (pointLightShadowMaps.length > 0) {
shadowUniforms.pointLightShadowMaps = { value: pointLightShadowMaps, type: 'tv' };
}
},
renderDirectionalLightShadow: (function () {
var splitFrustum = new Frustum();
var splitProjMatrix = new Matrix4();
var cropBBox = new BoundingBox();
var cropMatrix = new Matrix4();
var lightViewMatrix = new Matrix4();
var lightViewProjMatrix = new Matrix4();
var lightProjMatrix = new Matrix4();
return function (renderer, scene, sceneCamera, light, shadowCascadeClips, directionalLightMatrices, directionalLightShadowMaps) {
var defaultShadowMaterial = this._getDepthMaterial(light);
var passConfig = {
getMaterial: function (renderable) {
return renderable.shadowDepthMaterial || defaultShadowMaterial;
},
isMaterialChanged: isDepthMaterialChanged,
getUniform: getDepthMaterialUniform,
ifRender: function (renderable) {
return renderable.castShadow;
},
sortCompare: Renderer.opaqueSortCompare
};
// First frame
if (!scene.viewBoundingBoxLastFrame.isFinite()) {
var boundingBox = scene.getBoundingBox();
scene.viewBoundingBoxLastFrame
.copy(boundingBox).applyTransform(sceneCamera.viewMatrix);
}
// Considering moving speed since the bounding box is from last frame
// TODO: add a bias
var clippedFar = Math.min(-scene.viewBoundingBoxLastFrame.min.z, sceneCamera.far);
var clippedNear = Math.max(-scene.viewBoundingBoxLastFrame.max.z, sceneCamera.near);
var lightCamera = this._getDirectionalLightCamera(light, scene, sceneCamera);
var lvpMat4Arr = lightViewProjMatrix.array;
lightProjMatrix.copy(lightCamera.projectionMatrix);
mat4.invert(lightViewMatrix.array, lightCamera.worldTransform.array);
mat4.multiply(lightViewMatrix.array, lightViewMatrix.array, sceneCamera.worldTransform.array);
mat4.multiply(lvpMat4Arr, lightProjMatrix.array, lightViewMatrix.array);
var clipPlanes = [];
var isPerspective = sceneCamera instanceof PerspectiveCamera;
var scaleZ = (sceneCamera.near + sceneCamera.far) / (sceneCamera.near - sceneCamera.far);
var offsetZ = 2 * sceneCamera.near * sceneCamera.far / (sceneCamera.near - sceneCamera.far);
for (var i = 0; i <= light.shadowCascade; i++) {
var clog = clippedNear * Math.pow(clippedFar / clippedNear, i / light.shadowCascade);
var cuni = clippedNear + (clippedFar - clippedNear) * i / light.shadowCascade;
var c = clog * light.cascadeSplitLogFactor + cuni * (1 - light.cascadeSplitLogFactor);
clipPlanes.push(c);
shadowCascadeClips.push(-(-c * scaleZ + offsetZ) / -c);
}
var texture = this._getTexture(light, light.shadowCascade);
directionalLightShadowMaps.push(texture);
var viewport = renderer.viewport;
var _gl = renderer.gl;
this._frameBuffer.attach(texture);
this._frameBuffer.bind(renderer);
_gl.clear(_gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT);
for (var i = 0; i < light.shadowCascade; i++) {
// Get the splitted frustum
var nearPlane = clipPlanes[i];
var farPlane = clipPlanes[i + 1];
if (isPerspective) {
mat4.perspective(splitProjMatrix.array, sceneCamera.fov / 180 * Math.PI, sceneCamera.aspect, nearPlane, farPlane);
}
else {
mat4.ortho(
splitProjMatrix.array,
sceneCamera.left, sceneCamera.right, sceneCamera.bottom, sceneCamera.top,
nearPlane, farPlane
);
}
splitFrustum.setFromProjection(splitProjMatrix);
splitFrustum.getTransformedBoundingBox(cropBBox, lightViewMatrix);
cropBBox.applyProjection(lightProjMatrix);
var _min = cropBBox.min.array;
var _max = cropBBox.max.array;
_min[0] = Math.max(_min[0], -1);
_min[1] = Math.max(_min[1], -1);
_max[0] = Math.min(_max[0], 1);
_max[1] = Math.min(_max[1], 1);
cropMatrix.ortho(_min[0], _max[0], _min[1], _max[1], 1, -1);
lightCamera.projectionMatrix.multiplyLeft(cropMatrix);
var shadowSize = light.shadowResolution || 512;
// Reversed, left to right => far to near
renderer.setViewport((light.shadowCascade - i - 1) * shadowSize, 0, shadowSize, shadowSize, 1);
var renderList = scene.updateRenderList(lightCamera);
renderer.renderPass(renderList.opaque, lightCamera, passConfig);
// Filter for VSM
if (this.softShadow === ShadowMapPass.VSM) {
this._gaussianFilter(renderer, texture, texture.width);
}
var matrix = new Matrix4();
matrix.copy(lightCamera.viewMatrix)
.multiplyLeft(lightCamera.projectionMatrix);
directionalLightMatrices.push(matrix.array);
lightCamera.projectionMatrix.copy(lightProjMatrix);
}
this._frameBuffer.unbind(renderer);
renderer.setViewport(viewport);
};
})(),
renderSpotLightShadow: function (renderer, scene, light, spotLightMatrices, spotLightShadowMaps) {
var texture = this._getTexture(light);
var lightCamera = this._getSpotLightCamera(light);
var _gl = renderer.gl;
this._frameBuffer.attach(texture);
this._frameBuffer.bind(renderer);
_gl.clear(_gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT);
var defaultShadowMaterial = this._getDepthMaterial(light);
var passConfig = {
getMaterial: function (renderable) {
return renderable.shadowDepthMaterial || defaultShadowMaterial;
},
isMaterialChanged: isDepthMaterialChanged,
getUniform: getDepthMaterialUniform,
ifRender: function (renderable) {
return renderable.castShadow;
},
sortCompare: Renderer.opaqueSortCompare
};
var renderList = scene.updateRenderList(lightCamera);
renderer.renderPass(renderList.opaque, lightCamera, passConfig);
this._frameBuffer.unbind(renderer);
// Filter for VSM
if (this.softShadow === ShadowMapPass.VSM) {
this._gaussianFilter(renderer, texture, texture.width);
}
var matrix = new Matrix4();
matrix.copy(lightCamera.worldTransform)
.invert()
.multiplyLeft(lightCamera.projectionMatrix);
spotLightShadowMaps.push(texture);
spotLightMatrices.push(matrix.array);
},
renderPointLightShadow: function (renderer, scene, light, pointLightShadowMaps) {
var texture = this._getTexture(light);
var _gl = renderer.gl;
pointLightShadowMaps.push(texture);
var defaultShadowMaterial = this._getDepthMaterial(light);
var passConfig = {
getMaterial: function (renderable) {
return renderable.shadowDepthMaterial || defaultShadowMaterial;
},
getUniform: getDepthMaterialUniform,
sortCompare: Renderer.opaqueSortCompare
};
var renderListEachSide = {
px: [], py: [], pz: [], nx: [], ny: [], nz: []
};
var bbox = new BoundingBox();
var lightWorldPosition = light.getWorldPosition().array;
var lightBBox = new BoundingBox();
var range = light.range;
lightBBox.min.setArray(lightWorldPosition);
lightBBox.max.setArray(lightWorldPosition);
var extent = new Vector3(range, range, range);
lightBBox.max.add(extent);
lightBBox.min.sub(extent);
var targetsNeedRender = { px: false, py: false, pz: false, nx: false, ny: false, nz: false };
scene.traverse(function (renderable) {
if (renderable.isRenderable() && renderable.castShadow) {
var geometry = renderable.geometry;
if (!geometry.boundingBox) {
for (var i = 0; i < targets.length; i++) {
renderListEachSide[targets[i]].push(renderable);
}
return;
}
bbox.transformFrom(geometry.boundingBox, renderable.worldTransform);
if (!bbox.intersectBoundingBox(lightBBox)) {
return;
}
bbox.updateVertices();
for (var i = 0; i < targets.length; i++) {
targetsNeedRender[targets[i]] = false;
}
for (var i = 0; i < 8; i++) {
var vtx = bbox.vertices[i];
var x = vtx[0] - lightWorldPosition[0];
var y = vtx[1] - lightWorldPosition[1];
var z = vtx[2] - lightWorldPosition[2];
var absx = Math.abs(x);
var absy = Math.abs(y);
var absz = Math.abs(z);
if (absx > absy) {
if (absx > absz) {
targetsNeedRender[x > 0 ? 'px' : 'nx'] = true;
}
else {
targetsNeedRender[z > 0 ? 'pz' : 'nz'] = true;
}
}
else {
if (absy > absz) {
targetsNeedRender[y > 0 ? 'py' : 'ny'] = true;
}
else {
targetsNeedRender[z > 0 ? 'pz' : 'nz'] = true;
}
}
}
for (var i = 0; i < targets.length; i++) {
if (targetsNeedRender[targets[i]]) {
renderListEachSide[targets[i]].push(renderable);
}
}
}
});
for (var i = 0; i < 6; i++) {
var target = targets[i];
var camera = this._getPointLightCamera(light, target);
this._frameBuffer.attach(texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i);
this._frameBuffer.bind(renderer);
_gl.clear(_gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT);
renderer.renderPass(renderListEachSide[target], camera, passConfig);
}
this._frameBuffer.unbind(renderer);
},
_getDepthMaterial: function (light) {
var shadowMaterial = this._lightMaterials[light.__uid__];
var isPointLight = light.type === 'POINT_LIGHT';
if (!shadowMaterial) {
var shaderPrefix = isPointLight ? 'clay.sm.distance.' : 'clay.sm.depth.';
shadowMaterial = new Material({
precision: this.precision,
shader: new Shader(Shader.source(shaderPrefix + 'vertex'), Shader.source(shaderPrefix + 'fragment'))
});
this._lightMaterials[light.__uid__] = shadowMaterial;
}
if (light.shadowSlopeScale != null) {
shadowMaterial.setUniform('slopeScale', light.shadowSlopeScale);
}
if (light.shadowBias != null) {
shadowMaterial.setUniform('bias', light.shadowBias);
}
if (this.softShadow === ShadowMapPass.VSM) {
shadowMaterial.define('fragment', 'USE_VSM');
}
else {
shadowMaterial.undefine('fragment', 'USE_VSM');
}
if (isPointLight) {
shadowMaterial.set('lightPosition', light.getWorldPosition().array);
shadowMaterial.set('range', light.range);
}
return shadowMaterial;
},
_gaussianFilter: function (renderer, texture, size) {
var parameter = {
width: size,
height: size,
type: Texture.FLOAT
};
var tmpTexture = this._texturePool.get(parameter);
this._frameBuffer.attach(tmpTexture);
this._frameBuffer.bind(renderer);
this._gaussianPassH.setUniform('texture', texture);
this._gaussianPassH.setUniform('textureWidth', size);
this._gaussianPassH.render(renderer);
this._frameBuffer.attach(texture);
this._gaussianPassV.setUniform('texture', tmpTexture);
this._gaussianPassV.setUniform('textureHeight', size);
this._gaussianPassV.render(renderer);
this._frameBuffer.unbind(renderer);
this._texturePool.put(tmpTexture);
},
_getTexture: function (light, cascade) {
var key = light.__uid__;
var texture = this._textures[key];
var resolution = light.shadowResolution || 512;
cascade = cascade || 1;
if (!texture) {
if (light.type === 'POINT_LIGHT') {
texture = new TextureCube();
}
else {
texture = new Texture2D();
}
// At most 4 cascade
// TODO share with height ?
texture.width = resolution * cascade;
texture.height = resolution;
if (this.softShadow === ShadowMapPass.VSM) {
texture.type = Texture.FLOAT;
texture.anisotropic = 4;
}
else {
texture.minFilter = glenum.NEAREST;
texture.magFilter = glenum.NEAREST;
texture.useMipmap = false;
}
this._textures[key] = texture;
}
return texture;
},
_getPointLightCamera: function (light, target) {
if (!this._lightCameras.point) {
this._lightCameras.point = {
px: new PerspectiveCamera(),
nx: new PerspectiveCamera(),
py: new PerspectiveCamera(),
ny: new PerspectiveCamera(),
pz: new PerspectiveCamera(),
nz: new PerspectiveCamera()
};
}
var camera = this._lightCameras.point[target];
camera.far = light.range;
camera.fov = 90;
camera.position.set(0, 0, 0);
switch (target) {
case 'px':
camera.lookAt(Vector3.POSITIVE_X, Vector3.NEGATIVE_Y);
break;
case 'nx':
camera.lookAt(Vector3.NEGATIVE_X, Vector3.NEGATIVE_Y);
break;
case 'py':
camera.lookAt(Vector3.POSITIVE_Y, Vector3.POSITIVE_Z);
break;
case 'ny':
camera.lookAt(Vector3.NEGATIVE_Y, Vector3.NEGATIVE_Z);
break;
case 'pz':
camera.lookAt(Vector3.POSITIVE_Z, Vector3.NEGATIVE_Y);
break;
case 'nz':
camera.lookAt(Vector3.NEGATIVE_Z, Vector3.NEGATIVE_Y);
break;
}
light.getWorldPosition(camera.position);
camera.update();
return camera;
},
_getDirectionalLightCamera: (function () {
var lightViewMatrix = new Matrix4();
var sceneViewBoundingBox = new BoundingBox();
var lightViewBBox = new BoundingBox();
// Camera of directional light will be adjusted
// to contain the view frustum and scene bounding box as tightly as possible
return function (light, scene, sceneCamera) {
if (!this._lightCameras.directional) {
this._lightCameras.directional = new OrthoCamera();
}
var camera = this._lightCameras.directional;
sceneViewBoundingBox.copy(scene.viewBoundingBoxLastFrame);
sceneViewBoundingBox.intersection(sceneCamera.frustum.boundingBox);
// Move to the center of frustum(in world space)
camera.position
.copy(sceneViewBoundingBox.min)
.add(sceneViewBoundingBox.max)
.scale(0.5)
.transformMat4(sceneCamera.worldTransform);
camera.rotation.copy(light.rotation);
camera.scale.copy(light.scale);
camera.updateWorldTransform();
// Transform to light view space
Matrix4.invert(lightViewMatrix, camera.worldTransform);
Matrix4.multiply(lightViewMatrix, lightViewMatrix, sceneCamera.worldTransform);
lightViewBBox.copy(sceneViewBoundingBox).applyTransform(lightViewMatrix);
var min = lightViewBBox.min.array;
var max = lightViewBBox.max.array;
// Move camera to adjust the near to 0
camera.position.set((min[0] + max[0]) / 2, (min[1] + max[1]) / 2, max[2])
.transformMat4(camera.worldTransform);
camera.near = 0;
camera.far = -min[2] + max[2];
// Make sure receivers not in the frustum will stil receive the shadow.
if (isNaN(this.lightFrustumBias)) {
camera.far *= 4;
}
else {
camera.far += this.lightFrustumBias;
}
camera.left = min[0];
camera.right = max[0];
camera.top = max[1];
camera.bottom = min[1];
camera.update(true);
return camera;
};
})(),
_getSpotLightCamera: function (light) {
if (!this._lightCameras.spot) {
this._lightCameras.spot = new PerspectiveCamera();
}
var camera = this._lightCameras.spot;
// Update properties
camera.fov = light.penumbraAngle * 2;
camera.far = light.range;
camera.worldTransform.copy(light.worldTransform);
camera.updateProjectionMatrix();
mat4.invert(camera.viewMatrix.array, camera.worldTransform.array);
return camera;
},
/**
* @param {clay.Renderer|WebGLRenderingContext} [renderer]
* @memberOf clay.prePass.ShadowMap.prototype
*/
// PENDING Renderer or WebGLRenderingContext
dispose: function (renderer) {
var _gl = renderer.gl || renderer;
if (this._frameBuffer) {
this._frameBuffer.dispose(_gl);
}
for (var name in this._textures) {
this._textures[name].dispose(_gl);
}
this._texturePool.clear(renderer.gl);
this._depthMaterials = {};
this._distanceMaterials = {};
this._textures = {};
this._lightCameras = {};
this._shadowMapNumber = {
'POINT_LIGHT': 0,
'DIRECTIONAL_LIGHT': 0,
'SPOT_LIGHT': 0
};
this._meshMaterials = {};
for (var i = 0; i < this._receivers.length; i++) {
var mesh = this._receivers[i];
// Mesh may be disposed
if (mesh.material) {
var material = mesh.material;
material.undefine('fragment', 'POINT_LIGHT_SHADOW_COUNT');
material.undefine('fragment', 'DIRECTIONAL_LIGHT_SHADOW_COUNT');
material.undefine('fragment', 'AMBIENT_LIGHT_SHADOW_COUNT');
material.set('shadowEnabled', 0);
}
}
this._receivers = [];
this._lightsCastShadow = [];
}
});
/**
* @name clay.prePass.ShadowMap.VSM
* @type {number}
*/
ShadowMapPass.VSM = 1;
/**
* @name clay.prePass.ShadowMap.PCF
* @type {number}
*/
ShadowMapPass.PCF = 2;
export default ShadowMapPass;