774 lines
24 KiB
Haxe
774 lines
24 KiB
Haxe
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() {}
|
|
}
|