Object Occlusion Culling
This commit is contained in:
773
leenkx/Sources/iron/culling/CullSystem.hx
Normal file
773
leenkx/Sources/iron/culling/CullSystem.hx
Normal file
@ -0,0 +1,773 @@
|
||||
package iron.culling;
|
||||
|
||||
import iron.object.CameraObject;
|
||||
import iron.object.FrustumPlane;
|
||||
import iron.Scene;
|
||||
import iron.RenderPath;
|
||||
import iron.math.Mat4;
|
||||
|
||||
class CullSystem {
|
||||
|
||||
public static var active: CullSystem = null;
|
||||
|
||||
public var tier: CullTier;
|
||||
public var cullData: CullData;
|
||||
public var rasterizer: SoftwareOcclusionRasterizer;
|
||||
public var shadowRasterizer: SoftwareOcclusionRasterizer;
|
||||
public var occluderProxies: Array<OccluderProxy>;
|
||||
public var bvh: ObjectBVH;
|
||||
public var temporal: TemporalState;
|
||||
|
||||
public var enabled: Bool = true;
|
||||
public var occlusionEnabled: Bool = true;
|
||||
public var bvhDirty: Bool = true;
|
||||
public var proxiesDirty: Bool = true;
|
||||
var dataSyncedFrame: Int = -1;
|
||||
|
||||
var focalLengthPx: Float = 500.0;
|
||||
var orthoPxPerUnit: Float = 0.0;
|
||||
var screenW: Int = 1920;
|
||||
var screenH: Int = 1080;
|
||||
|
||||
public static var maxOccluders: Int = 48;
|
||||
public static var maxOccluderTris: Int = 4000;
|
||||
public static var maxTrisPerProxy: Int = 768;
|
||||
public static var maxShadowOccluders: Int = 24;
|
||||
public static var maxShadowOccluderTris: Int = 1536;
|
||||
public static var boxOccluders: Bool = false;
|
||||
public static var shadowBoxOccluders: Bool = false;
|
||||
public static var shadowBoxShrink: Float = 0.7;
|
||||
public static var shadowCullingEnabled: Bool = true;
|
||||
public static var minOccluderPx: Float = 24.0;
|
||||
public static var boxShrink: Float = 0.5;
|
||||
public static var depthEps: Float = 1e-6;
|
||||
|
||||
public static var shadowCullInterval: Int = 2;
|
||||
|
||||
static inline var MAX_SHADOW_SLOTS = 8;
|
||||
var shadowPasses: haxe.ds.ObjectMap<iron.object.LightObject, Array<ShadowPassSlot>> = new haxe.ds.ObjectMap();
|
||||
var shadowCallIdx: Map<iron.object.LightObject, Int> = new Map();
|
||||
var shadowCallFrame: Map<iron.object.LightObject, Int> = new Map();
|
||||
var lastShadowSlot: ShadowPassSlot = null;
|
||||
var lastShadowFrame: Int = -1;
|
||||
var lastShadowCallIdx: Int = -1;
|
||||
#if lnx_csm
|
||||
var shadowVps: haxe.ds.ObjectMap<iron.object.LightObject, Mat4> = new haxe.ds.ObjectMap();
|
||||
#end
|
||||
var sceneRev: Int = 0;
|
||||
var sceneRevFrame: Int = -1;
|
||||
var frameCounter: Int = 0;
|
||||
var bvhSyncedFrame: Int = -1; // Frame when BVH was built/refit
|
||||
|
||||
var occlusionFrustumPlanes: Array<FrustumPlane> = null;
|
||||
var frustumPlanes: Array<FrustumPlane> = null;
|
||||
|
||||
var shadowFrustumPlanes: Array<FrustumPlane> = null;
|
||||
|
||||
var uidToProxy: haxe.ds.IntMap<OccluderProxy> = new haxe.ds.IntMap<OccluderProxy>();
|
||||
var uidScan: haxe.ds.IntMap<Bool> = new haxe.ds.IntMap<Bool>();
|
||||
|
||||
var prevVP: Mat4 = null;
|
||||
#if lnx_taa
|
||||
var unjitteredVP: Mat4 = null;
|
||||
#end
|
||||
public var shadowFlagsValid: Bool = false;
|
||||
|
||||
var inFrustumList: Array<Int> = [];
|
||||
var selIdx: Array<Int> = [];
|
||||
var selScore: Array<Float> = [];
|
||||
var selCount: Int = 0;
|
||||
|
||||
public function new() {
|
||||
cullData = new CullData();
|
||||
rasterizer = new SoftwareOcclusionRasterizer(128, 64);
|
||||
shadowRasterizer = new SoftwareOcclusionRasterizer(128, 128);
|
||||
occluderProxies = [];
|
||||
temporal = new TemporalState();
|
||||
tier = detectTier();
|
||||
}
|
||||
|
||||
static inline var MAT_EPS = 1e-7;
|
||||
|
||||
static inline function matEquals(a: Mat4, b: Mat4): Bool {
|
||||
return
|
||||
Math.abs(a._00 - b._00) < MAT_EPS && Math.abs(a._10 - b._10) < MAT_EPS &&
|
||||
Math.abs(a._20 - b._20) < MAT_EPS && Math.abs(a._30 - b._30) < MAT_EPS &&
|
||||
Math.abs(a._01 - b._01) < MAT_EPS && Math.abs(a._11 - b._11) < MAT_EPS &&
|
||||
Math.abs(a._21 - b._21) < MAT_EPS && Math.abs(a._31 - b._31) < MAT_EPS &&
|
||||
Math.abs(a._02 - b._02) < MAT_EPS && Math.abs(a._12 - b._12) < MAT_EPS &&
|
||||
Math.abs(a._22 - b._22) < MAT_EPS && Math.abs(a._32 - b._32) < MAT_EPS &&
|
||||
Math.abs(a._03 - b._03) < MAT_EPS && Math.abs(a._13 - b._13) < MAT_EPS &&
|
||||
Math.abs(a._23 - b._23) < MAT_EPS && Math.abs(a._33 - b._33) < MAT_EPS;
|
||||
}
|
||||
|
||||
public static function detectTier(): CullTier {
|
||||
#if (target.threaded)
|
||||
return CPUMultiThread;
|
||||
#else
|
||||
return CPUSingleThread;
|
||||
#end
|
||||
}
|
||||
|
||||
public function cull(camera: CameraObject) {
|
||||
frameCounter = RenderPath.active != null ? RenderPath.active.frame : frameCounter + 1;
|
||||
if (!enabled || Scene.active == null || Scene.active.meshes.length == 0) {
|
||||
if (Scene.active != null) {
|
||||
var meshes = Scene.active.meshes;
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].occlusionVisible = true;
|
||||
meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var rp = RenderPath.active;
|
||||
if (rp != null) {
|
||||
screenW = rp.currentW;
|
||||
screenH = rp.currentH;
|
||||
}
|
||||
|
||||
var targetW = Std.int(Math.max(128, Math.min(512, screenW >> 4)));
|
||||
var targetH = Std.int(Math.max(64, Math.min(256, screenH >> 4)));
|
||||
if (rasterizer.width < (targetW >> 1) || rasterizer.width > (targetW << 1) ||
|
||||
rasterizer.height < (targetH >> 1) || rasterizer.height > (targetH << 1)) {
|
||||
rasterizer = new SoftwareOcclusionRasterizer(targetW, targetH);
|
||||
}
|
||||
|
||||
if (camera.data.raw.ortho == null) {
|
||||
focalLengthPx = ContributionCull.computeFocalLengthPx(
|
||||
camera.data.raw.fov, screenH
|
||||
);
|
||||
orthoPxPerUnit = 0.0;
|
||||
} else {
|
||||
focalLengthPx = 0;
|
||||
var o = camera.data.raw.ortho;
|
||||
var halfH = (o[3] - o[2]) * 0.5;
|
||||
orthoPxPerUnit = halfH > 1e-9 ? screenH * 0.5 / halfH : 0.0;
|
||||
}
|
||||
rasterizer.nearClip = camera.data.raw.ortho == null ?
|
||||
camera.data.raw.near_plane : 0.0001;
|
||||
|
||||
if (dataSyncedFrame != frameCounter) {
|
||||
cullData.syncFromScene();
|
||||
dataSyncedFrame = frameCounter;
|
||||
}
|
||||
if (cullData.slotsRebuilt) temporal.clear();
|
||||
if (sceneRevFrame != frameCounter) {
|
||||
sceneRevFrame = frameCounter;
|
||||
if (cullData.anyMoved || cullData.anyFlagsChanged || cullData.orderChanged) sceneRev++;
|
||||
}
|
||||
var cd = cullData;
|
||||
var meshes = cd.meshes;
|
||||
|
||||
#if lnx_taa
|
||||
if (unjitteredVP == null) unjitteredVP = Mat4.identity();
|
||||
unjitteredVP.multmats(camera.noJitterP, camera.V);
|
||||
var cmpVP = unjitteredVP;
|
||||
#else
|
||||
var cmpVP = camera.VP;
|
||||
#end
|
||||
var vpSame = prevVP != null && matEquals(cmpVP, prevVP);
|
||||
if (vpSame && !cd.anyMoved && !cd.anyFlagsChanged && !cd.orderChanged && !bvhDirty) {
|
||||
return;
|
||||
}
|
||||
var needBvhRebuild = bvhDirty;
|
||||
bvhDirty = false;
|
||||
if (prevVP == null) prevVP = Mat4.identity();
|
||||
prevVP.setFrom(cmpVP);
|
||||
|
||||
if (occlusionFrustumPlanes == null) {
|
||||
occlusionFrustumPlanes = [];
|
||||
for (i in 0...6) occlusionFrustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
{
|
||||
var vp = camera.VP;
|
||||
var p = occlusionFrustumPlanes;
|
||||
p[0].setComponents(vp._03 + vp._00, vp._13 + vp._10, vp._23 + vp._20, vp._33 + vp._30);
|
||||
p[1].setComponents(vp._03 - vp._00, vp._13 - vp._10, vp._23 - vp._20, vp._33 - vp._30);
|
||||
p[2].setComponents(vp._03 - vp._01, vp._13 - vp._11, vp._23 - vp._21, vp._33 - vp._31);
|
||||
p[3].setComponents(vp._03 + vp._01, vp._13 + vp._11, vp._23 + vp._21, vp._33 + vp._31);
|
||||
p[4].setComponents(vp._02, vp._12, vp._22, vp._32);
|
||||
p[5].setComponents(vp._03 - vp._02, vp._13 - vp._12, vp._23 - vp._22, vp._33 - vp._32);
|
||||
}
|
||||
frustumPlanes = occlusionFrustumPlanes;
|
||||
|
||||
var camX = camera.transform.worldx();
|
||||
var camY = camera.transform.worldy();
|
||||
var camZ = camera.transform.worldz();
|
||||
|
||||
if (bvh == null) bvh = new ObjectBVH();
|
||||
if (bvhSyncedFrame != frameCounter) {
|
||||
if (needBvhRebuild || bvh.root < 0) {
|
||||
bvh.build(cd);
|
||||
} else if (cd.anyMoved || cd.orderChanged) {
|
||||
bvh.refit(cd);
|
||||
}
|
||||
bvhSyncedFrame = frameCounter;
|
||||
}
|
||||
inFrustumList.resize(0);
|
||||
var planes = frustumPlanes;
|
||||
var fpx = focalLengthPx;
|
||||
bvh.traverseFrontToBack(camX, camY, camZ,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, planes)) return true;
|
||||
if (!ContributionCull.passesNode(node, camX, camY, camZ, fpx)) return true;
|
||||
return false;
|
||||
},
|
||||
function(objIdx: Int, _: Int) {
|
||||
if ((cd.getFlags(objIdx) & 8) == 0) return;
|
||||
if (!ContributionCull.passes(
|
||||
cd.getCenterX(objIdx), cd.getCenterY(objIdx), cd.getCenterZ(objIdx),
|
||||
cd.getRadius(objIdx), camX, camY, camZ, fpx
|
||||
)) return;
|
||||
inFrustumList.push(objIdx);
|
||||
}
|
||||
);
|
||||
cullData.clearVisible();
|
||||
|
||||
if (occlusionEnabled) {
|
||||
selectOccluders(meshes, camX, camY, camZ);
|
||||
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].occlusionVisible = true;
|
||||
}
|
||||
|
||||
rasterizer.clear();
|
||||
if (selCount > 0) rasterizeSelected(meshes, camera.VP);
|
||||
var trisDone = rasterizer.trianglesRasterized;
|
||||
if (trisDone > 0) rasterizer.buildHiZ();
|
||||
|
||||
if (trisDone > 0) {
|
||||
testVisibilityBVH(camera.VP, camX, camY, camZ, planes);
|
||||
} else {
|
||||
for (k in 0...inFrustumList.length) {
|
||||
var objIdx = inFrustumList[k];
|
||||
cd.addVisible(objIdx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].occlusionVisible = true;
|
||||
}
|
||||
for (k in 0...inFrustumList.length) {
|
||||
var objIdx = inFrustumList[k];
|
||||
cd.addVisible(objIdx);
|
||||
}
|
||||
}
|
||||
|
||||
temporal.advanceFrame();
|
||||
}
|
||||
|
||||
public function cullShadow(light: iron.object.LightObject) {
|
||||
if (!occlusionEnabled || light == null || Scene.active == null) return;
|
||||
if (!shadowCullingEnabled) {
|
||||
var ms = Scene.active.meshes;
|
||||
for (m in ms) m.shadowOcclusionVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var frame = RenderPath.active != null ? RenderPath.active.frame : frameCounter;
|
||||
var vp = light.VP;
|
||||
if (vp == null) return;
|
||||
|
||||
var cd = cullData;
|
||||
|
||||
if (dataSyncedFrame != frame) {
|
||||
cd.syncFromScene();
|
||||
dataSyncedFrame = frame;
|
||||
}
|
||||
if (sceneRevFrame != frame) {
|
||||
sceneRevFrame = frame;
|
||||
if (cd.anyMoved || cd.anyFlagsChanged || cd.orderChanged) sceneRev++;
|
||||
}
|
||||
var meshes = cd.meshes;
|
||||
|
||||
var slots = shadowPasses.get(light);
|
||||
if (slots == null) {
|
||||
slots = [];
|
||||
shadowPasses.set(light, slots);
|
||||
}
|
||||
var callIdx = 0;
|
||||
{
|
||||
var f = shadowCallFrame.get(light);
|
||||
callIdx = (f == frame) ? shadowCallIdx.get(light) + 1 : 0;
|
||||
shadowCallIdx.set(light, callIdx);
|
||||
shadowCallFrame.set(light, frame);
|
||||
}
|
||||
var slotIdx = callIdx < MAX_SHADOW_SLOTS ? callIdx : MAX_SHADOW_SLOTS - 1;
|
||||
while (slots.length <= slotIdx) slots.push(new ShadowPassSlot());
|
||||
var slot = slots[slotIdx];
|
||||
|
||||
if (slot != null && slot == lastShadowSlot && lastShadowFrame == frame &&
|
||||
lastShadowCallIdx == callIdx && slot.appliedRev == sceneRev) return;
|
||||
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
|
||||
#if lnx_csm
|
||||
var isCsmLight = light.data.raw.type == "sun" &&
|
||||
iron.object.LightObject.cascadeCount > 1;
|
||||
if (isCsmLight && callIdx == 0) {
|
||||
var vp0 = shadowVps.get(light);
|
||||
if (vp0 == null) { vp0 = Mat4.identity(); shadowVps.set(light, vp0); }
|
||||
vp0.setFrom(vp);
|
||||
}
|
||||
#end
|
||||
|
||||
if (shadowCullInterval > 1) {
|
||||
if ((frame + slotIdx) % shadowCullInterval != 0) {
|
||||
var applied = false;
|
||||
#if lnx_csm
|
||||
if (isCsmLight && callIdx > 0) {
|
||||
var head = slots[0];
|
||||
var vp0 = shadowVps.get(light);
|
||||
if (head.appliedRev == sceneRev && head.flagsLen > 0 &&
|
||||
head.vp != null && vp0 != null && matEquals(vp0, head.vp)) {
|
||||
var hn = head.flagsLen < meshes.length ? head.flagsLen : meshes.length;
|
||||
for (i in 0...hn) meshes[i].shadowOcclusionVisible = head.flags[i];
|
||||
}
|
||||
applied = true;
|
||||
}
|
||||
#end
|
||||
if (!applied && slot.appliedRev == sceneRev && slot.flagsLen > 0 &&
|
||||
slot.vp != null && matEquals(vp, slot.vp)) {
|
||||
var n = slot.flagsLen < meshes.length ? slot.flagsLen : meshes.length;
|
||||
for (i in 0...n) meshes[i].shadowOcclusionVisible = slot.flags[i];
|
||||
}
|
||||
lastShadowSlot = slot;
|
||||
lastShadowFrame = frame;
|
||||
lastShadowCallIdx = callIdx;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bvh == null) bvh = new ObjectBVH();
|
||||
if (bvhSyncedFrame != frame) {
|
||||
if (bvhDirty || bvh.root < 0) {
|
||||
bvh.build(cd);
|
||||
} else if (cd.anyMoved || cd.orderChanged) {
|
||||
bvh.refit(cd);
|
||||
}
|
||||
bvhSyncedFrame = frame;
|
||||
}
|
||||
|
||||
if (shadowFrustumPlanes == null) {
|
||||
shadowFrustumPlanes = [];
|
||||
for (i in 0...6) shadowFrustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
{
|
||||
var p = shadowFrustumPlanes;
|
||||
p[0].setComponents(vp._03 + vp._00, vp._13 + vp._10, vp._23 + vp._20, vp._33 + vp._30);
|
||||
p[1].setComponents(vp._03 - vp._00, vp._13 - vp._10, vp._23 - vp._20, vp._33 - vp._30);
|
||||
p[2].setComponents(vp._03 - vp._01, vp._13 - vp._11, vp._23 - vp._21, vp._33 - vp._31);
|
||||
p[3].setComponents(vp._03 + vp._01, vp._13 + vp._11, vp._23 + vp._21, vp._33 + vp._31);
|
||||
p[4].setComponents(vp._02, vp._12, vp._22, vp._32);
|
||||
p[5].setComponents(vp._03 - vp._02, vp._13 - vp._12, vp._23 - vp._22, vp._33 - vp._32);
|
||||
}
|
||||
var lightPlanes = shadowFrustumPlanes;
|
||||
|
||||
var lx = light.transform.worldx();
|
||||
var ly = light.transform.worldy();
|
||||
var lz = light.transform.worldz();
|
||||
|
||||
var raster = slot.rasterizer;
|
||||
var vpSame = slot.vp != null && matEquals(vp, slot.vp);
|
||||
if (slot.sceneRev == sceneRev && vpSame && raster != null) {
|
||||
// skip
|
||||
} else {
|
||||
if (raster == null) {
|
||||
raster = new SoftwareOcclusionRasterizer(
|
||||
shadowRasterizer.width, shadowRasterizer.height);
|
||||
slot.rasterizer = raster;
|
||||
}
|
||||
if (slot.vp == null) slot.vp = Mat4.identity();
|
||||
slot.vp.setFrom(vp);
|
||||
|
||||
var perspective = light.data.raw.type != "sun";
|
||||
selCount = 0;
|
||||
bvh.traverseFrontToBack(lx, ly, lz,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, lightPlanes)) return true;
|
||||
return false;
|
||||
},
|
||||
function(i: Int, _: Int) {
|
||||
if ((cd.getFlags(i) & 8) == 0) return;
|
||||
if ((cd.getFlags(i) & 16) != 0) return;
|
||||
if ((cd.getFlags(i) & 4) == 0) return;
|
||||
if ((cd.getFlags(i) & 32) != 0) return;
|
||||
if (i >= meshes.length) return;
|
||||
var r = cd.getRadius(i);
|
||||
var score = r;
|
||||
if (perspective) {
|
||||
var dx = cd.getCenterX(i) - lx;
|
||||
var dy = cd.getCenterY(i) - ly;
|
||||
var dz = cd.getCenterZ(i) - lz;
|
||||
var dist2 = dx * dx + dy * dy + dz * dz;
|
||||
if (dist2 <= r * r) return;
|
||||
score = r / Math.sqrt(dist2);
|
||||
}
|
||||
insertTopK(i, score, maxShadowOccluders);
|
||||
}
|
||||
);
|
||||
|
||||
raster = slot.rasterizer;
|
||||
raster.clear();
|
||||
var triBudget = maxShadowOccluderTris;
|
||||
for (s in 0...selCount) {
|
||||
if (triBudget < 12) break;
|
||||
var i = selIdx[s];
|
||||
var proxy = uidToProxy.get(cd.getObjectID(i));
|
||||
if (proxy != null && proxy.triangleCount > 0 &&
|
||||
proxy.triangleCount <= maxTrisPerProxy && proxy.triangleCount <= triBudget) {
|
||||
proxy.rasterize(raster, meshes[i].transform.world, vp,
|
||||
raster.width, raster.height);
|
||||
triBudget -= proxy.triangleCount;
|
||||
} else if (shadowBoxOccluders) {
|
||||
rasterizeBoxOccluder(raster, i, vp, shadowBoxShrink);
|
||||
triBudget -= 12;
|
||||
}
|
||||
}
|
||||
if (raster.trianglesRasterized > 0) raster.buildHiZ();
|
||||
}
|
||||
|
||||
if (raster.trianglesRasterized > 0) {
|
||||
var sw = raster.width;
|
||||
var sh = raster.height;
|
||||
|
||||
bvh.traverseFrontToBack(lx, ly, lz,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, lightPlanes)) return true;
|
||||
var anyVisible = false;
|
||||
if (node.isLeaf) {
|
||||
for (j in 0...node.objectCount) {
|
||||
var idx = bvh.objectIndices[node.firstObject + j];
|
||||
if ((cd.getFlags(idx) & 8) != 0 && idx < meshes.length) {
|
||||
anyVisible = true; break;
|
||||
}
|
||||
}
|
||||
if (!anyVisible) return true;
|
||||
}
|
||||
if (raster.isOccluded(
|
||||
node.minX, node.minY, node.minZ,
|
||||
node.maxX, node.maxY, node.maxZ,
|
||||
vp, sw, sh
|
||||
)) {
|
||||
bvh.forEachLeafInSubtree(node, function(idx: Int) {
|
||||
if (idx < meshes.length && (cd.getFlags(idx) & 8) != 0) {
|
||||
meshes[idx].shadowOcclusionVisible = false;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
function(objIdx: Int, _: Int) {
|
||||
if ((cd.getFlags(objIdx) & 8) == 0) return;
|
||||
if (objIdx >= meshes.length) return;
|
||||
meshes[objIdx].shadowOcclusionVisible = true;
|
||||
if (raster.isOccluded(
|
||||
cd.getMinX(objIdx), cd.getMinY(objIdx), cd.getMinZ(objIdx),
|
||||
cd.getMaxX(objIdx), cd.getMaxY(objIdx), cd.getMaxZ(objIdx),
|
||||
vp, sw, sh
|
||||
)) {
|
||||
meshes[objIdx].shadowOcclusionVisible = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#if lnx_csm
|
||||
if (isCsmLight) {
|
||||
for (c in 1...iron.object.LightObject.cascadeCount) {
|
||||
if (c >= slots.length) break;
|
||||
var s = slots[c];
|
||||
if (s == null || s == slot || s.vp == null || s.rasterizer == null) continue;
|
||||
for (i in 0...meshes.length) {
|
||||
if (meshes[i].shadowOcclusionVisible) continue;
|
||||
if (!s.rasterizer.isOccluded(
|
||||
cd.getMinX(i), cd.getMinY(i), cd.getMinZ(i),
|
||||
cd.getMaxX(i), cd.getMaxY(i), cd.getMaxZ(i),
|
||||
s.vp, s.rasterizer.width, s.rasterizer.height
|
||||
)) {
|
||||
meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
if (slot.flags.length < meshes.length) slot.flags.resize(meshes.length);
|
||||
slot.flagsLen = meshes.length;
|
||||
for (i in 0...meshes.length) slot.flags[i] = meshes[i].shadowOcclusionVisible;
|
||||
|
||||
#if lnx_csm
|
||||
if (isCsmLight && callIdx > 0) {
|
||||
var head = slots[0];
|
||||
var vp0 = shadowVps.get(light);
|
||||
if (head.appliedRev == sceneRev && head.flagsLen > 0 &&
|
||||
head.vp != null && vp0 != null && matEquals(vp0, head.vp)) {
|
||||
var hn = head.flagsLen < meshes.length ? head.flagsLen : meshes.length;
|
||||
for (i in 0...hn) meshes[i].shadowOcclusionVisible = head.flags[i];
|
||||
} else {
|
||||
for (i in 0...meshes.length) meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
slot.sceneRev = sceneRev;
|
||||
slot.appliedRev = sceneRev;
|
||||
slot.lastUsed = frame;
|
||||
lastShadowSlot = slot;
|
||||
lastShadowFrame = frame;
|
||||
lastShadowCallIdx = callIdx;
|
||||
shadowFlagsValid = true;
|
||||
}
|
||||
|
||||
function selectOccluders(
|
||||
meshes: Array<iron.object.MeshObject>,
|
||||
camX: Float, camY: Float, camZ: Float
|
||||
) {
|
||||
var cd = cullData;
|
||||
selCount = 0;
|
||||
var minPx = minOccluderPx;
|
||||
var autoPx = autoOccluderMinScreenRatio * screenH;
|
||||
for (k in 0...inFrustumList.length) {
|
||||
var i = inFrustumList[k];
|
||||
if (i >= meshes.length) continue;
|
||||
if (!meshes[i].occlusionVisible) continue;
|
||||
var flags = cd.getFlags(i);
|
||||
if ((flags & 16) != 0) continue;
|
||||
if ((flags & 32) != 0) continue;
|
||||
var r = cd.getRadius(i);
|
||||
var px: Float;
|
||||
var camInside = false;
|
||||
if (focalLengthPx > 0) {
|
||||
var dx = cd.getCenterX(i) - camX;
|
||||
var dy = cd.getCenterY(i) - camY;
|
||||
var dz = cd.getCenterZ(i) - camZ;
|
||||
var dist2 = dx * dx + dy * dy + dz * dz;
|
||||
if (dist2 <= r * r) camInside = true;
|
||||
px = camInside ? 1e6 : focalLengthPx * r / Math.sqrt(dist2);
|
||||
} else {
|
||||
px = r * orthoPxPerUnit;
|
||||
}
|
||||
if (px < minPx) continue;
|
||||
if ((flags & 1) == 0 && px < autoPx) continue;
|
||||
if (camInside || !boxOccluders) {
|
||||
var p = uidToProxy.get(cd.getObjectID(i));
|
||||
if (p == null || p.triangleCount <= 0 || p.triangleCount > maxTrisPerProxy) continue;
|
||||
}
|
||||
insertTopK(i, px, maxOccluders);
|
||||
}
|
||||
}
|
||||
|
||||
function insertTopK(idx: Int, score: Float, k: Int) {
|
||||
if (selIdx.length < k + 1) {
|
||||
selIdx.resize(k + 1);
|
||||
selScore.resize(k + 1);
|
||||
}
|
||||
var pos = selCount;
|
||||
while (pos > 0 && selScore[pos - 1] < score) pos--;
|
||||
if (pos >= k) return;
|
||||
var last = selCount < k ? selCount : k - 1;
|
||||
var j = last;
|
||||
while (j > pos) {
|
||||
selIdx[j] = selIdx[j - 1];
|
||||
selScore[j] = selScore[j - 1];
|
||||
j--;
|
||||
}
|
||||
selIdx[pos] = idx;
|
||||
selScore[pos] = score;
|
||||
if (selCount < k) selCount++;
|
||||
}
|
||||
|
||||
function rasterizeSelected(meshes: Array<iron.object.MeshObject>, vp: Mat4) {
|
||||
var cd = cullData;
|
||||
var triBudget = maxOccluderTris;
|
||||
#if (target.threaded)
|
||||
rasterizer.beginCollect();
|
||||
#end
|
||||
for (s in 0...selCount) {
|
||||
if (triBudget < 12) break;
|
||||
var i = selIdx[s];
|
||||
var proxy = uidToProxy.get(cd.getObjectID(i));
|
||||
if (proxy != null && proxy.triangleCount > 0 &&
|
||||
proxy.triangleCount <= maxTrisPerProxy && proxy.triangleCount <= triBudget) {
|
||||
var mesh = i < meshes.length ? meshes[i] : null;
|
||||
if (mesh == null) continue;
|
||||
proxy.rasterize(rasterizer, mesh.transform.world, vp, screenW, screenH);
|
||||
triBudget -= proxy.triangleCount;
|
||||
} else {
|
||||
if (!boxOccluders) continue;
|
||||
rasterizeBoxOccluder(rasterizer, i, vp);
|
||||
triBudget -= 12;
|
||||
}
|
||||
}
|
||||
#if (target.threaded)
|
||||
rasterizer.flushCollected();
|
||||
#end
|
||||
}
|
||||
|
||||
function rasterizeBoxOccluder(raster: SoftwareOcclusionRasterizer, objectIndex: Int, vp: Mat4, shrinkOverride = -1.0) {
|
||||
var cd = cullData;
|
||||
var bx0 = cd.getMinX(objectIndex), by0 = cd.getMinY(objectIndex), bz0 = cd.getMinZ(objectIndex);
|
||||
var bx1 = cd.getMaxX(objectIndex), by1 = cd.getMaxY(objectIndex), bz1 = cd.getMaxZ(objectIndex);
|
||||
var cx = (bx0 + bx1) * 0.5;
|
||||
var cy = (by0 + by1) * 0.5;
|
||||
var cz = (bz0 + bz1) * 0.5;
|
||||
var boxShrink = shrinkOverride >= 0.0 ? shrinkOverride : CullSystem.boxShrink;
|
||||
var hx = (bx1 - bx0) * 0.5 * boxShrink;
|
||||
var hy = (by1 - by0) * 0.5 * boxShrink;
|
||||
var hz = (bz1 - bz0) * 0.5 * boxShrink;
|
||||
raster.rasterizeBox(
|
||||
cx - hx, cy - hy, cz - hz,
|
||||
cx + hx, cy + hy, cz + hz,
|
||||
vp, raster.width, raster.height
|
||||
);
|
||||
}
|
||||
|
||||
function testVisibilityBVH(
|
||||
vp: Mat4, camX: Float, camY: Float, camZ: Float,
|
||||
planes: Array<FrustumPlane>
|
||||
) {
|
||||
var cd = cullData;
|
||||
var raster = rasterizer;
|
||||
var sw = screenW;
|
||||
var sh = screenH;
|
||||
var meshes = cd.meshes;
|
||||
var bvh = this.bvh;
|
||||
var temp = temporal;
|
||||
var retestInterval = 4;
|
||||
|
||||
bvh.traverseFrontToBack(camX, camY, camZ,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, planes)) return true;
|
||||
var anyVisible = false;
|
||||
if (node.isLeaf) {
|
||||
for (j in 0...node.objectCount) {
|
||||
var idx = bvh.objectIndices[node.firstObject + j];
|
||||
if ((cd.getFlags(idx) & 8) != 0) { anyVisible = true; break; }
|
||||
}
|
||||
if (!anyVisible) return true;
|
||||
}
|
||||
if (raster.isOccluded(
|
||||
node.minX, node.minY, node.minZ,
|
||||
node.maxX, node.maxY, node.maxZ,
|
||||
vp, sw, sh
|
||||
)) {
|
||||
bvh.forEachLeafInSubtree(node, function(idx: Int) {
|
||||
if (idx >= meshes.length || (cd.getFlags(idx) & 8) == 0) return;
|
||||
meshes[idx].occlusionVisible = false;
|
||||
var uid = cd.getObjectID(idx);
|
||||
temp.setVisible(uid, false);
|
||||
temp.markTested(uid);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
function(objIdx: Int, _: Int) {
|
||||
if ((cd.getFlags(objIdx) & 8) == 0) return;
|
||||
if (objIdx >= meshes.length) return;
|
||||
var uid = cd.getObjectID(objIdx);
|
||||
if (temp.wasVisibleLastFrame(uid) && !temp.needsVisibleReTest(uid, retestInterval)) {
|
||||
meshes[objIdx].occlusionVisible = true;
|
||||
cd.addVisible(objIdx);
|
||||
return;
|
||||
}
|
||||
if (raster.isOccluded(
|
||||
cd.getMinX(objIdx), cd.getMinY(objIdx), cd.getMinZ(objIdx),
|
||||
cd.getMaxX(objIdx), cd.getMaxY(objIdx), cd.getMaxZ(objIdx),
|
||||
vp, sw, sh
|
||||
)) {
|
||||
meshes[objIdx].occlusionVisible = false;
|
||||
temp.setVisible(uid, false);
|
||||
temp.markTested(uid);
|
||||
} else {
|
||||
meshes[objIdx].occlusionVisible = true;
|
||||
cd.addVisible(objIdx);
|
||||
temp.setVisible(uid, true);
|
||||
temp.markTested(uid);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static var autoOccluderMinScreenRatio: Float = 0.10;
|
||||
|
||||
public inline function isVisible(objectIndex: Int): Bool {
|
||||
var ms = cullData.meshes;
|
||||
return objectIndex >= 0 && objectIndex < ms.length && ms[objectIndex].occlusionVisible;
|
||||
}
|
||||
|
||||
public inline function markDirty() {
|
||||
bvhDirty = true;
|
||||
proxiesDirty = true;
|
||||
cullData.slotsDirty = true;
|
||||
shadowFlagsValid = false;
|
||||
}
|
||||
|
||||
public function removeLight(light: iron.object.LightObject) {
|
||||
shadowPasses.remove(light);
|
||||
shadowCallIdx.remove(light);
|
||||
shadowCallFrame.remove(light);
|
||||
#if lnx_csm
|
||||
shadowVps.remove(light);
|
||||
#end
|
||||
}
|
||||
|
||||
public static function init() {
|
||||
if (active == null) {
|
||||
active = new CullSystem();
|
||||
}
|
||||
}
|
||||
|
||||
public function loadOccluderProxies() {
|
||||
occluderProxies.resize(0);
|
||||
if (Scene.active == null) {
|
||||
uidToProxy.clear();
|
||||
return;
|
||||
}
|
||||
var meshes = Scene.active.meshes;
|
||||
uidScan.clear();
|
||||
for (i in 0...meshes.length) {
|
||||
var mesh = meshes[i];
|
||||
uidScan.set(mesh.uid, true);
|
||||
if (uidToProxy.exists(mesh.uid)) continue;
|
||||
if (mesh.data == null || mesh.data.raw == null) continue;
|
||||
var raw = mesh.data.raw;
|
||||
if (raw.occluder_proxy == null) continue;
|
||||
var proxyRaw = raw.occluder_proxy;
|
||||
if (proxyRaw.positions == null || proxyRaw.indices == null) continue;
|
||||
var proxy = new OccluderProxy();
|
||||
proxy.setData(proxyRaw.positions, proxyRaw.indices, mesh.uid);
|
||||
uidToProxy.set(mesh.uid, proxy);
|
||||
}
|
||||
var dead: Array<Int> = null;
|
||||
for (uid in uidToProxy.keys()) {
|
||||
if (!uidScan.exists(uid)) {
|
||||
if (dead == null) dead = [];
|
||||
dead.push(uid);
|
||||
}
|
||||
}
|
||||
if (dead != null) for (uid in dead) uidToProxy.remove(uid);
|
||||
for (proxy in uidToProxy.iterator()) occluderProxies.push(proxy);
|
||||
}
|
||||
|
||||
public static function shutdown() {
|
||||
active = null;
|
||||
}
|
||||
}
|
||||
|
||||
class ShadowPassSlot {
|
||||
public var vp: Mat4 = null;
|
||||
public var rasterizer: SoftwareOcclusionRasterizer = null;
|
||||
public var sceneRev: Int = -1;
|
||||
public var appliedRev: Int = -1;
|
||||
public var lastUsed: Int = -1;
|
||||
public var flags: Array<Bool> = [];
|
||||
public var flagsLen: Int = 0;
|
||||
public function new() {}
|
||||
}
|
||||
Reference in New Issue
Block a user