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

@ -62,7 +62,20 @@ class CameraObject extends Object {
public function buildProjection(screenAspect: Null<Float> = null) {
if (data.raw.ortho != null) {
P = Mat4.ortho(data.raw.ortho[0], data.raw.ortho[1], data.raw.ortho[2], data.raw.ortho[3], data.raw.near_plane, data.raw.far_plane);
var left = data.raw.ortho[0];
var right = data.raw.ortho[1];
var bottom = data.raw.ortho[2];
var top = data.raw.ortho[3];
if (data.raw.aspect != null) {
var aspect: kha.FastFloat = data.raw.aspect;
if (screenAspect == null) screenAspect = iron.App.w() / iron.App.h();
var scale = aspect / screenAspect;
var cx = (left + right) * 0.5;
var hw = (right - left) * 0.5 * scale;
left = cx - hw;
right = cx + hw;
}
P = Mat4.ortho(left, right, bottom, top, data.raw.near_plane, data.raw.far_plane);
}
else {
if (screenAspect == null) screenAspect = iron.App.w() / iron.App.h();
@ -247,25 +260,3 @@ class CameraObject extends Object {
return new Vec4(-transform.world._20, -transform.world._21, -transform.world._22);
}
}
class FrustumPlane {
public var normal = new Vec4(1.0, 0.0, 0.0);
public var constant = 0.0;
public function new() {}
public function normalize() {
var inverseNormalLength = 1.0 / normal.length();
normal.mult(inverseNormalLength);
constant *= inverseNormalLength;
}
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
return (normal.dot(sphereCenter) + constant) - sphereRadius;
}
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
normal.set(x, y, z);
constant = w;
}
}

View File

@ -0,0 +1,25 @@
package iron.object;
import iron.math.Vec4;
class FrustumPlane {
public var normal = new Vec4(1.0, 0.0, 0.0);
public var constant = 0.0;
public function new() {}
public function normalize() {
var inverseNormalLength = 1.0 / normal.length();
normal.mult(inverseNormalLength);
constant *= inverseNormalLength;
}
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
return (normal.dot(sphereCenter) + constant) - sphereRadius;
}
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
normal.set(x, y, z);
constant = w;
}
}

View File

@ -34,7 +34,7 @@ class LightObject extends Object {
// Cascades
public static var cascadeCount = 1;
public static var cascadeSplitFactor = 0.8;
public static var cascadeBounds = 1.0;
public static var cascadeBounds = 2.0; // occlusion culling shadows pop with 1.0
#if lnx_csm
var cascadeData: Float32Array = null;
var cascadeVP: Array<Mat4>;
@ -140,6 +140,9 @@ class LightObject extends Object {
}
#end
#end
#if lnx_occlusion_culling
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.removeLight(this);
#end
super.remove();
}
@ -278,12 +281,13 @@ class LightObject extends Object {
var worldPerTexelX = (maxx - minx) / smsize;
var worldPerTexelY = (maxy - miny) / smsize;
var worldPerTexelZ = (maxz - minz) / smsize;
var ex = maxx - minx, ey = maxy - miny, ez = maxz - minz;
minx = Math.floor(minx / worldPerTexelX) * worldPerTexelX;
miny = Math.floor(miny / worldPerTexelY) * worldPerTexelY;
minz = Math.floor(minz / worldPerTexelZ) * worldPerTexelZ;
maxx = Math.floor(maxx / worldPerTexelX) * worldPerTexelX;
maxy = Math.floor(maxy / worldPerTexelY) * worldPerTexelY;
maxz = Math.floor(maxz / worldPerTexelZ) * worldPerTexelZ;
maxx = minx + ex;
maxy = miny + ey;
maxz = minz + ez;
var hx = (maxx - minx) / 2;
var hy = (maxy - miny) / 2;

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;