wip: world rendering

This commit is contained in:
Bixilon 2021-11-10 23:52:59 +01:00
parent 821a849e25
commit 20c5901fea
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
8 changed files with 133 additions and 74 deletions

View File

@ -23,20 +23,26 @@ import de.bixilon.minosoft.gui.rendering.Renderer
import de.bixilon.minosoft.gui.rendering.RendererBuilder
import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes
import de.bixilon.minosoft.gui.rendering.block.preparer.AbstractSectionPreparer
import de.bixilon.minosoft.gui.rendering.block.preparer.CullSectionPreparer
import de.bixilon.minosoft.gui.rendering.block.preparer.GenericSectionPreparer
import de.bixilon.minosoft.gui.rendering.block.preparer.GreedySectionPreparer
import de.bixilon.minosoft.gui.rendering.input.camera.Frustum
import de.bixilon.minosoft.gui.rendering.modding.events.FrustumChangeEvent
import de.bixilon.minosoft.gui.rendering.models.ModelLoader
import de.bixilon.minosoft.gui.rendering.system.base.RenderSystem
import de.bixilon.minosoft.gui.rendering.system.base.phases.OpaqueDrawable
import de.bixilon.minosoft.gui.rendering.system.base.phases.TranslucentDrawable
import de.bixilon.minosoft.gui.rendering.system.base.phases.TransparentDrawable
import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent
import de.bixilon.minosoft.modding.event.events.ChunkUnloadEvent
import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.util.KUtil.synchronizedMapOf
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.KUtil.toSynchronizedMap
import de.bixilon.minosoft.util.collections.SynchronizedMap
import glm_.vec2.Vec2i
import java.io.FileInputStream
import java.util.zip.GZIPInputStream
import java.util.zip.ZipInputStream
import kotlin.random.Random
class WorldRenderer(
private val connection: PlayConnection,
@ -48,11 +54,14 @@ class WorldRenderer(
private val world: World = connection.world
private val sectionPreparer: AbstractSectionPreparer = GenericSectionPreparer(renderWindow)
private val lightMap = LightMap(connection)
private lateinit var mesh: ChunkSectionMeshes
private val meshes: SynchronizedMap<Vec2i, SynchronizedMap<Int, ChunkSectionMeshes>> = synchronizedMapOf()
private var visibleMeshes: MutableSet<ChunkSectionMeshes> = mutableSetOf() // ToDo: Split in opaque, transparent, translucent meshes
private val culledPreparer = GenericSectionPreparer(renderWindow, CullSectionPreparer(renderWindow))
private val greedyPreparer = GenericSectionPreparer(renderWindow, GreedySectionPreparer(renderWindow))
val visibleSize: Int
get() = visibleMeshes.size
val preparedSize: Int
get() = visibleMeshes.size
override fun init() {
val asset = Resources.getAssetVersionByVersion(connection.version)
@ -75,37 +84,49 @@ class WorldRenderer(
renderWindow.textureManager.staticTextures.animator.use(transparentShader)
lightMap.use(transparentShader)
connection.registerEvent(CallbackEventInvoker.of<FrustumChangeEvent> { onFrustumChange(it.frustum) })
val random = Random(0L)
val blockState1 = connection.registries.blockRegistry["grass_block"]?.defaultState
val blockState2 = connection.registries.blockRegistry["diamond_block"]!!.defaultState//.withProperties(BlockProperties.MULTIPART_SOUTH to MultipartDirectionParser.SIDE, BlockProperties.MULTIPART_NORTH to MultipartDirectionParser.SIDE, BlockProperties.MULTIPART_EAST to MultipartDirectionParser.SIDE, BlockProperties.MULTIPART_WEST to MultipartDirectionParser.SIDE)
val section = ChunkSection(Array(4096) {
when (random.nextInt(3)) {
1 -> blockState1
2 -> blockState2
else -> null
connection.registerEvent(CallbackEventInvoker.of<ChunkDataChangeEvent> {
val sections = it.chunk.sections ?: return@of
for ((sectionHeight, section) in sections) {
prepareSection(it.chunkPosition, sectionHeight, section)
}
})
//val section = ChunkSection(Array(4096) { if (it < 1) blockState else null })
mesh = sectionPreparer.prepare(section)
connection.registerEvent(CallbackEventInvoker.of<ChunkUnloadEvent> {
val meshes = this.meshes.remove(it.chunkPosition)?.values ?: return@of
for (i in 0 until 1000)
mesh = sectionPreparer.prepare(section)
/*
Log.log(LogMessageType.OTHER, LogLevels.WARN){"Culling now..."}
val culledMesh = culledPreparer.prepare(section)
for (i in 0 until 1000){
culledPreparer.prepare(section)
renderWindow.queue += {
for (mesh in meshes) {
mesh.unload()
this.visibleMeshes -= mesh
}
}
})
}
val greedyMesh = greedyPreparer.prepare(section)
Log.log(LogMessageType.OTHER,LogLevels.INFO){"Culling has ${culledMesh.data.size / ChunkSectionMesh.SectionArrayMeshStruct.FLOATS_PER_VERTEX}, greedy meshed has ${greedyMesh.data.size / ChunkSectionMesh.SectionArrayMeshStruct.FLOATS_PER_VERTEX}."}
@Synchronized
private fun prepareSection(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection? = world[chunkPosition]?.sections?.get(sectionHeight)) {
if (section == null) {
return
}
val mesh = sectionPreparer.prepare(chunkPosition, sectionHeight, section, arrayOfNulls(6))
val meshes = this.meshes.getOrPut(chunkPosition) { synchronizedMapOf() }
val currentMesh = meshes.remove(sectionHeight)
renderWindow.queue += {
if (currentMesh != null) {
currentMesh.unload()
this.visibleMeshes -= currentMesh
}
*/
mesh.load()
meshes[sectionHeight] = mesh
this.visibleMeshes += mesh
}
}
override fun setupOpaque() {
@ -114,8 +135,10 @@ class WorldRenderer(
}
override fun drawOpaque() {
for (mesh in visibleMeshes) {
mesh.opaqueMesh?.draw()
}
}
override fun setupTranslucent() {
super.setupTranslucent()
@ -123,8 +146,10 @@ class WorldRenderer(
}
override fun drawTranslucent() {
for (mesh in visibleMeshes) {
mesh.translucentMesh?.draw()
}
}
override fun setupTransparent() {
super.setupTransparent()
@ -132,8 +157,26 @@ class WorldRenderer(
}
override fun drawTransparent() {
for (mesh in visibleMeshes) {
mesh.transparentMesh?.draw()
}
}
private fun onFrustumChange(frustum: Frustum) {
val visible: MutableSet<ChunkSectionMeshes> = mutableSetOf()
// ToDo
for ((chunkPosition, meshes) in this.meshes.toSynchronizedMap()) {
for ((sectionHeight, mesh) in meshes) {
if (!frustum.containsChunk(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition)) {
continue
}
visible += mesh
}
}
this.visibleMeshes = visible
}
companion object : RendererBuilder<WorldRenderer> {

View File

@ -55,6 +55,13 @@ class ChunkSectionMeshes(
}
}
@Synchronized
fun unload() {
opaqueMesh?.unload()
translucentMesh?.unload()
transparentMesh?.unload()
}
fun addBlock(x: Int, y: Int, z: Int) {
if (x < minPosition.x) {
minPosition.x = x

View File

@ -2,9 +2,10 @@ package de.bixilon.minosoft.gui.rendering.block.preparer
import de.bixilon.minosoft.data.world.ChunkSection
import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes
import glm_.vec2.Vec2i
interface AbstractSectionPreparer {
fun prepare(section: ChunkSection): ChunkSectionMeshes
fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes
}

View File

@ -7,6 +7,7 @@ import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes
import de.bixilon.minosoft.gui.rendering.util.VecUtil
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import glm_.vec2.Vec2i
import glm_.vec3.Vec3i
import java.util.*
@ -14,53 +15,61 @@ class CullSectionPreparer(
val renderWindow: RenderWindow,
) : AbstractSectionPreparer {
override fun prepare(section: ChunkSection): ChunkSectionMeshes {
override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes {
val mesh = ChunkSectionMeshes(renderWindow)
val random = Random(0L)
val blocks = section.blocks
var block: BlockState?
val neighbours: Array<BlockState?> = arrayOfNulls(Directions.SIZE)
val neighbourBlocks: Array<BlockState?> = arrayOfNulls(Directions.SIZE)
val offsetX = chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X
val offsetY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y
val offsetZ = chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z
for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) {
for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) {
for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) {
block = section.blocks[ChunkSection.getIndex(x, y, z)]
block = blocks[ChunkSection.getIndex(x, y, z)]
val model = block?.model ?: continue
// ToDo: Chunk borders
neighbours[Directions.DOWN.ordinal] = if (y == 0) {
null
neighbourBlocks[Directions.DOWN.ordinal] = if (y == 0) {
neighbours[Directions.DOWN.ordinal]?.blocks?.get(ChunkSection.getIndex(x, ProtocolDefinition.SECTION_MAX_Y, z))
} else {
section.blocks[ChunkSection.getIndex(x, y - 1, z)]
blocks[ChunkSection.getIndex(x, y - 1, z)]
}
neighbours[Directions.UP.ordinal] = if (y == ProtocolDefinition.SECTION_MAX_Y) {
null
neighbourBlocks[Directions.UP.ordinal] = if (y == ProtocolDefinition.SECTION_MAX_Y) {
neighbours[Directions.UP.ordinal]?.blocks?.get(ChunkSection.getIndex(x, 0, z))
} else {
section.blocks[ChunkSection.getIndex(x, y + 1, z)]
}
neighbours[Directions.NORTH.ordinal] = if (z == 0) {
null
} else {
section.blocks[ChunkSection.getIndex(x, y, z - 1)]
}
neighbours[Directions.SOUTH.ordinal] = if (z == ProtocolDefinition.SECTION_MAX_Z) {
null
} else {
section.blocks[ChunkSection.getIndex(x, y, z + 1)]
}
neighbours[Directions.WEST.ordinal] = if (x == 0) {
null
} else {
section.blocks[ChunkSection.getIndex(x - 1, y, z)]
}
neighbours[Directions.EAST.ordinal] = if (x == ProtocolDefinition.SECTION_MAX_X) {
null
} else {
section.blocks[ChunkSection.getIndex(x + 1, y, z)]
blocks[ChunkSection.getIndex(x, y + 1, z)]
}
random.setSeed(VecUtil.generatePositionHash(x, y, z))
val rendered = model.singleRender(Vec3i(x, y, z), mesh, random, neighbours, 0xFF, floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f))
neighbourBlocks[Directions.NORTH.ordinal] = if (z == 0) {
neighbours[Directions.NORTH.ordinal]?.blocks?.get(ChunkSection.getIndex(x, y, ProtocolDefinition.SECTION_MAX_Z))
} else {
blocks[ChunkSection.getIndex(x, y, z - 1)]
}
neighbourBlocks[Directions.SOUTH.ordinal] = if (z == ProtocolDefinition.SECTION_MAX_Z) {
neighbours[Directions.NORTH.ordinal]?.blocks?.get(ChunkSection.getIndex(x, y, 0))
} else {
blocks[ChunkSection.getIndex(x, y, z + 1)]
}
neighbourBlocks[Directions.WEST.ordinal] = if (x == 0) {
neighbours[Directions.WEST.ordinal]?.blocks?.get(ChunkSection.getIndex(ProtocolDefinition.SECTION_MAX_X, y, z))
} else {
blocks[ChunkSection.getIndex(x - 1, y, z)]
}
neighbourBlocks[Directions.EAST.ordinal] = if (x == ProtocolDefinition.SECTION_MAX_X) {
neighbours[Directions.WEST.ordinal]?.blocks?.get(ChunkSection.getIndex(0, y, z))
} else {
blocks[ChunkSection.getIndex(x + 1, y, z)]
}
val position = Vec3i(offsetX + x, offsetY + y, offsetZ + z)
random.setSeed(VecUtil.generatePositionHash(position.x, position.y, position.z))
val rendered = model.singleRender(position, mesh, random, neighbourBlocks, 0xFF, floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f))
if (rendered) {
mesh.addBlock(x, y, z)
}

View File

@ -19,6 +19,7 @@ import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import glm_.vec2.Vec2i
class GenericSectionPreparer(
@ -26,10 +27,10 @@ class GenericSectionPreparer(
private val preparer: AbstractSectionPreparer = CullSectionPreparer(renderWindow),
) : AbstractSectionPreparer {
override fun prepare(section: ChunkSection): ChunkSectionMeshes {
override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes {
val startTime = System.nanoTime()
val mesh = preparer.prepare(section)
val mesh = preparer.prepare(chunkPosition, sectionHeight, section, neighbours)
val time = System.nanoTime()
val delta = time - startTime

View File

@ -22,6 +22,7 @@ import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes
import de.bixilon.minosoft.gui.rendering.models.baked.block.GreedyBakedBlockModel
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.SECTION_SIZE
import de.bixilon.minosoft.util.KUtil.decide
import glm_.vec2.Vec2i
import glm_.vec3.Vec3i
import java.util.*
@ -36,7 +37,7 @@ class GreedySectionPreparer(
// base taken from https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/
override fun prepare(section: ChunkSection): ChunkSectionMeshes {
override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes {
val mesh = ChunkSectionMesh(renderWindow)
val random = Random(0L)

View File

@ -87,7 +87,7 @@ class DebugHUDElement(hudRenderer: HUDRenderer) : LayoutedHUDElement<GridLayout>
layout += TextElement(hudRenderer, TextComponent(RunConfiguration.VERSION_STRING, ChatColors.RED))
layout += AutoTextElement(hudRenderer, 1) { "FPS ${renderWindow.renderStats.smoothAvgFPS.round10}" }
renderWindow[WorldRenderer]?.apply {
// ToDo: layout += AutoTextElement(hudRenderer, 1) { "C v=${visibleChunks.size}, p=${allChunkSections.size}, q=${queuedChunks.size}, t=${connection.world.chunks.size}" }
layout += AutoTextElement(hudRenderer, 1) { "C v=${visibleSize}, p=${preparedSize}, q=-1, t=${connection.world.chunks.size}" }
}
layout += AutoTextElement(hudRenderer, 1) { "E t=${connection.world.entities.size}" }

View File

@ -17,8 +17,6 @@ package de.bixilon.minosoft.gui.rendering.input.camera
import de.bixilon.minosoft.data.registries.AABB
import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.EMPTY
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil
import de.bixilon.minosoft.util.KUtil.get
import de.bixilon.minosoft.util.enum.ValuesEnum
@ -163,11 +161,10 @@ class Frustum(private val camera: Camera) {
return true
}
fun containsChunk(chunkPosition: Vec2i, lowestBlockHeight: Int, highestBlockHeight: Int): Boolean {
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)
fun containsChunk(chunkPosition: Vec2i, sectionHeight: Int, minPosition: Vec3i, maxPosition: Vec3i): Boolean {
val from = Vec3i.of(chunkPosition, sectionHeight, minPosition)
val to = Vec3i.of(chunkPosition, sectionHeight, maxPosition)
return containsRegion(Vec3(from), Vec3(to))
}
fun containsAABB(aabb: AABB): Boolean {