improve frustum culling a LOT

This commit is contained in:
Bixilon 2021-06-03 01:22:28 +02:00
parent b742f2b2cd
commit 30f02488ee
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
3 changed files with 156 additions and 65 deletions

View File

@ -52,7 +52,15 @@ class Camera(
var cameraRight = Vec3(0.0f, 0.0f, -1.0f) var cameraRight = Vec3(0.0f, 0.0f, -1.0f)
private var cameraUp = Vec3(0.0f, 1.0f, 0.0f) private var cameraUp = Vec3(0.0f, 1.0f, 0.0f)
val frustum: Frustum = Frustum(this) val fov: Float
get() {
val fov = Minosoft.config.config.game.camera.fov / (zoom + 1.0f)
if (!Minosoft.config.config.game.camera.dynamicFov) {
return fov
}
return fov * entity.fovMultiplier
}
var viewMatrix = calculateViewMatrix() var viewMatrix = calculateViewMatrix()
@ -62,15 +70,8 @@ class Camera(
var viewProjectionMatrix = projectionMatrix * viewMatrix var viewProjectionMatrix = projectionMatrix * viewMatrix
private set private set
val fov: Float
get() {
val fov = Minosoft.config.config.game.camera.fov
if (!Minosoft.config.config.game.camera.dynamicFov) { val frustum: Frustum = Frustum(this)
return fov
}
return fov * entity.fovMultiplier
}
fun mouseCallback(xPos: Double, yPos: Double) { fun mouseCallback(xPos: Double, yPos: Double) {
@ -151,7 +152,7 @@ class Camera(
} }
private fun calculateProjectionMatrix(screenDimensions: Vec2): Mat4 { private fun calculateProjectionMatrix(screenDimensions: Vec2): Mat4 {
return glm.perspective((fov / (zoom + 1.0f)).rad, screenDimensions.x / screenDimensions.y, 0.1f, 1000f) return glm.perspective(fov.rad, screenDimensions.x / screenDimensions.y, 0.1f, 1000f)
} }
private fun calculateViewMatrix(): Mat4 { private fun calculateViewMatrix(): Mat4 {

View File

@ -15,85 +15,171 @@ package de.bixilon.minosoft.gui.rendering.input.camera
import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.util.VecUtil.rotate import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil.synchronizedListOf import de.bixilon.minosoft.util.KUtil
import glm_.func.cos import de.bixilon.minosoft.util.KUtil.get
import glm_.func.rad import de.bixilon.minosoft.util.enum.ValuesEnum
import glm_.func.sin import glm_.mat3x3.Mat3
import glm_.vec2.Vec2i import glm_.vec2.Vec2i
import glm_.vec3.Vec3 import glm_.vec3.Vec3
import glm_.vec3.Vec3i
import glm_.vec4.Vec4
// Bit thanks to: https://gist.github.com/podgorskiy/e698d18879588ada9014768e3e82a644
class Frustum(private val camera: Camera) { class Frustum(private val camera: Camera) {
private val normals: MutableList<Vec3> = synchronizedListOf( private var normals: List<Vec3> = listOf()
camera.cameraFront.normalize(), private var planes: List<Vec4> = listOf()
)
init { init {
recalculate() recalculate()
} }
fun recalculate() { fun recalculate() {
synchronized(normals) { val matrix = camera.viewProjectionMatrix.transpose()
normals.clear() val planes = listOf(
normals.add(camera.cameraFront.normalize()) matrix[3] + matrix[0],
matrix[3] - matrix[0],
calculateSideNormals() matrix[3] + matrix[1],
calculateVerticalNormals() matrix[3] - matrix[1],
matrix[3] + matrix[2],
matrix[3] - matrix[2],
)
val crosses = listOf(
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.RIGHT]),
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.BOTTOM]),
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.TOP]),
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.NEAR]),
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.FAR]),
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.BOTTOM]),
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.TOP]),
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.NEAR]),
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.FAR]),
Vec3(planes[Planes.BOTTOM]) cross Vec3(planes[Planes.TOP]),
Vec3(planes[Planes.BOTTOM]) cross Vec3(planes[Planes.NEAR]),
Vec3(planes[Planes.BOTTOM]) cross Vec3(planes[Planes.FAR]),
Vec3(planes[Planes.TOP]) cross Vec3(planes[Planes.NEAR]),
Vec3(planes[Planes.TOP]) cross Vec3(planes[Planes.FAR]),
Vec3(planes[Planes.NEAR]) cross Vec3(planes[Planes.FAR]),
)
fun ij2k(i: Planes, j: Planes): Int {
return i.ordinal * (9 - i.ordinal) / 2 + j.ordinal - 1
}
fun intersections(a: Planes, b: Planes, c: Planes): Vec3 {
val d = Vec3(planes[a]) dot crosses[ij2k(b, c)]
val res = Mat3(crosses[ij2k(b, c)], -crosses[ij2k(a, c)], crosses[ij2k(a, b)]) * Vec3(planes[a].w, planes[b].w, planes[c].w)
return res * (-1.0f / d)
}
val normals: List<Vec3> = listOf(
intersections(Planes.LEFT, Planes.BOTTOM, Planes.NEAR),
intersections(Planes.LEFT, Planes.TOP, Planes.NEAR),
intersections(Planes.RIGHT, Planes.BOTTOM, Planes.NEAR),
intersections(Planes.RIGHT, Planes.TOP, Planes.NEAR),
intersections(Planes.LEFT, Planes.BOTTOM, Planes.FAR),
intersections(Planes.LEFT, Planes.TOP, Planes.FAR),
intersections(Planes.RIGHT, Planes.BOTTOM, Planes.FAR),
intersections(Planes.RIGHT, Planes.TOP, Planes.FAR),
)
synchronized(this.normals) {
this.normals = normals
}
synchronized(this.planes) {
this.planes = planes
} }
} }
private fun calculateSideNormals() {
val cameraRealUp = (camera.cameraRight cross camera.cameraFront).normalize() private fun containsRegion(min: Vec3, max: Vec3): Boolean {
val angle = (camera.fov - 90.0f).rad if (!RenderConstants.FRUSTUM_CULLING_ENABLED) {
val sin = angle.sin return true
val cos = angle.cos
normals.add(camera.cameraFront.rotate(cameraRealUp, sin, cos).normalize())
normals.add(camera.cameraFront.rotate(cameraRealUp, -sin, cos).normalize()) // negate angle -> negate sin
} }
private fun calculateVerticalNormals() { val normals: List<Vec3>
val aspect = camera.renderWindow.screenDimensions.y.toFloat() / camera.renderWindow.screenDimensions.x // ToDo: x/y or y/x synchronized(this.normals) {
val angle = (camera.fov * aspect - 90.0f).rad normals = this.normals
val sin = angle.sin }
val cos = angle.cos val planes: List<Vec4>
normals.add(camera.cameraFront.rotate(camera.cameraRight, sin, cos).normalize()) synchronized(this.planes) {
normals.add(camera.cameraFront.rotate(camera.cameraRight, -sin, cos).normalize()) // negate angle -> negate sin planes = this.planes
} }
private fun containsRegion(from: Vec3, to: Vec3): Boolean { for (i in 0 until Planes.VALUES.size) {
val min = Vec3() if (
for (normal in normals) { (planes[i] dot Vec4(min.x, min.y, min.z, 1.0f)) < 0.0f &&
// get the point most likely to be in the frustum (planes[i] dot Vec4(max.x, min.y, min.z, 1.0f)) < 0.0f &&
min.x = if (normal.x < 0) { (planes[i] dot Vec4(min.x, max.y, min.z, 1.0f)) < 0.0f &&
from.x (planes[i] dot Vec4(max.x, max.y, min.z, 1.0f)) < 0.0f &&
} else { (planes[i] dot Vec4(min.x, min.y, max.z, 1.0f)) < 0.0f &&
to.x (planes[i] dot Vec4(max.x, min.y, max.z, 1.0f)) < 0.0f &&
(planes[i] dot Vec4(min.x, max.y, max.z, 1.0f)) < 0.0f &&
(planes[i] dot Vec4(max.x, max.y, max.z, 1.0f)) < 0.0f
) {
return false
} }
min.y = if (normal.y < 0) {
from.y
} else {
to.y
} }
min.z = if (normal.z < 0) { fun checkPoint(check: (Vec3) -> Boolean): Boolean {
from.z var out = 0
} else { for (i in 0 until 8) {
to.z if (check(normals[i])) {
out++
}
}
return out == 8
} }
if (normal dot (min - camera.entity.eyePosition) < 0.0f) { val checks: List<(Vec3) -> Boolean> = listOf(
return false // region is outside of frustum { it.x > max.x },
{ it.x < min.x },
{ it.y > max.y },
{ it.y < min.y },
{ it.z > max.z },
{ it.z < min.z },
)
for (check in checks) {
if (checkPoint(check)) {
return false
} }
} }
return true return true
} }
fun containsChunk(chunkPosition: Vec2i, lowestBlockHeight: Int, highestBlockHeight: Int): Boolean { fun containsChunk(chunkPosition: Vec2i, lowestBlockHeight: Int, highestBlockHeight: Int): Boolean {
if (!RenderConstants.FRUSTUM_CULLING_ENABLED) { val from = Vec3i.of(chunkPosition, 0, Vec3i.EMPTY)
return true from.y = lowestBlockHeight
val to = Vec3(from.x + ProtocolDefinition.SECTION_WIDTH_X, highestBlockHeight, from.z + ProtocolDefinition.SECTION_WIDTH_Z)
return containsRegion(Vec3(from), to)
}
private enum class Planes {
LEFT,
RIGHT,
BOTTOM,
TOP,
NEAR,
FAR,
;
companion object : ValuesEnum<Planes> {
override val VALUES: Array<Planes> = values()
override val NAME_MAP: Map<String, Planes> = KUtil.getEnumValues(VALUES)
} }
val from = Vec3(chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X, lowestBlockHeight, chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z)
val to = from + Vec3(ProtocolDefinition.SECTION_WIDTH_X, highestBlockHeight, ProtocolDefinition.SECTION_WIDTH_Z)
return containsRegion(from, to)
} }
} }

View File

@ -153,4 +153,8 @@ object KUtil {
} }
return entities.toSet() return entities.toSet()
} }
operator fun <T> List<T>.get(enum: Enum<*>): T {
return this[enum.ordinal]
}
} }