Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix shadow LOD #27

Merged
merged 1 commit into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/trees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Sky } from 'three/examples/jsm/objects/Sky.js';
import { InstancedMesh2 } from '../src/index.js';

const count = 1000000;
const terrainSize = 50000;
const terrainSize = 20000;

const main = new Main(); // init renderer and other stuff
main.renderer.toneMapping = ACESFilmicToneMapping;
Expand Down Expand Up @@ -63,10 +63,10 @@ scene.on('animate', (e) => scene.fog.color.setHSL(0, 0, sun.y));
const dirLight = new DirectionalLight();
dirLight.castShadow = true;
dirLight.shadow.mapSize.set(1024, 1024);
dirLight.shadow.camera.left = -500;
dirLight.shadow.camera.right = 500;
dirLight.shadow.camera.top = 500;
dirLight.shadow.camera.bottom = -500;
dirLight.shadow.camera.left = -300;
dirLight.shadow.camera.right = 300;
dirLight.shadow.camera.top = 300;
dirLight.shadow.camera.bottom = -300;
dirLight.shadow.camera.far = 2000;
dirLight.shadow.camera.updateProjectionMatrix();

Expand Down
29 changes: 12 additions & 17 deletions src/core/InstancedMesh2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,6 @@ export class InstancedMesh2<
this.patchMaterials(value);
}

public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group): void {
if (!this.instanceIndex) return;

if (this.levels?.length > 0) this.frustumCullingLOD(camera);
else if (!this._LOD) this.frustumCulling(camera);

this.instanceIndex.update(renderer, this._count);
}

public override onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group): void {
if (!this.instanceIndex) this.initIndexAttribute(renderer);
}

override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: Group): void {
this.onBeforeRender(renderer, null, shadowCamera, geometry, depthMaterial, group);
}

/** THIS MATERIAL AND GEOMETRY CANNOT BE SHARED */
constructor(renderer: WebGLRenderer, count: number, geometry: TGeometry, material?: TMaterial, LOD?: InstancedMesh2, instancesUseEuler = false) {
if (!count || count < 0) throw new Error("'count' must be greater than 0.");
Expand All @@ -144,6 +127,18 @@ export class InstancedMesh2<
this.patchMaterial(this.customDistanceMaterial);
}

public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: Group): void {
if (this.instanceIndex) this.performFrustumCulling(renderer, shadowCamera, camera);
}

public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group): void {
if (this.instanceIndex) this.performFrustumCulling(renderer, camera);
}

public override onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group): void {
if (!this.instanceIndex) this.initIndexAttribute(renderer);
}

protected initIndexArray(): void {
const count = this._maxCount;
const array = new Uint32Array(count); // use uint16 if less 32k
Expand Down
58 changes: 36 additions & 22 deletions src/core/feature/FrustumCulling.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { BVHNode } from "bvh.js";
import { Camera, Frustum, Material, Matrix4, Sphere, Vector3 } from "three";
import { Camera, Frustum, Material, Matrix4, Sphere, Vector3, WebGLRenderer } from "three";
import { getMaxScaleOnAxisAt, getPositionAt } from "../../utils/MatrixUtils.js";
import { sortOpaque, sortTransparent } from "../../utils/SortingUtils.js";
import { InstancedMesh2 } from "../InstancedMesh2.js";
import { InstancedRenderList } from "../utils/InstancedRenderList.js";

// TODO: fix shadowMap LOD sorting objects?

declare module '../InstancedMesh2.js' {
interface InstancedMesh2 {
frustumCulling(camera: Camera): void;
performFrustumCulling(renderer: WebGLRenderer, camera: Camera, cameraLOD?: Camera): void;

/** @internal */ frustumCulling(camera: Camera): void;
/** @internal */ updateIndexArray(): void;
/** @internal */ updateRenderList(): void;
/** @internal */ BVHCulling(): void;
/** @internal */ linearCulling(): void;

frustumCullingLOD(camera: Camera): void;
/** @internal */ BVHCullingLOD(): void;
/** @internal */ linearCullingLOD(): void;
/** @internal */ frustumCullingLOD(camera: Camera, cameraLOD?: Camera): void;
/** @internal */ BVHCullingLOD(sortObjects: boolean): void;
/** @internal */ linearCullingLOD(sortObjects: boolean): void;
}
}

Expand All @@ -25,10 +29,18 @@ const _projScreenMatrix = new Matrix4();
const _invMatrixWorld = new Matrix4();
const _forward = new Vector3();
const _cameraPos = new Vector3();
const _cameraLODPos = new Vector3();
const _position = new Vector3();
const _sphere = new Sphere();

InstancedMesh2.prototype.frustumCulling = function(camera: Camera): void {
InstancedMesh2.prototype.performFrustumCulling = function (renderer: WebGLRenderer, camera: Camera, cameraLOD = camera): void {
if (this.levels?.length > 0) this.frustumCullingLOD(camera, cameraLOD);
else if (!this._LOD) this.frustumCulling(camera);

this.instanceIndex.update(renderer, this._count);
}

InstancedMesh2.prototype.frustumCulling = function (camera: Camera): void {
const sortObjects = this._sortObjects;
const perObjectFrustumCulled = this._perObjectFrustumCulled;
const array = this._indexArray;
Expand Down Expand Up @@ -79,7 +91,7 @@ InstancedMesh2.prototype.frustumCulling = function(camera: Camera): void {
}
}

InstancedMesh2.prototype.updateIndexArray = function(): void {
InstancedMesh2.prototype.updateIndexArray = function (): void {
if (!this._visibilityChanged) return;

const array = this._indexArray;
Expand All @@ -96,7 +108,7 @@ InstancedMesh2.prototype.updateIndexArray = function(): void {
this._visibilityChanged = false;
}

InstancedMesh2.prototype.updateRenderList = function(): void {
InstancedMesh2.prototype.updateRenderList = function (): void {
const instancesCount = this.instancesCount;

for (let i = 0; i < instancesCount; i++) {
Expand All @@ -108,7 +120,7 @@ InstancedMesh2.prototype.updateRenderList = function(): void {
}
}

InstancedMesh2.prototype.BVHCulling = function(): void {
InstancedMesh2.prototype.BVHCulling = function (): void {
const array = this._indexArray;
const matrixArray = this._matrixArray;
const instancesCount = this.instancesCount;
Expand All @@ -132,7 +144,7 @@ InstancedMesh2.prototype.BVHCulling = function(): void {
this._count = count;
}

InstancedMesh2.prototype.linearCulling = function(): void {
InstancedMesh2.prototype.linearCulling = function (): void {
const array = this._indexArray;
const matrixArray = this._matrixArray;
const bSphere = this.geometry.boundingSphere;
Expand Down Expand Up @@ -170,9 +182,11 @@ InstancedMesh2.prototype.linearCulling = function(): void {
this._count = count;
}

InstancedMesh2.prototype.frustumCullingLOD = function(camera: Camera): void {
InstancedMesh2.prototype.frustumCullingLOD = function (camera: Camera, cameraLOD = camera): void {
const levels = this.levels;
const count = this._countIndexes;
const isShadowRendering = camera !== cameraLOD;
const sortObjects = !isShadowRendering && this._sortObjects;

for (let i = 0; i < levels.length; i++) {
count[i] = 0;
Expand All @@ -185,11 +199,12 @@ InstancedMesh2.prototype.frustumCullingLOD = function(camera: Camera): void {
_projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(this.matrixWorld);
_invMatrixWorld.copy(this.matrixWorld).invert();
_cameraPos.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(_invMatrixWorld);
_cameraLODPos.setFromMatrixPosition(cameraLOD.matrixWorld).applyMatrix4(_invMatrixWorld);

if (this.bvh) this.BVHCullingLOD();
else this.linearCullingLOD();
if (this.bvh) this.BVHCullingLOD(sortObjects);
else this.linearCullingLOD(sortObjects);

if (this.sortObjects) {
if (sortObjects) {
const customSort = this.customSort;
const list = _renderList.list;
const indexes = this._indexes;
Expand Down Expand Up @@ -224,31 +239,31 @@ InstancedMesh2.prototype.frustumCullingLOD = function(camera: Camera): void {
}
}

InstancedMesh2.prototype.BVHCullingLOD = function(): void {
InstancedMesh2.prototype.BVHCullingLOD = function (sortObjects: boolean): void {
const matrixArray = this._matrixArray;
const instancesCount = this.instancesCount;
const count = this._countIndexes; // reuse the same? also uintarray?
const indexes = this._indexes;
const visibilityArray = this.visibilityArray;

if (this.sortObjects) { // todo refactor
if (sortObjects) { // todo refactor

this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => {
const index = node.object;
if (index < instancesCount && visibilityArray[index]) {
const distance = getPositionAt(index, matrixArray, _position).distanceToSquared(_cameraPos);
const distance = getPositionAt(index, matrixArray, _position).distanceToSquared(_cameraLODPos);
_renderList.push(distance, index);
}
});

} else {

this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraPos, this.levels, (node: BVHNode<{}, number>, level: number) => {
this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraLODPos, this.levels, (node: BVHNode<{}, number>, level: number) => {
const index = node.object;
if (index < instancesCount && visibilityArray[index]) {

if (level === null) {
const distance = getPositionAt(index, matrixArray, _position).distanceToSquared(_cameraPos); // distance can be get by BVH
const distance = getPositionAt(index, matrixArray, _position).distanceToSquared(_cameraLODPos); // distance can be get by BVH
level = this.getObjectLODIndexForDistance(distance);
}

Expand All @@ -259,8 +274,7 @@ InstancedMesh2.prototype.BVHCullingLOD = function(): void {
}
}

InstancedMesh2.prototype.linearCullingLOD = function(): void {
const sortObjects = this.sortObjects;
InstancedMesh2.prototype.linearCullingLOD = function (sortObjects: boolean): void {
const matrixArray = this._matrixArray;
const bSphere = this.levels[this.levels.length - 1].object.geometry.boundingSphere; // TODO check se esiste?
const radius = bSphere.radius;
Expand All @@ -286,7 +300,7 @@ InstancedMesh2.prototype.linearCullingLOD = function(): void {
}

if (_frustum.intersectsSphere(_sphere)) {
const distance = _sphere.center.distanceToSquared(_cameraPos);
const distance = _sphere.center.distanceToSquared(_cameraLODPos);

if (sortObjects) {
_renderList.push(distance, i);
Expand Down