better lighting

This commit is contained in:
asrael 2026-01-31 09:31:17 -06:00
parent 805a2713a3
commit 6ed4e17065
75 changed files with 6417 additions and 3667 deletions

View file

@ -56,6 +56,14 @@ function Lights.new(capacity)
end
function Lights:add(x, y, z, r, g, b, intensity, radius)
if r and r > 255 then
local rgb = r
intensity = g
radius = b
r = bit.band(bit.rshift(rgb, 16), 0xFF)
g = bit.band(bit.rshift(rgb, 8), 0xFF)
b = bit.band(rgb, 0xFF)
end
C.pxl8_lights_add(self._ptr, x, y, z, r or 255, g or 255, b or 255, intensity or 255, radius or 10)
end

View file

@ -49,10 +49,9 @@ function graphics.load_palette(filepath)
end
function graphics.load_sprite(filepath)
local sprite_id = ffi.new("unsigned int[1]")
local result = C.pxl8_gfx_load_sprite(core.gfx, filepath, sprite_id)
if result == 0 then
return sprite_id[0]
local result = C.pxl8_gfx_load_sprite(core.gfx, filepath)
if result >= 0 and result < 100 then
return result
else
return nil, result
end

View file

@ -23,6 +23,18 @@ function Gui:button(id, x, y, w, h, label)
return C.pxl8_gui_button(self._ptr, core.gfx, id, x, y, w, h, label)
end
function Gui:slider(id, x, y, w, h, value, min_val, max_val)
local val = ffi.new("float[1]", value)
local changed = C.pxl8_gui_slider(self._ptr, core.gfx, id, x, y, w, h, val, min_val, max_val)
return changed, val[0]
end
function Gui:slider_int(id, x, y, w, h, value, min_val, max_val)
local val = ffi.new("i32[1]", value)
local changed = C.pxl8_gui_slider_int(self._ptr, core.gfx, id, x, y, w, h, val, min_val, max_val)
return changed, val[0]
end
function Gui:cursor_down()
C.pxl8_gui_cursor_down(self._ptr)
end

View file

@ -15,91 +15,20 @@ function net.get()
return setmetatable({ _ptr = ptr }, Net)
end
function Net:chunk_id()
return C.pxl8_net_chunk_id(self._ptr)
end
function Net:connected()
return C.pxl8_net_connected(self._ptr)
end
function Net:entities()
local snap = C.pxl8_net_snapshot(self._ptr)
if snap == nil then
return {}
end
local ents = C.pxl8_net_entities(self._ptr)
if ents == nil then
return {}
end
local result = {}
for i = 0, snap.entity_count - 1 do
result[i + 1] = {
entity_id = tonumber(ents[i].entity_id),
userdata = ents[i].userdata
}
end
return result
function Net:enter_chunk(chunk_id)
return C.pxl8_net_enter_chunk(self._ptr, chunk_id or 1) == 0
end
function Net:entity_prev_userdata(entity_id)
return C.pxl8_net_entity_prev_userdata(self._ptr, entity_id)
end
function Net:entity_userdata(entity_id)
return C.pxl8_net_entity_userdata(self._ptr, entity_id)
end
function Net:input_at(tick)
local input = C.pxl8_net_input_at(self._ptr, tick)
if input == nil then return nil end
return {
buttons = input.buttons,
look_dx = input.look_dx,
look_dy = input.look_dy,
move_x = input.move_x,
move_y = input.move_y,
yaw = input.yaw,
tick = tonumber(input.tick),
timestamp = tonumber(input.timestamp)
}
end
function Net:input_oldest_tick()
return tonumber(C.pxl8_net_input_oldest_tick(self._ptr))
end
function Net:input_push(input)
local msg = ffi.new("pxl8_input_msg")
msg.buttons = input.buttons or 0
msg.look_dx = input.look_dx or 0
msg.look_dy = input.look_dy or 0
msg.move_x = input.move_x or 0
msg.move_y = input.move_y or 0
msg.yaw = input.yaw or 0
msg.tick = input.tick or 0
msg.timestamp = input.timestamp or 0
C.pxl8_net_input_push(self._ptr, msg)
end
function Net:lerp_alpha()
return C.pxl8_net_lerp_alpha(self._ptr)
end
function Net:needs_correction()
return C.pxl8_net_needs_correction(self._ptr)
end
function Net:player_id()
return tonumber(C.pxl8_net_player_id(self._ptr))
end
function Net:predicted_state()
return C.pxl8_net_predicted_state(self._ptr)
end
function Net:predicted_tick_set(tick)
C.pxl8_net_predicted_tick_set(self._ptr, tick)
end
function Net:send_command(cmd)
return C.pxl8_net_send_command(self._ptr, cmd) == 0
function Net:exit_chunk(x, y, z)
return C.pxl8_net_exit_chunk(self._ptr, x or 0, y or 0, z or 0) == 0
end
function Net:send_input(input)
@ -115,35 +44,12 @@ function Net:send_input(input)
return C.pxl8_net_send_input(self._ptr, msg) == 0
end
function Net:snapshot()
local snap = C.pxl8_net_snapshot(self._ptr)
if snap == nil then
return nil
end
return {
entity_count = snap.entity_count,
event_count = snap.event_count,
player_id = tonumber(snap.player_id),
tick = tonumber(snap.tick),
time = snap.time
}
function Net:set_chunk_settings(render_distance, sim_distance)
return C.pxl8_net_send_chunk_settings(self._ptr, render_distance or 3, sim_distance or 4) == 0
end
function Net:spawn(x, y, z, yaw, pitch)
local cmd = ffi.new("pxl8_command_msg")
cmd.cmd_type = C.PXL8_CMD_SPAWN_ENTITY
C.pxl8_pack_f32_be(cmd.payload, 0, x or 0)
C.pxl8_pack_f32_be(cmd.payload, 4, y or 0)
C.pxl8_pack_f32_be(cmd.payload, 8, z or 0)
C.pxl8_pack_f32_be(cmd.payload, 12, yaw or 0)
C.pxl8_pack_f32_be(cmd.payload, 16, pitch or 0)
cmd.payload_size = 20
cmd.tick = 0
return self:send_command(cmd)
end
function Net:tick()
return tonumber(C.pxl8_net_tick(self._ptr))
return C.pxl8_net_spawn(self._ptr, x or 0, y or 0, z or 0, yaw or 0, pitch or 0) == 0
end
return net

281
src/lua/pxl8/shader.lua Normal file
View file

@ -0,0 +1,281 @@
local ffi = require("ffi")
local bit = require("bit")
local core = require("pxl8.core")
ffi.cdef[[
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx);
const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);
u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);
]]
local C = ffi.C
local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift
local floor, sqrt, max, min, abs = math.floor, math.sqrt, math.max, math.min, math.abs
local sin, cos, fmod = math.sin, math.cos, math.fmod
local shader = {}
local fb = ffi.new("u8*")
local light = ffi.new("u32*")
local pal = ffi.new("const u32*")
local zbuf = ffi.new("u16*")
local w, h, count = 0, 0, 0
function shader.begin_frame()
fb = C.pxl8_gfx_get_framebuffer_indexed(core.gfx)
light = C.pxl8_gfx_get_light_accum(core.gfx)
pal = C.pxl8_gfx_palette_colors(core.gfx)
zbuf = C.pxl8_gfx_get_zbuffer(core.gfx)
w = C.pxl8_gfx_get_width(core.gfx)
h = C.pxl8_gfx_get_height(core.gfx)
count = w * h
end
function shader.get_buffers()
return fb, light, pal, zbuf, w, h
end
local function clamp(x, lo, hi)
if x < lo then return lo end
if x > hi then return hi end
return x
end
shader.resolve_tint = function()
if fb == nil or light == nil or pal == nil then return end
local fb_l, light_l, pal_l = fb, light, pal
local count_l = count
local band_l, rshift_l, bor_l, lshift_l = band, rshift, bor, lshift
local floor_l = floor
for i = 0, count_l - 1 do
local lv = light_l[i]
if lv ~= 0 then
local a = rshift_l(lv, 24)
if a > 0 then
local base = pal_l[fb_l[i]]
local br = band_l(base, 0xFF)
local bg = band_l(rshift_l(base, 8), 0xFF)
local bb = band_l(rshift_l(base, 16), 0xFF)
local lr = band_l(lv, 0xFF)
local lg = band_l(rshift_l(lv, 8), 0xFF)
local lb = band_l(rshift_l(lv, 16), 0xFF)
local t = a * 0.00392156862
local r = floor_l(br + (lr - 128) * t * 2)
local g = floor_l(bg + (lg - 128) * t * 2)
local b = floor_l(bb + (lb - 128) * t * 2)
if r < 0 then r = 0 elseif r > 255 then r = 255 end
if g < 0 then g = 0 elseif g > 255 then g = 255 end
if b < 0 then b = 0 elseif b > 255 then b = 255 end
light_l[i] = bor_l(r, lshift_l(g, 8), lshift_l(b, 16), 0xFF000000)
end
end
end
end
shader.compile = function(source)
local header = [[
local ffi = require("ffi")
local bit = require("bit")
local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift
local floor, sqrt, max, min, abs = math.floor, math.sqrt, math.max, math.min, math.abs
local sin, cos, fmod = math.sin, math.cos, math.fmod
local clamp = function(x, lo, hi) if x < lo then return lo elseif x > hi then return hi else return x end end
local mix = function(a, b, t) return a + (b - a) * t end
local smoothstep = function(e0, e1, x) local t = clamp((x - e0) / (e1 - e0), 0, 1); return t * t * (3 - 2 * t) end
local saturate = function(x) if x < 0 then return 0 elseif x > 1 then return 1 else return x end end
local length2 = function(x, y) return sqrt(x*x + y*y) end
local length3 = function(x, y, z) return sqrt(x*x + y*y + z*z) end
local dot2 = function(ax, ay, bx, by) return ax*bx + ay*by end
local dot3 = function(ax, ay, az, bx, by, bz) return ax*bx + ay*by + az*bz end
local fract = function(x) return x - floor(x) end
return function(fb, light, pal, zbuf, w, h, uniforms)
uniforms = uniforms or {}
local count = w * h
local time = uniforms.time or 0
local band_l, rshift_l, bor_l, lshift_l = band, rshift, bor, lshift
local floor_l, sqrt_l, max_l, min_l = floor, sqrt, max, min
for i = 0, count - 1 do
local x = i % w
local y = floor_l(i / w)
local uv_x = x / w
local uv_y = y / h
local idx = fb[i]
local base = pal[idx]
local br = band_l(base, 0xFF)
local bg = band_l(rshift_l(base, 8), 0xFF)
local bb = band_l(rshift_l(base, 16), 0xFF)
local lv = light[i]
local lr = band_l(lv, 0xFF)
local lg = band_l(rshift_l(lv, 8), 0xFF)
local lb = band_l(rshift_l(lv, 16), 0xFF)
local la = rshift_l(lv, 24)
local depth = zbuf and zbuf[i] or 0
local depth_n = depth / 65535.0
local r, g, b = br, bg, bb
]]
local footer = [[
r = floor_l(clamp(r, 0, 255))
g = floor_l(clamp(g, 0, 255))
b = floor_l(clamp(b, 0, 255))
light[i] = bor_l(r, lshift_l(g, 8), lshift_l(b, 16), 0xFF000000)
end
end
]]
local code = header .. source .. footer
local fn, err = loadstring(code)
if not fn then
error("Shader compile error: " .. tostring(err))
end
return fn()
end
shader.run = function(compiled_shader, uniforms)
if fb == nil or light == nil or pal == nil then return end
compiled_shader(fb, light, pal, zbuf, w, h, uniforms)
end
shader.presets = {}
shader.presets.passthrough = shader.compile([[
-- passthrough: just use base color
]])
shader.presets.light_tint = shader.compile([[
if la > 0 then
local t = la / 255.0
r = br + (lr - 128) * t * 2
g = bg + (lg - 128) * t * 2
b = bb + (lb - 128) * t * 2
end
]])
shader.presets.vignette = shader.compile([[
local cx = uv_x - 0.5
local cy = uv_y - 0.5
local dist = sqrt_l(cx*cx + cy*cy)
local vig = 1.0 - saturate(dist * 1.5)
vig = vig * vig
r = br * vig
g = bg * vig
b = bb * vig
]])
shader.presets.scanlines = shader.compile([[
local scan = 0.8 + 0.2 * (y % 2)
r = br * scan
g = bg * scan
b = bb * scan
]])
shader.presets.crt = shader.compile([[
-- Scanlines
local scan = 0.85 + 0.15 * (y % 2)
-- Vignette
local cx = uv_x - 0.5
local cy = uv_y - 0.5
local dist = sqrt_l(cx*cx + cy*cy)
local vig = 1.0 - saturate(dist * 1.2)
-- RGB shift based on x position
local shift = (uv_x - 0.5) * 0.02
local mult = scan * vig
r = br * mult * (1.0 + shift)
g = bg * mult
b = bb * mult * (1.0 - shift)
]])
shader.presets.dither_fade = shader.compile([[
local threshold = fract(sin(x * 12.9898 + y * 78.233) * 43758.5453)
local fade = uniforms.fade or 0.5
if threshold > fade then
r, g, b = 0, 0, 0
else
r, g, b = br, bg, bb
end
]])
shader.presets.fog = shader.compile([[
local fog_color_r = uniforms.fog_r or 32
local fog_color_g = uniforms.fog_g or 32
local fog_color_b = uniforms.fog_b or 48
local fog_start = uniforms.fog_start or 0.3
local fog_end = uniforms.fog_end or 0.9
local fog_t = saturate((depth_n - fog_start) / (fog_end - fog_start))
fog_t = fog_t * fog_t
r = mix(br, fog_color_r, fog_t)
g = mix(bg, fog_color_g, fog_t)
b = mix(bb, fog_color_b, fog_t)
]])
shader.presets.light_with_fog = shader.compile([[
-- Apply light tint first
if la > 0 then
local t = la / 255.0
r = br + (lr - 128) * t * 2
g = bg + (lg - 128) * t * 2
b = bb + (lb - 128) * t * 2
else
r, g, b = br, bg, bb
end
-- Then fog
local fog_r = uniforms.fog_r or 16
local fog_g = uniforms.fog_g or 16
local fog_b = uniforms.fog_b or 24
local fog_start = uniforms.fog_start or 0.2
local fog_end = uniforms.fog_end or 0.95
local fog_t = saturate((depth_n - fog_start) / (fog_end - fog_start))
fog_t = fog_t * fog_t
r = mix(r, fog_r, fog_t)
g = mix(g, fog_g, fog_t)
b = mix(b, fog_b, fog_t)
]])
shader.presets.posterize = shader.compile([[
local levels = uniforms.levels or 4
local step = 255 / levels
r = floor_l(br / step) * step
g = floor_l(bg / step) * step
b = floor_l(bb / step) * step
]])
shader.presets.chromatic = shader.compile([=[
local amount = uniforms.amount or 2
local ox = floor_l(amount * (uv_x - 0.5))
local r_i = clamp(i - ox, 0, count - 1)
local b_i = clamp(i + ox, 0, count - 1)
local r_base = pal[fb[r_i]]
local b_base = pal[fb[b_i]]
r = band_l(r_base, 0xFF)
g = bg
b = band_l(rshift_l(b_base, 16), 0xFF)
]=])
shader.clear_light = function()
if light == nil then return end
ffi.fill(light, count * 4, 0)
end
shader.fill_output = function(r, g, b)
if light == nil then return end
local color = bor(r, lshift(g, 8), lshift(b, 16), 0xFF000000)
for i = 0, count - 1 do
light[i] = color
end
end
return shader

View file

@ -6,8 +6,6 @@ local world = {}
world.CHUNK_VXL = 0
world.CHUNK_BSP = 1
world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
local Bsp = {}
Bsp.__index = Bsp
@ -24,14 +22,6 @@ function Bsp:face_set_material(face_id, material_id)
C.pxl8_bsp_face_set_material(self._ptr, face_id, material_id)
end
function Bsp:set_material(material_id, material)
C.pxl8_bsp_set_material(self._ptr, material_id, material._ptr)
end
function Bsp:set_wireframe(wireframe)
C.pxl8_bsp_set_wireframe(self._ptr, wireframe)
end
world.Bsp = Bsp
local Chunk = {}
@ -45,21 +35,26 @@ function Chunk:bsp()
return setmetatable({ _ptr = ptr }, Bsp)
end
function Chunk:type()
if self._ptr == nil then return nil end
return self._ptr.type
end
function Chunk:ready()
if self._ptr == nil then return false end
if self._ptr.type == world.CHUNK_BSP then
return self._ptr.bsp ~= nil
elseif self._ptr.type == world.CHUNK_VXL then
return self._ptr.voxel ~= nil
return self._ptr.voxels ~= nil
end
return false
end
function Chunk:type()
if self._ptr == nil then return nil end
return self._ptr.type
end
function Chunk:version()
if self._ptr == nil then return 0 end
return self._ptr.version
end
world.Chunk = Chunk
local World = {}
@ -77,9 +72,32 @@ function World:active_chunk()
return setmetatable({ _ptr = ptr }, Chunk)
end
function World:check_collision(x, y, z, radius)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
return C.pxl8_world_check_collision(self._ptr, pos, radius)
function World:get_render_distance()
return C.pxl8_world_get_render_distance(self._ptr)
end
function World:get_sim_distance()
return C.pxl8_world_get_sim_distance(self._ptr)
end
function World:init_local_player(x, y, z)
C.pxl8_world_init_local_player(self._ptr, x, y, z)
end
function World:local_player()
local ptr = C.pxl8_world_local_player(self._ptr)
if ptr == nil then return nil end
return ptr
end
function World:point_solid(x, y, z)
return C.pxl8_world_point_solid(self._ptr, x, y, z)
end
function World:ray(from_x, from_y, from_z, to_x, to_y, to_z)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
return C.pxl8_world_ray(self._ptr, from, to)
end
function World:render(camera_pos)
@ -87,11 +105,26 @@ function World:render(camera_pos)
C.pxl8_world_render(self._ptr, core.gfx, vec)
end
function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radius)
function World:set_bsp_material(material_id, material)
C.pxl8_world_set_bsp_material(self._ptr, material_id, material._ptr)
end
function World:set_render_distance(distance)
C.pxl8_world_set_render_distance(self._ptr, distance)
end
function World:set_sim_distance(distance)
C.pxl8_world_set_sim_distance(self._ptr, distance)
end
function World:set_wireframe(enabled)
C.pxl8_world_set_wireframe(self._ptr, enabled)
end
function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
local result = C.pxl8_world_resolve_collision(self._ptr, from, to, radius)
return result.x, result.y, result.z
return C.pxl8_world_sweep(self._ptr, from, to, radius)
end
world.World = World