Skip to content

Instantly share code, notes, and snippets.

@jmiskovic
Last active May 20, 2025 14:26
Show Gist options
  • Select an option

  • Save jmiskovic/cf2713b6b476f2c49f10e701ae3fe545 to your computer and use it in GitHub Desktop.

Select an option

Save jmiskovic/cf2713b6b476f2c49f10e701ae3fe545 to your computer and use it in GitHub Desktop.
A testbed for light probe generation from a dynamically rendered skybox
local shh = require'shh' -- grab from https://github.com/bjornbytes/shh
-- a newer version of skybox might be available https://github.com/jmiskovic/lovr-atmo
local skybox = {}
skybox.__index = skybox
local cubemap_transforms = {
Mat4():lookAt(vec3(0, 0, 0), vec3( 1, 0, 0), vec3(0, 1, 0)),
Mat4():lookAt(vec3(0, 0, 0), vec3(-1, 0, 0), vec3(0, 1, 0)),
Mat4():lookAt(vec3(0, 0, 0), vec3( 0, 1, 0), vec3(0, 0,-1)),
Mat4():lookAt(vec3(0, 0, 0), vec3( 0,-1, 0), vec3(0, 0, 1)),
Mat4():lookAt(vec3(0, 0, 0), vec3( 0, 0, 1), vec3(0, 1, 0)),
Mat4():lookAt(vec3(0, 0, 0), vec3( 0, 0,-1), vec3(0, 1, 0))
}
function skybox.new(resolution)
local self = setmetatable({}, skybox)
self.resolution = math.floor(resolution or 256)
assert(self.resolution > 0 and self.resolution < 6000)
self.cubetex = lovr.graphics.newTexture(
self.resolution, self.resolution, 6,
{type='cube', mipmaps=false, usage = {'render', 'sample', 'storage'}})
self.horizon_color = lovr.math.newVec3()
return self
end
function skybox:bake(drawfn)
-- render scene to cubemap texture
self.render_pass = self.render_pass or lovr.graphics.newPass({self.cubetex, samples=1})
self.render_pass:reset()
local projection = mat4():perspective(math.pi / 2, 1, 0, 0)
for i, transform in ipairs(cubemap_transforms) do
self.render_pass:setProjection(i, projection)
self.render_pass:setViewPose(i, transform)
end
drawfn(self.render_pass)
lovr.graphics.submit(self.render_pass)
end
function skybox:draw(pass)
pass:setColor(1,1,1)
pass:skybox(self.cubetex)
end
--------------------------------------
local box = skybox.new(64)
probeShader = lovr.graphics.newShader('unlit', [[
layout(set = 2, binding = 0, std140) uniform LightProbe { vec3 lightProbe[9]; };
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() {
vec3 lpc = evaluateLightProbe(lightProbe, normalize(Normal));
float bias = 0;
float scl = 1;
lpc = (lpc - bias) * scl + bias;
return vec4(lpc, 1.);
}
]])
sh = shh.new()
light_pos = lovr.math.newVec3(0, 10, 0)
function drawLight(pass)
pass:setColor(1,1,1)
pass:sphere(light_pos, 2)
end
function lovr.update(dt)
if lovr.headset.isDown('left', 'trigger') then
local v = vec3(quat(lovr.headset.getOrientation('left')):direction()):mul(5)
v.x = -v.x -- TODO: this shouldn't be needed, something's off in skybox
v.z = -v.z
light_pos:lerp(v, dt)
end
if lovr.system.isKeyDown('1') then
light_pos:lerp(0, 1, 0, dt * 4):normalize():mul(5)
elseif lovr.system.isKeyDown('2') then
light_pos:lerp(1, 0, 0, dt * 4):normalize():mul(5)
elseif lovr.system.isKeyDown('3') then
light_pos:lerp(0, -1, 0, dt * 4):normalize():mul(5)
end
box:bake(drawLight)
sh = shh.new(box.cubetex)
end
function lovr.draw(pass)
pass:skybox(box.cubetex)
pass:setColor(0.2, 0.2, 0.2)
pass:setColor(1,1,1)
shh.setShader(pass, sh)
pass:monkey(mat4(0, 1, -3))
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment