Послойная отрисовка всей сцены!
This commit is contained in:
parent
ff6dba143e
commit
d5b20c9bda
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user