diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 25ca8147..80cce4e6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -4,21 +4,17 @@ import org.apache.logging.log4j.LogManager 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 org.lwjgl.opengl.GL11.GL_TRIANGLES 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.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.program.GLHardLightGeometryProgram +import ru.dbotthepony.kstarbound.client.gl.program.GLSoftLightGeometryProgram +import ru.dbotthepony.kstarbound.client.gl.vertex.shadowQuad 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.matrix.Matrix4fStack import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.nfloat.Vector2f @@ -195,25 +191,47 @@ fun main() { lightRenderer.resizeFramebuffer(client.viewportWidth, client.viewportHeight) client.onViewportChanged(lightRenderer::resizeFramebuffer) - lightRenderer.addShadowGeometry { _, _ -> - val builder = client.gl.programs.lightOccluder.builder + lightRenderer.addShadowGeometry(object : LightRenderer.ShadowGeometryRenderer { + override fun renderHardGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLHardLightGeometryProgram) { + val builder = lightRenderer.builder - builder.begin() - builder.quad(0f, 0f, 2f, 2f) - builder.quad(-6f, 0f, -2f, 2f) - builder.upload() - builder.draw(GL_LINES) - } + builder.begin() + builder.quad(-6f, 0f, -2f, 2f) + builder.quad(0f, 0f, 2f, 2f) + + builder.upload() + builder.draw(GL_LINES) + } + + override fun renderSoftGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLSoftLightGeometryProgram) { + val builder = lightRenderer.builderSoft + + builder.begin() + builder.shadowQuad(-6f, 0f, -2f, 2f) + builder.shadowQuad(0f, 0f, 2f, 2f) + + builder.upload() + builder.draw(GL_TRIANGLES) + } + }) 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, + (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, )) { - lightRenderer.renderLight(lightPosition, color) + lightRenderer.renderHardLight(lightPosition, color, radius = 10f) + } + + for ((lightPosition, color) in listOf( + (client.screenToWorld(client.mouseCoordinatesF) + Vector2f(10.1f)) to Color.RED, + (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.renderOutputAdditive() 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 0e5393e3..a47f89ff 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt @@ -117,6 +117,13 @@ data class BlendFunc( Func.ZERO ) + val ONLY_BLEND_ALPHA = BlendFunc( + Func.ZERO, + Func.ONE, + Func.ONE, + Func.ONE + ) + val ONLY_COLOR = BlendFunc( Func.ONE, Func.ZERO, @@ -202,6 +209,7 @@ class GLStateTracker { } var blend by GLStateSwitchTracker(GL_BLEND) + var scissor by GLStateSwitchTracker(GL_SCISSOR_TEST) var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST) var VBO: VertexBufferObject? = null 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 71e48eea..d450ee2c 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 @@ -10,6 +10,7 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType 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.kvector.vector.Color import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -78,21 +79,34 @@ class GLLightProgram(state: GLStateTracker) : GLInternalProgram(state, "light") } } -class GLLightOccluderProgram(state: GLStateTracker) : GLInternalProgram(state, listOf("light_occluder"), listOf("light_occluder"), listOf("light_occluder")) { +class GLHardLightGeometryProgram(state: GLStateTracker) : GLInternalProgram(state, listOf("hard_light_geometry"), listOf("hard_light_geometry"), listOf("hard_light_geometry")) { init { link() } - // val baselineColor = this["baselineColor"]!! val transform = this["transform"]!! val lightPosition = this["lightPosition"]!! val builder by lazy { - StreamVertexBuilder(state, FORMAT, GeometryType.QUADS_AS_LINES, 32) + StreamVertexBuilder(state, LightRenderer.SHADOW_FORMAT, GeometryType.QUADS_AS_LINES, 32) + } +} + +class GLSoftLightGeometryProgram(state: GLStateTracker) : GLInternalProgram(state, listOf("soft_light_geometry2"), listOf("soft_light_geometry2")) { + init { + link() } - companion object { - val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build() + val transform = this["transform"]!! + val lightPenetration = this["lightPenetration"]!! + + /** + * Vector3f(x, y, size) + */ + val lightPositionAndSize = this["lightPositionAndSize"]!! + + val builder by lazy { + StreamVertexBuilder(state, LightRenderer.SHADOW_FORMAT_SOFT, GeometryType.QUADS_AS_LINES, 32) } } @@ -190,7 +204,8 @@ class GLPrograms(val state: GLStateTracker) { val flat by SimpleProgram("flat", ::GLFlatProgram) val liquid by lazy { GLLiquidProgram(state) } val light by lazy { GLLightProgram(state) } - val lightOccluder by lazy { GLLightOccluderProgram(state) } + val hardLightGeometry by lazy { GLHardLightGeometryProgram(state) } + val softLightGeometry by lazy { GLSoftLightGeometryProgram(state) } val colorQuad by lazy { GLColorQuadProgram(state) } val textureQuad by lazy { GLTextureQuadProgram(state) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt index 5dec8a9a..899fa094 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/AbstractVertexBuilder.kt @@ -6,6 +6,7 @@ import org.lwjgl.opengl.GL46.GL_UNSIGNED_SHORT import org.lwjgl.opengl.GL46.GL_UNSIGNED_BYTE import ru.dbotthepony.kstarbound.client.gl.GLType import ru.dbotthepony.kstarbound.client.gl.VertexBufferObject +import ru.dbotthepony.kstarbound.client.gl.checkForGLError import ru.dbotthepony.kstarbound.util.writeLEFloat import ru.dbotthepony.kstarbound.util.writeLEInt import ru.dbotthepony.kstarbound.util.writeLEShort @@ -101,6 +102,7 @@ abstract class AbstractVertexBuilder>( check(elementVertices == 0) { "Not fully built vertex element ($type requires ${type.elements} vertex points to be present, yet last strip has only $elementVertices elements)" } doUpload(vbo, ebo, drawType) + checkForGLError() } private var elementVertices = 0 @@ -123,12 +125,12 @@ abstract class AbstractVertexBuilder>( val elementMemory = elementMemory val elementIndexType = elementIndexType - for (index in type.indicies) { + for (index in type.indices) { put(elementIndexType, elementMemory, index + elementIndexOffset) } elementIndexOffset += type.elements - indexCount += type.indicies.size + indexCount += type.indices.size } } } @@ -140,6 +142,17 @@ abstract class AbstractVertexBuilder>( return this as T } + fun pushVec4f(x: Float, y: Float, z: Float, w: Float): T { + expect(GLType.VEC4F) + val memory = vertexMemory + memory.writeLEFloat(x) + memory.writeLEFloat(y) + memory.writeLEFloat(z) + memory.writeLEFloat(w) + attributeIndex++ + return this as T + } + fun pushVec3f(x: Float, y: Float, z: Float): T { expect(GLType.VEC3F) val memory = vertexMemory @@ -167,6 +180,39 @@ abstract class AbstractVertexBuilder>( } } +fun > T.shadowLine(x0: Float, y0: Float, x1: Float, y1: Float): T { + vertex() + pushVec4f(x0, y0, x1, y1) + pushVec2f(0f, 0f) + + vertex() + pushVec4f(x0, y0, x1, y1) + pushVec2f(0f, 1f) + + vertex() + pushVec4f(x0, y0, x1, y1) + pushVec2f(1f, 1f) + + vertex() + pushVec4f(x0, y0, x1, y1) + pushVec2f(1f, 0f) + + return this +} + +fun > T.dfShadowLine(x0: Float, y0: Float, x1: Float, y1: Float): T { + shadowLine(x0, y0, x1, y1) + shadowLine(x1, y1, x0, y0) + return this +} + +fun > T.shadowQuad(x0: Float, y0: Float, x1: Float, y1: Float): T { + shadowLine(x0, y0, x1, y0) + shadowLine(x1, y0, x1, y1) + shadowLine(x1, y1, x0, y1) + shadowLine(x0, y1, x0, y0) + return this +} // Помощники fun > T.quad( diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt index affbbf99..614408f8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/DirectVertexBuilder.kt @@ -9,7 +9,7 @@ open class DirectVertexBuilder>( type: GeometryType, val maxElements: Int, ) : AbstractVertexBuilder>(attributes, type) { - val maxIndexCount = maxElements * type.indicies.size + val maxIndexCount = maxElements * type.indices.size val maxVertexCount = maxElements * type.elements final override val elementIndexType: Int = when (maxIndexCount) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt index e6a41669..e7d1eeff 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/GeometryType.kt @@ -1,9 +1,20 @@ package ru.dbotthepony.kstarbound.client.gl.vertex -enum class GeometryType(val elements: Int, val indicies: IntArray) { +enum class GeometryType(val elements: Int, val indices: IntArray) { + AS_IS(1, intArrayOf(0)), LINES(2, intArrayOf(0, 1)), TRIANGLES(3, intArrayOf(0, 1, 2)), + + /** + * A B C B C D + */ QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3)), + + /** + * A B C C D A + */ + QUADS_ALTERNATIVE(4, intArrayOf(0, 1, 2, 2, 3, 0)), + QUADS_AS_LINES(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3)), QUADS_AS_LINES_WIREFRAME(4, intArrayOf(0, 1, 0, 2, 1, 3, 2, 3, 0, 3, 1, 2)), } 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 15a11f71..c7f11721 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 @@ -26,7 +26,7 @@ class HeapVertexBuilder( private set override fun ensureIndexCapacity() { - if (elementIndexType == GL_UNSIGNED_SHORT && elementMemory.length / 2 + type.indicies.size >= 30000) { + if (elementIndexType == GL_UNSIGNED_SHORT && elementMemory.length / 2 + type.indices.size >= 30000) { val backing = elementMemory.array val copy = FastByteArrayInputStream(ByteArray(elementMemory.length) { backing[it] }) 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 f2ce2fa2..b706348e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LightRenderer.kt @@ -7,17 +7,27 @@ 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.GLType import ru.dbotthepony.kstarbound.client.gl.checkForGLError +import ru.dbotthepony.kstarbound.client.gl.program.GLHardLightGeometryProgram +import ru.dbotthepony.kstarbound.client.gl.program.GLSoftLightGeometryProgram +import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList +import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers +import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder 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 +import ru.dbotthepony.kvector.vector.nfloat.Vector3f +// Huge thanks to articles by Scott [slembcke] Lembcke! // https://slembcke.github.io/SuperFastHardShadows +// https://slembcke.github.io/SuperFastSoftShadows class LightRenderer(val state: GLStateTracker) { - fun interface ShadowGeometryRenderer { - fun render(lightPosition: Vector2f, stack: Matrix4fStack) + interface ShadowGeometryRenderer { + fun renderHardGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLHardLightGeometryProgram) + fun renderSoftGeometry(lightPosition: Vector2f, stack: Matrix4fStack, program: GLSoftLightGeometryProgram) } private val geometry = ArrayList() @@ -101,7 +111,23 @@ class LightRenderer(val state: GLStateTracker) { } } - fun renderLight( + private fun blendResult() { + val oldFunc = state.blendFunc + + 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() + } + } + + fun renderHardLight( position: Vector2f, color: Color = Color.WHITE, radius: Float = 10f, @@ -128,14 +154,14 @@ class LightRenderer(val state: GLStateTracker) { checkForGLError() state.clearColor = old - state.programs.lightOccluder.use() - state.programs.lightOccluder.transform.set(stack.last) - state.programs.lightOccluder.lightPosition.set(position) + state.programs.hardLightGeometry.use() + state.programs.hardLightGeometry.transform.set(stack.last) + state.programs.hardLightGeometry.lightPosition.set(position) state.blendFunc = BlendFunc.ONLY_ALPHA for (renderer in geometry) { - renderer.render(position, stack) + renderer.renderHardGeometry(position, stack, state.programs.hardLightGeometry) } state.programs.light.use() @@ -156,26 +182,102 @@ class LightRenderer(val state: GLStateTracker) { framebufferRender.unbind() } - // накопление - try { - framebufferAccumulator.bind() + blendResult() + } - state.blendFunc = BlendFunc.ADDITIVE - state.activeTexture = 0 - state.texture2D = framebufferRender.texture - state.programs.textureQuad.run(0) + fun renderSoftLight( + position: Vector2f, + color: Color = Color.WHITE, + radius: Float = 10f, + innerRadius: Float = 1f, + 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 = CLEAR_COLOR_SOFT + glClear(GL_COLOR_BUFFER_BIT) + checkForGLError() + state.clearColor = old + + state.programs.softLightGeometry.use() + state.programs.softLightGeometry.transform.set(stack.last) + state.programs.softLightGeometry.lightPositionAndSize.set(Vector3f(position, innerRadius)) + + state.blendFunc = BlendFunc.ONLY_BLEND_ALPHA + + for (renderer in geometry) { + renderer.renderSoftGeometry(position, stack, state.programs.softLightGeometry) + } + + 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_SOFT + builder.draw() } finally { state.blendFunc = oldFunc - framebufferAccumulator.unbind() + framebufferRender.unbind() } + + blendResult() + } + + val builder by lazy { + StreamVertexBuilder(state, SHADOW_FORMAT, GeometryType.QUADS_AS_LINES, 256) + } + + val lineBuilder by lazy { + StreamVertexBuilder(state, SHADOW_FORMAT, GeometryType.LINES, 256) + } + + val builderSoft by lazy { + StreamVertexBuilder(state, SHADOW_FORMAT_SOFT, GeometryType.QUADS_ALTERNATIVE, 256) + } + + val lineBuilderSoft by lazy { + StreamVertexBuilder(state, SHADOW_FORMAT_SOFT, GeometryType.LINES, 256) } companion object { + val CLEAR_COLOR_SOFT = Color(0f, 0f, 0f, 0f) + val BLEND_MODE = BlendFunc( BlendFunc.Func.DST_ALPHA, BlendFunc.Func.ZERO, BlendFunc.Func.ZERO, BlendFunc.Func.ONE, ) + + val BLEND_MODE_SOFT = BlendFunc( + BlendFunc.Func.ONE_MINUS_DST_ALPHA, + BlendFunc.Func.ZERO, + BlendFunc.Func.ZERO, + BlendFunc.Func.ONE, + ) + + val SHADOW_FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).build() + val SHADOW_FORMAT_SOFT = GLAttributeList.Builder().push(GLType.VEC4F).push(GLType.VEC2F).build() } } diff --git a/src/main/resources/shaders/light_occluder.fsh b/src/main/resources/shaders/hard_light_geometry.fsh similarity index 100% rename from src/main/resources/shaders/light_occluder.fsh rename to src/main/resources/shaders/hard_light_geometry.fsh diff --git a/src/main/resources/shaders/light_occluder.gsh b/src/main/resources/shaders/hard_light_geometry.gsh similarity index 100% rename from src/main/resources/shaders/light_occluder.gsh rename to src/main/resources/shaders/hard_light_geometry.gsh diff --git a/src/main/resources/shaders/light_occluder.vsh b/src/main/resources/shaders/hard_light_geometry.vsh similarity index 100% rename from src/main/resources/shaders/light_occluder.vsh rename to src/main/resources/shaders/hard_light_geometry.vsh diff --git a/src/main/resources/shaders/soft_light_geometry.fsh b/src/main/resources/shaders/soft_light_geometry.fsh new file mode 100644 index 00000000..9b58167e --- /dev/null +++ b/src/main/resources/shaders/soft_light_geometry.fsh @@ -0,0 +1,10 @@ + +#version 460 + +out vec4 resultColor; + +in vec4 penumbras; + +void main() { + resultColor = vec4(1.0, 1.0, 1.0, 0.0); +} diff --git a/src/main/resources/shaders/soft_light_geometry.gsh b/src/main/resources/shaders/soft_light_geometry.gsh new file mode 100644 index 00000000..fbd3f1af --- /dev/null +++ b/src/main/resources/shaders/soft_light_geometry.gsh @@ -0,0 +1,150 @@ + +#version 460 + +layout (lines) in; + +layout (triangle_strip, max_vertices = 5) out; + +uniform mat4 transform; +uniform vec3 lightPositionAndSize; + +in vec2 originalPos[]; + +out vec4 penumbras; + +mat2 adjugate(mat2 m) { + return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]); +} + +void workWithSegmentForward(float x) { + vec2 endpoint_a = originalPos[0]; + vec2 endpoint_b = originalPos[1]; + + vec2 endpoint = mix(endpoint_a, endpoint_b, x); + float light_radius = lightPositionAndSize.z; + + // Deltas from the segment to the light center. + vec2 delta_a = endpoint_a - lightPositionAndSize.xy; + vec2 delta_b = endpoint_b - lightPositionAndSize.xy; + vec2 delta = endpoint - lightPositionAndSize.xy; + + // Offsets from the light center to the edge of the light volume. + vec2 offset_a = vec2(-light_radius, light_radius) * normalize(delta_a).xy; + vec2 offset_b = vec2( light_radius, -light_radius) * normalize(delta_b).xy; + vec2 offset = mix(offset_a, offset_b, x); + + vec2 penumbra_a = adjugate(mat2( offset_a, -delta_a))*(delta - mix(offset, delta_a, 0)); + vec2 penumbra_b = adjugate(mat2(-offset_b, delta_b))*(delta - mix(offset, delta_b, 0)); + + // Vertex projection. + gl_Position = transform * vec4(delta - offset, 0.0, 1.0); + EmitVertex(); +} + +void main() { + vec4 a = gl_in[0].gl_Position; + vec4 b = gl_in[1].gl_Position; + + vec2 aInv = originalPos[0]; + vec2 bInv = originalPos[1]; + + vec2 lightPosition = lightPositionAndSize.xy; + float lightSize = lightPositionAndSize.z; + + gl_Position = b; + EmitVertex(); + + gl_Position = a; + EmitVertex(); + + workWithSegmentForward(0); + workWithSegmentForward(1); + + EndPrimitive(); + + // hard shadow geometry (umbra) + //gl_Position = transform * vec4(aInv + (aInv - lightPositionAndSize.xy) * 10000, 0, 1); + //EmitVertex(); + + //gl_Position = transform * vec4(bInv + (bInv - lightPositionAndSize.xy) * 10000, 0, 1); + //EmitVertex(); + + // находим направления к вершинам линии + /*vec2 deltaA = normalize(aInv - lightPosition); + vec2 deltaB = normalize(bInv - lightPosition); + + // находим наклон прямых из центра + float degA = acos(deltaA.x); + float degB = acos(deltaB.x); + + // корректируем угол в зависимости, находимся ли мы в отрицательном Y + if (deltaA.y < 0) { + degA = -degA; + } + + if (deltaB.y < 0) { + degB = -degB; + } + + /*if (degA < 0) { + degA = 360 + degA; + } + + if (degB < 0) { + degB = 360 + degB; + } + + // определяем, находимся ли мы "сзади" + if (degA < degB) { + vec2 temp = aInv; + aInv = bInv; + bInv = temp; + + // находим направления к вершинам линии + deltaA = normalize(aInv - lightPosition); + deltaB = normalize(bInv - lightPosition); + + // находим наклон прямых из центра + degA = acos(deltaA.x); + degB = acos(deltaB.x); + + // корректируем угол в зависимости, находимся ли мы в отрицательном Y + if (deltaA.y < 0) { + degA = -degA; + } + + if (deltaB.y < 0) { + degB = -degB; + } + + if (degA < 0) { + degA = 360 + degA; + } + + if (degB < 0) { + degB = 360 + degB; + } + }*/ + + // поворачиваем наши углы на 90 градусов + /*degA -= 1.57079632; + degB += 1.57079632; + + // Теперь для каждой вершины вычисляем касательную точку на окружности + vec2 pointA = vec2(cos(degA), sin(degA)) * lightSize + lightPosition; + vec2 pointB = vec2(cos(degB), sin(degB)) * lightSize + lightPosition; + + // мы нашли касательные точки, теперь просто надо провести прямые начиная от вершин + + // это у нас "hard shadows" + gl_Position = transform * vec4(aInv + (aInv - pointA) * 10000, 0, 1); + EmitVertex(); + + gl_Position = transform * vec4(bInv + (bInv - pointB) * 10000, 0, 1); + EmitVertex(); + + gl_Position = b; + EmitVertex(); + + EndPrimitive();*/ +} diff --git a/src/main/resources/shaders/soft_light_geometry.vsh b/src/main/resources/shaders/soft_light_geometry.vsh new file mode 100644 index 00000000..c63e6291 --- /dev/null +++ b/src/main/resources/shaders/soft_light_geometry.vsh @@ -0,0 +1,13 @@ + +#version 460 + +layout (location = 0) in vec2 vertexPos; + +uniform mat4 transform; + +out vec2 originalPos; + +void main() { + gl_Position = transform * vec4(vertexPos, 0.0, 1.0); + originalPos = vertexPos; +} diff --git a/src/main/resources/shaders/soft_light_geometry2.fsh b/src/main/resources/shaders/soft_light_geometry2.fsh new file mode 100644 index 00000000..18cf12aa --- /dev/null +++ b/src/main/resources/shaders/soft_light_geometry2.fsh @@ -0,0 +1,30 @@ + +#version 460 + +// Huge thanks to articles by Scott [slembcke] Lembcke! +// https://slembcke.github.io/SuperFastHardShadows +// https://slembcke.github.io/SuperFastSoftShadows + +out vec4 resultColor; + +in vec4 v_penumbras; +in vec3 v_edges; +in vec3 v_proj_pos; +in vec4 v_endpoints; + +void main() { + // Calculate the light intersection point, but clamp to endpoints to avoid artifacts. + float intersection_t = clamp(v_edges.x/abs(v_edges.y), -0.5, 0.5); + vec2 intersection_point = (0.5 - intersection_t)*v_endpoints.xy + (0.5 + intersection_t)*v_endpoints.zw; + // The delta from the intersection to the pixel. + vec2 penetration_delta = intersection_point - v_proj_pos.xy/v_proj_pos.z; + // Apply a simple falloff function. + float bleed = min(dot(penetration_delta, penetration_delta), 1.0); + + // Penumbra mixing. + vec2 penumbras = smoothstep(-1.0, 1.0, v_penumbras.xz/v_penumbras.yw); + float penumbra = dot(penumbras, step(v_penumbras.yw, vec2(0.0))); + penumbra -= 1.0/64.0; // Numerical precision fudge factor. + + resultColor = vec4(0.0, 0.0, 0.0, bleed * (1.0 - penumbra) * step(v_edges.z, 0.0)); +} diff --git a/src/main/resources/shaders/soft_light_geometry2.vsh b/src/main/resources/shaders/soft_light_geometry2.vsh new file mode 100644 index 00000000..8fb43336 --- /dev/null +++ b/src/main/resources/shaders/soft_light_geometry2.vsh @@ -0,0 +1,69 @@ + +#version 460 + +// Huge thanks to articles by Scott [slembcke] Lembcke! +// https://slembcke.github.io/SuperFastHardShadows +// https://slembcke.github.io/SuperFastSoftShadows + +layout (location = 0) in vec4 packedVertexPos; +layout (location = 1) in vec2 edgeType; + +uniform mat4 transform; + +uniform vec3 lightPositionAndSize; +uniform float lightPenetration; + +varying vec4 v_penumbras; +varying vec3 v_edges; +varying vec3 v_proj_pos; +varying vec4 v_endpoints; + +// Keep in mind GLSL is column major. You'll need to swap row/column for HLSL. +mat2 adjugate(mat2 m) { + return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]); +} + +void main() { + vec2 lightPosition = lightPositionAndSize.xy; + float lightSize = lightPositionAndSize.z; + + // Unpack the vertex shader input. + vec2 endpoint_a = packedVertexPos.zw; + vec2 endpoint_b = packedVertexPos.xy; + vec2 endpoint = mix(endpoint_a, endpoint_b, edgeType.x); + + // Deltas from the segment to the light center. + vec2 delta_a = endpoint_a - lightPosition; + vec2 delta_b = endpoint_b - lightPosition; + vec2 delta = endpoint - lightPosition; + + // Offsets from the light center to the edge of the light volume. + vec2 offset_a = vec2(-lightSize, lightSize) * normalize(delta_a).yx; + vec2 offset_b = vec2( lightSize, -lightSize) * normalize(delta_b).yx; + vec2 offset = mix(offset_a, offset_b, edgeType.x); + + // Vertex projection. + float w = edgeType.y; + vec4 proj_pos = vec4(mix(delta - offset, endpoint, w), 0.0, w); + + gl_Position = transform * proj_pos; + + vec2 penumbra_a = adjugate(mat2( offset_a, -delta_a))*(delta - mix(offset, delta_a, w)); + vec2 penumbra_b = adjugate(mat2(-offset_b, delta_b))*(delta - mix(offset, delta_b, w)); + + v_penumbras = (lightSize > 0.0 ? vec4(penumbra_a, penumbra_b) : vec4(0, 1, 0, 1)); + + // Edge values for light penetration and clipping. + vec2 seg_delta = endpoint_b - endpoint_a; + vec2 seg_normal = seg_delta.yx*vec2(-1.0, 1.0); + // Calculate where the light -> pixel ray will intersect with the segment. + v_edges.xy = -adjugate(mat2(seg_delta, delta_a + delta_b))*(delta - offset*(1.0 - w)); + v_edges.y *= 2.0; // Skip a multiply in the fragment shader. + // Calculate a clipping coordinate that is 0 at the near edge (when w = 1)... + // otherwise calculate the dot product with the projected coordinate. + v_edges.z = dot(seg_normal, delta - offset)*(1.0 - w); + + // Light penetration values. + v_proj_pos = vec3(proj_pos.xy, w * lightPenetration); + v_endpoints = vec4(endpoint_a, endpoint_b) / lightPenetration; +}