Files
LNXSDK/leenkx/Sources/iron/culling/SoftwareOcclusionRasterizer.hx

508 lines
17 KiB
Haxe
Raw Normal View History

2026-10-15 02:41:03 -07:00
package iron.culling;
class SoftwareOcclusionRasterizer {
public var hiz: HiZPyramid;
public var width: Int;
public var height: Int;
public var nearClip: Float = 0.0001;
static inline var TILE_SIZE = 32;
public var trianglesRasterized: Int = 0;
public var collectMode = false;
var screenTris: Array<Float> = [];
var screenTriCount = 0;
static final BOX_FACES = [
[0, 1, 3, 2], // -Z
[4, 5, 7, 6], // +Z
[0, 1, 5, 4], // -Y
[2, 3, 7, 6], // +Y
[0, 2, 6, 4], // -X
[1, 3, 7, 5], // +X
];
var inX: Array<Float> = [0,0,0];
var inY: Array<Float> = [0,0,0];
var inZ: Array<Float> = [0,0,0];
var inW: Array<Float> = [0,0,0];
var outX: Array<Float> = [0,0,0,0];
var outY: Array<Float> = [0,0,0,0];
var outZ: Array<Float> = [0,0,0,0];
var outW: Array<Float> = [0,0,0,0];
var projSx: Array<Float> = [0,0,0,0];
var projSy: Array<Float> = [0,0,0,0];
var projSz: Array<Float> = [0,0,0,0];
var boxClipX: Array<Float> = [0,0,0,0,0,0,0,0];
var boxClipY: Array<Float> = [0,0,0,0,0,0,0,0];
var boxClipZ: Array<Float> = [0,0,0,0,0,0,0,0];
var boxClipW: Array<Float> = [0,0,0,0,0,0,0,0];
public function new(width: Int = 256, height: Int = 128) {
this.width = width;
this.height = height;
hiz = new HiZPyramid(width, height);
}
public function clear() {
hiz.clear(1e20);
trianglesRasterized = 0;
}
public function rasterizeTriangle(
v0x: Float, v0y: Float, v0z: Float,
v1x: Float, v1y: Float, v1z: Float,
v2x: Float, v2y: Float, v2z: Float
) {
var e0a = v1y - v0y; var e0b = v0x - v1x;
var e1a = v2y - v1y; var e1b = v1x - v2x;
var e2a = v0y - v2y; var e2b = v2x - v0x;
var e0c = -e0a * v0x - e0b * v0y;
var e1c = -e1a * v1x - e1b * v1y;
var e2c = -e2a * v2x - e2b * v2y;
var area = e0a * v2x + e0b * v2y + e0c;
if (area < 0) {
area = -area;
e0a = -e0a; e0b = -e0b; e0c = -e0c;
e1a = -e1a; e1b = -e1b; e1c = -e1c;
e2a = -e2a; e2b = -e2b; e2c = -e2c;
}
if (area < 1e-8) return;
var minX = Std.int(Math.max(0, Math.floor(Math.min(v0x, Math.min(v1x, v2x)))));
var maxX = Std.int(Math.min(width - 1, Math.ceil(Math.max(v0x, Math.max(v1x, v2x)))));
var minY = Std.int(Math.max(0, Math.floor(Math.min(v0y, Math.min(v1y, v2y)))));
var maxY = Std.int(Math.min(height - 1, Math.ceil(Math.max(v0y, Math.max(v1y, v2y)))));
if (minX > maxX || minY > maxY) return;
var d = (v1x - v0x) * (v2y - v0y) - (v2x - v0x) * (v1y - v0y);
var invD = 1.0 / d;
var dzdx = ((v1z - v0z) * (v2y - v0y) - (v2z - v0z) * (v1y - v0y)) * invD;
var dzdy = ((v2z - v0z) * (v1x - v0x) - (v1z - v0z) * (v2x - v0x)) * invD;
var zPush = (Math.abs(dzdx) + Math.abs(dzdy)) * 0.5;
var zMaxTri = v0z;
if (v1z > zMaxTri) zMaxTri = v1z;
if (v2z > zMaxTri) zMaxTri = v2z;
var cx0 = minX + 0.5;
var cy0 = minY + 0.5;
var e0row = e0a * cx0 + e0b * cy0 + e0c;
var e1row = e1a * cx0 + e1b * cy0 + e1c;
var e2row = e2a * cx0 + e2b * cy0 + e2c;
var zrow = v0z + dzdx * (cx0 - v0x) + dzdy * (cy0 - v0y);
var buf = hiz.mipBuffers[0];
var w = width;
for (py in minY...maxY + 1) {
var e0 = e0row; var e1 = e1row; var e2 = e2row;
var z = zrow;
var idx = py * w + minX;
for (px in minX...maxX + 1) {
if (e0 >= 0 && e1 >= 0 && e2 >= 0) {
var zPix = z + zPush;
if (zPix > zMaxTri) zPix = zMaxTri;
if (zPix < 0.0) zPix = 0.0;
if (zPix > 1.0) zPix = 1.0;
if (zPix < buf[idx]) buf[idx] = zPix;
}
e0 += e0a; e1 += e1a; e2 += e2a;
z += dzdx;
idx++;
}
e0row += e0b; e1row += e1b; e2row += e2b;
zrow += dzdy;
}
}
function clipNearAndRasterize(
cx0: Float, cy0: Float, cz0: Float, cw0: Float,
cx1: Float, cy1: Float, cz1: Float, cw1: Float,
cx2: Float, cy2: Float, cz2: Float, cw2: Float
) {
inX[0] = cx0; inY[0] = cy0; inZ[0] = cz0; inW[0] = cw0;
inX[1] = cx1; inY[1] = cy1; inZ[1] = cz1; inW[1] = cw1;
inX[2] = cx2; inY[2] = cy2; inZ[2] = cz2; inW[2] = cw2;
var outCount = 0;
for (i in 0...3) {
var aIdx = (i + 2) % 3;
var bIdx = i;
var aInside = inW[aIdx] >= nearClip;
var bInside = inW[bIdx] >= nearClip;
if (aInside) {
if (bInside) {
outX[outCount] = inX[bIdx]; outY[outCount] = inY[bIdx];
outZ[outCount] = inZ[bIdx]; outW[outCount] = inW[bIdx];
outCount++;
} else {
var t = (inW[aIdx] - nearClip) / (inW[aIdx] - inW[bIdx]);
outX[outCount] = inX[aIdx] + t * (inX[bIdx] - inX[aIdx]);
outY[outCount] = inY[aIdx] + t * (inY[bIdx] - inY[aIdx]);
outZ[outCount] = inZ[aIdx] + t * (inZ[bIdx] - inZ[aIdx]);
outW[outCount] = inW[aIdx] + t * (inW[bIdx] - inW[aIdx]);
outCount++;
}
} else {
if (bInside) {
var t = (inW[aIdx] - nearClip) / (inW[aIdx] - inW[bIdx]);
outX[outCount] = inX[aIdx] + t * (inX[bIdx] - inX[aIdx]);
outY[outCount] = inY[aIdx] + t * (inY[bIdx] - inY[aIdx]);
outZ[outCount] = inZ[aIdx] + t * (inZ[bIdx] - inZ[aIdx]);
outW[outCount] = inW[aIdx] + t * (inW[bIdx] - inW[aIdx]);
outCount++;
outX[outCount] = inX[bIdx]; outY[outCount] = inY[bIdx];
outZ[outCount] = inZ[bIdx]; outW[outCount] = inW[bIdx];
outCount++;
}
}
}
if (outCount < 3) return;
for (i in 0...outCount) {
var invW = SimdOps.reciprocal(outW[i]);
projSx[i] = (outX[i] * invW * 0.5 + 0.5) * width;
projSy[i] = (outY[i] * invW * 0.5 + 0.5) * height;
projSz[i] = outZ[i] * invW;
}
var signedArea = (projSx[1] - projSx[0]) * (projSy[2] - projSy[0]) -
(projSy[1] - projSy[0]) * (projSx[2] - projSx[0]);
if (Math.abs(signedArea) < 1e-8) return;
trianglesRasterized++;
if (collectMode) {
screenTris[screenTriCount++] = projSx[0]; screenTris[screenTriCount++] = projSy[0]; screenTris[screenTriCount++] = projSz[0];
screenTris[screenTriCount++] = projSx[1]; screenTris[screenTriCount++] = projSy[1]; screenTris[screenTriCount++] = projSz[1];
screenTris[screenTriCount++] = projSx[2]; screenTris[screenTriCount++] = projSy[2]; screenTris[screenTriCount++] = projSz[2];
if (outCount == 4) {
screenTris[screenTriCount++] = projSx[0]; screenTris[screenTriCount++] = projSy[0]; screenTris[screenTriCount++] = projSz[0];
screenTris[screenTriCount++] = projSx[2]; screenTris[screenTriCount++] = projSy[2]; screenTris[screenTriCount++] = projSz[2];
screenTris[screenTriCount++] = projSx[3]; screenTris[screenTriCount++] = projSy[3]; screenTris[screenTriCount++] = projSz[3];
}
} else {
rasterizeTriangle(
projSx[0], projSy[0], projSz[0],
projSx[1], projSy[1], projSz[1],
projSx[2], projSy[2], projSz[2]
);
if (outCount == 4) {
rasterizeTriangle(
projSx[0], projSy[0], projSz[0],
projSx[2], projSy[2], projSz[2],
projSx[3], projSy[3], projSz[3]
);
}
}
}
public function rasterizeProxy(
positions: kha.arrays.Float32Array,
indices: kha.arrays.Uint32Array,
vp: iron.math.Mat4,
screenW: Int, screenH: Int
) {
var triCount = Std.int(indices.length / 3);
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
for (t in 0...triCount) {
var i0 = indices[t * 3] * 3;
var i1 = indices[t * 3 + 1] * 3;
var i2 = indices[t * 3 + 2] * 3;
var px0 = positions[i0], py0 = positions[i0 + 1], pz0 = positions[i0 + 2];
var px1 = positions[i1], py1 = positions[i1 + 1], pz1 = positions[i1 + 2];
var px2 = positions[i2], py2 = positions[i2 + 1], pz2 = positions[i2 + 2];
var cw0 = m03 * px0 + m13 * py0 + m23 * pz0 + m33;
var cw1 = m03 * px1 + m13 * py1 + m23 * pz1 + m33;
var cw2 = m03 * px2 + m13 * py2 + m23 * pz2 + m33;
if (cw0 < nearClip && cw1 < nearClip && cw2 < nearClip) continue;
var cx0 = m00 * px0 + m10 * py0 + m20 * pz0 + m30;
var cy0 = m01 * px0 + m11 * py0 + m21 * pz0 + m31;
var cz0 = m02 * px0 + m12 * py0 + m22 * pz0 + m32;
var cx1 = m00 * px1 + m10 * py1 + m20 * pz1 + m30;
var cy1 = m01 * px1 + m11 * py1 + m21 * pz1 + m31;
var cz1 = m02 * px1 + m12 * py1 + m22 * pz1 + m32;
var cx2 = m00 * px2 + m10 * py2 + m20 * pz2 + m30;
var cy2 = m01 * px2 + m11 * py2 + m21 * pz2 + m31;
var cz2 = m02 * px2 + m12 * py2 + m22 * pz2 + m32;
clipNearAndRasterize(
cx0, cy0, cz0, cw0,
cx1, cy1, cz1, cw1,
cx2, cy2, cz2, cw2
);
}
}
public function rasterizeProxyTransformed(
clipPositions: kha.arrays.Float32Array,
indices: kha.arrays.Uint32Array,
screenW: Int, screenH: Int
) {
var triCount = Std.int(indices.length / 3);
for (t in 0...triCount) {
var i0 = indices[t * 3] * 4;
var i1 = indices[t * 3 + 1] * 4;
var i2 = indices[t * 3 + 2] * 4;
var cx0 = clipPositions[i0], cy0 = clipPositions[i0 + 1], cz0 = clipPositions[i0 + 2], cw0 = clipPositions[i0 + 3];
var cx1 = clipPositions[i1], cy1 = clipPositions[i1 + 1], cz1 = clipPositions[i1 + 2], cw1 = clipPositions[i1 + 3];
var cx2 = clipPositions[i2], cy2 = clipPositions[i2 + 1], cz2 = clipPositions[i2 + 2], cw2 = clipPositions[i2 + 3];
if (cw0 < nearClip && cw1 < nearClip && cw2 < nearClip) continue;
clipNearAndRasterize(
cx0, cy0, cz0, cw0,
cx1, cy1, cz1, cw1,
cx2, cy2, cz2, cw2
);
}
}
public function rasterizeBox(
minX: Float, minY: Float, minZ: Float,
maxX: Float, maxY: Float, maxZ: Float,
vp: iron.math.Mat4,
screenW: Int, screenH: Int
) {
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
for (c in 0...8) {
var x = (c & 1) != 0 ? maxX : minX;
var y = (c & 2) != 0 ? maxY : minY;
var z = (c & 4) != 0 ? maxZ : minZ;
boxClipX[c] = m00 * x + m10 * y + m20 * z + m30;
boxClipY[c] = m01 * x + m11 * y + m21 * z + m31;
boxClipZ[c] = m02 * x + m12 * y + m22 * z + m32;
boxClipW[c] = m03 * x + m13 * y + m23 * z + m33;
}
for (face in BOX_FACES) {
clipNearAndRasterize(
boxClipX[face[0]], boxClipY[face[0]], boxClipZ[face[0]], boxClipW[face[0]],
boxClipX[face[1]], boxClipY[face[1]], boxClipZ[face[1]], boxClipW[face[1]],
boxClipX[face[2]], boxClipY[face[2]], boxClipZ[face[2]], boxClipW[face[2]]
);
clipNearAndRasterize(
boxClipX[face[0]], boxClipY[face[0]], boxClipZ[face[0]], boxClipW[face[0]],
boxClipX[face[2]], boxClipY[face[2]], boxClipZ[face[2]], boxClipW[face[2]],
boxClipX[face[3]], boxClipY[face[3]], boxClipZ[face[3]], boxClipW[face[3]]
);
}
}
public function buildHiZ() {
hiz.buildPyramid();
}
public function beginCollect() {
collectMode = true;
screenTriCount = 0;
}
var tileTriCounts: Array<Int> = [];
var tileTriOffsets: Array<Int> = [];
var tileTriLists: Array<Array<Int>> = [];
public function flushCollected() {
collectMode = false;
var nTris = Std.int(screenTriCount / 9);
if (nTris == 0) return;
var tilesX = Std.int(Math.ceil(width / TILE_SIZE));
var tilesY = Std.int(Math.ceil(height / TILE_SIZE));
var totalTiles = tilesX * tilesY;
if (tileTriCounts.length < totalTiles) {
tileTriCounts.resize(totalTiles);
tileTriOffsets.resize(totalTiles);
tileTriLists.resize(totalTiles);
}
for (t in 0...totalTiles) tileTriCounts[t] = 0;
for (t in 0...nTris) {
var off = t * 9;
var tx0 = Std.int(Math.max(0, Math.floor(Math.min(
Math.min(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
var tx1 = Std.int(Math.min(tilesX - 1, Math.ceil(Math.max(
Math.max(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
var ty0 = Std.int(Math.max(0, Math.floor(Math.min(
Math.min(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
var ty1 = Std.int(Math.min(tilesY - 1, Math.ceil(Math.max(
Math.max(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
for (ty in ty0...ty1 + 1) {
for (tx in tx0...tx1 + 1) {
tileTriCounts[ty * tilesX + tx]++;
}
}
}
for (t in 0...totalTiles) {
var l = tileTriLists[t];
if (l == null) { l = []; tileTriLists[t] = l; }
if (l.length < tileTriCounts[t]) l.resize(tileTriCounts[t]);
tileTriOffsets[t] = 0;
}
for (t in 0...nTris) {
var off = t * 9;
var tx0 = Std.int(Math.max(0, Math.floor(Math.min(
Math.min(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
var tx1 = Std.int(Math.min(tilesX - 1, Math.ceil(Math.max(
Math.max(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
var ty0 = Std.int(Math.max(0, Math.floor(Math.min(
Math.min(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
var ty1 = Std.int(Math.min(tilesY - 1, Math.ceil(Math.max(
Math.max(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
for (ty in ty0...ty1 + 1) {
for (tx in tx0...tx1 + 1) {
var ti = ty * tilesX + tx;
tileTriLists[ti][tileTriOffsets[ti]++] = t;
}
}
}
var buf = hiz.mipBuffers[0];
var w = width;
var tris = screenTris;
#if (target.threaded)
JobSystem.parallelFor(totalTiles, function(start, end) {
for (ti in start...end) {
var tx = ti % tilesX;
var ty = Std.int(ti / tilesX);
var px0 = tx * TILE_SIZE;
var py0 = ty * TILE_SIZE;
var px1 = Std.int(Math.min(w - 1, px0 + TILE_SIZE - 1));
var py1 = Std.int(Math.min(height - 1, py0 + TILE_SIZE - 1));
var list = tileTriLists[ti];
var listLen = tileTriCounts[ti];
for (li in 0...listLen) {
var off = list[li] * 9;
rasterizeTriangleBounded(
tris[off], tris[off + 1], tris[off + 2],
tris[off + 3], tris[off + 4], tris[off + 5],
tris[off + 6], tris[off + 7], tris[off + 8],
px0, py0, px1, py1, buf, w
);
}
}
});
#else
for (ti in 0...totalTiles) {
var tx = ti % tilesX;
var ty = Std.int(ti / tilesX);
var px0 = tx * TILE_SIZE;
var py0 = ty * TILE_SIZE;
var px1 = Std.int(Math.min(w - 1, px0 + TILE_SIZE - 1));
var py1 = Std.int(Math.min(height - 1, py0 + TILE_SIZE - 1));
var list = tileTriLists[ti];
for (li in 0...list.length) {
var off = list[li] * 9;
rasterizeTriangleBounded(
tris[off], tris[off + 1], tris[off + 2],
tris[off + 3], tris[off + 4], tris[off + 5],
tris[off + 6], tris[off + 7], tris[off + 8],
px0, py0, px1, py1, buf, w
);
}
}
#end
}
function rasterizeTriangleBounded(
v0x: Float, v0y: Float, v0z: Float,
v1x: Float, v1y: Float, v1z: Float,
v2x: Float, v2y: Float, v2z: Float,
tileX0: Int, tileY0: Int, tileX1: Int, tileY1: Int,
buf: kha.arrays.Float32Array, w: Int
) {
var e0a = v1y - v0y; var e0b = v0x - v1x;
var e1a = v2y - v1y; var e1b = v1x - v2x;
var e2a = v0y - v2y; var e2b = v2x - v0x;
var e0c = -e0a * v0x - e0b * v0y;
var e1c = -e1a * v1x - e1b * v1y;
var e2c = -e2a * v2x - e2b * v2y;
var area = e0a * v2x + e0b * v2y + e0c;
if (area < 0) {
area = -area;
e0a = -e0a; e0b = -e0b; e0c = -e0c;
e1a = -e1a; e1b = -e1b; e1c = -e1c;
e2a = -e2a; e2b = -e2b; e2c = -e2c;
}
if (area < 1e-8) return;
var minX = Std.int(Math.max(tileX0, Math.floor(Math.min(v0x, Math.min(v1x, v2x)))));
var maxX = Std.int(Math.min(tileX1, Math.ceil(Math.max(v0x, Math.max(v1x, v2x)))));
var minY = Std.int(Math.max(tileY0, Math.floor(Math.min(v0y, Math.min(v1y, v2y)))));
var maxY = Std.int(Math.min(tileY1, Math.ceil(Math.max(v0y, Math.max(v1y, v2y)))));
if (minX > maxX || minY > maxY) return;
var d = (v1x - v0x) * (v2y - v0y) - (v2x - v0x) * (v1y - v0y);
var invD = 1.0 / d;
var dzdx = ((v1z - v0z) * (v2y - v0y) - (v2z - v0z) * (v1y - v0y)) * invD;
var dzdy = ((v2z - v0z) * (v1x - v0x) - (v1z - v0z) * (v2x - v0x)) * invD;
var zPush = (Math.abs(dzdx) + Math.abs(dzdy)) * 0.5;
var zMaxTri = v0z;
if (v1z > zMaxTri) zMaxTri = v1z;
if (v2z > zMaxTri) zMaxTri = v2z;
var cx0 = minX + 0.5;
var cy0 = minY + 0.5;
var e0row = e0a * cx0 + e0b * cy0 + e0c;
var e1row = e1a * cx0 + e1b * cy0 + e1c;
var e2row = e2a * cx0 + e2b * cy0 + e2c;
var zrow = v0z + dzdx * (cx0 - v0x) + dzdy * (cy0 - v0y);
for (py in minY...maxY + 1) {
var e0 = e0row; var e1 = e1row; var e2 = e2row;
var z = zrow;
var idx = py * w + minX;
for (px in minX...maxX + 1) {
if (e0 >= 0 && e1 >= 0 && e2 >= 0) {
var zPix = z + zPush;
if (zPix > zMaxTri) zPix = zMaxTri;
if (zPix > 1.0) zPix = 1.0;
if (zPix < buf[idx]) { buf[idx] = zPix; }
}
e0 += e0a; e1 += e1a; e2 += e2a;
z += dzdx;
idx++;
}
e0row += e0b; e1row += e1b; e2row += e2b;
zrow += dzdy;
}
}
public function isOccluded(
minX: Float, minY: Float, minZ: Float,
maxX: Float, maxY: Float, maxZ: Float,
vp: iron.math.Mat4,
screenW: Int, screenH: Int
): Bool {
return hiz.isOccluded(minX, minY, minZ, maxX, maxY, maxZ, vp, screenW, screenH);
}
}