diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 9082fd81..25ca8147 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -5,18 +5,23 @@ import org.lwjgl.Version import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import org.lwjgl.opengl.GL11.GL_LINES import org.lwjgl.opengl.GL11.GL_RGBA +import org.lwjgl.opengl.GL11.GL_SCISSOR_TEST +import org.lwjgl.opengl.GL11.glDisable +import org.lwjgl.opengl.GL11.glEnable +import org.lwjgl.opengl.GL11.glScissor import org.lwjgl.opengl.GL46 import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.BlendFunc import ru.dbotthepony.kstarbound.client.gl.GLFrameBuffer -import ru.dbotthepony.kstarbound.client.gl.GLTexture2D import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.quad +import ru.dbotthepony.kstarbound.client.render.LightRenderer import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.entities.PlayerEntity import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.ndouble.Vector2d +import ru.dbotthepony.kvector.vector.nfloat.Vector2f import java.io.ByteArrayInputStream import java.io.DataInputStream import java.io.File @@ -180,88 +185,40 @@ fun main() { client.gl.font.render("${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) } - val lightmap = GLFrameBuffer(client.gl) - - lightmap.reattachTexture(client.viewportWidth, client.viewportHeight, GL_RGBA) - - client.onViewportChanged { width, height -> lightmap.reattachTexture(width, height, GL_RGBA) } - - client.onPostDrawWorld { - lightmap.bind() - GL46.glClear(GL46.GL_COLOR_BUFFER_BIT or GL46.GL_DEPTH_BUFFER_BIT) - //client.gl.programs.colorQuad.clearAlpha() - - client.gl.programs.light.use() - client.gl.programs.light.transform.set(client.gl.matrixStack.last) - // client.gl.programs.light.transform.set(Matrix4f.IDENTITY) - client.gl.programs.light.baselineColor.set(Color.WHITE) - - var builder = client.gl.programs.light.builder - - val (rX, rY) = client.screenToWorld(client.mouseCoordinatesF) - - builder.begin() - - //val size = 20f / client.settings.scale - val size = 10f - - builder.quad(rX - size, rY - size, rX + size, rY + size, QuadTransformers.uv()) - - builder.upload() - - //client.gl.blendFunc = BlendFunc( - // BlendFunc.Func.DST_ALPHA, - // BlendFunc.Func.ONE_MINUS_DST_ALPHA - //) - - builder.draw() - client.gl.blendFunc = BlendFunc.ALPHA_TEST - - client.gl.programs.lightOccluder.use() - client.gl.programs.lightOccluder.transform.set(client.gl.matrixStack.last) - - builder = client.gl.programs.lightOccluder.builder - - builder.begin() - - val lightPosition = client.screenToWorld(client.mouseCoordinatesF) - - builder.quad(0f, 0f, 2f, 2f) - builder.quad(-6f, 0f, -2f, 2f) - - client.gl.programs.lightOccluder.lightPosition.set(lightPosition) - - builder.upload() - - client.gl.blendFunc = BlendFunc.ONLY_ALPHA - builder.draw(GL_LINES) - client.gl.blendFunc = BlendFunc.ALPHA_TEST - - lightmap.unbind() - - client.gl.activeTexture = 0 - client.gl.texture2D = lightmap.texture - client.gl.programs.textureQuad.run(0) - - client.gl.programs.flat.use() - client.gl.programs.flat.color.set(Color.BLACK) - client.gl.programs.flat.transform.set(client.gl.matrixStack.last) - builder = client.gl.programs.flat.builder - - builder.begin() - - builder.quad(0f, 0f, 2f, 2f) - builder.quad(-6f, 0f, -2f, 2f) - - builder.upload() - builder.draw() - } - client.onPreDrawWorld { //client.camera.pos.x = ent.pos.x.toFloat() //client.camera.pos.y = ent.pos.y.toFloat() } + val lightRenderer = LightRenderer(client.gl) + + lightRenderer.resizeFramebuffer(client.viewportWidth, client.viewportHeight) + client.onViewportChanged(lightRenderer::resizeFramebuffer) + + lightRenderer.addShadowGeometry { _, _ -> + val builder = client.gl.programs.lightOccluder.builder + + builder.begin() + builder.quad(0f, 0f, 2f, 2f) + builder.quad(-6f, 0f, -2f, 2f) + builder.upload() + builder.draw(GL_LINES) + } + + client.onPostDrawWorld { + lightRenderer.begin() + + for ((lightPosition, color) in listOf( + (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(-2f)) to Color.RED, + (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(2f)) to Color.GREEN, + (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(y = -2f)) to Color.BLUE, + )) { + lightRenderer.renderLight(lightPosition, color) + } + + lightRenderer.renderOutputAdditive() + } + client.gl.box2dRenderer.drawShapes = false client.gl.box2dRenderer.drawPairs = false client.gl.box2dRenderer.drawAABB = false diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index a698fb1a..3c49815f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -133,6 +133,16 @@ class StarboundClient : AutoCloseable { return screenToWorld(value.x, value.y) } + /** + * Преобразует мировые координаты в экранные + */ + fun worldToScreen(x: Float, y: Float): Vector2f { + val relativeX = (x - camera.pos.x) * PIXELS_IN_STARBOUND_UNITf * settings.zoom + viewportWidth / 2f + val relativeY = (camera.pos.y - y) * PIXELS_IN_STARBOUND_UNITf * settings.zoom + viewportHeight / 2f + + return Vector2f(relativeX, relativeY) + } + init { GLFWErrorCallback.create { error, description -> LOGGER.error("LWJGL error {}: {}", error, description) @@ -205,7 +215,7 @@ class StarboundClient : AutoCloseable { gl.clearColor = Color.SLATE_GREY gl.blend = true - gl.blendFunc = BlendFunc.ALPHA_TEST + gl.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA } var frameRenderTime = 0.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 5a8ce2d7..0e5393e3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.client.gl import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL -import org.lwjgl.opengl.GL14 import org.lwjgl.opengl.GL46.* import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.client.freetype.FreeType @@ -25,7 +24,6 @@ import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.Color import java.io.FileNotFoundException import java.lang.ref.Cleaner -import java.util.* import java.util.concurrent.ThreadFactory import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -109,7 +107,8 @@ data class BlendFunc( companion object { val DEFAULT = BlendFunc() - val ALPHA_TEST = BlendFunc(Func.SRC_ALPHA, Func.ONE_MINUS_SRC_ALPHA) + val MULTIPLY_WITH_ALPHA = BlendFunc(Func.SRC_ALPHA, Func.ONE_MINUS_SRC_ALPHA) + val ADDITIVE = BlendFunc(Func.ONE, Func.ONE) val ONLY_ALPHA = BlendFunc( Func.ZERO, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt new file mode 100644 index 00000000..f2ce2fa2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt @@ -0,0 +1,181 @@ +package ru.dbotthepony.kstarbound.client.render + +import org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT +import org.lwjgl.opengl.GL11.GL_RGBA +import org.lwjgl.opengl.GL11.glClear +import ru.dbotthepony.kstarbound.client.gl.BlendFunc +import ru.dbotthepony.kstarbound.client.gl.GLFrameBuffer +import ru.dbotthepony.kstarbound.client.gl.GLStateTracker +import ru.dbotthepony.kstarbound.client.gl.GLTexture2D +import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.quad +import ru.dbotthepony.kvector.matrix.Matrix4fStack +import ru.dbotthepony.kvector.vector.Color +import ru.dbotthepony.kvector.vector.nfloat.Vector2f + +// https://slembcke.github.io/SuperFastHardShadows +class LightRenderer(val state: GLStateTracker) { + fun interface ShadowGeometryRenderer { + fun render(lightPosition: Vector2f, stack: Matrix4fStack) + } + + private val geometry = ArrayList() + + fun addShadowGeometry(geometry: ShadowGeometryRenderer): LightRenderer { + this.geometry.add(geometry) + return this + } + + fun removeShadowGeometry(geometry: ShadowGeometryRenderer): Boolean { + return this.geometry.remove(geometry) + } + + /** + * Сюда происходит рендер маски света и самого света + */ + private val framebufferRender = GLFrameBuffer(state) + + /** + * Сюда накапливается отрисованный свет + */ + private val framebufferAccumulator = GLFrameBuffer(state) + + val outputTexture: GLTexture2D? get() = framebufferAccumulator.texture + + fun resizeFramebuffer(width: Int, height: Int) { + framebufferRender.reattachTexture(width, height, GL_RGBA) + framebufferAccumulator.reattachTexture(width, height, GL_RGBA) + } + + fun begin() { + state.ensureSameThread() + + if (!framebufferRender.isComplete || !framebufferAccumulator.isComplete) { + return + } + + val old = state.clearColor + state.clearColor = Color.BLACK + + framebufferRender.bind() + glClear(GL_COLOR_BUFFER_BIT) + checkForGLError() + framebufferAccumulator.bind() + glClear(GL_COLOR_BUFFER_BIT) + checkForGLError() + state.clearColor = old + + framebufferAccumulator.unbind() + } + + /** + * Отрисовывает результат на весь текущий framebuffer в режиме additive + */ + fun renderOutputAdditive() { + val oldFunc = state.blendFunc + + try { + state.activeTexture = 0 + state.texture2D = outputTexture + state.blendFunc = BlendFunc.ADDITIVE + state.programs.textureQuad.run(0) + } finally { + state.blendFunc = oldFunc + } + } + + /** + * Отрисовывает результат на весь текущий framebuffer в режиме additive + */ + fun renderOutputMultiplicative() { + val oldFunc = state.blendFunc + + try { + state.activeTexture = 0 + state.texture2D = outputTexture + state.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA + state.programs.textureQuad.run(0) + } finally { + state.blendFunc = oldFunc + } + } + + fun renderLight( + position: Vector2f, + color: Color = Color.WHITE, + radius: Float = 10f, + stack: Matrix4fStack = state.matrixStack + ) { + state.ensureSameThread() + + if (!framebufferRender.isComplete || !framebufferAccumulator.isComplete) { + return + } + + val oldFunc = state.blendFunc + + // отрисовка + try { + framebufferRender.bind() + + // state.programs.colorQuad.clearAlpha() + + // Геометрия + val old = state.clearColor + state.clearColor = Color.BLACK + glClear(GL_COLOR_BUFFER_BIT) + checkForGLError() + state.clearColor = old + + state.programs.lightOccluder.use() + state.programs.lightOccluder.transform.set(stack.last) + state.programs.lightOccluder.lightPosition.set(position) + + state.blendFunc = BlendFunc.ONLY_ALPHA + + for (renderer in geometry) { + renderer.render(position, stack) + } + + state.programs.light.use() + state.programs.light.transform.set(stack.last) + state.programs.light.baselineColor.set(color) + + // Свет + val builder = state.programs.light.builder + + builder.begin() + builder.quad(position.x - radius, position.y - radius, position.x + radius, position.y + radius, QuadTransformers.uv()) + builder.upload() + + state.blendFunc = BLEND_MODE + builder.draw() + } finally { + state.blendFunc = oldFunc + framebufferRender.unbind() + } + + // накопление + try { + framebufferAccumulator.bind() + + state.blendFunc = BlendFunc.ADDITIVE + state.activeTexture = 0 + state.texture2D = framebufferRender.texture + state.programs.textureQuad.run(0) + } finally { + state.blendFunc = oldFunc + framebufferAccumulator.unbind() + } + } + + companion object { + val BLEND_MODE = BlendFunc( + BlendFunc.Func.DST_ALPHA, + BlendFunc.Func.ZERO, + BlendFunc.Func.ZERO, + BlendFunc.Func.ONE, + ) + } +} diff --git a/src/main/resources/shaders/light.fsh b/src/main/resources/shaders/light.fsh index 329b33e8..07268458 100644 --- a/src/main/resources/shaders/light.fsh +++ b/src/main/resources/shaders/light.fsh @@ -7,5 +7,5 @@ out vec4 resultColor; uniform vec4 baselineColor; void main() { - resultColor = vec4(baselineColor) * smoothstep(0.0, 1.0, 1.0 - sqrt(pow(oUVCoords.x - 0.5, 2.0) + pow(oUVCoords.y - 0.5, 2.0)) * 2.0); + resultColor = vec4(baselineColor.rgb * smoothstep(0.0, 1.0, 1.0 - sqrt(pow(oUVCoords.x - 0.5, 2.0) + pow(oUVCoords.y - 0.5, 2.0)) * 2.0), baselineColor.a); }