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

Add addShadowLOD method #30

Merged
merged 6 commits into from
Nov 4, 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
8 changes: 4 additions & 4 deletions examples/LOD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ const scene = new Scene();

const instancedMeshLOD = new InstancedMesh2(main.renderer, 1000000, new SphereGeometry(5, 30, 15), new MeshLambertMaterial({ color: 'green' }));

instancedMeshLOD.addLevel(new SphereGeometry(5, 30, 15), new MeshLambertMaterial({ color: 'green' }));
instancedMeshLOD.addLevel(new SphereGeometry(5, 20, 10), new MeshLambertMaterial({ color: 'yellow' }), 50);
instancedMeshLOD.addLevel(new SphereGeometry(5, 10, 5), new MeshLambertMaterial({ color: 'orange' }), 500);
instancedMeshLOD.addLevel(new SphereGeometry(5, 5, 3), new MeshLambertMaterial({ color: 'red' }), 1000);
instancedMeshLOD.addLOD(new SphereGeometry(5, 30, 15), new MeshLambertMaterial({ color: 'green' }));
instancedMeshLOD.addLOD(new SphereGeometry(5, 20, 10), new MeshLambertMaterial({ color: 'yellow' }), 50);
instancedMeshLOD.addLOD(new SphereGeometry(5, 10, 5), new MeshLambertMaterial({ color: 'orange' }), 500);
instancedMeshLOD.addLOD(new SphereGeometry(5, 5, 3), new MeshLambertMaterial({ color: 'red' }), 1000);

instancedMeshLOD.levels[0].object.geometry.computeBoundingSphere(); // improve

Expand Down
23 changes: 12 additions & 11 deletions examples/trees.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Asset, Main, PerspectiveCameraAuto } from '@three.ez/main';
import { ACESFilmicToneMapping, AmbientLight, BoxGeometry, BufferGeometry, DirectionalLight, FogExp2, Mesh, MeshLambertMaterial, MeshStandardMaterial, PCFSoftShadowMap, PlaneGeometry, Scene, Vector3 } from 'three';
import { ACESFilmicToneMapping, AmbientLight, BoxGeometry, BufferGeometry, DirectionalLight, FogExp2, Mesh, MeshBasicMaterial, MeshLambertMaterial, MeshStandardMaterial, PCFSoftShadowMap, PlaneGeometry, Scene, Vector3 } from 'three';
import { MapControls } from 'three/examples/jsm/controls/MapControls.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { Sky } from 'three/examples/jsm/objects/Sky.js';
import { InstancedMesh2 } from '../src/index.js';

const count = 1000000;
const terrainSize = 20000;
const count = 10000;
const terrainSize = 1000;

const main = new Main(); // init renderer and other stuff
main.renderer.toneMapping = ACESFilmicToneMapping;
Expand All @@ -24,9 +24,10 @@ const trees = new InstancedMesh2(main.renderer, count, treeGLTF.geometry, treeGL
trees.castShadow = true;
trees.cursor = 'pointer';

trees.addLevel(new BoxGeometry(100, 1000, 100), new MeshLambertMaterial(), 100);
trees.levels[0].object.geometry.computeBoundingSphere(); // improve
trees.levels[1].object.castShadow = true;
trees.addLOD(new BoxGeometry(100, 1000, 100), new MeshLambertMaterial(), 100);
// trees.addShadowLOD(trees.geometry, new MeshBasicMaterial());
trees.addShadowLOD(new BoxGeometry(100, 1000, 100), new MeshBasicMaterial(), 100);
trees.levels.shadowRender.levels[0].object.castShadow = true; // TODO create utility methods

trees.createInstances((obj, index) => {
obj.position.setX(Math.random() * terrainSize - terrainSize / 2).setZ(Math.random() * terrainSize - terrainSize / 2);
Expand Down Expand Up @@ -62,11 +63,11 @@ 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 = -300;
dirLight.shadow.camera.right = 300;
dirLight.shadow.camera.top = 300;
dirLight.shadow.camera.bottom = -300;
dirLight.shadow.mapSize.set(2048, 2048);
dirLight.shadow.camera.left = -200;
dirLight.shadow.camera.right = 200;
dirLight.shadow.camera.top = 200;
dirLight.shadow.camera.bottom = -200;
dirLight.shadow.camera.far = 2000;
dirLight.shadow.camera.updateProjectionMatrix();

Expand Down
14 changes: 3 additions & 11 deletions src/core/InstancedMesh2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GLInstancedBufferAttribute } from "./utils/GLInstancedBufferAttribute.j
import { InstancedEntity, UniformValue, UniformValueNoNumber } from "./InstancedEntity.js";
import { InstancedMeshBVH } from "./InstancedMeshBVH.js";
import { InstancedRenderItem } from "./utils/InstancedRenderList.js";
import { LODInfo } from "./feature/LOD.js";

// TODO: Add expand and count/maxCount when create?
// TODO: partial texture update
Expand All @@ -21,12 +22,6 @@ export type Entity<T> = InstancedEntity & T;
export type UpdateEntityCallback<T> = (obj: Entity<T>, index: number) => void;
export type CustomSortCallback = (list: InstancedRenderItem[]) => void;

export interface LODLevel<TCustomData = {}> {
distance: number;
hysteresis: number;
object: InstancedMesh2<TCustomData>;
}

export interface BVHParams {
margin?: number;
highPrecision?: boolean;
Expand All @@ -53,23 +48,20 @@ export class InstancedMesh2<
public customSort: CustomSortCallback = null;
public raycastOnlyFrustum = false;
public visibilityArray: boolean[];
public levels: LODLevel<TCustomData>[] = null;
public levels: LODInfo<TCustomData> = null; // TODO rename
/** @internal */ public _indexArray: Uint16Array | Uint32Array;
/** @internal */ public _matrixArray: Float32Array;
/** @internal */ public _colorArray: Float32Array = null;
/** @internal */ public _count: number;
/** @internal */ public _perObjectFrustumCulled = true;
/** @internal */ public _sortObjects = false;
/** @internal */ public _maxCount: number;
/** @internal */ public _visibilityChanged = false;
protected _material: TMaterial;
protected _uniformsSetCallback = new Map<string, (id: number, value: UniformValue) => void>();
protected _LOD: InstancedMesh2;
protected readonly _instancesUseEuler: boolean;
protected readonly _instance: InstancedEntity;
/** @internal */ public _visibilityChanged = false;

protected _indexes: (Uint16Array | Uint32Array)[] = null; // TODO can be also uin16
protected _countIndexes: number[] = null;

public override customDepthMaterial = new MeshDepthMaterial({ depthPacking: RGBADepthPacking });
public override customDistanceMaterial = new MeshDistanceMaterial();
Expand Down
58 changes: 32 additions & 26 deletions src/core/feature/FrustumCulling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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";
import { LODRenderList } from "./LOD.js";

// TODO: fix shadowMap LOD sorting objects?

Expand All @@ -17,9 +18,9 @@ declare module '../InstancedMesh2.js' {
/** @internal */ BVHCulling(): void;
/** @internal */ linearCulling(): void;

/** @internal */ frustumCullingLOD(camera: Camera, cameraLOD?: Camera): void;
/** @internal */ BVHCullingLOD(sortObjects: boolean): void;
/** @internal */ linearCullingLOD(sortObjects: boolean): void;
/** @internal */ frustumCullingLOD(renderList: LODRenderList, objects: InstancedMesh2[], camera: Camera, cameraLOD: Camera): void;
/** @internal */ BVHCullingLOD(renderList: LODRenderList, sortObjects: boolean): void;
/** @internal */ linearCullingLOD(renderList: LODRenderList, sortObjects: boolean): void;
}
}

Expand All @@ -34,9 +35,13 @@ const _position = new Vector3();
const _sphere = new Sphere();

InstancedMesh2.prototype.performFrustumCulling = function (renderer: WebGLRenderer, camera: Camera, cameraLOD = camera): void {
if (this.levels?.length > 0) this.frustumCullingLOD(camera, cameraLOD);
const info = this.levels;
const isShadowRendering = camera !== cameraLOD;
const renderList = !isShadowRendering ? info?.render : (info?.shadowRender ?? info?.render);

if (renderList?.levels.length > 0) this.frustumCullingLOD(renderList, info.objects, camera, cameraLOD);
else if (!this._LOD) this.frustumCulling(camera);

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

Expand Down Expand Up @@ -182,11 +187,10 @@ InstancedMesh2.prototype.linearCulling = function (): void {
this._count = count;
}

InstancedMesh2.prototype.frustumCullingLOD = function (camera: Camera, cameraLOD = camera): void {
const levels = this.levels;
const count = this._countIndexes;
InstancedMesh2.prototype.frustumCullingLOD = function (renderList: LODRenderList, objects: InstancedMesh2[], camera: Camera, cameraLOD: Camera): void {
const { count, levels } = renderList;
const isShadowRendering = camera !== cameraLOD;
const sortObjects = !isShadowRendering && this._sortObjects;
const sortObjects = !isShadowRendering && this._sortObjects; // sort is disabled when render shadows

for (let i = 0; i < levels.length; i++) {
count[i] = 0;
Expand All @@ -196,18 +200,23 @@ InstancedMesh2.prototype.frustumCullingLOD = function (camera: Camera, cameraLOD
}
}

for (const object of objects) {
if (object === this) object._count = 0;
else object.visible = false;
}

_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(sortObjects);
else this.linearCullingLOD(sortObjects);
if (this.bvh) this.BVHCullingLOD(renderList, sortObjects);
else this.linearCullingLOD(renderList, sortObjects);

if (sortObjects) {
const customSort = this.customSort;
const list = _renderList.list;
const indexes = this._indexes;
const list = _renderList.list; // TODO better name...
const indexes = renderList.indexes;
let levelIndex = 0;
let levelDistance = levels[1].distance;

Expand All @@ -226,24 +235,23 @@ InstancedMesh2.prototype.frustumCullingLOD = function (camera: Camera, cameraLOD
// for fixa
}

indexes[levelIndex][count[levelIndex]++] = item.index; // TODO COUNT ARRAY QUI NON SERVE
indexes[levelIndex][count[levelIndex]++] = item.index;
}

_renderList.reset();
}

for (let i = 0; i < levels.length; i++) {
const object = levels[i].object;
object.visible = i === 0 || count[i] > 0;
object.visible = object === this || count[i] > 0;
object._count = count[i];
}
}

InstancedMesh2.prototype.BVHCullingLOD = function (sortObjects: boolean): void {
InstancedMesh2.prototype.BVHCullingLOD = function (renderList: LODRenderList, sortObjects: boolean): void {
const { count, indexes, levels } = renderList;
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 (sortObjects) { // todo refactor
Expand All @@ -258,13 +266,13 @@ InstancedMesh2.prototype.BVHCullingLOD = function (sortObjects: boolean): void {

} else {

this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraLODPos, this.levels, (node: BVHNode<{}, number>, level: number) => {
this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraLODPos, 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(_cameraLODPos); // distance can be get by BVH
level = this.getObjectLODIndexForDistance(distance);
level = this.getObjectLODIndexForDistance(levels, distance);
}

indexes[level][count[level]++] = index;
Expand All @@ -274,19 +282,17 @@ InstancedMesh2.prototype.BVHCullingLOD = function (sortObjects: boolean): void {
}
}

InstancedMesh2.prototype.linearCullingLOD = function (sortObjects: boolean): void {
InstancedMesh2.prototype.linearCullingLOD = function (renderList: LODRenderList, sortObjects: boolean): void {
const { count, indexes, levels } = renderList;
const matrixArray = this._matrixArray;
const bSphere = this.levels[this.levels.length - 1].object.geometry.boundingSphere; // TODO check se esiste?
const bSphere = this.geometry.boundingSphere; // TODO check se esiste?
const radius = bSphere.radius;
const center = bSphere.center;
const instancesCount = this.instancesCount;
const geometryCentered = center.x === 0 && center.y === 0 && center.z === 0;

_frustum.setFromProjectionMatrix(_projScreenMatrix);

const count = this._countIndexes;
const indexes = this._indexes;

for (let i = 0; i < instancesCount; i++) {
if (!this.visibilityArray[i]) continue; // opt anche nell'altra classe

Expand All @@ -305,7 +311,7 @@ InstancedMesh2.prototype.linearCullingLOD = function (sortObjects: boolean): voi
if (sortObjects) {
_renderList.push(distance, i);
} else {
const levelIndex = this.getObjectLODIndexForDistance(distance);
const levelIndex = this.getObjectLODIndexForDistance(levels, distance);
indexes[levelIndex][count[levelIndex]++] = i;
}
}
Expand Down
Loading