Послойная отрисовка всей сцены!

This commit is contained in:
DBotThePony 2022-02-04 20:50:20 +07:00
parent ff6dba143e
commit d5b20c9bda
Signed by: DBot
GPG Key ID: DCC23B5715498507
4 changed files with 229 additions and 16 deletions

View File

@ -1,9 +1,12 @@
package ru.dbotthepony.kstarbound.client
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.api.IStruct2f
import ru.dbotthepony.kstarbound.client.render.ChunkRenderer
import ru.dbotthepony.kstarbound.world.*
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.IWorldChunkTuple
import ru.dbotthepony.kstarbound.world.MutableWorldChunkTuple
import ru.dbotthepony.kstarbound.world.World
class ClientWorldChunkTuple(
world: World<*>,
@ -64,12 +67,13 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWo
determineRenderers.add(chunk.renderer)
}
for (renderer in determineRenderers) {
val (x, y) = renderer.chunk.pos
val renderList = ArrayList<ChunkRenderer>()
client.gl.matrixStack.push().translateWithScale(x = x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf, y = y * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf)
renderer.bakeAndRender()
client.gl.matrixStack.pop()
for (renderer in determineRenderers) {
renderList.add(renderer)
renderer.autoBakeStatic()
}
renderLayeredList(client.gl.matrixStack, renderList)
}
}

View File

@ -1,27 +1,42 @@
package ru.dbotthepony.kstarbound.client.render
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.ClientWorld
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.math.FloatMatrix
import ru.dbotthepony.kstarbound.math.Matrix4f
import ru.dbotthepony.kstarbound.math.Matrix4fStack
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ITileChunk
import kotlin.collections.ArrayList
class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: ClientWorld? = null) : AutoCloseable {
/**
* Псевдо zPos у фоновых тайлов
*
* Добавление этого числа к zPos гарантирует, что фоновые тайлы будут отрисованы
* первыми (на самом дальнем плане)
*/
const val Z_LEVEL_BACKGROUND = 60000
class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: ClientWorld? = null) : AutoCloseable, ILayeredRenderer {
private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable {
private val layers = TileLayerList()
private val bakedMeshes = ArrayList<BakedStaticMesh>()
val bakedMeshes = ArrayList<Pair<BakedStaticMesh, Int>>()
private var changeset = -1
fun tesselateStatic(view: ITileChunk) {
if (state.isSameThread()) {
for (mesh in bakedMeshes) {
mesh.close()
mesh.first.close()
}
bakedMeshes.clear()
} else {
unloadableBakedMeshes.addAll(bakedMeshes)
for (mesh in bakedMeshes) {
unloadableBakedMeshes.add(mesh.first)
}
bakedMeshes.clear()
}
@ -44,8 +59,8 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
}
fun uploadStatic(clear: Boolean = true) {
for ((baked, builder) in layers.buildList()) {
bakedMeshes.add(BakedStaticMesh(baked, builder))
for ((baked, builder, zLevel) in layers.buildList()) {
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
}
if (clear) {
@ -55,7 +70,7 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
fun render(transform: FloatMatrix<*>) {
for (mesh in bakedMeshes) {
mesh.render(transform)
mesh.first.render(transform)
}
}
@ -70,14 +85,40 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
render(transform)
}
fun autoBake(provider: () -> ITileChunk) {
if (changeset != layerChangeset.invoke()) {
this.tesselateStatic(provider.invoke())
this.uploadStatic()
changeset = layerChangeset.invoke()
}
}
fun autoUpload() {
if (layers.isNotEmpty) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.buildList()) {
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
}
layers.clear()
}
}
override fun close() {
for (mesh in bakedMeshes) {
mesh.close()
mesh.first.close()
}
}
}
private val bakedMeshesForeground = ArrayList<BakedStaticMesh>()
val transform = Matrix4f().translate(x = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf, y = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf)
private val unloadableBakedMeshes = ArrayList<BakedStaticMesh>()
private val foreground = TileLayerRenderer(chunk.foreground::changeset, isBackground = false)
@ -131,6 +172,9 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
background.uploadStatic(clear)
}
/**
* Отрисовывает всю геометрию напрямую
*/
fun render(transform: FloatMatrix<*> = state.matrixStack.last) {
unloadUnused()
@ -138,6 +182,9 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
foreground.render(transform)
}
/**
* Отрисовывает всю геометрию напрямую, с проверкой, изменился ли чанк
*/
fun bakeAndRender(transform: FloatMatrix<*> = state.matrixStack.last) {
unloadUnused()
@ -145,6 +192,79 @@ class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk, val world: Clie
foreground.bakeAndRender(transform, this::getForeground)
}
/**
* Запекает всю геометрию напрямую, с проверкой, изменился ли чанк,
* и загружает её, если вызвано в рендер потоке
*/
fun autoBakeStatic() {
if (state.isSameThread())
unloadUnused()
background.autoBake(this::getBackground)
foreground.autoBake(this::getForeground)
if (state.isSameThread())
autoUploadStatic()
}
/**
* Загружает в видеопамять всю геометрию напрямую, если есть что загружать
*/
fun autoUploadStatic() {
unloadUnused()
background.autoUpload()
foreground.autoUpload()
}
private val meshDeque = ArrayDeque<Pair<BakedStaticMesh, Int>>()
override fun renderLayerFromStack(zPos: Int, transform: Matrix4fStack): Int {
if (meshDeque.isEmpty())
return -1
transform.push().translateWithScale(x = chunk.pos.x * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf, y = chunk.pos.y * CHUNK_SIZE * PIXELS_IN_STARBOUND_UNITf)
var pair = meshDeque.last()
while (pair.second >= zPos) {
pair.first.render(transform.last)
meshDeque.removeLast()
if (meshDeque.isEmpty()) {
transform.pop()
return -1
}
pair = meshDeque.last()
}
transform.pop()
return meshDeque.last().second
}
override fun bottomMostZLevel(): Int {
if (meshDeque.isEmpty()) {
return -1
}
return meshDeque.last().second
}
override fun prepareForLayeredRender() {
meshDeque.clear()
for ((baked, zLevel) in background.bakedMeshes) {
meshDeque.add(baked to (zLevel + Z_LEVEL_BACKGROUND))
}
meshDeque.addAll(foreground.bakedMeshes)
meshDeque.sortBy {
return@sortBy it.second
}
}
override fun close() {
background.close()
foreground.close()

View File

@ -0,0 +1,86 @@
package ru.dbotthepony.kstarbound.client.render
import ru.dbotthepony.kstarbound.math.FloatMatrix
import ru.dbotthepony.kstarbound.math.Matrix4f
import ru.dbotthepony.kstarbound.math.Matrix4fStack
/**
* Интерфейс для отрисовки комплексных объектов, в которых множество слоёв, который
* определяет лишь один метод: [renderLayer]
*
* Используется вместе с другими [ILayeredRenderer], где необходимо отрисовывать комплексную сцену,
* реализуя ручную сортировку геометрии.
*/
interface ILayeredRenderer {
/**
* Главный метод отрисовки данной стопки слоёв. Вызов данного метода может что-либо
* отрисовать, а может вообще ничего не отрисовать.
*
* zLevel всегда положителен, и указывает на то, какой слой (или все слои за) надо отрисовать,
* т.е. при вызове этого метода отрисовываются все слои, у которых z позиция >= [zPos]
*
* Возвращается zNew следующего слоя (такой, что [zPos] > zNew).
*
* Если следующего слоя нет, вернуть -1, и данный объект
* будет считаться отрисованным.
*/
fun renderLayerFromStack(zPos: Int, transform: Matrix4fStack): Int
/**
* Возвращает наибольшее zPos в данной стопке.
*
* Если стопка пуста, то необходимо вернуть -1.
*
* В зависимости от сцены, которую необходимо отрисовать,
* [renderLayerFromStack] может быть вызван сразу с этим же значением,
* если этот объект имеет самый дальний слой
*/
fun bottomMostZLevel(): Int
/**
* Говорит о том, что вот-вот будет начата отрисовка в render pipeline
* и будут вызываться [renderLayerFromStack]. В данном методе должна построиться
* и отсортироваться стопка слоёв
*/
fun prepareForLayeredRender()
}
fun renderLayeredList(transform: Matrix4fStack, potentialRenderers: List<ILayeredRenderer>): Int {
val renderers = ArrayList<ILayeredRenderer>(potentialRenderers.size)
var bottomMost = -1
for (render in potentialRenderers) {
render.prepareForLayeredRender()
val zLevel = render.bottomMostZLevel()
if (zLevel >= 0) {
bottomMost = bottomMost.coerceAtLeast(zLevel)
renderers.add(render)
}
}
var lastBottom = bottomMost
var renderCalls = 0
while (lastBottom > 0 && renderers.isNotEmpty()) {
var newBottom = lastBottom
for (i in renderers.size - 1 downTo 0) {
val renderer = renderers[i]
val newLevel = renderer.renderLayerFromStack(lastBottom, transform)
renderCalls++
if (newLevel <= -1) {
renderers.removeAt(i)
} else {
newBottom = newBottom.coerceAtMost(newLevel)
}
}
lastBottom = newBottom
}
return renderCalls
}

View File

@ -51,6 +51,9 @@ class TileLayerList {
}
fun clear() = layers.clear()
val isEmpty get() = layers.isEmpty()
val isNotEmpty get() = layers.isNotEmpty()
}
class TileRenderers(val state: GLStateTracker) {