From 86a8c4a1307be09ce994c64af1969373201c4cac Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 14 Sep 2022 21:35:23 +0700 Subject: [PATCH] more shadows tests --- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 16 +-- .../kstarbound/client/ClientChunk.kt | 117 +++++++++++++----- .../kstarbound/client/ClientWorld.kt | 23 ++++ .../kstarbound/client/StarboundClient.kt | 8 ++ .../kstarbound/client/gl/GLStateTracker.kt | 60 ++++++--- .../kstarbound/client/gl/program/Programs.kt | 2 + .../client/gl/vertex/HeapVertexBuilder.kt | 16 +-- .../client/gl/vertex/StatefulVertexBuilder.kt | 47 +++++++ .../kstarbound/client/render/LightRenderer.kt | 26 ++-- .../resources/shaders/hard_light_geometry.vsh | 5 +- .../shaders/soft_light_geometry2.vsh | 5 +- 11 files changed, 252 insertions(+), 73 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StatefulVertexBuilder.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 80cce4e6..2dde9dbd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -37,9 +37,9 @@ fun main() { Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) //Starbound.addPakPath(File("packed.pak")) - /*Starbound.initializeGame { finished, replaceStatus, status -> + Starbound.initializeGame { finished, replaceStatus, status -> client.putDebugLog(status, replaceStatus) - }*/ + } client.onTermination { Starbound.terminateLoading = true @@ -172,8 +172,8 @@ fun main() { //ent.position += Vector2d(y = 14.0, x = -10.0) ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0) - //client.camera.pos.x = 578f - //client.camera.pos.y = 695f + client.camera.pos.x = 578f + client.camera.pos.y = 695f client.onDrawGUI { client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f) @@ -186,7 +186,7 @@ fun main() { //client.camera.pos.y = ent.pos.y.toFloat() } - val lightRenderer = LightRenderer(client.gl) + /*val lightRenderer = LightRenderer(client.gl) lightRenderer.resizeFramebuffer(client.viewportWidth, client.viewportHeight) client.onViewportChanged(lightRenderer::resizeFramebuffer) @@ -223,7 +223,7 @@ fun main() { (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(0.1f)) to Color.GREEN, (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(-0.1f)) to Color.BLUE, )) { - lightRenderer.renderHardLight(lightPosition, color, radius = 10f) + lightRenderer.renderSoftLight(lightPosition, color, radius = 40f) } for ((lightPosition, color) in listOf( @@ -231,11 +231,11 @@ fun main() { (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(10.1f)) to Color.GREEN, (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(9.9f)) to Color.BLUE, )) { - lightRenderer.renderSoftLight(lightPosition, color, radius = 10f) + //lightRenderer.renderSoftLight(lightPosition, color, radius = 10f) } lightRenderer.renderOutputAdditive() - } + }*/ client.gl.box2dRenderer.drawShapes = false client.gl.box2dRenderer.drawPairs = false diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index 2f96cac6..9909bdd4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -1,17 +1,28 @@ package ru.dbotthepony.kstarbound.client import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import org.lwjgl.opengl.GL11.GL_LINES +import org.lwjgl.opengl.GL11.GL_TRIANGLES import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.gl.program.GLHardLightGeometryProgram +import ru.dbotthepony.kstarbound.client.gl.program.GLSoftLightGeometryProgram +import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType +import ru.dbotthepony.kstarbound.client.gl.vertex.StatefulVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.quad +import ru.dbotthepony.kstarbound.client.gl.vertex.shadowQuad import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh import ru.dbotthepony.kstarbound.client.render.EntityRenderer import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer +import ru.dbotthepony.kstarbound.client.render.LightRenderer import ru.dbotthepony.kstarbound.client.render.TileLayerList import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.world.* import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kvector.matrix.Matrix4fStack +import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f import ru.dbotthepony.kvector.vector.ndouble.Vector2d +import ru.dbotthepony.kvector.vector.nfloat.Vector2f +import ru.dbotthepony.kvector.vector.nfloat.Vector3f import java.io.Closeable import java.util.IdentityHashMap import java.util.LinkedList @@ -38,16 +49,10 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk() - private val foregroundRenderer = TileLayerRenderer(foreground::changeset, isBackground = false) private val backgroundRenderer = TileLayerRenderer(background::changeset, isBackground = true) @@ -160,28 +163,14 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk() + client.lightRenderer.begin() + for (chunk in collectPositionAware(size.encasingChunkPosAABB())) { determineRenderers.add(chunk.second.OneShotRenderer(chunk.first)) chunk.second.bake() + client.lightRenderer.addShadowGeometry(chunk.second.shadowGeometry) } renderLayeredList(client.gl.matrixStack, determineRenderers) + for ((lightPosition, color) in listOf( + (client.screenToWorld(client.mouseCoordinatesF)) to Color.RED, + (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(0.1f)) to Color.GREEN, + (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(-0.1f)) to Color.BLUE, + )) { + client.lightRenderer.renderSoftLight(lightPosition, color, radius = 100f) + } + + val old = client.gl.blendFunc + client.gl.blendFunc = BlendFunc.MULTIPLY_BY_SRC + + client.gl.activeTexture = 0 + client.gl.texture2D = client.lightRenderer.outputTexture + client.gl.programs.textureQuad.run() + + client.gl.blendFunc = old + physics.debugDraw() /*for (renderer in determineRenderers) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 3c49815f..153c382e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -14,6 +14,7 @@ import ru.dbotthepony.kstarbound.client.gl.BlendFunc import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.render.Camera +import ru.dbotthepony.kstarbound.client.render.LightRenderer import ru.dbotthepony.kstarbound.client.render.TextAlignY import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f @@ -171,6 +172,8 @@ class StarboundClient : AutoCloseable { viewportMatrixWorld = updateViewportMatrixWorld() glViewport(0, 0, w, h) + lightRenderer.resizeFramebuffer(viewportWidth, viewportHeight) + for (callback in onViewportChanged) { callback.invoke(w, h) } @@ -205,6 +208,11 @@ class StarboundClient : AutoCloseable { } val gl = GLStateTracker() + val lightRenderer = LightRenderer(gl) + + init { + lightRenderer.resizeFramebuffer(viewportWidth, viewportHeight) + } var world: ClientWorld? = ClientWorld(this, 0L, 0) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt index a47f89ff..3fb61c39 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt @@ -27,7 +27,10 @@ import java.lang.ref.Cleaner import java.util.concurrent.ThreadFactory import kotlin.collections.ArrayList import kotlin.collections.HashMap +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KProperty +import kotlin.reflect.KProperty0 private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) { operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Boolean { @@ -51,13 +54,13 @@ private class GLStateSwitchTracker(private val enum: Int, private var value: Boo } } -private class GLStateGenericTracker(private var value: T, private val callback: (T) -> Unit) { - operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): T { +private class GLStateGenericTracker(private var value: T, private val callback: (T) -> Unit) : ReadWriteProperty { + override fun getValue(thisRef: GLStateTracker, property: KProperty<*>): T { return value } - operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: T) { - glStateTracker.ensureSameThread() + override fun setValue(thisRef: GLStateTracker, property: KProperty<*>, value: T) { + thisRef.ensureSameThread() if (value == this.value) return @@ -68,6 +71,35 @@ private class GLStateGenericTracker(private var value: T, private val callbac } } +private class TexturesTracker(maxValue: Int) : ReadWriteProperty { + private val values = arrayOfNulls(maxValue) + + override fun getValue(thisRef: GLStateTracker, property: KProperty<*>): GLTexture2D? { + return values[thisRef.activeTexture] + } + + override fun setValue(thisRef: GLStateTracker, property: KProperty<*>, value: GLTexture2D?) { + thisRef.ensureSameThread() + + require(value == null || thisRef === value.state) { "$value does not belong to $thisRef" } + + if (values[thisRef.activeTexture] === value) { + return + } + + values[thisRef.activeTexture] = value + + if (value == null) { + glBindTexture(GL_TEXTURE_2D, 0) + checkForGLError() + return + } + + glBindTexture(GL_TEXTURE_2D, value.pointer) + checkForGLError() + } +} + @Suppress("unused") data class BlendFunc( val sourceColor: Func, @@ -130,6 +162,13 @@ data class BlendFunc( Func.ZERO, Func.ONE ) + + val MULTIPLY_BY_SRC = BlendFunc( + Func.ZERO, + Func.SRC_COLOR, + Func.ONE, + Func.ZERO + ) } } @@ -318,17 +357,6 @@ class GLStateTracker { var program: GLShaderProgram? = null private set - var texture2D: GLTexture2D? = null - set(value) { - ensureSameThread() - if (field === value) return - isMe(value?.state) - field = value - if (value == null) return - glBindTexture(GL_TEXTURE_2D, value.pointer) - checkForGLError() - } - var activeTexture = 0 set(value) { ensureSameThread() @@ -340,6 +368,8 @@ class GLStateTracker { checkForGLError() } + var texture2D: GLTexture2D? by TexturesTracker(80) + var clearColor by GLStateGenericTracker(Color.WHITE) { val (r, g, b, a) = it glClearColor(r, g, b, a) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt index d450ee2c..df8f9916 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/program/Programs.kt @@ -85,6 +85,7 @@ class GLHardLightGeometryProgram(state: GLStateTracker) : GLInternalProgram(stat } val transform = this["transform"]!! + val localToWorldTransform = this["localToWorldTransform"]!! val lightPosition = this["lightPosition"]!! val builder by lazy { @@ -99,6 +100,7 @@ class GLSoftLightGeometryProgram(state: GLStateTracker) : GLInternalProgram(stat val transform = this["transform"]!! val lightPenetration = this["lightPenetration"]!! + val localToWorldTransform = this["localToWorldTransform"]!! /** * Vector3f(x, y, size) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt index c7f11721..3cbd256f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/HeapVertexBuilder.kt @@ -15,17 +15,17 @@ import java.nio.ByteBuffer * * Полезен в случаях, когда размер данных для загрузки заранее не известен. */ -class HeapVertexBuilder( +open class HeapVertexBuilder>( attributes: GLAttributeList, type: GeometryType, -) : AbstractVertexBuilder(attributes, type) { - override val vertexMemory = FastByteArrayOutputStream() - override val elementMemory = FastByteArrayOutputStream() +) : AbstractVertexBuilder(attributes, type) { + final override val vertexMemory = FastByteArrayOutputStream() + final override val elementMemory = FastByteArrayOutputStream() - override var elementIndexType: Int = GL_UNSIGNED_SHORT + final override var elementIndexType: Int = GL_UNSIGNED_SHORT private set - override fun ensureIndexCapacity() { + final override fun ensureIndexCapacity() { if (elementIndexType == GL_UNSIGNED_SHORT && elementMemory.length / 2 + type.indices.size >= 30000) { val backing = elementMemory.array val copy = FastByteArrayInputStream(ByteArray(elementMemory.length) { backing[it] }) @@ -40,12 +40,12 @@ class HeapVertexBuilder( } } - override fun resetMemory() { + final override fun resetMemory() { vertexMemory.reset() elementMemory.reset() } - override fun doUpload(vbo: VertexBufferObject, ebo: VertexBufferObject, drawType: Int) { + final override fun doUpload(vbo: VertexBufferObject, ebo: VertexBufferObject, drawType: Int) { val vboMemory = ByteBuffer.allocateDirect(vertexMemory.length) vboMemory.put(vertexMemory.array, 0, vertexMemory.length) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StatefulVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StatefulVertexBuilder.kt new file mode 100644 index 00000000..f5385ad3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StatefulVertexBuilder.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.kstarbound.client.gl.vertex + +import org.lwjgl.opengl.GL46 +import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import java.io.Closeable + +class StatefulVertexBuilder( + val state: GLStateTracker, + attributes: GLAttributeList, + type: GeometryType, +) : HeapVertexBuilder(attributes, type), Closeable { + private val vao = state.newVAO() + private val vbo = state.newVBO() + private val ebo = state.newEBO() + + init { + vao.bind() + vbo.bind() + ebo.bind() + + attributes.apply(vao, true) + + vao.unbind() + vbo.unbind() + ebo.unbind() + } + + fun upload(drawType: Int = GL46.GL_STATIC_DRAW) { + upload(vbo, ebo, drawType) + } + + fun bind() = vao.bind() + fun unbind() = vao.unbind() + + fun draw(primitives: Int = GL46.GL_TRIANGLES) { + bind() + GL46.glDrawElements(primitives, indexCount, elementIndexType, 0L) + checkForGLError() + } + + override fun close() { + vao.close() + vbo.close() + ebo.close() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt index b706348e..f573619a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt @@ -26,8 +26,8 @@ import ru.dbotthepony.kvector.vector.nfloat.Vector3f // https://slembcke.github.io/SuperFastSoftShadows class LightRenderer(val state: GLStateTracker) { interface ShadowGeometryRenderer { - fun renderHardGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLHardLightGeometryProgram) - fun renderSoftGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLSoftLightGeometryProgram) + fun renderHardGeometry(renderer: LightRenderer, lightPosition: Vector2f, stack: Matrix4fStack, program: GLHardLightGeometryProgram) + fun renderSoftGeometry(renderer: LightRenderer, lightPosition: Vector2f, stack: Matrix4fStack, program: GLSoftLightGeometryProgram) } private val geometry = ArrayList() @@ -41,6 +41,10 @@ class LightRenderer(val state: GLStateTracker) { return this.geometry.remove(geometry) } + fun clearShadowGeometry() { + geometry.clear() + } + /** * Сюда происходит рендер маски света и самого света */ @@ -49,7 +53,7 @@ class LightRenderer(val state: GLStateTracker) { /** * Сюда накапливается отрисованный свет */ - private val framebufferAccumulator = GLFrameBuffer(state) + val framebufferAccumulator = GLFrameBuffer(state) val outputTexture: GLTexture2D? get() = framebufferAccumulator.texture @@ -58,7 +62,7 @@ class LightRenderer(val state: GLStateTracker) { framebufferAccumulator.reattachTexture(width, height, GL_RGBA) } - fun begin() { + fun begin(clearGeometry: Boolean = true) { state.ensureSameThread() if (!framebufferRender.isComplete || !framebufferAccumulator.isComplete) { @@ -77,6 +81,10 @@ class LightRenderer(val state: GLStateTracker) { state.clearColor = old framebufferAccumulator.unbind() + + if (clearGeometry) { + geometry.clear() + } } /** @@ -161,7 +169,7 @@ class LightRenderer(val state: GLStateTracker) { state.blendFunc = BlendFunc.ONLY_ALPHA for (renderer in geometry) { - renderer.renderHardGeometry(position, stack, state.programs.hardLightGeometry) + renderer.renderHardGeometry(this, position, stack, state.programs.hardLightGeometry) } state.programs.light.use() @@ -189,7 +197,7 @@ class LightRenderer(val state: GLStateTracker) { position: Vector2f, color: Color = Color.WHITE, radius: Float = 10f, - innerRadius: Float = 1f, + innerRadius: Float = radius / 3f, stack: Matrix4fStack = state.matrixStack ) { state.ensureSameThread() @@ -220,7 +228,7 @@ class LightRenderer(val state: GLStateTracker) { state.blendFunc = BlendFunc.ONLY_BLEND_ALPHA for (renderer in geometry) { - renderer.renderSoftGeometry(position, stack, state.programs.softLightGeometry) + renderer.renderSoftGeometry(this, position, stack, state.programs.softLightGeometry) } state.programs.light.use() @@ -248,6 +256,10 @@ class LightRenderer(val state: GLStateTracker) { StreamVertexBuilder(state, SHADOW_FORMAT, GeometryType.QUADS_AS_LINES, 256) } + val hugeBuilder by lazy { + StreamVertexBuilder(state, SHADOW_FORMAT, GeometryType.QUADS_AS_LINES, 16384) + } + val lineBuilder by lazy { StreamVertexBuilder(state, SHADOW_FORMAT, GeometryType.LINES, 256) } diff --git a/src/main/resources/shaders/hard_light_geometry.vsh b/src/main/resources/shaders/hard_light_geometry.vsh index c63e6291..047d6dce 100644 --- a/src/main/resources/shaders/hard_light_geometry.vsh +++ b/src/main/resources/shaders/hard_light_geometry.vsh @@ -4,10 +4,11 @@ layout (location = 0) in vec2 vertexPos; uniform mat4 transform; +uniform mat4 localToWorldTransform; out vec2 originalPos; void main() { - gl_Position = transform * vec4(vertexPos, 0.0, 1.0); - originalPos = vertexPos; + gl_Position = transform * localToWorldTransform * vec4(vertexPos, 0.0, 1.0); + originalPos = (localToWorldTransform * vec4(vertexPos, 0.0, 1.0)).xy; } diff --git a/src/main/resources/shaders/soft_light_geometry2.vsh b/src/main/resources/shaders/soft_light_geometry2.vsh index 8fb43336..edc6e050 100644 --- a/src/main/resources/shaders/soft_light_geometry2.vsh +++ b/src/main/resources/shaders/soft_light_geometry2.vsh @@ -9,6 +9,7 @@ layout (location = 0) in vec4 packedVertexPos; layout (location = 1) in vec2 edgeType; uniform mat4 transform; +uniform mat4 localToWorldTransform; uniform vec3 lightPositionAndSize; uniform float lightPenetration; @@ -28,8 +29,8 @@ void main() { float lightSize = lightPositionAndSize.z; // Unpack the vertex shader input. - vec2 endpoint_a = packedVertexPos.zw; - vec2 endpoint_b = packedVertexPos.xy; + vec2 endpoint_a = (localToWorldTransform * vec4(packedVertexPos.zw, 0.0, 1.0)).xy; + vec2 endpoint_b = (localToWorldTransform * vec4(packedVertexPos.xy, 0.0, 1.0)).xy; vec2 endpoint = mix(endpoint_a, endpoint_b, edgeType.x); // Deltas from the segment to the light center.