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)
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()
@ -62,15 +70,8 @@ class Camera(
var viewProjectionMatrix = projectionMatrix * viewMatrix
private set
val fov: Float
get() {
val fov = Minosoft.config.config.game.camera.fov
if (!Minosoft.config.config.game.camera.dynamicFov) {
return fov
}
return fov * entity.fovMultiplier
}
val frustum: Frustum = Frustum(this)
fun mouseCallback(xPos: Double, yPos: Double) {
@ -151,7 +152,7 @@ class Camera(
}
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 {

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.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.util.KUtil.synchronizedListOf
import glm_.func.cos
import glm_.func.rad
import glm_.func.sin
import de.bixilon.minosoft.util.KUtil
import de.bixilon.minosoft.util.KUtil.get
import de.bixilon.minosoft.util.enum.ValuesEnum
import glm_.mat3x3.Mat3
import glm_.vec2.Vec2i
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) {
private val normals: MutableList<Vec3> = synchronizedListOf(
camera.cameraFront.normalize(),
)
private var normals: List<Vec3> = listOf()
private var planes: List<Vec4> = listOf()
init {
recalculate()
}
fun recalculate() {
synchronized(normals) {
normals.clear()
normals.add(camera.cameraFront.normalize())
val matrix = camera.viewProjectionMatrix.transpose()
val planes = listOf(
matrix[3] + matrix[0],
matrix[3] - matrix[0],
calculateSideNormals()
calculateVerticalNormals()
matrix[3] + matrix[1],
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()
val angle = (camera.fov - 90.0f).rad
val sin = angle.sin
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 aspect = camera.renderWindow.screenDimensions.y.toFloat() / camera.renderWindow.screenDimensions.x // ToDo: x/y or y/x
val angle = (camera.fov * aspect - 90.0f).rad
val sin = angle.sin
val cos = angle.cos
normals.add(camera.cameraFront.rotate(camera.cameraRight, sin, cos).normalize())
normals.add(camera.cameraFront.rotate(camera.cameraRight, -sin, cos).normalize()) // negate angle -> negate sin
}
private fun containsRegion(min: Vec3, max: Vec3): Boolean {
if (!RenderConstants.FRUSTUM_CULLING_ENABLED) {
return true
}
private fun containsRegion(from: Vec3, to: Vec3): Boolean {
val min = Vec3()
for (normal in normals) {
// get the point most likely to be in the frustum
min.x = if (normal.x < 0) {
from.x
} else {
to.x
}
min.y = if (normal.y < 0) {
from.y
} else {
to.y
}
min.z = if (normal.z < 0) {
from.z
} else {
to.z
}
val normals: List<Vec3>
synchronized(this.normals) {
normals = this.normals
}
val planes: List<Vec4>
synchronized(this.planes) {
planes = this.planes
}
if (normal dot (min - camera.entity.eyePosition) < 0.0f) {
return false // region is outside of frustum
for (i in 0 until Planes.VALUES.size) {
if (
(planes[i] dot Vec4(min.x, min.y, min.z, 1.0f)) < 0.0f &&
(planes[i] dot Vec4(max.x, min.y, min.z, 1.0f)) < 0.0f &&
(planes[i] dot Vec4(min.x, max.y, min.z, 1.0f)) < 0.0f &&
(planes[i] dot Vec4(max.x, max.y, min.z, 1.0f)) < 0.0f &&
(planes[i] dot Vec4(min.x, min.y, max.z, 1.0f)) < 0.0f &&
(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
}
}
fun checkPoint(check: (Vec3) -> Boolean): Boolean {
var out = 0
for (i in 0 until 8) {
if (check(normals[i])) {
out++
}
}
return out == 8
}
val checks: List<(Vec3) -> Boolean> = listOf(
{ 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
}
fun containsChunk(chunkPosition: Vec2i, lowestBlockHeight: Int, highestBlockHeight: Int): Boolean {
if (!RenderConstants.FRUSTUM_CULLING_ENABLED) {
return true
val from = Vec3i.of(chunkPosition, 0, Vec3i.EMPTY)
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()
}
operator fun <T> List<T>.get(enum: Enum<*>): T {
return this[enum.ordinal]
}
}