mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
Fix terrain disappearing at screen edges with compute culling (#2212)
This commit is contained in:
1 parent
6dfb0312b1
commit
fcf8ad064a
8 files changed
+168
-98
No files matched your search
@@ -63,6 +63,7 @@ public final class SystemProperties {
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
|
||||
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
|
||||
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
|
||||
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
|
||||
@@ -131,6 +132,12 @@ public final class SystemProperties {
|
||||
CONFIGURED
|
||||
}
|
||||
|
||||
public enum FlybyCamera {
|
||||
FIRST_PERSON,
|
||||
THIRD_PERSON_BACK,
|
||||
THIRD_PERSON_FRONT
|
||||
}
|
||||
|
||||
public enum FlybyWeather {
|
||||
CLEAR,
|
||||
RAIN,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
# route - straight | pan | circuit | static
|
||||
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
|
||||
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
|
||||
# -Dangelica.flyby.camera=FIRST_PERSON|THIRD_PERSON_BACK|THIRD_PERSON_FRONT (default FIRST_PERSON).
|
||||
#
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
|
||||
|
||||
@@ -84,6 +84,8 @@ public final class FlybyRunner {
|
||||
private boolean vsyncOverridden;
|
||||
private boolean showDebugInfoSaved;
|
||||
private boolean showDebugInfoOverridden;
|
||||
private int cameraSaved;
|
||||
private boolean cameraOverridden;
|
||||
private int shotEvery;
|
||||
private int nextShot;
|
||||
private int shot;
|
||||
@@ -375,6 +377,11 @@ public final class FlybyRunner {
|
||||
this.showDebugInfoOverridden = true;
|
||||
mc.gameSettings.showDebugInfo = true;
|
||||
}
|
||||
if (!this.cameraOverridden) {
|
||||
this.cameraSaved = mc.gameSettings.thirdPersonView;
|
||||
this.cameraOverridden = true;
|
||||
mc.gameSettings.thirdPersonView = SystemProperties.FLYBY_CAMERA.ordinal();
|
||||
}
|
||||
if ("isbrh".equals(SystemProperties.FLYBY_CRASH_TEST)) {
|
||||
IsbrhTestRenderer.armCrashTest();
|
||||
}
|
||||
@@ -535,7 +542,7 @@ public final class FlybyRunner {
|
||||
this.nextShot = this.shotEvery / 2;
|
||||
FramePacer.beginStats();
|
||||
|
||||
final String config = "backend=" + GLStateManager.getRenderBackendName() + " pacing=" + SystemProperties.FLYBY_PACING + " vsync=" + GLStateManager.getEffectiveVSyncMode() + " discard=" + (mc.getIntegratedServer() != null);
|
||||
final String config = "backend=" + GLStateManager.getRenderBackendName() + " pacing=" + SystemProperties.FLYBY_PACING + " vsync=" + GLStateManager.getEffectiveVSyncMode() + " discard=" + (mc.getIntegratedServer() != null) + " camera=" + SystemProperties.FLYBY_CAMERA;
|
||||
if (Tracy.ENABLED) {
|
||||
Tracy.message("flyby start route=" + this.route.id() + " length=" + this.runLength + this.route.lengthUnit() + " speed=" + this.route.speedOr(this.speed) + "b/t ticks=" + this.runTicks + " " + config);
|
||||
}
|
||||
@@ -757,6 +764,9 @@ public final class FlybyRunner {
|
||||
final String summary = this.summarise(elapsedNs, player);
|
||||
LOGGER.info(summary);
|
||||
LOGGER.info(FramePacer.endStats());
|
||||
if (mc.gameSettings.thirdPersonView != SystemProperties.FLYBY_CAMERA.ordinal()) {
|
||||
LOGGER.warn("Flyby: camera left {} during the run (thirdPersonView={}), screenshots are not comparable", SystemProperties.FLYBY_CAMERA, mc.gameSettings.thirdPersonView);
|
||||
}
|
||||
if (mc.thePlayer != null) {
|
||||
mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + summary));
|
||||
}
|
||||
@@ -787,6 +797,7 @@ public final class FlybyRunner {
|
||||
this.returnToOrigin(player);
|
||||
this.restorePauseOnLostFocus(mc);
|
||||
this.restoreShowDebugInfo(mc);
|
||||
this.restoreCamera(mc);
|
||||
this.armed = false;
|
||||
sceneGuarded = false;
|
||||
this.sceneClearRequested = this.sceneCommands.length > 0;
|
||||
@@ -819,6 +830,12 @@ public final class FlybyRunner {
|
||||
this.showDebugInfoOverridden = false;
|
||||
}
|
||||
|
||||
private void restoreCamera(Minecraft mc) {
|
||||
if (!this.cameraOverridden) return;
|
||||
mc.gameSettings.thirdPersonView = this.cameraSaved;
|
||||
this.cameraOverridden = false;
|
||||
}
|
||||
|
||||
private void returnToOrigin(EntityClientPlayerMP player) {
|
||||
player.motionX = 0.0D;
|
||||
player.motionY = 0.0D;
|
||||
@@ -969,6 +986,7 @@ public final class FlybyRunner {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
this.restorePauseOnLostFocus(mc);
|
||||
this.restoreShowDebugInfo(mc);
|
||||
this.restoreCamera(mc);
|
||||
this.armed = false;
|
||||
this.stopRecording(mc);
|
||||
this.pendingRequest.set(null);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.rendering.culling;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -10,9 +12,11 @@ public final class FrustumExtractor {
|
||||
public static final int UBO_SIZE_BYTES = 160;
|
||||
public static final int PLANE_COUNT = 6;
|
||||
|
||||
private static final float SECTION_HALF_EXTENT = 8.0f + 1.0f + 0.125f;
|
||||
private static final float SECTION_CENTER = 8.0f;
|
||||
private static final int PLANE_STRIDE = 16;
|
||||
private static final int CONTROL_OFFSET = 96;
|
||||
private static final int CAMERA_WORLD_OFFSET = 112;
|
||||
private static final int CAMERA_BLOCK_OFFSET = 112;
|
||||
private static final int BATCH_OFFSET = 128;
|
||||
private static final int PYR_OFFSET = 144;
|
||||
|
||||
@@ -22,24 +26,26 @@ public final class FrustumExtractor {
|
||||
return ByteBuffer.allocateDirect(UBO_SIZE_BYTES).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
public static void writeStd140(Matrix4fc mvp, int visibleCount, int indexPointerMask, ByteBuffer out) {
|
||||
public static void writeFrustum(Matrix4fc projection, Matrix4fc modelView, CameraTransform camera, Matrix4f mvp, ByteBuffer out) {
|
||||
projection.mul(modelView, mvp);
|
||||
final float r0x = mvp.m00(), r0y = mvp.m10(), r0z = mvp.m20(), r0w = mvp.m30();
|
||||
final float r1x = mvp.m01(), r1y = mvp.m11(), r1z = mvp.m21(), r1w = mvp.m31();
|
||||
final float r2x = mvp.m02(), r2y = mvp.m12(), r2z = mvp.m22(), r2w = mvp.m32();
|
||||
final float r3x = mvp.m03(), r3y = mvp.m13(), r3z = mvp.m23(), r3w = mvp.m33();
|
||||
final float cx = SECTION_CENTER - camera.fracX;
|
||||
final float cy = SECTION_CENTER - camera.fracY;
|
||||
final float cz = SECTION_CENTER - camera.fracZ;
|
||||
|
||||
writePlane(out, 0 * PLANE_STRIDE, r3x + r0x, r3y + r0y, r3z + r0z, r3w + r0w);
|
||||
writePlane(out, 1 * PLANE_STRIDE, r3x - r0x, r3y - r0y, r3z - r0z, r3w - r0w);
|
||||
writePlane(out, 2 * PLANE_STRIDE, r3x + r1x, r3y + r1y, r3z + r1z, r3w + r1w);
|
||||
writePlane(out, 3 * PLANE_STRIDE, r3x - r1x, r3y - r1y, r3z - r1z, r3w - r1w);
|
||||
writePlane(out, 4 * PLANE_STRIDE, r3x + r2x, r3y + r2y, r3z + r2z, r3w + r2w);
|
||||
writePlane(out, 5 * PLANE_STRIDE, r3x - r2x, r3y - r2y, r3z - r2z, r3w - r2w);
|
||||
writePlane(out, 0 * PLANE_STRIDE, r3x + r0x, r3y + r0y, r3z + r0z, r3w + r0w, cx, cy, cz);
|
||||
writePlane(out, 1 * PLANE_STRIDE, r3x - r0x, r3y - r0y, r3z - r0z, r3w - r0w, cx, cy, cz);
|
||||
writePlane(out, 2 * PLANE_STRIDE, r3x + r1x, r3y + r1y, r3z + r1z, r3w + r1w, cx, cy, cz);
|
||||
writePlane(out, 3 * PLANE_STRIDE, r3x - r1x, r3y - r1y, r3z - r1z, r3w - r1w, cx, cy, cz);
|
||||
writePlane(out, 4 * PLANE_STRIDE, r3x + r2x, r3y + r2y, r3z + r2z, r3w + r2w, cx, cy, cz);
|
||||
writePlane(out, 5 * PLANE_STRIDE, r3x - r2x, r3y - r2y, r3z - r2z, r3w - r2w, cx, cy, cz);
|
||||
|
||||
out.putInt(CONTROL_OFFSET + 0, visibleCount);
|
||||
out.putInt(CONTROL_OFFSET + 4, indexPointerMask);
|
||||
out.putInt(CONTROL_OFFSET + 8, 0);
|
||||
out.putInt(CONTROL_OFFSET + 12, 0);
|
||||
out.putInt(BATCH_OFFSET, 0);
|
||||
out.putInt(CAMERA_BLOCK_OFFSET + 0, camera.intX);
|
||||
out.putInt(CAMERA_BLOCK_OFFSET + 4, camera.intY);
|
||||
out.putInt(CAMERA_BLOCK_OFFSET + 8, camera.intZ);
|
||||
}
|
||||
|
||||
public static void patchBatchEntryBase(int entryBase, ByteBuffer out) {
|
||||
@@ -61,17 +67,10 @@ public final class FrustumExtractor {
|
||||
out.putInt(CONTROL_OFFSET + 8, bypass ? 1 : 0);
|
||||
}
|
||||
|
||||
public static void patchCameraWorld(float x, float y, float z, ByteBuffer out) {
|
||||
out.putFloat(CAMERA_WORLD_OFFSET + 0, x);
|
||||
out.putFloat(CAMERA_WORLD_OFFSET + 4, y);
|
||||
out.putFloat(CAMERA_WORLD_OFFSET + 8, z);
|
||||
}
|
||||
|
||||
private static void writePlane(ByteBuffer out, int offset, float a, float b, float c, float d) {
|
||||
final float invLen = 1.0f / (float) Math.sqrt(a * a + b * b + c * c);
|
||||
out.putFloat(offset + 0, a * invLen);
|
||||
out.putFloat(offset + 4, b * invLen);
|
||||
out.putFloat(offset + 8, c * invLen);
|
||||
out.putFloat(offset + 12, d * invLen);
|
||||
private static void writePlane(ByteBuffer out, int offset, float a, float b, float c, float d, float cx, float cy, float cz) {
|
||||
out.putFloat(offset + 0, a);
|
||||
out.putFloat(offset + 4, b);
|
||||
out.putFloat(offset + 8, c);
|
||||
out.putFloat(offset + 12, d + SECTION_HALF_EXTENT * (Math.abs(a) + Math.abs(b) + Math.abs(c)) + a * cx + b * cy + c * cz);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
@@ -132,7 +131,6 @@ public final class GpuTerrainCuller {
|
||||
|
||||
private ByteBuffer uboBytes;
|
||||
private Matrix4f mvp;
|
||||
private Matrix4f mvNoTranslation;
|
||||
private int passOutputBase;
|
||||
private ChunkPrimitiveType walkPrimitiveType;
|
||||
|
||||
@@ -399,17 +397,8 @@ public final class GpuTerrainCuller {
|
||||
uboBytes = FrustumExtractor.allocateUboByteBuffer();
|
||||
}
|
||||
final boolean shadow = ShadowRenderer.ACTIVE;
|
||||
final Matrix4fc proj = matrices.projection();
|
||||
final Matrix4fc mv = matrices.modelView();
|
||||
if (mvp == null) mvp = new Matrix4f();
|
||||
if (mvNoTranslation == null) mvNoTranslation = new Matrix4f();
|
||||
mvNoTranslation.set(mv).setTranslation(0f, 0f, 0f);
|
||||
proj.mul(mvNoTranslation, mvp);
|
||||
final float camX = (float) camera.intX + camera.fracX;
|
||||
final float camY = (float) camera.intY + camera.fracY;
|
||||
final float camZ = (float) camera.intZ + camera.fracZ;
|
||||
FrustumExtractor.writeStd140(mvp, 0, 0, uboBytes);
|
||||
FrustumExtractor.patchCameraWorld(camX, camY, camZ, uboBytes);
|
||||
FrustumExtractor.writeFrustum(matrices.projection(), matrices.modelView(), camera, mvp, uboBytes);
|
||||
FrustumExtractor.patchBypassFrustum(shadow, uboBytes);
|
||||
|
||||
frustumUboBytes = uboBytes;
|
||||
@@ -459,7 +448,7 @@ public final class GpuTerrainCuller {
|
||||
}
|
||||
|
||||
if (computeActiveThisPass) {
|
||||
prepareFrustumUbo(matrices, camera);
|
||||
if (!chained) prepareFrustumUbo(matrices, camera);
|
||||
syncSectionMetaIfDirty();
|
||||
walkPass(renderLists, AngelicaRenderPassConfiguration.TRANSLUCENT_PASS, occlusionCamera, useBlockFaceCulling);
|
||||
|
||||
|
||||
@@ -34,30 +34,24 @@ layout(std430, binding = 2) writeonly buffer IndirectCmds {
|
||||
} indirect;
|
||||
|
||||
layout(std140, binding = 1) uniform Frustum {
|
||||
vec4 planes[6]; // camera-relative planes from `proj * mv` (no translate baked in)
|
||||
vec4 planes[6]; // planes from `proj * mv`; .w has the padded section box and camera fraction folded in
|
||||
// control.x = visibleCount (iteration bound)
|
||||
// control.y = indexPointerMask (0 non-sorted -> firstIndex masked to 0; 0xFFFFFFFF sorted)
|
||||
// control.z = bypassFrustum (shadow passes; renderLists already carry the caster set)
|
||||
uvec4 control;
|
||||
// cameraWorld.xyz = current-frame camera (subtract from world AABB for frustum test)
|
||||
vec4 cameraWorld;
|
||||
// cameraBlock.xyz = integer part of the current-frame camera (subtract from section origin for frustum test)
|
||||
ivec4 cameraBlock;
|
||||
// batch.x = visible-list entryBase for this dispatch (batched culls)
|
||||
uvec4 batch;
|
||||
// pyr.zw = vertices and index elements per primitive, from ChunkPrimitiveType (exact as floats)
|
||||
vec4 pyr;
|
||||
} frustum;
|
||||
|
||||
bool insideFrustum(vec3 minP, vec3 maxP) {
|
||||
// Planes are camera-relative; reduce world AABB to camera-relative before the n-vertex test.
|
||||
vec3 cam = frustum.cameraWorld.xyz;
|
||||
vec3 minR = minP - cam;
|
||||
vec3 maxR = maxP - cam;
|
||||
bool insideFrustum(ivec3 origin) {
|
||||
vec3 rel = vec3(origin - frustum.cameraBlock.xyz);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
vec4 p = frustum.planes[i];
|
||||
vec3 ext = vec3(p.x > 0.0 ? maxR.x : minR.x,
|
||||
p.y > 0.0 ? maxR.y : minR.y,
|
||||
p.z > 0.0 ? maxR.z : minR.z);
|
||||
if (dot(p.xyz, ext) + p.w < 0.0) return false;
|
||||
if (dot(p.xyz, rel) + p.w < 0.0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -74,10 +68,8 @@ void main() {
|
||||
uint outputBase = packedEntry >> 8;
|
||||
|
||||
Section s = meta.sections[slot];
|
||||
vec3 minP = vec3(s.origin.xyz);
|
||||
vec3 maxP = minP + vec3(16.0);
|
||||
|
||||
bool inside = (frustum.control.z != 0u) || insideFrustum(minP, maxP);
|
||||
bool inside = (frustum.control.z != 0u) || insideFrustum(s.origin.xyz);
|
||||
|
||||
uint indexMask = frustum.control.y;
|
||||
uint vertsPerPrim = uint(frustum.pyr.z);
|
||||
|
||||
+101
-37
@@ -1,7 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.rendering.culling;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -11,6 +12,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class FrustumExtractorTest {
|
||||
private static final float EPS = 1e-4f;
|
||||
private static final Matrix4f IDENTITY = new Matrix4f();
|
||||
private static final CameraTransform ORIGIN = new CameraTransform(0.0, 0.0, 0.0);
|
||||
private static final float EYE_HEIGHT = 0.12f;
|
||||
private static final double SECTION_PAD = 1.125;
|
||||
private static final float HALF_EXTENT = 8f + (float) SECTION_PAD;
|
||||
private static final double BOUNDARY_SKIP = 1e-3;
|
||||
private static final int SWEEP_SECTIONS = 6;
|
||||
|
||||
@Test
|
||||
void uboSizeIs160() {
|
||||
@@ -19,73 +27,129 @@ class FrustumExtractorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void patchBatchEntryBase_writesAtOffset128_andWriteStd140Resets() {
|
||||
void patchBatchEntryBase_writesAtOffset128() {
|
||||
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
|
||||
FrustumExtractor.patchBatchEntryBase(777, out);
|
||||
assertEquals(777, out.getInt(128));
|
||||
FrustumExtractor.writeStd140(new Matrix4f(), 0, 0, out);
|
||||
assertEquals(0, out.getInt(128), "writeStd140 must reset entryBase");
|
||||
}
|
||||
|
||||
@Test
|
||||
void identityMatrix_planesAreUnitNormalsAtUnitDistance() {
|
||||
final Matrix4f identity = new Matrix4f();
|
||||
void cameraFractionFoldsIntoPlaneConstant() {
|
||||
final CameraTransform camera = new CameraTransform(-3.25, 70.5, 12.75);
|
||||
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
|
||||
FrustumExtractor.writeStd140(identity, 0, 0, out);
|
||||
FrustumExtractor.writeFrustum(IDENTITY, IDENTITY, camera, new Matrix4f(), out);
|
||||
|
||||
assertPlane(out, 0, 1f, 0f, 0f, 1f);
|
||||
assertPlane(out, 16, -1f, 0f, 0f, 1f);
|
||||
assertPlane(out, 32, 0f, 1f, 0f, 1f);
|
||||
assertPlane(out, 48, 0f, -1f, 0f, 1f);
|
||||
assertPlane(out, 64, 0f, 0f, 1f, 1f);
|
||||
assertPlane(out, 80, 0f, 0f, -1f, 1f);
|
||||
assertEquals(1f + HALF_EXTENT + (8f + 0.25f), out.getFloat(12), EPS);
|
||||
assertEquals(1f + HALF_EXTENT - (8f + 0.25f), out.getFloat(28), EPS);
|
||||
assertEquals(1f + HALF_EXTENT + (8f - 0.5f), out.getFloat(44), EPS);
|
||||
assertEquals(1f + HALF_EXTENT - (8f - 0.5f), out.getFloat(60), EPS);
|
||||
assertEquals(1f + HALF_EXTENT + (8f - 0.75f), out.getFloat(76), EPS);
|
||||
assertEquals(1f + HALF_EXTENT - (8f - 0.75f), out.getFloat(92), EPS);
|
||||
assertEquals(-3, out.getInt(112));
|
||||
assertEquals(70, out.getInt(116));
|
||||
assertEquals(12, out.getInt(120));
|
||||
assertEquals(0, out.getInt(124));
|
||||
}
|
||||
|
||||
@Test
|
||||
void identityMatrix_left_correct() {
|
||||
final Matrix4f identity = new Matrix4f();
|
||||
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
|
||||
FrustumExtractor.writeStd140(identity, 0, 0, out);
|
||||
assertPlane(out, 0, 1f, 0f, 0f, 1f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void perspectiveAxisAlignedPoint_insideFrustum() {
|
||||
void perspectiveAxisAlignedSection_insideFrustum() {
|
||||
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(70.0), 16f / 9f, 0.1f, 1000.0f);
|
||||
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
|
||||
FrustumExtractor.writeStd140(proj, 0, 0, out);
|
||||
FrustumExtractor.writeFrustum(proj, IDENTITY, ORIGIN, new Matrix4f(), out);
|
||||
|
||||
assertTrue(insideAllPlanes(out, new Vector3f(0f, 0f, -10f)), "point ahead should be inside");
|
||||
assertTrue(!insideAllPlanes(out, new Vector3f(0f, 0f, 10f)), "point behind should be outside");
|
||||
assertTrue(shaderInsideFrustum(out, -8, -8, -32), "section ahead should be inside");
|
||||
assertTrue(!shaderInsideFrustum(out, -8, -8, 16), "section behind should be outside");
|
||||
}
|
||||
|
||||
@Test
|
||||
void visibleCountStored() {
|
||||
final Matrix4f mvp = new Matrix4f();
|
||||
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
|
||||
FrustumExtractor.writeStd140(mvp, 4242, 0, out);
|
||||
FrustumExtractor.patchControl(4242, 0, out);
|
||||
assertEquals(4242, out.getInt(96), "visibleCount at offset 96");
|
||||
assertEquals(0, out.getInt(100));
|
||||
assertEquals(0, out.getInt(104));
|
||||
assertEquals(0, out.getInt(108));
|
||||
}
|
||||
|
||||
private static void assertPlane(ByteBuffer out, int offset, float a, float b, float c, float d) {
|
||||
final float len = (float) Math.sqrt(a * a + b * b + c * c);
|
||||
final float inv = len == 0.0f ? 1.0f : 1.0f / len;
|
||||
assertEquals(a * inv, out.getFloat(offset + 0), EPS, "a @" + offset);
|
||||
assertEquals(b * inv, out.getFloat(offset + 4), EPS, "b @" + offset);
|
||||
assertEquals(c * inv, out.getFloat(offset + 8), EPS, "c @" + offset);
|
||||
assertEquals(d * inv, out.getFloat(offset + 12), EPS, "d @" + offset);
|
||||
@Test
|
||||
void matchesCpuFrustumForVanillaCameras() {
|
||||
final float[] distances = { 0.1f, 4.0f };
|
||||
final double[][] cameras = { { 8.3, 70.6, 8.7 }, { -1234.4, 65.2, -987.6 }, { 3000000.37, 80.5, -2999999.63 } };
|
||||
final float[] yaws = { 0f, 37f, 90f, 163f, 271f };
|
||||
final float[] pitches = { -60f, 0f, 25f, 80f };
|
||||
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(69.0), 16f / 9f, 0.05f, 512.0f);
|
||||
final Matrix4f mvFull = new Matrix4f();
|
||||
final Matrix4f refMvp = new Matrix4f();
|
||||
final Matrix4f mvp = new Matrix4f();
|
||||
final FrustumIntersection reference = new FrustumIntersection();
|
||||
final ByteBuffer out = FrustumExtractor.allocateUboByteBuffer();
|
||||
final StringBuilder report = new StringBuilder();
|
||||
int mismatches = 0;
|
||||
|
||||
for (final float d : distances) {
|
||||
for (final double[] cam : cameras) {
|
||||
final CameraTransform camera = new CameraTransform(cam[0], cam[1], cam[2]);
|
||||
final int baseX = Math.floorDiv((int) Math.floor(cam[0]), 16);
|
||||
final int baseY = Math.floorDiv((int) Math.floor(cam[1]), 16);
|
||||
final int baseZ = Math.floorDiv((int) Math.floor(cam[2]), 16);
|
||||
int tested = 0;
|
||||
int wronglyCulled = 0;
|
||||
int wronglyKept = 0;
|
||||
|
||||
for (final float yaw : yaws) {
|
||||
for (final float pitch : pitches) {
|
||||
mvFull.identity().translate(0f, 0f, -d).rotateX((float) Math.toRadians(pitch)).rotateY((float) Math.toRadians(yaw + 180.0f)).translate(0f, EYE_HEIGHT, 0f);
|
||||
reference.set(proj.mul(mvFull, refMvp));
|
||||
FrustumExtractor.writeFrustum(proj, mvFull, camera, mvp, out);
|
||||
|
||||
for (int sx = -SWEEP_SECTIONS; sx <= SWEEP_SECTIONS; sx++) {
|
||||
for (int sy = -SWEEP_SECTIONS; sy <= SWEEP_SECTIONS; sy++) {
|
||||
for (int sz = -SWEEP_SECTIONS; sz <= SWEEP_SECTIONS; sz++) {
|
||||
final int ox = (baseX + sx) * 16;
|
||||
final int oy = (baseY + sy) * 16;
|
||||
final int oz = (baseZ + sz) * 16;
|
||||
final boolean tight = referenceVisible(reference, camera, ox, oy, oz, SECTION_PAD - BOUNDARY_SKIP);
|
||||
final boolean loose = referenceVisible(reference, camera, ox, oy, oz, SECTION_PAD + BOUNDARY_SKIP);
|
||||
if (tight != loose) continue;
|
||||
final boolean expected = tight;
|
||||
final boolean actual = shaderInsideFrustum(out, ox, oy, oz);
|
||||
tested++;
|
||||
if (expected && !actual) wronglyCulled++;
|
||||
if (!expected && actual) wronglyKept++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mismatches += wronglyCulled + wronglyKept;
|
||||
report.append("\n d=").append(d).append(" camera=(").append(cam[0]).append(", ").append(cam[1]).append(", ").append(cam[2]).append("): tested=").append(tested).append(" wronglyCulled=").append(wronglyCulled).append(" wronglyKept=").append(wronglyKept);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(0, mismatches, "shader frustum test disagrees with the CPU frustum:" + report);
|
||||
}
|
||||
|
||||
private static boolean insideAllPlanes(ByteBuffer out, Vector3f p) {
|
||||
private static boolean referenceVisible(FrustumIntersection reference, CameraTransform camera, int ox, int oy, int oz, double pad) {
|
||||
final float minX = (float) (ox - pad - camera.x);
|
||||
final float minY = (float) (oy - pad - camera.y);
|
||||
final float minZ = (float) (oz - pad - camera.z);
|
||||
final float maxX = (float) (ox + 16.0 + pad - camera.x);
|
||||
final float maxY = (float) (oy + 16.0 + pad - camera.y);
|
||||
final float maxZ = (float) (oz + 16.0 + pad - camera.z);
|
||||
return reference.testAab(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
private static boolean shaderInsideFrustum(ByteBuffer out, int ox, int oy, int oz) {
|
||||
final float x = (float) (ox - out.getInt(112));
|
||||
final float y = (float) (oy - out.getInt(116));
|
||||
final float z = (float) (oz - out.getInt(120));
|
||||
for (int i = 0; i < 6; i++) {
|
||||
final float a = out.getFloat(i * 16 + 0);
|
||||
final float b = out.getFloat(i * 16 + 4);
|
||||
final float c = out.getFloat(i * 16 + 8);
|
||||
final float d = out.getFloat(i * 16 + 12);
|
||||
if (a * p.x + b * p.y + c * p.z + d < 0.0f) return false;
|
||||
final float w = out.getFloat(i * 16 + 12);
|
||||
if (a * x + b * y + c * z + w < 0.0f) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+7
-7
@@ -26,6 +26,7 @@ import org.embeddedt.embeddium.impl.gl.device.IndirectMultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.device.MultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.device.MultiDrawBatchFactory;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
|
||||
import org.joml.Matrix4f;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -433,11 +434,10 @@ class IndirectCullDrawParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer frustumUbo(int indexPointerMask) {
|
||||
private static ByteBuffer frustumUbo() {
|
||||
final ByteBuffer ubo = FrustumExtractor.allocateUboByteBuffer();
|
||||
final Matrix4f identity = new Matrix4f();
|
||||
FrustumExtractor.writeStd140(identity, 0, indexPointerMask, ubo);
|
||||
FrustumExtractor.patchCameraWorld(0f, 0f, 0f, ubo);
|
||||
FrustumExtractor.writeFrustum(identity, identity, new CameraTransform(0.0, 0.0, 0.0), new Matrix4f(), ubo);
|
||||
FrustumExtractor.patchBypassFrustum(true, ubo);
|
||||
FrustumExtractor.patchPrimitiveRatio(QuadPrimitiveType.TRIANGULATED.getVerticesPerPrimitive(), QuadPrimitiveType.TRIANGULATED.getIndexBufferElementsPerPrimitive(), ubo);
|
||||
return ubo;
|
||||
@@ -528,7 +528,7 @@ class IndirectCullDrawParityTest {
|
||||
|
||||
gpu.beginCombinedPasses(0, 0);
|
||||
assertTrue(gpu.isComputeActiveThisPass(), "compute path unexpectedly inactive");
|
||||
setFrustumUbo(gpu, frustumUbo(0));
|
||||
setFrustumUbo(gpu, frustumUbo());
|
||||
gpu.syncSectionMetaIfDirty();
|
||||
|
||||
int outputBase = 0;
|
||||
@@ -549,7 +549,7 @@ class IndirectCullDrawParityTest {
|
||||
assertIndirectBufferMatches(result.combinedIndirectBuffer, result.expectedCombined, frameLabel + " combined solid+cutout pass");
|
||||
|
||||
gpu.beginCullPass(0xFFFFFFFF);
|
||||
setFrustumUbo(gpu, frustumUbo(0xFFFFFFFF));
|
||||
setFrustumUbo(gpu, frustumUbo());
|
||||
gpu.syncSectionMetaIfDirty();
|
||||
walkSections(scene.sorted, gpu, region1, 0, result.expectedSorted, 0xFFFFFFFF);
|
||||
executeRegion(gpu, region1, tessSorted);
|
||||
@@ -757,12 +757,12 @@ class IndirectCullDrawParityTest {
|
||||
|
||||
gpu.beginCullPass(0);
|
||||
assertTrue(gpu.isComputeActiveThisPass(), "compute path unexpectedly inactive");
|
||||
setFrustumUbo(gpu, frustumUbo(0));
|
||||
setFrustumUbo(gpu, frustumUbo());
|
||||
gpu.syncSectionMetaIfDirty();
|
||||
final int afterSolid = walkSections(unsorted, gpu, solidRegion, 0, new ArrayList<>(), 0);
|
||||
|
||||
gpu.startSortedPass(0xFFFFFFFF);
|
||||
setFrustumUbo(gpu, frustumUbo(0xFFFFFFFF));
|
||||
setFrustumUbo(gpu, frustumUbo());
|
||||
gpu.syncSectionMetaIfDirty();
|
||||
walkSections(scene.sorted, gpu, sortedRegion, afterSolid, new ArrayList<>(), 0xFFFFFFFF);
|
||||
gpu.finishSortedPass();
|
||||
|
||||
Reference in new issue
Block a user