Last active
March 13, 2024 16:14
-
-
Save jmiskovic/0665e7113206858e4593588968b8101a to your computer and use it in GitHub Desktop.
Point light shadow mapping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ Point light shaddow mapping | |
This is obsolete version that requires few modifications to work on post 0.17 lovr | |
See this updated version: https://github.com/Tackyflea/lovrShadows/ | |
-- usage snippets | |
local point_light = require('point_light') | |
point_light.load(1024, vec3(0, 1, 0)) -- depth texture resolution & position | |
-- in lovr.update(dt), or at start of lovr.draw(pass) | |
local pass = point_light.getPass() | |
-- ... draw the scene using this pass ... | |
lovr.graphics.submit(pass) | |
-- if using within draw(), submit it together with main pass | |
-- in lovr.draw(pass) | |
pass:sphere(mat4(point_light.position):scale(0.05)) | |
point_light.setShader(pass) | |
-- ... draw the scene again ... | |
-- change light parameters | |
point_light.position:set(1, 2, -1) | |
point_light.light_color:set(1, 0, 0) -- rgb, can go over 1 for stronger lights | |
point_light.ambient = 0.02 | |
point_light.shadow_strength = 0.1 | |
point_light.diffuse_strength = 0.8 | |
point_light.falloff = 0.08 | |
--]] | |
local m = {} | |
local depthwritter = lovr.graphics.newShader('unlit', [[//glsl | |
layout( push_constant ) uniform constants { | |
float LightFarPlane; | |
vec3 LightWorld; | |
} Push; | |
vec4 lovrmain() { | |
FragDepth = distance(Push.LightWorld, PositionWorld) / Push.LightFarPlane; | |
if (Color.a <= 0) { | |
FragDepth = Push.LightFarPlane; | |
discard; | |
} | |
return DefaultColor; | |
} | |
]]) | |
local shadowmapper = lovr.graphics.newShader('unlit', [[//glsl | |
layout(set = 2, binding = 0) uniform textureCube DepthBuffer; | |
layout( push_constant ) uniform constants { | |
vec3 LightWorld; | |
vec3 LightColor; | |
float Ambient; | |
float Shading; | |
float Falloff; | |
float ShadowStrength; | |
float DiffuseStrength; | |
float LightFarPlane; | |
} Push; | |
vec3 pcf_offset[20] = vec3[]( | |
vec3( 1, 1, 1), vec3( 1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), | |
vec3( 1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), | |
vec3( 1, 1, 0), vec3( 1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), | |
vec3( 1, 0, 1), vec3(-1, 0, 1), vec3( 1, 0, -1), vec3(-1, 0, -1), | |
vec3( 0, 1, 1), vec3( 0, -1, 1), vec3( 0, -1, -1), vec3( 0, 1, -1)); | |
vec3 lightProbe[9] = { // extracted from yellowish surrounding and purple floor | |
vec3(0.3264819085598, 0.3535004556179, 0.21810184419155), | |
vec3(0.45082664489746, 0.49788716435432, 0.31052857637405), | |
vec3(-0.050872970372438, -0.031880494207144, 0.007200816180557), | |
vec3(-0.11037144809961, -0.069166347384453, 0.015622524544597), | |
vec3(-0.1914529800415, -0.11997760087252, 0.027099214494228), | |
vec3(-0.094650238752365, -0.059314340353012, 0.013397279195487), | |
vec3(-0.17679689824581, -0.20411671698093, -0.13814227283001), | |
vec3(0, 0, 0), | |
vec3(-0.30622118711472, -0.35354053974152, -0.23926958441734), | |
}; | |
vec3 evaluateLightProbe(vec3 probe[9], vec3 dir) { | |
return | |
.88622692545276 * probe[0] + | |
1.0233267079465 * probe[1] * dir.y + | |
1.0233267079465 * probe[2] * dir.z + | |
1.0233267079465 * probe[3] * dir.x + | |
.85808553080978 * probe[4] * dir.x * dir.y + | |
.85808553080978 * probe[5] * dir.y * dir.z + | |
.24770795610038 * probe[6] * (3. * dir.z * dir.z - 1.) + | |
.85808553080978 * probe[7] * dir.x * dir.z + | |
.42904276540489 * probe[8] * (dir.x * dir.x - dir.y * dir.y); | |
} | |
vec4 lovrmain() { | |
const int pcf_samples = 20; // only up to 20 | |
const float c = 1.0; | |
vec4 color = DefaultColor; | |
vec3 lightVec = PositionWorld - Push.LightWorld; | |
float dist = distance(Push.LightWorld, PositionWorld); | |
//const float bias = 0.002; | |
//float bias = max(0.0002 * (1.0 - dot(Normal, normalize(Push.LightWorld - PositionWorld))), 0.0005); | |
float bias = max(0.005 * (1.0 - dot(normalize(Normal), normalize(lightVec))), 0.005); | |
float shadowing = 0.; | |
float radius = 0.01 * distance(PositionWorld, CameraPositionWorld); | |
for (int i = 0; i < pcf_samples; ++i) { | |
float closest_dist = getPixel(DepthBuffer, vec3(-lightVec.x, lightVec.y, lightVec.z) + pcf_offset[i] * radius).r * Push.LightFarPlane; | |
shadowing += (dist + bias > closest_dist) ? 0. : Push.ShadowStrength / pcf_samples; | |
} | |
// shadowing is 1 where fully lit and 0 in complete shadow | |
shadowing = clamp(shadowing, 0.0, 1.0); | |
color.rgb *= (Push.Ambient + shadowing * Push.LightColor); | |
color.rgb *= Push.Shading * (evaluateLightProbe(lightProbe, normalize(Normal)) - 1.) + 1.; | |
return color; | |
} | |
]]) | |
m.position = lovr.math.newVec3(0, 1, 0) | |
m.light_color = lovr.math.newVec3(1, 1, 1) | |
m.draw_distance = 1000 | |
m.ambient = 0.05 | |
m.shading = 0.5 | |
m.shadow_strength = 0.8 | |
m.diffuse_strength = 0.4 | |
m.falloff = 0.05 | |
local perspective = lovr.math.newMat4():perspective(math.pi / 2, 1, 0.01, m.draw_distance) | |
local transforms = { | |
lovr.math.newMat4():lookAt(vec3(), vec3( 1, 0, 0), vec3(0, 1, 0)), | |
lovr.math.newMat4():lookAt(vec3(), vec3(-1, 0, 0), vec3(0, 1, 0)), | |
lovr.math.newMat4():lookAt(vec3(), vec3( 0, 1, 0), vec3(0, 0,-1)), | |
lovr.math.newMat4():lookAt(vec3(), vec3( 0,-1, 0), vec3(0, 0, 1)), | |
lovr.math.newMat4():lookAt(vec3(), vec3( 0, 0, 1), vec3(0, 1, 0)), | |
lovr.math.newMat4():lookAt(vec3(), vec3( 0, 0,-1), vec3(0, 1, 0)) | |
} | |
function m.load(resolution, position) | |
m.position:set(position) | |
m.resolution = resolution or 1024 | |
m.texture = lovr.graphics.newTexture(m.resolution, m.resolution, 6, {type='cube', format = 'd32f', mipmaps = false, usage = {'render', 'sample'}}) | |
end | |
function m.getPass() | |
-- render to depth buffer from light's perspective | |
-- texture is set to 1 (infinite distance) and "rendered" geometry increases depth texture towards 0 | |
local pass = lovr.graphics.getPass('render', {depth = {texture = m.texture, clear = 1}, samples = 1, mipmap = false}) | |
pass:setClear({ depth = 1 }) | |
for i, transform in ipairs(transforms) do | |
transform[13], transform[14], transform[15] = m.position:unpack() | |
pass:setViewPose(i, transform) | |
pass:setProjection(i, perspective) | |
end | |
pass:setDepthTest('<=') -- draw only closer (numerically smaller) geometry | |
pass:setCullMode('front') -- shadow-mapping trick to avoid z-fighting face acne | |
pass:setShader(depthwritter) | |
pass:send('LightWorld', m.position) | |
pass:send('LightFarPlane', m.draw_distance) | |
return pass -- pass is prepared for receiving geometry, should also be submitted | |
end | |
function m.setShader(pass) | |
pass:setShader(shadowmapper) | |
pass:send('DepthBuffer', m.texture) | |
pass:send('LightWorld', m.position) | |
pass:send('LightColor', m.light_color) | |
pass:send('LightFarPlane', m.draw_distance) | |
pass:send('Ambient', m.ambient) | |
pass:send('Shading', m.shading) | |
pass:send('ShadowStrength', m.shadow_strength) | |
pass:send('DiffuseStrength', m.diffuse_strength) | |
pass:send('Falloff', m.falloff) | |
end | |
return m |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment