Soft light test

This commit is contained in:
DBotThePony 2022-09-14 20:28:19 +07:00
parent 7eeb5f8a12
commit 1254fb276c
Signed by: DBot
GPG Key ID: DCC23B5715498507
16 changed files with 519 additions and 47 deletions

View File

@ -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()

View File

@ -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

View File

@ -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) }

View File

@ -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<out T : AbstractVertexBuilder<T>>(
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<out T : AbstractVertexBuilder<T>>(
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<out T : AbstractVertexBuilder<T>>(
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<out T : AbstractVertexBuilder<T>>(
}
}
fun <T : AbstractVertexBuilder<T>> 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 : AbstractVertexBuilder<T>> 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 : AbstractVertexBuilder<T>> 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 : AbstractVertexBuilder<T>> T.quad(

View File

@ -9,7 +9,7 @@ open class DirectVertexBuilder<T : DirectVertexBuilder<T>>(
type: GeometryType,
val maxElements: Int,
) : AbstractVertexBuilder<DirectVertexBuilder<T>>(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) {

View File

@ -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)),
}

View File

@ -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] })

View File

@ -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<ShadowGeometryRenderer>()
@ -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()
}
}

View File

@ -0,0 +1,10 @@
#version 460
out vec4 resultColor;
in vec4 penumbras;
void main() {
resultColor = vec4(1.0, 1.0, 1.0, 0.0);
}

View File

@ -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();*/
}

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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;
}