Revamp shadow map system into a working state. Fix related bugs.
* `light.configure_shadow` has been added. It can either use a camera frustum or calculate the bounds for a static shadow that doesn't need to be updated every frame. * `Light` and `newLight` have `use_shadow`, however it's not used automatically at the moment. Instead you should call `configure_shadow`. * Added `updateShadowStorage` which at the moment has to be called manually after using `configure_shadow`. * All shadows now share a single texture array per scene, so they can be accessed by index in the shader and take less shader locations and draw calls. * Shadow map resolution is also global per scene. * Add `set_texture_shadow` and `unset_texture_shadow` to configure a texture as shadow sampler. * Bias has been removed as an option, it's calculated automatically instead. * Bias now moves along the normal of the mesh instead of towards the light, allowing very small biases.
This commit is contained in:
parent
c8f9beaff9
commit
c77187fa9f
16 changed files with 409 additions and 189 deletions
|
@ -53,6 +53,7 @@ import ./objects/cubemap_probe
|
||||||
import ./objects/gameobject
|
import ./objects/gameobject
|
||||||
import ./objects/light
|
import ./objects/light
|
||||||
import ./objects/mesh
|
import ./objects/mesh
|
||||||
|
import ./shadows/shadow_common
|
||||||
import ./shadows/simple_shadow
|
import ./shadows/simple_shadow
|
||||||
import ./quat
|
import ./quat
|
||||||
import ./scene
|
import ./scene
|
||||||
|
@ -81,6 +82,7 @@ export quat
|
||||||
export render
|
export render
|
||||||
export scene
|
export scene
|
||||||
export screen
|
export screen
|
||||||
|
export shadow_common
|
||||||
export simple_shadow
|
export simple_shadow
|
||||||
export tables
|
export tables
|
||||||
export texture
|
export texture
|
||||||
|
|
|
@ -101,6 +101,7 @@ proc newFramebuffer*(engine: MyouEngine,
|
||||||
self.engine = engine
|
self.engine = engine
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
self.layer_count = layer_count
|
||||||
self.format = format
|
self.format = format
|
||||||
self.depth_type = depth_type
|
self.depth_type = depth_type
|
||||||
var tex_type = tex_type
|
var tex_type = tex_type
|
||||||
|
|
|
@ -491,22 +491,18 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
||||||
ubo_names.setLen 0
|
ubo_names.setLen 0
|
||||||
|
|
||||||
self.texture_locations.setLen 0
|
self.texture_locations.setLen 0
|
||||||
|
var extra_location_count = 0
|
||||||
for name in material.textures.keys:
|
for name in material.textures.keys:
|
||||||
self.texture_locations.add glGetUniformLocation(prog, name.cstring)
|
self.texture_locations.add glGetUniformLocation(prog, name.cstring)
|
||||||
if defined(myouEnsureTextureLocations) and not defined(release):
|
if defined(myouEnsureTextureLocations) and not defined(release):
|
||||||
assert self.texture_locations[^1] != -1, "invalid texture location for " & name
|
assert self.texture_locations[^1] != -1, "invalid texture location for " & name
|
||||||
|
|
||||||
self.shadowmap_locations.setLen 0
|
|
||||||
self.cubemap_locations.setLen 0
|
self.cubemap_locations.setLen 0
|
||||||
|
self.shadowmap_location = -1
|
||||||
if material.scene != nil:
|
if material.scene != nil:
|
||||||
for i in 0 ..< material.scene.max_shadow_maps:
|
self.shadowmap_location = glGetUniformLocation(prog, "shadow_maps".cstring)
|
||||||
# TODO: test with a single glUniform1iv instead of changing
|
if self.shadowmap_location != -1:
|
||||||
# the individual array elements
|
extra_location_count.inc
|
||||||
self.shadowmap_locations.add glGetUniformLocation(prog,
|
|
||||||
(&"shadow_maps[{i}]").cstring)
|
|
||||||
# TODO: do this when we know they're used
|
|
||||||
# when not defined(release):
|
|
||||||
# assert self.shadowmap_locations[^1] != -1
|
|
||||||
|
|
||||||
for i in 0 ..< material.scene.max_cubemaps:
|
for i in 0 ..< material.scene.max_cubemaps:
|
||||||
# TODO: test with a single glUniform1iv instead of changing
|
# TODO: test with a single glUniform1iv instead of changing
|
||||||
|
@ -519,7 +515,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
||||||
# dump self.cubemap_locations
|
# dump self.cubemap_locations
|
||||||
|
|
||||||
assert self.texture_locations.len + self.cubemap_locations.len +
|
assert self.texture_locations.len + self.cubemap_locations.len +
|
||||||
self.shadowmap_locations.len <= self.engine.renderer.max_textures
|
extra_location_count <= self.engine.renderer.max_textures
|
||||||
|
|
||||||
self.program = prog
|
self.program = prog
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ import ../objects/gameobject
|
||||||
# import ../objects/light
|
# import ../objects/light
|
||||||
import ../objects/mesh
|
import ../objects/mesh
|
||||||
import ../platform/platform
|
import ../platform/platform
|
||||||
|
import ../shadows/shadow_common
|
||||||
import ../quat
|
import ../quat
|
||||||
import ../scene
|
import ../scene
|
||||||
import ../util
|
import ../util
|
||||||
|
@ -209,7 +210,19 @@ proc draw_all*(self: RenderManager) =
|
||||||
if update_probe_ubo:
|
if update_probe_ubo:
|
||||||
scene.cubemap_UBO.update()
|
scene.cubemap_UBO.update()
|
||||||
|
|
||||||
# TODO: render shadows required by cameras of enabled screens
|
# TODO: we need to refactor all this loop that was originally just
|
||||||
|
# to update matrices
|
||||||
|
var update_shadow_ubo = false
|
||||||
|
for light in scene.lights:
|
||||||
|
if light.shadows.len != 0:
|
||||||
|
for s in light.shadows:
|
||||||
|
# TODO: also use UBO's logic for UpdateOnFirstUse
|
||||||
|
if s.auto_render and s.shadow_index != -1:
|
||||||
|
let u = s.renderShadow(scene.active_camera)
|
||||||
|
update_shadow_ubo = update_shadow_ubo or u
|
||||||
|
if update_shadow_ubo:
|
||||||
|
scene.shadow_maps_ubo.update()
|
||||||
|
|
||||||
for screen in self.engine.screens:
|
for screen in self.engine.screens:
|
||||||
if not screen.enabled:
|
if not screen.enabled:
|
||||||
continue
|
continue
|
||||||
|
@ -354,13 +367,12 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren
|
||||||
|
|
||||||
let scene = mesh.scene
|
let scene = mesh.scene
|
||||||
if scene != nil:
|
if scene != nil:
|
||||||
let shadow_maps = scene.shadow_maps
|
let loc = shader.shadowmap_location
|
||||||
for i,loc in shader.shadowmap_locations:
|
let fb = scene.shadow_maps
|
||||||
if i == shadow_maps.len:
|
if loc != -1 and fb != nil:
|
||||||
break
|
# TODO: allow access to both texture and depth_texure
|
||||||
if loc == -1:
|
let tex = when USE_SHADOW_SAMPLERS: fb.depth_texture
|
||||||
continue
|
else: fb.texture
|
||||||
let tex = shadow_maps[i]
|
|
||||||
if tex != nil:
|
if tex != nil:
|
||||||
textures_to_bind.add tex
|
textures_to_bind.add tex
|
||||||
texture_locations.add loc
|
texture_locations.add loc
|
||||||
|
|
|
@ -398,10 +398,6 @@ proc ensure_storage*(self: Texture) =
|
||||||
self.storage = newTextureStorage(self.tex_type, self.width, self.height, self.depth, self.format)
|
self.storage = newTextureStorage(self.tex_type, self.width, self.height, self.depth, self.format)
|
||||||
self.setFilter self.filter
|
self.setFilter self.filter
|
||||||
self.mipmap_range = (0, self.mipmapHigh)
|
self.mipmap_range = (0, self.mipmapHigh)
|
||||||
# TODO: toggle support for classic sampler2DShadow textures
|
|
||||||
# if self.format in [Depth_u16, Depth_u24, Depth_f32]:
|
|
||||||
# glTexParameteri(self.storage.target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE.GLint)
|
|
||||||
# glTexParameteri(self.storage.target, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL.GLint)
|
|
||||||
|
|
||||||
proc newTexture*(engine: MyouEngine, name: string,
|
proc newTexture*(engine: MyouEngine, name: string,
|
||||||
width, height: int; depth: int = 1,
|
width, height: int; depth: int = 1,
|
||||||
|
@ -570,3 +566,16 @@ func vec3size*(self: Texture, mip_level = -1): Vec3 =
|
||||||
vec3((max(1, self.width shr mip_level)).float32,
|
vec3((max(1, self.width shr mip_level)).float32,
|
||||||
(max(1, self.height shr mip_level)).float32,
|
(max(1, self.height shr mip_level)).float32,
|
||||||
(max(1, depth shr mip_level)).float32)
|
(max(1, depth shr mip_level)).float32)
|
||||||
|
|
||||||
|
proc set_texture_shadow*(self: Texture) =
|
||||||
|
self.bind_it(needs_active_texture=true)
|
||||||
|
if self.format in [Depth_u16, Depth_u24, Depth_f32]:
|
||||||
|
glTexParameteri(self.storage.target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE.GLint)
|
||||||
|
# TODO: would GL_LESS be better? That way depth 1.0 would always be not in shadow
|
||||||
|
# so we wouldn't need to clamp in shader
|
||||||
|
glTexParameteri(self.storage.target, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL.GLint)
|
||||||
|
|
||||||
|
proc unset_texture_shadow*(self: Texture) =
|
||||||
|
self.bind_it(needs_active_texture=true)
|
||||||
|
glTexParameteri(self.storage.target, GL_TEXTURE_COMPARE_MODE, GL_NONE.GLint)
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,9 @@ proc initUBO*[T](self: UBO, renderer: RenderManager, name: string, utype: typede
|
||||||
|
|
||||||
template storage*[T](self: UBO, utype: typedesc[T]): ArrRef[T] = self.byte_storage.to(T)
|
template storage*[T](self: UBO, utype: typedesc[T]): ArrRef[T] = self.byte_storage.to(T)
|
||||||
|
|
||||||
|
proc len*[T](self: UBO, utype: typedesc[T]): int =
|
||||||
|
self.byte_storage.byte_len div sizeof(T)
|
||||||
|
|
||||||
# NOTE: does not preserve contents
|
# NOTE: does not preserve contents
|
||||||
proc resize*[T](self: UBO, utype: typedesc[T], count: int) =
|
proc resize*[T](self: UBO, utype: typedesc[T], count: int) =
|
||||||
if count == self.storage(T).len:
|
if count == self.storage(T).len:
|
||||||
|
|
|
@ -367,7 +367,7 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
|
||||||
PointLight
|
PointLight
|
||||||
let color = vec3(data.r.f32[0], data.g.f32[0], data.b.f32[0])
|
let color = vec3(data.r.f32[0], data.g.f32[0], data.b.f32[0])
|
||||||
let mode = data.mode.i32[0]
|
let mode = data.mode.i32[0]
|
||||||
# let use_shadow = mode.testBit(0)
|
let use_shadow = mode.testBit(0)
|
||||||
let use_dist = mode.testBit(20)
|
let use_dist = mode.testBit(20)
|
||||||
let cutoff = if use_dist: data.att_dist.f32[0] else: 0'f32
|
let cutoff = if use_dist: data.att_dist.f32[0] else: 0'f32
|
||||||
var ob = self.engine.new_light(
|
var ob = self.engine.new_light(
|
||||||
|
@ -377,7 +377,7 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
|
||||||
energy = data.energy.f32[0],
|
energy = data.energy.f32[0],
|
||||||
spot_size = data.spotsize.f32[0],
|
spot_size = data.spotsize.f32[0],
|
||||||
spot_blend = data.spotblend.f32[0],
|
spot_blend = data.spotblend.f32[0],
|
||||||
# use_shadow = use_shadow,
|
use_shadow = use_shadow,
|
||||||
cutoff_distance = cutoff,
|
cutoff_distance = cutoff,
|
||||||
diffuse_factor = data.diff_fac.f32[0],
|
diffuse_factor = data.diff_fac.f32[0],
|
||||||
specular_factor = data.spec_fac.f32[0],
|
specular_factor = data.spec_fac.f32[0],
|
||||||
|
@ -560,6 +560,7 @@ proc loadSceneImpl(self: BlendLoader, scn: FNode, scene: Scene) =
|
||||||
# dump (k, nodes[i].mat[0].id.name.str, nodes[i].data.mat[0].id.name.str)
|
# dump (k, nodes[i].mat[0].id.name.str, nodes[i].data.mat[0].id.name.str)
|
||||||
# echo nodes[i]
|
# echo nodes[i]
|
||||||
i += 1
|
i += 1
|
||||||
|
scene.reorder_children()
|
||||||
|
|
||||||
if update_light_counts:
|
if update_light_counts:
|
||||||
scene.calculate_max_lights_and_cubemaps()
|
scene.calculate_max_lights_and_cubemaps()
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
import ../types
|
import ../types
|
||||||
import vmath except Quat
|
import vmath except Quat
|
||||||
|
import ../util
|
||||||
|
|
||||||
when defined(nimdoc):
|
when defined(nimdoc):
|
||||||
type TYPES* = LightType | Light
|
type TYPES* = LightType | Light
|
||||||
|
@ -48,6 +49,7 @@ proc newLight*(engine: MyouEngine, name: string="",
|
||||||
light_radius: float32 = 0.01,
|
light_radius: float32 = 0.01,
|
||||||
spot_size: float32 = 1.3,
|
spot_size: float32 = 1.3,
|
||||||
spot_blend: float32 = 0.15,
|
spot_blend: float32 = 0.15,
|
||||||
|
use_shadow = false,
|
||||||
): Light
|
): Light
|
||||||
proc instance_physics*(this: Light)
|
proc instance_physics*(this: Light)
|
||||||
|
|
||||||
|
@ -62,7 +64,11 @@ method get_light*(this: Light): Light =
|
||||||
# End forward declarations and ob type methods
|
# End forward declarations and ob type methods
|
||||||
|
|
||||||
import ./gameobject
|
import ./gameobject
|
||||||
|
import ./mesh
|
||||||
import ../scene
|
import ../scene
|
||||||
|
import ../shadows/shadow_common
|
||||||
|
import ../shadows/simple_shadow
|
||||||
|
import quickhull
|
||||||
|
|
||||||
proc newLight*(engine: MyouEngine, name: string="",
|
proc newLight*(engine: MyouEngine, name: string="",
|
||||||
scene: Scene = nil,
|
scene: Scene = nil,
|
||||||
|
@ -75,6 +81,7 @@ proc newLight*(engine: MyouEngine, name: string="",
|
||||||
light_radius: float32 = 0.01,
|
light_radius: float32 = 0.01,
|
||||||
spot_size: float32 = 1.3,
|
spot_size: float32 = 1.3,
|
||||||
spot_blend: float32 = 0.15,
|
spot_blend: float32 = 0.15,
|
||||||
|
use_shadow = false,
|
||||||
): Light =
|
): Light =
|
||||||
var this = new Light
|
var this = new Light
|
||||||
discard procCall(this.GameObject.initGameObject(engine, name))
|
discard procCall(this.GameObject.initGameObject(engine, name))
|
||||||
|
@ -88,10 +95,81 @@ proc newLight*(engine: MyouEngine, name: string="",
|
||||||
this.cutoff_distance = cutoff_distance
|
this.cutoff_distance = cutoff_distance
|
||||||
this.diffuse_factor = diffuse_factor
|
this.diffuse_factor = diffuse_factor
|
||||||
this.specular_factor = specular_factor
|
this.specular_factor = specular_factor
|
||||||
|
this.use_shadow = use_shadow
|
||||||
if scene != nil:
|
if scene != nil:
|
||||||
scene.add_object(this, name=name)
|
scene.add_object(this, name=name)
|
||||||
return this
|
return this
|
||||||
|
|
||||||
|
proc configure_shadow*(self: Light,
|
||||||
|
camera: Camera = nil,
|
||||||
|
max_distance: float32 = 0.0,
|
||||||
|
objects = self.scene.children) =
|
||||||
|
# Configure a shadow for this light object, either by following a camera
|
||||||
|
# passed as argument, or a static shadow for the specified objects.
|
||||||
|
#
|
||||||
|
# `max_distance` is only used when `camera` is supplied.
|
||||||
|
#
|
||||||
|
# `objects` is only used in static mode (when `camera` is nil). In this
|
||||||
|
# mode, the shadow map will be tailored for the size of the bounding boxes
|
||||||
|
# of the objects. It will exclude flat objects (planes) that are under every
|
||||||
|
# other object because they can't project a shadow to themselves.
|
||||||
|
for s in self.shadows:
|
||||||
|
s.destroy()
|
||||||
|
self.shadows.setLen 0
|
||||||
|
|
||||||
|
if camera == nil:
|
||||||
|
# Static shadow strategy (ideal for small scenes):
|
||||||
|
# For casting shadows, include bounds for all visible meshes
|
||||||
|
# except for ground planes.
|
||||||
|
|
||||||
|
# TODO: when a ground plane is detected, if light Z is positive
|
||||||
|
# (pointing upwards) the shadow map should just be cleared with a depth
|
||||||
|
# of -1.0 (all in shadow); or render it with some offset, or allow the
|
||||||
|
# detection of ground planes with some amount of thickness to prevent
|
||||||
|
# light bleeding.
|
||||||
|
|
||||||
|
# TODO: instead of a ground plane, we can just exclude the largest
|
||||||
|
# convex object, as long as we can clamp the polygons outside the
|
||||||
|
# frustum. Can we avoid clipping by setting W?
|
||||||
|
self.scene.update_all_matrices()
|
||||||
|
var casters: seq[Vec3]
|
||||||
|
for ob in self.scene.children:
|
||||||
|
if not (ob.is_mesh and ob.visible):
|
||||||
|
continue
|
||||||
|
echo "adding ", ob.name, " otype ", ob.otype
|
||||||
|
# discard ob.get_world_matrix
|
||||||
|
let me = ob.get_mesh
|
||||||
|
let bb = me.bound_box
|
||||||
|
let world_dim = (ob.world_matrix * vec4(bb[1] - bb[0], 0.0)).xyz
|
||||||
|
var casts = true
|
||||||
|
if world_dim.z < 0.00001:
|
||||||
|
# Possible flat floor plane,
|
||||||
|
# check if all meshes have their centers above it
|
||||||
|
when defined(myouDebugShadows):
|
||||||
|
echo "Floor plane detected: ", ob.name
|
||||||
|
var all_above = true
|
||||||
|
let z = ob.world_center.z - 0.00001
|
||||||
|
for ob2 in self.scene.children:
|
||||||
|
if ob2.is_mesh and ob2.visible and ob2.world_center.z < z:
|
||||||
|
when defined(myouDebugShadows):
|
||||||
|
echo " Object is below floor plane: ", ob2.name
|
||||||
|
all_above = false
|
||||||
|
break
|
||||||
|
casts = not all_above
|
||||||
|
if casts:
|
||||||
|
for v in box_corners(bb):
|
||||||
|
casters.add ob.world_matrix * v
|
||||||
|
|
||||||
|
casters = quickhull_points(casters)
|
||||||
|
let shadow = newSimpleShadowManager(self, use_camera = false)
|
||||||
|
shadow.caster_bounding_points = casters
|
||||||
|
else:
|
||||||
|
# TODO: Use a sphere as bounds.
|
||||||
|
# TODO: Have a mode where the sphere surrounds all possible orientations
|
||||||
|
# of the camera and some amount of translation.
|
||||||
|
newSimpleShadowManager(self, use_camera = true, far = max_distance)
|
||||||
|
|
||||||
|
|
||||||
proc instance_physics*(this: Light) =
|
proc instance_physics*(this: Light) =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
|
@ -605,22 +605,16 @@ proc update_bounding_box*(self: Mesh) =
|
||||||
raise newException(Defect, "not implemented")
|
raise newException(Defect, "not implemented")
|
||||||
let varray = self.data.varrays[0]
|
let varray = self.data.varrays[0]
|
||||||
let stride = self.data.stride div 4
|
let stride = self.data.stride div 4
|
||||||
var minx, miny, minz = Inf.float32
|
var minv = Vec3.high
|
||||||
var maxx, maxy, maxz = -Inf.float32
|
var maxv = Vec3.low
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < varray.len:
|
while i < varray.len:
|
||||||
var v = varray[i]
|
var v = cast[ptr Vec3](varray[i].addr)
|
||||||
minx = min(minx, v)
|
minv = min(minv, v[])
|
||||||
maxx = max(maxx, v)
|
maxv = max(maxv, v[])
|
||||||
v = varray[i + 1]
|
|
||||||
miny = min(miny, v)
|
|
||||||
maxy = max(maxy, v)
|
|
||||||
v = varray[i + 2]
|
|
||||||
minz = min(minz, v)
|
|
||||||
maxz = max(maxz, v)
|
|
||||||
i += stride
|
i += stride
|
||||||
self.bound_box[0] = vec3(minx, miny, minz)
|
self.bound_box = (minv, maxv)
|
||||||
self.bound_box[1] = vec3(maxx, maxy, maxz)
|
self.center = mix(minv, maxv, 0.5)
|
||||||
|
|
||||||
proc debug_print_vertices*(self: Mesh, starts: int = 0, ends: int = 10) =
|
proc debug_print_vertices*(self: Mesh, starts: int = 0, ends: int = 10) =
|
||||||
if self.data.varrays[0].len == 0:
|
if self.data.varrays[0].len == 0:
|
||||||
|
|
|
@ -73,6 +73,7 @@ import std/strutils
|
||||||
import std/tables
|
import std/tables
|
||||||
import ./graphics/framebuffer
|
import ./graphics/framebuffer
|
||||||
import ./graphics/material
|
import ./graphics/material
|
||||||
|
import ./graphics/texture
|
||||||
import ./graphics/ubo
|
import ./graphics/ubo
|
||||||
import ./incomplete
|
import ./incomplete
|
||||||
import ./objects/camera
|
import ./objects/camera
|
||||||
|
@ -80,7 +81,7 @@ import ./objects/cubemap_probe
|
||||||
import ./objects/gameobject
|
import ./objects/gameobject
|
||||||
import ./objects/light
|
import ./objects/light
|
||||||
import ./objects/mesh
|
import ./objects/mesh
|
||||||
import ./shadows/simple_shadow
|
import ./shadows/shadow_common
|
||||||
import ./screen
|
import ./screen
|
||||||
import ./util
|
import ./util
|
||||||
|
|
||||||
|
@ -100,6 +101,7 @@ proc initScene*(self: Scene, engine: MyouEngine, name: string = "Scene",
|
||||||
self.world = newWorld(self)
|
self.world = newWorld(self)
|
||||||
self.background_color = vec4(0, 0, 0, 1)
|
self.background_color = vec4(0, 0, 0, 1)
|
||||||
self.cubemap_resolution = 128
|
self.cubemap_resolution = 128
|
||||||
|
self.shadow_map_resolution = 1024
|
||||||
self.max_point_lights = 8
|
self.max_point_lights = 8
|
||||||
self.max_sun_lights = 1
|
self.max_sun_lights = 1
|
||||||
self.max_spot_lights = 0
|
self.max_spot_lights = 0
|
||||||
|
@ -331,24 +333,26 @@ proc update_all_matrices*(self: Scene) =
|
||||||
# if c.visible:
|
# if c.visible:
|
||||||
# ob.recalculate_bone_matrices()
|
# ob.recalculate_bone_matrices()
|
||||||
# break
|
# break
|
||||||
# TODO: why can't I mutate in for loops???
|
for ob in self.auto_updated_children:
|
||||||
for i in 0 .. self.auto_updated_children.high:
|
ob.update_matrices()
|
||||||
self.auto_updated_children[i].update_matrices()
|
|
||||||
return
|
|
||||||
|
|
||||||
proc set_objects_auto_update_matrix*(self: Scene, objects: seq[GameObject], auto_update: bool) =
|
proc set_objects_auto_update_matrix*(self: Scene, objects: seq[GameObject], auto_update: bool) =
|
||||||
for ob in objects:
|
for ob in objects:
|
||||||
ob.auto_update_matrix = auto_update
|
ob.auto_update_matrix = auto_update
|
||||||
self.children_are_ordered = false
|
self.children_are_ordered = false
|
||||||
return
|
|
||||||
|
|
||||||
proc destroy*(self: Scene) =
|
proc destroy*(self: Scene) =
|
||||||
for ob in reversed(self.children):
|
for ob in reversed(self.children):
|
||||||
var ob = ob
|
var ob = ob
|
||||||
ob.destroy(recursive=false)
|
ob.destroy(recursive=false)
|
||||||
self.world.destroy()
|
self.world.destroy()
|
||||||
|
for mat in self.materials.values:
|
||||||
|
mat.destroy()
|
||||||
|
if self.shadow_maps != nil:
|
||||||
|
self.shadow_maps.destroy()
|
||||||
|
for ubo in self.lighting_UBOs:
|
||||||
|
ubo.destroy()
|
||||||
self.engine.scenes.del(self.name)
|
self.engine.scenes.del(self.name)
|
||||||
return
|
|
||||||
|
|
||||||
proc enable_render*(self: Scene) =
|
proc enable_render*(self: Scene) =
|
||||||
self.enabled = true
|
self.enabled = true
|
||||||
|
@ -401,8 +405,8 @@ proc calculate_max_lights_and_cubemaps*(self: Scene) =
|
||||||
of SunLight: self.max_sun_lights += 1
|
of SunLight: self.max_sun_lights += 1
|
||||||
of SpotLight: self.max_spot_lights += 1
|
of SpotLight: self.max_spot_lights += 1
|
||||||
of AreaLight: self.max_area_lights += 1
|
of AreaLight: self.max_area_lights += 1
|
||||||
if light.shadows.len != 0:
|
if self.shadow_maps != nil:
|
||||||
self.max_shadow_maps += 1
|
self.max_shadow_maps = self.shadow_maps.layer_count.int32
|
||||||
self.max_cubemaps = self.cubemap_probes.len.int32
|
self.max_cubemaps = self.cubemap_probes.len.int32
|
||||||
if self.world_material != nil:
|
if self.world_material != nil:
|
||||||
self.max_cubemaps += 1
|
self.max_cubemaps += 1
|
||||||
|
@ -416,8 +420,6 @@ proc calculate_max_lights_and_cubemaps*(self: Scene) =
|
||||||
echo &"calculated max lights {self.max_point_lights} {self.max_sun_lights}"
|
echo &"calculated max lights {self.max_point_lights} {self.max_sun_lights}"
|
||||||
echo &"calculated max shadow maps {self.max_shadow_maps}"
|
echo &"calculated max shadow maps {self.max_shadow_maps}"
|
||||||
echo &"calculated max cubemaps {self.max_cubemaps}"
|
echo &"calculated max cubemaps {self.max_cubemaps}"
|
||||||
# TODO: rethink how shadow maps are handled
|
|
||||||
self.shadow_maps.setLen self.max_shadow_maps
|
|
||||||
|
|
||||||
proc get_lighting_UBOs*(self: Scene): seq[UBO] =
|
proc get_lighting_UBOs*(self: Scene): seq[UBO] =
|
||||||
# echo &"getting UBOs {self.max_point_lights} {self.max_sun_lights}"
|
# echo &"getting UBOs {self.max_point_lights} {self.max_sun_lights}"
|
||||||
|
@ -456,7 +458,7 @@ proc get_lighting_code_defines*(self: Scene): seq[string] =
|
||||||
"#define MAX_SHADOW_MAPS " & $(if self.isNil: 0 else: self.max_shadow_maps),
|
"#define MAX_SHADOW_MAPS " & $(if self.isNil: 0 else: self.max_shadow_maps),
|
||||||
"#define MAX_CUBE_MAPS " & $(if self.isNil: 0 else: self.max_cubemaps),
|
"#define MAX_CUBE_MAPS " & $(if self.isNil: 0 else: self.max_cubemaps),
|
||||||
"#define MAX_SPHERICAL_HARMONICS " & $(if self.isNil: 0 else: self.max_spherical_harmonics),
|
"#define MAX_SPHERICAL_HARMONICS " & $(if self.isNil: 0 else: self.max_spherical_harmonics),
|
||||||
"#define USE_SHADOW_SAMPLERS 0",
|
"#define USE_SHADOW_SAMPLERS " & $(USE_SHADOW_SAMPLERS.int),
|
||||||
]
|
]
|
||||||
|
|
||||||
proc get_lighting_code*(self: Scene): string =
|
proc get_lighting_code*(self: Scene): string =
|
||||||
|
@ -468,6 +470,7 @@ proc get_lighting_code*(self: Scene): string =
|
||||||
#define light_specular_power powers.g
|
#define light_specular_power powers.g
|
||||||
#define light_inv_squared_cutoff powers.a
|
#define light_inv_squared_cutoff powers.a
|
||||||
#define light_direction dir.xyz
|
#define light_direction dir.xyz
|
||||||
|
#define light_shadow_index color.w
|
||||||
struct PointLight {
|
struct PointLight {
|
||||||
vec4 pos;
|
vec4 pos;
|
||||||
vec4 color;
|
vec4 color;
|
||||||
|
@ -491,16 +494,17 @@ proc get_lighting_code*(self: Scene): string =
|
||||||
struct ShadowMapInfo {
|
struct ShadowMapInfo {
|
||||||
mat4 depth_matrix;
|
mat4 depth_matrix;
|
||||||
float size;
|
float size;
|
||||||
float pad0, pad1, pad2;
|
float bias;
|
||||||
|
float pad0, pad1;
|
||||||
};
|
};
|
||||||
#if MAX_SHADOW_MAPS
|
#if MAX_SHADOW_MAPS
|
||||||
layout(std140) uniform ShadowMapInfos {
|
layout(std140) uniform ShadowMapInfos {
|
||||||
ShadowMapInfo shadow_map_infos[MAX_SHADOW_MAPS];
|
ShadowMapInfo shadow_map_infos[MAX_SHADOW_MAPS];
|
||||||
};
|
};
|
||||||
#if USE_SHADOW_SAMPLERS
|
#if USE_SHADOW_SAMPLERS
|
||||||
uniform sampler2DShadow shadow_maps[MAX_SHADOW_MAPS];
|
uniform sampler2DArrayShadow shadow_maps;
|
||||||
#else
|
#else
|
||||||
uniform sampler2D shadow_maps[MAX_SHADOW_MAPS];
|
uniform sampler2DArray shadow_maps;
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
struct CubemapInfo {
|
struct CubemapInfo {
|
||||||
|
@ -563,11 +567,17 @@ proc update_lights*(self: Scene) =
|
||||||
point += 1
|
point += 1
|
||||||
of SunLight:
|
of SunLight:
|
||||||
if sun < self.max_sun_lights:
|
if sun < self.max_sun_lights:
|
||||||
|
let shadow_index = if ob.shadows.len != 0:
|
||||||
|
# TODO: Allow more than 1 shadow per light
|
||||||
|
ob.shadows[0].shadow_index
|
||||||
|
else:
|
||||||
|
-1
|
||||||
self.sun_light_UBO.storage(SunLightUniform)[sun] = SunLightUniform(
|
self.sun_light_UBO.storage(SunLightUniform)[sun] = SunLightUniform(
|
||||||
direction: ob.get_world_Z_vector.normalize,
|
direction: ob.get_world_Z_vector.normalize,
|
||||||
color: ob.color.xyz,
|
color: ob.color.xyz,
|
||||||
diffuse_power: ob.energy * ob.diffuse_factor,
|
diffuse_power: ob.energy * ob.diffuse_factor,
|
||||||
specular_power: ob.energy * ob.specular_factor,
|
specular_power: ob.energy * ob.specular_factor,
|
||||||
|
shadow_index: shadow_index.float32,
|
||||||
)
|
)
|
||||||
sun += 1
|
sun += 1
|
||||||
of SpotLight:
|
of SpotLight:
|
||||||
|
|
|
@ -275,9 +275,11 @@ vec4 principled_node(vec3 base, vec3 I, vec3 normal, vec3 pos, float metallic, f
|
||||||
vec3 spec = D * G * spec_common / dotNL;
|
vec3 spec = D * G * spec_common / dotNL;
|
||||||
spec *= ld.light_specular_power;
|
spec *= ld.light_specular_power;
|
||||||
vec3 dif = diffuse * diffuse_sun_light(ld.light_diffuse_power);
|
vec3 dif = diffuse * diffuse_sun_light(ld.light_diffuse_power);
|
||||||
float shadow = get_shadow(pos, 0);
|
float shadow = 1.0;
|
||||||
|
if(ld.light_shadow_index >= 0.0){
|
||||||
|
shadow = get_shadow(pos, normal, ld.light_shadow_index);
|
||||||
|
}
|
||||||
color += dotNL * (dif + spec) * ld.color.rgb * shadow;
|
color += dotNL * (dif + spec) * ld.color.rgb * shadow;
|
||||||
// color += debug_shadow(pos);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// NOTE TODO: these factors are fake, I'm still figuring out how to make
|
// NOTE TODO: these factors are fake, I'm still figuring out how to make
|
||||||
|
|
61
src/shadows/shadow_common.nim
Normal file
61
src/shadows/shadow_common.nim
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
|
||||||
|
import ../types
|
||||||
|
import ../graphics/framebuffer
|
||||||
|
import ../graphics/texture
|
||||||
|
import ../scene
|
||||||
|
|
||||||
|
const USE_SHADOW_SAMPLERS* = false
|
||||||
|
|
||||||
|
when defined(nimdoc):
|
||||||
|
type TYPES* = ShadowManager | ShadowMapUniform
|
||||||
|
|
||||||
|
method renderShadow*(self: ShadowManager, camera: Camera): bool {.base,locks:"unknown".} =
|
||||||
|
raise Defect.newException "Not implemented"
|
||||||
|
method destroy*(self: ShadowManager) {.base,locks:"unknown".} =
|
||||||
|
raise Defect.newException "Not implemented"
|
||||||
|
|
||||||
|
|
||||||
|
# TODO! Call this automatically before render after one or more shadows have
|
||||||
|
# been configured.
|
||||||
|
proc updateShadowStorage*(scene: Scene) =
|
||||||
|
## Creates or updates the storage for all shadow maps of the scene. It must
|
||||||
|
## be called after `configure_shadow` and before render.
|
||||||
|
var count = 0'i32
|
||||||
|
var color_channels = 0
|
||||||
|
var uses_depth = false
|
||||||
|
|
||||||
|
for light in scene.lights:
|
||||||
|
for s in light.shadows:
|
||||||
|
s.shadow_index = count
|
||||||
|
color_channels = max(color_channels, s.uses_color_channels)
|
||||||
|
uses_depth = uses_depth or s.uses_depth
|
||||||
|
count += s.required_texture_count
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
let fb_size = scene.shadow_map_resolution
|
||||||
|
assert color_channels <= 4
|
||||||
|
if scene.shadow_maps != nil:
|
||||||
|
scene.shadow_maps.destroy()
|
||||||
|
scene.shadow_maps = scene.engine.newFramebuffer(fb_size, fb_size,
|
||||||
|
if color_channels != 0:
|
||||||
|
(R_f16.int - 1 + color_channels).TextureFormat
|
||||||
|
else:
|
||||||
|
R_u8, # TODO: make fb without color attachment
|
||||||
|
if uses_depth: DepthTexture else: DepthRenderBuffer,
|
||||||
|
filter = Linear,
|
||||||
|
tex_type = if color_channels != 0: Tex2DArray else: Tex2D,
|
||||||
|
depth_tex_type = if uses_depth: Tex2DArray else: Tex2D,
|
||||||
|
layer_count = count)
|
||||||
|
|
||||||
|
scene.shadow_maps.texture.setExtrapolation Clamp
|
||||||
|
if uses_depth:
|
||||||
|
set_texture_shadow(scene.shadow_maps.depth_texture)
|
||||||
|
|
||||||
|
# TODO: being able to use set_texture_shadow on color when needed
|
||||||
|
|
||||||
|
# TODO: separate logic for each type of UBO?
|
||||||
|
scene.calculate_max_lights_and_cubemaps()
|
||||||
|
|
|
@ -1,28 +1,32 @@
|
||||||
|
|
||||||
|
|
||||||
|
float get_shadow(vec3 position, vec3 normal, float findex) {
|
||||||
#if MAX_SHADOW_MAPS
|
#if MAX_SHADOW_MAPS
|
||||||
float get_shadow(vec3 position, int index) {
|
ShadowMapInfo info = shadow_map_infos[int(findex)];
|
||||||
vec3 co = vec3(shadow_map_infos[0].depth_matrix * vec4(position, 1.0));
|
float px_size = 1.5/info.size;
|
||||||
if(co.x < 0.0 || co.x >= 1.0 || co.y < 0.0 || co.y >= 1.0) return 1.0;
|
vec3 p = position + normal * info.bias * 2.0;
|
||||||
|
vec3 co = vec3(info.depth_matrix * vec4(p, 1.0));
|
||||||
|
if(co.x <= 0.0 || co.x >= 0.999999 || co.y <= 0.0 || co.y >= 0.999999) return 1.0;
|
||||||
co.z = min(co.z, 0.999999);
|
co.z = min(co.z, 0.999999);
|
||||||
#if USE_SHADOW_SAMPLERS
|
|
||||||
return texture(shadow_maps[0], co);
|
|
||||||
#else
|
|
||||||
// vec2 co2 = co.xy * shadow_map_infos[0].size;
|
|
||||||
// vec2 co20 = floor(co2);
|
|
||||||
// ivec2 ico = vec2(co20);
|
|
||||||
// return mix(
|
|
||||||
// texelFetch(shadow_maps[0], )
|
|
||||||
// )
|
|
||||||
float px_size = 2.0/shadow_map_infos[0].size;
|
|
||||||
float count = 0.0;
|
float count = 0.0;
|
||||||
float vis = 0.0;
|
float vis = 0.0;
|
||||||
|
// TODO: better and faster filtering by using radial samples
|
||||||
|
// and testing outer ring first
|
||||||
|
// return texture(shadow_maps, vec4(co.xy, findex, co.z)).r;
|
||||||
|
// return texture(shadow_maps, vec3(co.xy,findex)).r;
|
||||||
for(float y=co.y-px_size;y<=co.y+px_size;y+=px_size*0.499){
|
for(float y=co.y-px_size;y<=co.y+px_size;y+=px_size*0.499){
|
||||||
for(float x=co.x-px_size;x<=co.x+px_size;x+=px_size*0.499){
|
for(float x=co.x-px_size;x<=co.x+px_size;x+=px_size*0.499){
|
||||||
vis += texture(shadow_maps[0], vec2(x,y)).r <= co.z?0.0:1.0;
|
#if USE_SHADOW_SAMPLERS
|
||||||
|
vis += texture(shadow_maps, vec4(x,y, findex, co.z));
|
||||||
|
#else
|
||||||
|
vis += texture(shadow_maps, vec3(x,y,findex)).r <= co.z?0.0:1.0;
|
||||||
|
#endif // USE_SHADOW_SAMPLERS
|
||||||
count += 1.0;
|
count += 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return mix(vis/count, 1.0, 0.14);
|
|
||||||
return vis/count;
|
return vis/count;
|
||||||
#endif
|
#else
|
||||||
|
return 1.0;
|
||||||
|
#endif // MAX_SHADOW_MAPS
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
@ -36,83 +36,77 @@ import vmath except Quat
|
||||||
import ../graphics/framebuffer
|
import ../graphics/framebuffer
|
||||||
import ../graphics/material
|
import ../graphics/material
|
||||||
import ../graphics/render
|
import ../graphics/render
|
||||||
import ../graphics/texture
|
|
||||||
import ../graphics/ubo
|
import ../graphics/ubo
|
||||||
# import ../objects/light
|
# import ../objects/light
|
||||||
import ../objects/mesh
|
import ../objects/mesh
|
||||||
|
import ../objects/gameobject
|
||||||
import ../quat
|
import ../quat
|
||||||
import ../scene
|
import ../scene
|
||||||
import ../util
|
import ../util
|
||||||
|
import ./shadow_common
|
||||||
# TODO: Move these base methods
|
|
||||||
method renderShadow*(self: ShadowManager, camera: Camera) {.base.} =
|
|
||||||
discard
|
|
||||||
method destroy*(self: ShadowManager) {.base.} =
|
|
||||||
discard
|
|
||||||
|
|
||||||
when defined(nimdoc):
|
when defined(nimdoc):
|
||||||
# TODO: ShadowMapUniform goes elsewhere
|
type TYPES* = SimpleShadowManager
|
||||||
type TYPES* = SimpleShadowManager | ShadowMapUniform
|
|
||||||
|
|
||||||
# Z depth is not modified, only X and Y for UVs
|
when USE_SHADOW_SAMPLERS:
|
||||||
const LIGHT_PROJ_TO_DEPTH = mat4(0.5, 0, 0, 0, 0, 0.5, 0, 0,
|
const LIGHT_PROJ_TO_DEPTH = mat4(0.5, 0, 0, 0, 0, 0.5, 0, 0,
|
||||||
0, 0, 1, 0, 0.5, 0.5, 0, 1)
|
0, 0, 0.5, 0, 0.5, 0.5, 0.5, 1)
|
||||||
|
else:
|
||||||
|
# Z depth is not modified, only X and Y for UVs
|
||||||
|
const LIGHT_PROJ_TO_DEPTH = mat4(0.5, 0, 0, 0, 0, 0.5, 0, 0,
|
||||||
|
0, 0, 1, 0, 0.5, 0.5, 0, 1)
|
||||||
|
|
||||||
when not defined(release):
|
when defined(myouDebugShadows):
|
||||||
var debug_mesh: Mesh
|
var debug_mesh: Mesh
|
||||||
|
|
||||||
proc newSimpleShadowManager*(light: Light; fb_size: int;
|
proc newSimpleShadowManager*(light: Light;
|
||||||
|
use_camera: bool;
|
||||||
near, far: float32 = 0.0;
|
near, far: float32 = 0.0;
|
||||||
bias: float32 = 0.001;
|
): SimpleShadowManager {.discardable.} =
|
||||||
): SimpleShadowManager =
|
|
||||||
|
|
||||||
new(result)
|
new(result)
|
||||||
result.engine = light.engine
|
result.engine = light.engine
|
||||||
result.light = light
|
result.light = light
|
||||||
result.depth_range = (near, far)
|
result.depth_range = (near, far)
|
||||||
result.bias = bias
|
|
||||||
result.auto_render = true
|
result.auto_render = true
|
||||||
|
result.required_texture_count = 1
|
||||||
|
result.shadow_index = -1
|
||||||
|
result.use_camera = use_camera
|
||||||
light.shadows.add result
|
light.shadows.add result
|
||||||
let self = result
|
let self = result
|
||||||
result.engine.renderer.enqueue proc()=
|
result.engine.renderer.enqueue proc()=
|
||||||
# TODO: allow configuring use of a depth-only pass with this code
|
when USE_SHADOW_SAMPLERS:
|
||||||
# and with sampler2DShadow support in texture.nim
|
# depth-only pass
|
||||||
|
self.material = light.engine.newMaterial("simple_shadow", nil,
|
||||||
|
fragment=dedent "",
|
||||||
|
)
|
||||||
|
# TODO: Disable color texture
|
||||||
|
self.uses_color_channels = 0
|
||||||
|
self.uses_depth = true
|
||||||
|
else:
|
||||||
|
self.material = light.engine.newMaterial("simple_shadow", nil,
|
||||||
|
fragment=dedent """
|
||||||
|
in vec4 ppos;
|
||||||
|
out float color;
|
||||||
|
void main(){
|
||||||
|
color = ppos.z/ppos.w;
|
||||||
|
}""",
|
||||||
|
varyings = @[Varying(vtype: ProjPosition, varname: "ppos")]
|
||||||
|
)
|
||||||
|
self.uses_color_channels = 1
|
||||||
|
self.uses_depth = false
|
||||||
|
|
||||||
# self.material = light.engine.newMaterial("simple_shadow", nil,
|
when defined(myouDebugShadows):
|
||||||
# fragment="",
|
|
||||||
# )
|
|
||||||
# self.framebuffer = light.engine.newFramebuffer(fb_size, fb_size,
|
|
||||||
# RGB_u8, DepthTexture, depth_format = Depth_u16, filter=Linear)
|
|
||||||
# self.texture = self.framebuffer.depth_texture
|
|
||||||
|
|
||||||
self.material = light.engine.newMaterial("simple_shadow", nil,
|
|
||||||
fragment=dedent """
|
|
||||||
in vec4 ppos;
|
|
||||||
out float color;
|
|
||||||
void main(){
|
|
||||||
color = ppos.z/ppos.w;
|
|
||||||
}""",
|
|
||||||
varyings = @[Varying(vtype: ProjPosition, varname: "ppos")]
|
|
||||||
)
|
|
||||||
self.framebuffer = light.engine.newFramebuffer(fb_size, fb_size,
|
|
||||||
R_f16, DepthRenderBuffer, filter=Linear)
|
|
||||||
self.texture = self.framebuffer.texture
|
|
||||||
self.texture.setExtrapolation Clamp
|
|
||||||
when not defined(release):
|
|
||||||
debug_mesh = self.engine.newMesh(vertex_count = 100, draw_method = Lines)
|
debug_mesh = self.engine.newMesh(vertex_count = 100, draw_method = Lines)
|
||||||
debug_mesh.materials.add self.engine.newSolidMaterial("white", vec4(0,1,0,1))
|
debug_mesh.materials.add self.engine.newSolidMaterial("green", vec4(0,1,0,1))
|
||||||
debug_mesh.visible = false
|
debug_mesh.visible = false
|
||||||
# TODO: why the FUCK do I need this here?
|
light.scene.add_object debug_mesh
|
||||||
self.engine.renderer.enqueue proc()=
|
|
||||||
light.scene.add_object debug_mesh
|
|
||||||
|
|
||||||
method destroy*(self: SimpleShadowManager) =
|
method destroy*(self: SimpleShadowManager) {.locks:"unknown".} =
|
||||||
if self.material != nil:
|
if self.material != nil:
|
||||||
self.material.destroy()
|
self.material.destroy()
|
||||||
if self.framebuffer != nil:
|
|
||||||
self.framebuffer.destroy()
|
|
||||||
|
|
||||||
when not defined(release):
|
when defined(myouDebugShadows):
|
||||||
proc show_mat(mat: Mat4) =
|
proc show_mat(mat: Mat4) =
|
||||||
var points = newSeqOfCap[Vec3](8)
|
var points = newSeqOfCap[Vec3](8)
|
||||||
for z in [-1'f32,1'f32]:
|
for z in [-1'f32,1'f32]:
|
||||||
|
@ -125,9 +119,64 @@ when not defined(release):
|
||||||
debug_mesh.data.update_varray
|
debug_mesh.data.update_varray
|
||||||
debug_mesh.visible = true
|
debug_mesh.visible = true
|
||||||
|
|
||||||
method renderShadow*(self: SimpleShadowManager, scene: Scene, bounding_points: seq[Vec3], min_z: float32, index: int)
|
proc renderShadow*(self: SimpleShadowManager, scene: Scene,
|
||||||
|
bounding_points: seq[Vec3],
|
||||||
|
sphere_center: Vec3 = vec3(0),
|
||||||
|
sphere_radius: float32 = 0,
|
||||||
|
min_z: float32 = 0) =
|
||||||
|
# Find the projection matrix that fits all bounding points and sphere
|
||||||
|
# TODO: option to rotate proj to favor vertical lines?
|
||||||
|
when not defined(release):
|
||||||
|
debug_mesh.clear_vertices()
|
||||||
|
let rotm = self.light.world_matrix.to_mat3_rotation
|
||||||
|
let rotmi = rotm.inverse
|
||||||
|
var pmin = vec3(Inf)
|
||||||
|
var pmax = vec3(-Inf)
|
||||||
|
if sphere_radius != 0:
|
||||||
|
pmin = sphere_center - vec3(sphere_radius)
|
||||||
|
pmax = sphere_center + vec3(sphere_radius)
|
||||||
|
for p in bounding_points:
|
||||||
|
let p = rotmi * p
|
||||||
|
pmin = min(pmin, p)
|
||||||
|
pmax = max(pmax, p)
|
||||||
|
pmax.z = max(pmin.z+min_z, pmax.z)
|
||||||
|
let mid = mix(pmin, pmax, 0.5)
|
||||||
|
var scale = (pmax-pmin) * vec3(0.5, 0.5, -0.5)
|
||||||
|
scale.xy = vec2(max(scale.x, scale.y)) # make it square
|
||||||
|
# TODO: grow 1 px and snap the box to increments of pixels
|
||||||
|
let proj = scale(1'f32/scale)
|
||||||
|
let light_matrix = rotm.to_mat4 * translate(mid)
|
||||||
|
# calculate the bias = the diagonal of half a pixel
|
||||||
|
# multiplied by twice the radius in the shader (1.5*2 at the moment)
|
||||||
|
# plus 1 because of texture filtering (GL_COMPARE_REF_TO_TEXTURE)
|
||||||
|
# TODO: test at multiple resolutions and different filtering radii
|
||||||
|
let resolution = scene.shadow_maps.width.float32
|
||||||
|
let bias = 4.0 * sqrt(2.0f) * scale.x/resolution
|
||||||
|
|
||||||
method renderShadow*(self: SimpleShadowManager, camera: Camera, index: int) =
|
when defined(myouDebugShadows):
|
||||||
|
show_mat inverse(proj * inverse(light_matrix))
|
||||||
|
|
||||||
|
scene.shadow_maps.enable(layer = self.shadow_index)
|
||||||
|
scene.shadow_maps.clear(color=vec4(1), layer = self.shadow_index)
|
||||||
|
let cd = newRenderCameraData(light_matrix,
|
||||||
|
proj, proj.get_culling_planes(), vec2(scene.shadow_maps.width.float32))
|
||||||
|
|
||||||
|
let rm = self.engine.renderer
|
||||||
|
if not rm.initialized: # TODO: enqueue but only once
|
||||||
|
return
|
||||||
|
let material = self.material
|
||||||
|
for ob in scene.mesh_passes[0]:
|
||||||
|
if ob.visible and ob.draw_method != Lines:
|
||||||
|
rm.draw_mesh(ob, ob.world_matrix, cd, 0, material)
|
||||||
|
|
||||||
|
scene.shadow_maps.disable()
|
||||||
|
|
||||||
|
var depth_matrix = LIGHT_PROJ_TO_DEPTH * proj * inverse(light_matrix)
|
||||||
|
scene.shadow_maps_ubo.storage(ShadowMapUniform)[self.shadow_index] =
|
||||||
|
ShadowMapUniform(depth_matrix: depth_matrix,
|
||||||
|
tex_size: resolution, bias: bias)
|
||||||
|
|
||||||
|
proc renderShadowWithCamera*(self: SimpleShadowManager, camera: Camera) =
|
||||||
# first we'll make a list of points that matches the corners of the camera
|
# first we'll make a list of points that matches the corners of the camera
|
||||||
# frustum and given depth range
|
# frustum and given depth range
|
||||||
|
|
||||||
|
@ -150,15 +199,17 @@ method renderShadow*(self: SimpleShadowManager, camera: Camera, index: int) =
|
||||||
depth_to_screen(self.depth_range[1], camera.projection_matrix),
|
depth_to_screen(self.depth_range[1], camera.projection_matrix),
|
||||||
]
|
]
|
||||||
let mat = camera.world_matrix.remove_scale_skew * camera.projection_matrix_inverse
|
let mat = camera.world_matrix.remove_scale_skew * camera.projection_matrix_inverse
|
||||||
# show_mat mat
|
when defined(myouDebugShadows):
|
||||||
|
show_mat mat
|
||||||
var points = newSeqOfCap[Vec3](8)
|
var points = newSeqOfCap[Vec3](8)
|
||||||
for z in screen_depth:
|
for z in screen_depth:
|
||||||
for y in [-1'f32,1'f32]:
|
for y in [-1'f32,1'f32]:
|
||||||
for x in [-1'f32,1'f32]:
|
for x in [-1'f32,1'f32]:
|
||||||
let v = mat * vec4(x,y,z,1)
|
let v = mat * vec4(x,y,z,1)
|
||||||
points.add v.xyz/v.w
|
points.add v.xyz/v.w
|
||||||
# for i in [0,1,1,3,3,2,2,0,4,5,5,7,7,6,6,4,0,4,1,5,2,6,3,7]:
|
when defined(myouDebugShadows):
|
||||||
# debug_mesh.add_vertex points[i], vec4(1)
|
for i in [0,1,1,3,3,2,2,0,4,5,5,7,7,6,6,4,0,4,1,5,2,6,3,7]:
|
||||||
|
debug_mesh.add_vertex points[i], vec4(1)
|
||||||
|
|
||||||
var zrange = self.depth_range[1] - self.depth_range[0]
|
var zrange = self.depth_range[1] - self.depth_range[0]
|
||||||
if zrange == 0.0:
|
if zrange == 0.0:
|
||||||
|
@ -166,58 +217,23 @@ method renderShadow*(self: SimpleShadowManager, camera: Camera, index: int) =
|
||||||
# extend z range an arbitrary amount
|
# extend z range an arbitrary amount
|
||||||
# TODO: make it configurable
|
# TODO: make it configurable
|
||||||
# or better: clamp polygons in the shader!
|
# or better: clamp polygons in the shader!
|
||||||
self.renderShadow(camera.scene, points, min_z=zrange*4, index)
|
self.renderShadow(camera.scene, points, min_z=zrange*4)
|
||||||
|
|
||||||
method renderShadow*(self: SimpleShadowManager, scene: Scene, bounding_points: seq[Vec3], min_z: float32, index: int) =
|
method renderShadow*(self: SimpleShadowManager, camera: Camera): bool {.locks:"unknown".} =
|
||||||
# Find the projection matrix that fits all bounding points
|
if self.use_camera:
|
||||||
# TODO: find bounds of bounding sphere instead
|
self.renderShadowWithCamera(
|
||||||
# TODO: option to favor vertical lines?
|
camera = camera)
|
||||||
when not defined(release):
|
return true
|
||||||
debug_mesh.clear_vertices()
|
else:
|
||||||
let rotm = self.light.world_matrix.to_mat3_rotation
|
let v = get_world_Z_vector(self.light)
|
||||||
let rotmi = rotm.inverse
|
let angle = angle(v, self.last_light_dir)
|
||||||
var pmin = vec3(Inf)
|
# We use < and not <= so it always renders when min delta is 0
|
||||||
var pmax = vec3(-Inf)
|
if angle < self.min_light_angle_delta:
|
||||||
for p in bounding_points:
|
return
|
||||||
let p = rotmi * p
|
self.last_light_dir = v
|
||||||
pmin = min(pmin, p)
|
self.renderShadow(
|
||||||
pmax = max(pmax, p)
|
scene = camera.scene,
|
||||||
# TODO: extend pmin.z so it never falls inside the complete frustum
|
bounding_points = self.caster_bounding_points,
|
||||||
pmax.z = max(pmin.z+min_z, pmax.z)
|
min_z = -Inf)
|
||||||
let mid = mix(pmin, pmax, 0.5)
|
return true
|
||||||
var scale = (pmax-pmin) * vec3(0.5, 0.5, -0.5)
|
|
||||||
scale.xy = vec2(max(scale.x, scale.y))
|
|
||||||
let proj = scale(1'f32/scale)
|
|
||||||
let light_matrix = rotm.to_mat4 * translate(mid)
|
|
||||||
|
|
||||||
# show_mat inverse(proj * inverse(light_matrix))
|
|
||||||
|
|
||||||
|
|
||||||
self.texture.unbind()
|
|
||||||
self.framebuffer.enable()
|
|
||||||
self.framebuffer.clear(color=vec4(1))
|
|
||||||
let cd = newRenderCameraData(light_matrix,
|
|
||||||
proj, proj.get_culling_planes(), vec2(self.framebuffer.width.float32))
|
|
||||||
|
|
||||||
let rm = self.engine.renderer
|
|
||||||
if not rm.initialized: # TODO: enqueue but only once
|
|
||||||
return
|
|
||||||
let material = self.material
|
|
||||||
for ob in scene.mesh_passes[0]:
|
|
||||||
if ob.visible:
|
|
||||||
rm.draw_mesh(ob, ob.world_matrix, cd, 0, material)
|
|
||||||
|
|
||||||
self.framebuffer.disable()
|
|
||||||
|
|
||||||
scene.shadow_maps[index] = self.texture
|
|
||||||
var depth_matrix = LIGHT_PROJ_TO_DEPTH * proj * inverse(light_matrix)
|
|
||||||
# vmath doesn't let me do this properly
|
|
||||||
depth_matrix[3,2] = depth_matrix[3,2] - self.bias
|
|
||||||
# cast[ptr float32](depth_matrix[3,2].addr)[] -= self.bias
|
|
||||||
scene.shadow_maps_ubo.storage(ShadowMapUniform)[index] =
|
|
||||||
ShadowMapUniform(depth_matrix: depth_matrix,
|
|
||||||
tex_size: self.framebuffer.width.float32)
|
|
||||||
|
|
||||||
# TODO: do this outside
|
|
||||||
scene.shadow_maps_ubo.update()
|
|
||||||
|
|
||||||
|
|
|
@ -269,9 +269,7 @@ type
|
||||||
Light* = ref object of GameObject
|
Light* = ref object of GameObject
|
||||||
light_type*: LightType
|
light_type*: LightType
|
||||||
shadows*: seq[ShadowManager]
|
shadows*: seq[ShadowManager]
|
||||||
# shadow_fb*: Framebuffer
|
use_shadow*: bool
|
||||||
# shadow_texture*: Texture
|
|
||||||
# render_shadow*: bool
|
|
||||||
color*: Vec3
|
color*: Vec3
|
||||||
energy*: float32
|
energy*: float32
|
||||||
diffuse_factor*: float32
|
diffuse_factor*: float32
|
||||||
|
@ -292,21 +290,27 @@ type
|
||||||
ShadowManager* = ref object of RootObj
|
ShadowManager* = ref object of RootObj
|
||||||
engine* {.cursor.}: MyouEngine
|
engine* {.cursor.}: MyouEngine
|
||||||
light* {.cursor.}: Light
|
light* {.cursor.}: Light
|
||||||
framebuffer*: Framebuffer
|
required_texture_count*: int32
|
||||||
texture*: Texture
|
uses_color_channels*: int8
|
||||||
|
uses_depth*: bool
|
||||||
|
shadow_index*: int32
|
||||||
sampler_type*: string
|
sampler_type*: string
|
||||||
material*: Material
|
material*: Material
|
||||||
auto_render*: bool
|
auto_render*: bool
|
||||||
|
min_light_angle_delta*: float32
|
||||||
|
last_light_dir*: Vec3
|
||||||
|
|
||||||
SimpleShadowManager* = ref object of ShadowManager
|
SimpleShadowManager* = ref object of ShadowManager
|
||||||
depth_range*: (float32, float32)
|
depth_range*: (float32, float32)
|
||||||
bias*: float32
|
caster_bounding_points*: seq[Vec3]
|
||||||
|
use_camera*: bool
|
||||||
|
|
||||||
# NOTE: layout must match struct ShadowMapInfo in scene.nim
|
# NOTE: layout must match struct ShadowMapInfo in scene.nim
|
||||||
ShadowMapUniform* = object
|
ShadowMapUniform* = object
|
||||||
depth_matrix*: Mat4
|
depth_matrix*: Mat4
|
||||||
tex_size*: float32
|
tex_size*: float32
|
||||||
pad0, pad1, pad2: float32
|
bias*: float32
|
||||||
|
pad0, pad1: float32
|
||||||
|
|
||||||
# scene.nim
|
# scene.nim
|
||||||
|
|
||||||
|
@ -326,11 +330,11 @@ type
|
||||||
direction*: Vec3
|
direction*: Vec3
|
||||||
padding0: float32
|
padding0: float32
|
||||||
color*: Vec3
|
color*: Vec3
|
||||||
padding1: float32
|
shadow_index*: float32
|
||||||
diffuse_power*: float32
|
diffuse_power*: float32
|
||||||
specular_power*: float32
|
specular_power*: float32
|
||||||
|
padding1: float32
|
||||||
padding2: float32
|
padding2: float32
|
||||||
padding3: float32
|
|
||||||
|
|
||||||
Scene* = ref object
|
Scene* = ref object
|
||||||
engine* {.cursor.}: MyouEngine
|
engine* {.cursor.}: MyouEngine
|
||||||
|
@ -361,6 +365,7 @@ type
|
||||||
|
|
||||||
# render options
|
# render options
|
||||||
background_color*: Vec4
|
background_color*: Vec4
|
||||||
|
shadow_map_resolution*: int32
|
||||||
cubemap_resolution*: int32
|
cubemap_resolution*: int32
|
||||||
max_point_lights*: int32
|
max_point_lights*: int32
|
||||||
max_sun_lights*: int32
|
max_sun_lights*: int32
|
||||||
|
@ -370,7 +375,7 @@ type
|
||||||
max_cubemaps*: int32
|
max_cubemaps*: int32
|
||||||
max_spherical_harmonics*: int32
|
max_spherical_harmonics*: int32
|
||||||
|
|
||||||
shadow_maps*: seq[Texture]
|
shadow_maps*: Framebuffer
|
||||||
world_material*: Material
|
world_material*: Material
|
||||||
background_cubemap*: Framebuffer
|
background_cubemap*: Framebuffer
|
||||||
cubemaps*: seq[Framebuffer]
|
cubemaps*: seq[Framebuffer]
|
||||||
|
@ -576,7 +581,7 @@ type
|
||||||
object_ubo_index*: GLuint
|
object_ubo_index*: GLuint
|
||||||
|
|
||||||
texture_locations*: seq[GLint]
|
texture_locations*: seq[GLint]
|
||||||
shadowmap_locations*: seq[GLint]
|
shadowmap_location*: GLint
|
||||||
cubemap_locations*: seq[GLint]
|
cubemap_locations*: seq[GLint]
|
||||||
|
|
||||||
when not defined(release):
|
when not defined(release):
|
||||||
|
@ -666,7 +671,7 @@ type
|
||||||
|
|
||||||
Framebuffer* = ref object of RootObj
|
Framebuffer* = ref object of RootObj
|
||||||
engine* {.cursor.}: MyouEngine
|
engine* {.cursor.}: MyouEngine
|
||||||
width*, height*: int
|
width*, height*, layer_count*: int
|
||||||
format*: TextureFormat
|
format*: TextureFormat
|
||||||
depth_type*: FramebufferDepthType
|
depth_type*: FramebufferDepthType
|
||||||
texture*, depth_texture*: Texture
|
texture*, depth_texture*: Texture
|
||||||
|
|
26
src/util.nim
26
src/util.nim
|
@ -232,3 +232,29 @@ template staticOrDebugRead*(path: string): string =
|
||||||
staticRead path
|
staticRead path
|
||||||
|
|
||||||
template nonNil*(x: untyped): bool = x != nil
|
template nonNil*(x: untyped): bool = x != nil
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: move to vmath fork
|
||||||
|
|
||||||
|
template low*[T](x: typedesc[GVec2[T]]): GVec2[T] = gvec2[T](T.low,T.low)
|
||||||
|
template low*[T](x: typedesc[GVec3[T]]): GVec3[T] = gvec3[T](T.low,T.low,T.low)
|
||||||
|
template low*[T](x: typedesc[GVec4[T]]): GVec4[T] = gvec4[T](T.low,T.low,T.low,T.low)
|
||||||
|
template high*[T](x: typedesc[GVec2[T]]): GVec2[T] = gvec2[T](T.high,T.high)
|
||||||
|
template high*[T](x: typedesc[GVec3[T]]): GVec3[T] = gvec3[T](T.high,T.high,T.high)
|
||||||
|
template high*[T](x: typedesc[GVec4[T]]): GVec4[T] = gvec4[T](T.high,T.high,T.high,T.high)
|
||||||
|
|
||||||
|
# bounding box operations
|
||||||
|
|
||||||
|
template `&`*[T](a, b: (GVec3[T], GVec3[T])): (GVec3[T], GVec3[T]) =
|
||||||
|
(min(a[0], b[0]), max(a[1], b[1]))
|
||||||
|
|
||||||
|
template `|`*[T](a, b: (GVec3[T], GVec3[T])): (GVec3[T], GVec3[T]) =
|
||||||
|
(max(a[0], b[0]), min(a[1], b[1]))
|
||||||
|
|
||||||
|
iterator box_corners*[T](bb: (GVec3[T], GVec3[T])): GVec3[T] =
|
||||||
|
let bb = cast[array[2, GVec3[T]]](bb)
|
||||||
|
for z in [0,1]:
|
||||||
|
for y in [0,1]:
|
||||||
|
for x in [0,1]:
|
||||||
|
yield gvec3[T](bb[x].x, bb[y].y, bb[z].z)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue