Discovering the intriguing world of Cascade Shadow Mapping has been quite insightful (check out this helpful tutorial I stumbled upon: link). I'm currently exploring how to implement a similar approach in Three.js, with the added advantage of having the 'light' source coincide with the camera position.
CSM recommends dividing the camera frustum into multiple segments and generating a distinct orthographic projection matrix for each segment. This process has been posing a challenge for me. Initially, I attempted to locate frustum corners with the following code:
function cameraToWorld(point, camera) {
camera.updateWorldMatrix();
return point.applyMatrix4(camera.matrixWorld);
}
function calculateCameraFrustumCorners(camera) {
// Consider this camera as a PerspectiveCamera instance
const hFOV = 2 * Math.atan(Math.tan(THREE.Math.degToRad(camera.fov) / 2) * camera.aspect);
const xNear = Math.tan(hFOV / 2) * camera.near;
const xFar = Math.tan(hFOV / 2) * camera.far;
const yNear = Math.tan(THREE.Math.degToRad(camera.fov) / 2) * camera.near;
const yFar = Math.tan(THREE.Math.degToRad(camera.fov) / 2) * camera.far;
let arr = [new THREE.Vector3(xNear, yNear, camera.near),
new THREE.Vector3(xNear * -1, yNear, camera.near),
new THREE.Vector3(xNear, yNear * -1, camera.near),
new THREE.Vector3(xNear * -1, yNear * -1, camera.near),
new THREE.Vector3(xFar, yFar, camera.far),
new THREE.Vector3(xFar * -1, yFar, camera.far),
new THREE.Vector3(xFar, yFar * -1, camera.far),
new THREE.Vector3(xFar * -1, yFar * -1, camera.far)];
return arr.map(function (val) {
return cameraToWorld(val, camera);
});
}
Subsequently, I proceed to create a bounding box around the frustum corners and utilize it to form an OrthographicCamera
:
function getOrthographicCameraForPerspectiveCamera(camera) {
const frustumCorners = calculateCameraFrustumCorners(camera);
let minX = Number.MAX_VALUE;
let maxX = Number.MIN_VALUE;
let minY = Number.MAX_VALUE;
let maxY = Number.MIN_VALUE;
let minZ = Number.MAX_VALUE;
let maxZ = Number.MIN_VALUE;
for (let i = 0; i < frustumCorners.length; i++) {
let corner = frustumCorners[i];
let vec = new THREE.Vector4(corner.x, corner.y, corner.z, 1);
minX = Math.min(vec.x, minX);
maxX = Math.max(vec.x, maxX);
minY = Math.min(vec.y, minY);
maxY = Math.max(vec.y, maxY);
minZ = Math.min(vec.z, minZ);
maxZ = Math.max(vec.z, maxZ);
}
return new THREE.OrthographicCamera(minX, maxX, minY, maxY, minZ, maxZ);
}
Following this step, I intend to pass the orthographic camera matrix to the shader and employ it for texture mapping (particularly the 'shadow map' texture, rendered using the orthographic camera obtained from the aforementioned function).
Unfortunately, this implementation isn't yielding the desired results: the bounding box coordinates obtained do not align well with the typical parameters expected for an orthographic camera setup, which usually consists of the format
width / -2, width / 2, height / 2, height / -2, 0.1, 1000
, diverging significantly from the output of the provided code. Is there a requirement for an additional transformation on the bounding box corners? Perhaps obtaining their coordinates in screen space rather than world space? My grasp of the distinct coordinate systems remains limited. Could it be an error in my calculation of frustum corners?