Liquid rendering in regions

This commit is contained in:
DBotThePony 2023-09-05 22:02:48 +07:00
parent 36d83b6a8e
commit f71b561ad7
Signed by: DBot
GPG Key ID: DCC23B5715498507
4 changed files with 99 additions and 218 deletions

View File

@ -26,75 +26,9 @@ import java.util.LinkedList
const val Z_LEVEL_BACKGROUND = 60000 const val Z_LEVEL_BACKGROUND = 60000
const val Z_LEVEL_LIQUID = 10000 const val Z_LEVEL_LIQUID = 10000
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable { class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
val state: GLStateTracker get() = world.client.gl val state: GLStateTracker get() = world.client.gl
private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
private val layers = TileLayerList()
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
var isDirty = false
fun bake() {
if (!isDirty) return
isDirty = false
if (state.isSameThread()) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
}
bakedMeshes.clear()
layers.clear()
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
if (!world.inBounds(x, y)) continue
val tile = view.getTile(x, y) ?: continue
val material = tile.material
if (material != null) {
world.client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground)
}
val modifier = tile.modifier
if (modifier != null) {
world.client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true)
}
}
}
}
fun upload() {
if (layers.isNotEmpty) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
}
layers.clear()
}
}
override fun close() {
for (mesh in bakedMeshes) {
mesh.first.close()
}
}
}
val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd)
private val foregroundRenderer = TileLayerRenderer(worldForegroundView, isBackground = false)
private val backgroundRenderer = TileLayerRenderer(worldBackgroundView, isBackground = true)
override fun foregroundChanges(cell: Cell) { override fun foregroundChanges(cell: Cell) {
super.foregroundChanges(cell) super.foregroundChanges(cell)
@ -111,99 +45,11 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
} }
} }
fun bake() { override fun liquidChanges(cell: Cell) {
backgroundRenderer.bake() super.liquidChanges(cell)
foregroundRenderer.bake()
if (state.isSameThread()) world.forEachRenderRegion(cell) {
upload() it.liquidIsDirty = true
}
fun upload() {
backgroundRenderer.upload()
foregroundRenderer.upload()
}
private val liquidTypes = ReferenceArraySet<LiquidDefinition>()
private var liquidTypesVer = 0
private fun getLiquidTypes(): Collection<LiquidDefinition> {
if (liquidTypesVer != liquidChangeset) {
liquidTypes.clear()
liquidTypesVer = liquidChangeset
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
getCell(x, y).liquid.def?.let { liquidTypes.add(it) }
}
}
}
return liquidTypes
}
inner class Renderer(val renderOrigin: Vector2f) {
fun addLayers(layers: LayeredRenderer) {
for ((baked, zLevel) in backgroundRenderer.bakedMeshes) {
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.renderStacked(it)
it.pop()
}
}
for ((baked, zLevel) in foregroundRenderer.bakedMeshes) {
layers.add(zLevel) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.renderStacked(it)
it.pop()
}
}
for (renderer in entityRenderers.values) {
layers.add(renderer.layer) {
val relative = renderer.renderPos - posVector2d
it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat())
renderer.render(it)
it.pop()
}
}
val types = getLiquidTypes()
if (types.isNotEmpty()) {
layers.add(Z_LEVEL_LIQUID) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
val program = state.programs.liquid
program.use()
program.transform = it.last()
val builder = program.builder
for (type in types) {
builder.builder.begin()
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
val state = getCell(x, y)
if (state.liquid.def === type) {
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
}
}
}
program.baselineColor = type.color
builder.upload()
builder.draw()
}
it.pop()
}
}
} }
} }
@ -226,13 +72,4 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
override fun onEntityRemoved(entity: Entity) { override fun onEntityRemoved(entity: Entity) {
entityRenderers.remove(entity)!!.close() entityRenderers.remove(entity)!!.close()
} }
override fun close() {
backgroundRenderer.close()
foregroundRenderer.close()
for (renderer in entityRenderers.values) {
renderer.close()
}
}
} }

View File

@ -3,9 +3,12 @@ package ru.dbotthepony.kstarbound.client
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.longs.LongArraySet import it.unimi.dsi.fastutil.longs.LongArraySet
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.Mesh
import ru.dbotthepony.kstarbound.client.render.TileLayerList import ru.dbotthepony.kstarbound.client.render.TileLayerList
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.world.* import ru.dbotthepony.kstarbound.world.*
@ -15,6 +18,7 @@ import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.api.IStruct2i import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
@ -95,7 +99,7 @@ class ClientWorld(
bakedMeshes.clear() bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) { for ((baked, builder, zLevel) in layers.layers()) {
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel) bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
} }
@ -104,6 +108,9 @@ class ClientWorld(
} }
} }
private val liquidMesh = ArrayList<Pair<Mesh, RGBAColor>>()
var liquidIsDirty = true
val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight) val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight)
val background = Layer(TileView.Background(view), true) val background = Layer(TileView.Background(view), true)
@ -113,6 +120,37 @@ class ClientWorld(
background.bake() background.bake()
foreground.bake() foreground.bake()
if (liquidIsDirty) {
liquidIsDirty = false
liquidMesh.clear()
val liquidTypes = ReferenceArraySet<LiquidDefinition>()
for (x in 0 until renderRegionWidth) {
for (y in 0 until renderRegionHeight) {
view.getCell(x, y)?.liquid?.def?.let { liquidTypes.add(it) }
}
}
for (type in liquidTypes) {
val builder = client.gl.programs.liquid.builder.builder
builder.begin()
for (x in 0 until renderRegionWidth) {
for (y in 0 until renderRegionHeight) {
val state = view.getCell(x, y)
if (state?.liquid?.def === type) {
builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state!!.liquid.level)
}
}
}
liquidMesh.add(Mesh(client.gl, builder) to type.color)
}
}
for ((baked, zLevel) in background.bakedMeshes) { for ((baked, zLevel) in background.bakedMeshes) {
layers.add(zLevel + Z_LEVEL_BACKGROUND) { layers.add(zLevel + Z_LEVEL_BACKGROUND) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
@ -129,50 +167,23 @@ class ClientWorld(
} }
} }
/*for (renderer in entityRenderers.values) { if (liquidMesh.isNotEmpty()) {
layers.add(renderer.layer) {
val relative = renderer.renderPos - posVector2d
it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat())
renderer.render(it)
it.pop()
}
}*/
/*val types = getLiquidTypes()
if (types.isNotEmpty()) {
layers.add(Z_LEVEL_LIQUID) { layers.add(Z_LEVEL_LIQUID) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y) it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
val program = state.programs.liquid val program = client.gl.programs.liquid
program.use() program.use()
program.transform = it.last() program.transform = it.last()
val builder = program.builder for ((mesh, color) in liquidMesh) {
program.baselineColor = color
for (type in types) { mesh.render()
builder.builder.begin()
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
val state = getCell(x, y)
if (state.liquid.def === type) {
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
}
}
}
program.baselineColor = type.color
builder.upload()
builder.draw()
} }
it.pop() it.pop()
} }
}*/ }
} }
} }

View File

@ -0,0 +1,43 @@
package ru.dbotthepony.kstarbound.client.render
import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
class Mesh(state: GLStateTracker) {
constructor(state: GLStateTracker, builder: VertexBuilder) : this(state) {
load(builder, GL46.GL_STATIC_DRAW)
}
val vao = state.newVAO()
val vbo = state.newVBO()
val ebo = state.newEBO()
var indexCount = 0
private set
var indexType = 0
private set
fun load(builder: VertexBuilder, mode: Int = GL46.GL_DYNAMIC_DRAW) {
vao.bind()
vbo.bind()
ebo.bind()
builder.upload(vbo, ebo, mode)
builder.attributes.apply(vao, true)
indexCount = builder.indexCount
indexType = builder.indexType
vao.unbind()
vbo.unbind()
ebo.unbind()
}
fun render() {
vao.bind()
GL46.glDrawElements(GL46.GL_TRIANGLES, indexCount, indexType, 0L)
checkForGLError()
}
}

View File

@ -1,7 +1,7 @@
package ru.dbotthepony.kstarbound.client.render package ru.dbotthepony.kstarbound.client.render
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import java.util.stream.Stream
import kotlin.collections.HashMap import kotlin.collections.HashMap
data class TileLayer( data class TileLayer(
@ -25,7 +26,7 @@ data class TileLayer(
) )
class TileLayerList { class TileLayerList {
private val layers = HashMap<ConfiguredShaderProgram<GLTileProgram>, Int2ObjectAVLTreeMap<TileLayer>>() private val layers = HashMap<ConfiguredShaderProgram<GLTileProgram>, Int2ObjectOpenHashMap<TileLayer>>()
/** /**
* Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel]. * Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel].
@ -33,24 +34,13 @@ class TileLayerList {
* Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute]. * Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
*/ */
fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder { fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction { return layers.computeIfAbsent(program) { Int2ObjectOpenHashMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel) return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
}).vertices }).vertices
} }
fun buildSortedLayerList(): List<TileLayer> { fun layers(): Stream<TileLayer> {
val list = ArrayList<TileLayer>() return layers.values.stream().flatMap { it.values.stream() }
for (getList in layers.values) {
list.addAll(getList.values)
}
list.sortBy {
// унарный минус для инвентирования порядка (сначала большие, потом маленькие)
return@sortBy -it.zPos
}
return list
} }
fun clear() = layers.clear() fun clear() = layers.clear()