Skip to content

Instantly share code, notes, and snippets.

@jmiskovic
Last active March 17, 2024 09:25
Show Gist options
  • Save jmiskovic/3b687bb3052d930f03c2648c54366a12 to your computer and use it in GitHub Desktop.
Save jmiskovic/3b687bb3052d930f03c2648c54366a12 to your computer and use it in GitHub Desktop.
Directional light shadow mapping for lovr
-- directional light shadow mapping for lovr 0.18 (currenly on post 0.17.1 dev branch)
local m = {}
m.near = 0.01
m.far = 500
m.orthographic_span = 40
m.perspective_fov = 90
m.view_pose = lovr.math.newMat4()
:lookAt(
vec3(10, 70, -10),
vec3(0, 0, 0))
m.projection = Mat4()
:orthographic(-m.orthographic_span, m.orthographic_span, m.orthographic_span, -m.orthographic_span, m.near, m.far)
--:perspective(math.rad(m.perspective_fov), 1, m.near, m.far)
local shadowmapper = lovr.graphics.newShader('unlit',
[[ //glsl
uniform mat4 LightSpaceMatrix;
uniform texture2D DepthBuffer;
/* on lovr 0.17.1 the above uniforms should be replaced with:
layout(set = 2, binding = 0) uniform texture2D DepthBuffer;
layout( push_constant ) uniform constants {
mat4 LightSpaceMatrix;
} Push;
*/
vec2 pcf_offset[20] = vec2[](
vec2(0.0, 0.0), vec2(0.1, -0.1), vec2(-0.4, 0.7), vec2(-0.5, 0.4),
vec2(0.9, -0.9), vec2(0.8, 0.1), vec2(-0.6, -1.0), vec2(0.1, 0.5),
vec2(0.2, -0.6), vec2(0.2, -0.5), vec2(-0.2, -0.3), vec2(0.7, -0.1),
vec2(1.0, 0.3), vec2(0.3, -0.9), vec2(-0.9, 0.7), vec2(-0.4, -0.4),
vec2(0.1, 0.3), vec2(-0.1, -0.8), vec2(-0.4, -0.0), vec2(0.3, 0.2)
);
vec4 lovrmain() {
vec4 color = DefaultColor;
const int pcf_samples = 20; // only up to 20
vec4 PositionLightSpace = LightSpaceMatrix * vec4(PositionWorld, 1.);
vec2 lightSpaceUV = PositionLightSpace.xy / PositionLightSpace.w * 0.5 + 0.5;
float depth_biased = 8e-7;
float currentDepth = PositionLightSpace.z / PositionLightSpace.w + depth_biased;
float shadowing = 0.; // collect shadow across pcf samples
for (int i = 0; i < pcf_samples; ++i) {
int index = i;
lightSpaceUV += pcf_offset[index] * 2e-3;
float closestDepth;
if ((lightSpaceUV.x > 0.) && (lightSpaceUV.x < 1.) && (lightSpaceUV.y > 0.) && (lightSpaceUV.y < 1.)) {
closestDepth = getPixel(DepthBuffer, lightSpaceUV).r;
} else {
closestDepth = 1e9;
}
if (currentDepth < closestDepth)
shadowing += 1.0 / pcf_samples;
}
shadowing = -0.2 + pow(1.8 * shadowing, 0.4);
color.rgb *= vec3(shadowing);
return color;
}
]])
function m.load(resolution, pose)
if pose then
m.view_pose:set(pose):invert()
end
m.resolution = resolution or 1024
m.texture = lovr.graphics.newTexture(m.resolution, m.resolution,
{ format = 'd32f', mipmaps = false, linear = true, usage = {'render', 'sample'}} )
m.pass = lovr.graphics.newPass{ depth = m.texture, samples = 1 }
end
function m.getPass()
-- render to depth buffer from light perspective
m.pass:setClear({ depth = 1 })
m.pass:reset()
m.pass:setCullMode('front')
m.pass:setDepthTest('lequal')
m.pass:setProjection(1, m.projection)
m.pass:setViewPose(1, m.view_pose, true)
return m.pass
end
function m.setShader(pass)
pass:setCullMode('back')
pass:setShader(shadowmapper)
local light_space_matrix = m.projection * m.view_pose
pass:send('LightSpaceMatrix', light_space_matrix)
pass:send('DepthBuffer', m.texture)
end
function m.debugDraw(pass)
pass:setColor(1, 0.8, 0.5)
local pose = mat4(m.view_pose):invert()
pass:cone(vec3(pose:mul(0, 0, -0.3)), vec3(pose), 0.2)
end
function m.setPose(pose_or_origin, target)
if not target then
local pose = pose_or_origin
m.view_pose:set(pose):invert()
else
local origin = pose_or_origin
m.view_pose:lookAt(origin, target)
end
end
-- end of library code; now follows a testing scene, accessed with `lovr dir_light.lua`
if string.find(arg[0], 'dir_light') then
-- in main.lua this would be: local dir_light = require('dir_light')
local dir_light = m
dir_light.load(1024 * 4)
local palette = { -- https://lospec.com/palette-list/breathless-calm
{0.200, 0.247, 0.345},
{0.933, 0.525, 0.584},
{0.655, 0.322, 0.322},
{0.976, 0.976, 0.976},
{0.467, 0.227, 0.302},
{0.812, 0.471, 0.384},
{0.290, 0.478, 0.588},
{0.329, 0.196, 0.275},
{0.161, 0.157, 0.192},
}
lovr.graphics.setBackgroundColor(palette[#palette])
local is_wobbling = false -- togglable with ` or tilda key
local is_hiding_hints = false -- togglable with ESC
local is_orthographic = true -- p for projection, o for orthographic
local function scene(pass)
pass:setColor(1,1,1)
pass:setColor(palette[1])
pass:circle(0, 0, 0, 500, -math.pi / 2, 1,0,0)
lovr.math.setRandomSeed(0)
for i = 1, 50 do
local x = (lovr.math.random() - 0.5) * 100
local z = (lovr.math.random() - 0.5) * 100
local s = 4 * math.log(1.1 + 0.4 * lovr.math.random())
local pose = mat4(
x, 1.5 * s, z,
10 * s, 1, 0.8 * s,
lovr.math.random() * math.pi * 2, 0,1,0)
pass:setColor(palette[2 + i % (#palette - 2)])
pass:torus(pose, 6, 6)
pass:torus(pose:translate(0, - s, -15 * s):scale(0.7), 6, 6)
end
for i = 1, 50 do
local x = (lovr.math.random() - 0.5) * 120
local z = (lovr.math.random() - 0.5) * 120
local s = 0.5 + 15 * lovr.math.random()
pass:setColor(palette[2 + i % (#palette - 2)])
local o = vec3(x, 0, z)
local t = vec3(x, s, z)
pass:cylinder(o, t, 1, true, nil, nil, 6, 6)
local o = vec3(x + 6 * (-0.5 + lovr.math.random()), 0, z + 6 * (-0.5 + lovr.math.random()))
local t = vec3(o):add(0, s * 0.3, 0)
pass:cylinder(o, t, 2.5, true, nil, nil, 6, 6)
end
end
function lovr.draw(pass)
local viewer_pose = mat4(pass:getViewPose(1))
if is_wobbling then
local pose = mat4(dir_light.view_pose):invert()
local o = vec3(pose)
o:add(
0.01 * math.cos(1.7 * lovr.timer.getTime()),
0.03 * math.sin(1.7 * lovr.timer.getTime()),
0)
t = vec3(pose:mul(0, 0, -10))
dir_light.setPose(o, t)
end
-- set light pose to current camera when G is pressed
if lovr.system.isKeyDown('space') then
local pose = mat4(viewer_pose):translate(0, -0.2, -0.1)
dir_light.setPose(pose)
end
-- draw only depth texture from light perspective while SPACE is held
if lovr.system.isKeyDown('g') then
pass:fill(dir_light.texture)
end
if not is_hiding_hints then
local color_text = palette[3]
local color_value = palette[12]
pass:text({
color_text, 'wobble is ',
color_value, is_wobbling and 'on' or 'off',
color_text, ' (use ` or ~ tilda to toggle)\n',
color_text, 'camera is ',
color_value, is_orthographic and 'orthographic' or 'perspective',
color_text, ' (use p and o)\n',
color_text, '(hold SPACE to set camera pose)\n',
color_text, '(hold G to see light depth map)\n',
color_text, '(press ESC to hide)\n',
}, mat4(viewer_pose):translate(-0.2, 0.4, -1):scale(0.06))
end
-- the most important part:
local gpass = dir_light.getPass()
scene(gpass)
dir_light.debugDraw(pass)
dir_light.setShader(pass)
scene(pass)
return lovr.graphics.submit(gpass, pass)
end
function lovr.keypressed(key)
if key == 'p' then
dir_light.projection:perspective(math.rad(m.perspective_fov), 1, m.near, m.far)
is_orthographic = false
elseif key == 'o' then
dir_light.projection:orthographic(-m.orthographic_span, m.orthographic_span, m.orthographic_span, -m.orthographic_span, m.near, m.far)
is_orthographic = true
elseif key == '`' then
is_wobbling = not is_wobbling -- ~~
elseif key == 'escape' then
is_hiding_hints = not is_hiding_hints
end
end
end -- this would be end of main.lua improting this lib
return m
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment