Object Occlusion Culling

This commit is contained in:
2026-10-15 02:41:03 -07:00
parent 3273054766
commit 612a3d0a09
33 changed files with 3629 additions and 84 deletions

View File

@ -30,9 +30,21 @@ class MeshObject extends Object {
public var frustumCulling = true;
public var activeTilesheet: Tilesheet = null;
public var tilesheets: Array<Tilesheet> = null;
#if lnx_occlusion_culling
public var occlusionVisible = true;
public var shadowOcclusionVisible = true;
#end
var shadowCameraCulled: Bool = false;
var shadowCameraCullFrame: Int = -1;
var shadowCameraCullCamera: CameraObject = null;
var shadowCameraCullLight: LightObject = null;
public var skip_context: String = null; // Do not draw this context
public var force_context: String = null; // Draw only this context
static var lastPipeline: PipelineState = null;
var cachedMats: Map<String, Vector<MaterialData>> = null;
var cachedMaterialContexts: Map<String, Array<MaterialContext>> = null;
var cachedShaderContexts: Map<String, Array<ShaderContext>> = null;
#if lnx_morph_target
public var morphTarget: MorphTarget = null;
#end
@ -48,6 +60,9 @@ class MeshObject extends Object {
this.materials = materials;
setData(data);
Scene.active.meshes.push(this);
#if lnx_occlusion_culling
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.markDirty();
#end
}
public function setData(data: MeshData) {
@ -96,6 +111,9 @@ class MeshObject extends Object {
if (activeTilesheet != null) activeTilesheet.remove();
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
if (Scene.active != null) Scene.active.meshes.remove(this);
#if lnx_occlusion_culling
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.markDirty();
#end
#if (rp_renderer == "Deferred")
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
#end
@ -196,7 +214,7 @@ class MeshObject extends Object {
var isShadow = context == "shadowmap";
if (camera == null) return false;
if (camera.data.raw.frustum_culling && frustumCulling) {
if (camera.data.raw.frustum_culling && frustumCulling && RenderPath.active != null && RenderPath.active.frustumCulling) {
// Scale radius for skinned mesh and particle system
// TODO: define skin & particle bounds
var radiusScale = data.isSkinned ? 2.0 : 1.0;
@ -211,7 +229,8 @@ class MeshObject extends Object {
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
light.transform.radius = light.data.raw.far_plane;
var farPlane: kha.FastFloat = light.data.raw.far_plane;
light.transform.radius = farPlane * 1.25;
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, light.transform)) {
return setCulled(isShadow, true);
}
@ -220,6 +239,37 @@ class MeshObject extends Object {
if (!CameraObject.sphereInFrustum(frustumPlanes, transform, radiusScale)) {
return setCulled(isShadow, true);
}
if (isShadow && light.data.raw.type == "sun") {
var frame = RenderPath.active.frame;
if (shadowCameraCullFrame == frame && shadowCameraCullCamera == camera &&
shadowCameraCullLight == light) {
if (shadowCameraCulled) return setCulled(isShadow, true);
} else {
shadowCameraCullFrame = frame;
shadowCameraCullCamera = camera;
shadowCameraCullLight = light;
shadowCameraCulled = false;
var ldx = -light.V._02;
var ldy = -light.V._12;
var ldz = -light.V._22;
var radius = transform.radius * radiusScale;
var cx = transform.worldx();
var cy = transform.worldy();
var cz = transform.worldz();
var range = camera.data.raw.far_plane * (LightObject.cascadeBounds * 4.0);
var margin = radius * 0.1;
for (plane in camera.frustumPlanes) {
var reach = plane.normal.x * cx + plane.normal.y * cy + plane.normal.z * cz + plane.constant + radius + margin;
var dot = plane.normal.x * ldx + plane.normal.y * ldy + plane.normal.z * ldz;
if (dot > 0) reach += dot * range;
if (reach < 0) {
shadowCameraCulled = true;
return setCulled(isShadow, true);
}
}
}
}
}
//culled = false;
@ -253,6 +303,12 @@ class MeshObject extends Object {
}
}
static function matsEqual(a: Vector<MaterialData>, b: Vector<MaterialData>): Bool {
if (a == null || b == null || a.length != b.length) return false;
for (i in 0...a.length) if (a[i] != b[i]) return false;
return true;
}
public function render(g: Graphics, context: String, bindParams: Array<String>) {
if (data == null || !data.geom.ready) return; // Data not yet streamed
if (!visible) return; // Skip render if object is hidden
@ -293,6 +349,7 @@ class MeshObject extends Object {
// Get lod
var mats = materials;
var lod = this;
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
if (raw != null && raw.lods != null && raw.lods.length > 0) {
computeScreenSize(Scene.active.camera);
initLods();
@ -317,13 +374,29 @@ class MeshObject extends Object {
if (isLodMaterial() && !validContext(mats, context)) return;
// Get context
var materialContexts: Array<MaterialContext> = [];
var shaderContexts: Array<ShaderContext> = [];
getContexts(context, mats, materialContexts, shaderContexts);
var materialContexts: Array<MaterialContext> = null;
var shaderContexts: Array<ShaderContext> = null;
if (cachedMaterialContexts != null && matsEqual(cachedMats.get(context), mats)) {
materialContexts = cachedMaterialContexts.get(context);
shaderContexts = cachedShaderContexts.get(context);
}
if (materialContexts == null) {
materialContexts = [];
shaderContexts = [];
getContexts(context, mats, materialContexts, shaderContexts);
if (cachedMaterialContexts == null) {
cachedMats = new Map();
cachedMaterialContexts = new Map();
cachedShaderContexts = new Map();
}
cachedMats.set(context, mats);
cachedMaterialContexts.set(context, materialContexts);
cachedShaderContexts.set(context, shaderContexts);
}
Uniforms.posUnpack = data.scalePos;
Uniforms.texUnpack = data.scaleTex;
transform.update();
if (!isShadowCtx) transform.update();
// Render mesh
var ldata = lod.data;