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
|
@ -53,6 +53,7 @@ import ./objects/cubemap_probe
|
|||
import ./objects/gameobject
|
||||
import ./objects/light
|
||||
import ./objects/mesh
|
||||
import ./shadows/shadow_common
|
||||
import ./shadows/simple_shadow
|
||||
import ./quat
|
||||
import ./scene
|
||||
|
@ -81,6 +82,7 @@ export quat
|
|||
export render
|
||||
export scene
|
||||
export screen
|
||||
export shadow_common
|
||||
export simple_shadow
|
||||
export tables
|
||||
export texture
|
||||
|
|
|
@ -101,6 +101,7 @@ proc newFramebuffer*(engine: MyouEngine,
|
|||
self.engine = engine
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.layer_count = layer_count
|
||||
self.format = format
|
||||
self.depth_type = depth_type
|
||||
var tex_type = tex_type
|
||||
|
|
|
@ -491,22 +491,18 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
ubo_names.setLen 0
|
||||
|
||||
self.texture_locations.setLen 0
|
||||
var extra_location_count = 0
|
||||
for name in material.textures.keys:
|
||||
self.texture_locations.add glGetUniformLocation(prog, name.cstring)
|
||||
if defined(myouEnsureTextureLocations) and not defined(release):
|
||||
assert self.texture_locations[^1] != -1, "invalid texture location for " & name
|
||||
|
||||
self.shadowmap_locations.setLen 0
|
||||
self.cubemap_locations.setLen 0
|
||||
self.shadowmap_location = -1
|
||||
if material.scene != nil:
|
||||
for i in 0 ..< material.scene.max_shadow_maps:
|
||||
# TODO: test with a single glUniform1iv instead of changing
|
||||
# the individual array elements
|
||||
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
|
||||
self.shadowmap_location = glGetUniformLocation(prog, "shadow_maps".cstring)
|
||||
if self.shadowmap_location != -1:
|
||||
extra_location_count.inc
|
||||
|
||||
for i in 0 ..< material.scene.max_cubemaps:
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ import ../objects/gameobject
|
|||
# import ../objects/light
|
||||
import ../objects/mesh
|
||||
import ../platform/platform
|
||||
import ../shadows/shadow_common
|
||||
import ../quat
|
||||
import ../scene
|
||||
import ../util
|
||||
|
@ -209,7 +210,19 @@ proc draw_all*(self: RenderManager) =
|
|||
if update_probe_ubo:
|
||||
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:
|
||||
if not screen.enabled:
|
||||
continue
|
||||
|
@ -354,13 +367,12 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren
|
|||
|
||||
let scene = mesh.scene
|
||||
if scene != nil:
|
||||
let shadow_maps = scene.shadow_maps
|
||||
for i,loc in shader.shadowmap_locations:
|
||||
if i == shadow_maps.len:
|
||||
break
|
||||
if loc == -1:
|
||||
continue
|
||||
let tex = shadow_maps[i]
|
||||
let loc = shader.shadowmap_location
|
||||
let fb = scene.shadow_maps
|
||||
if loc != -1 and fb != nil:
|
||||
# TODO: allow access to both texture and depth_texure
|
||||
let tex = when USE_SHADOW_SAMPLERS: fb.depth_texture
|
||||
else: fb.texture
|
||||
if tex != nil:
|
||||
textures_to_bind.add tex
|
||||
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.setFilter self.filter
|
||||
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,
|
||||
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,
|
||||
(max(1, self.height 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)
|
||||
|
||||
proc len*[T](self: UBO, utype: typedesc[T]): int =
|
||||
self.byte_storage.byte_len div sizeof(T)
|
||||
|
||||
# NOTE: does not preserve contents
|
||||
proc resize*[T](self: UBO, utype: typedesc[T], count: int) =
|
||||
if count == self.storage(T).len:
|
||||
|
|
|
@ -367,7 +367,7 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
|
|||
PointLight
|
||||
let color = vec3(data.r.f32[0], data.g.f32[0], data.b.f32[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 cutoff = if use_dist: data.att_dist.f32[0] else: 0'f32
|
||||
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],
|
||||
spot_size = data.spotsize.f32[0],
|
||||
spot_blend = data.spotblend.f32[0],
|
||||
# use_shadow = use_shadow,
|
||||
use_shadow = use_shadow,
|
||||
cutoff_distance = cutoff,
|
||||
diffuse_factor = data.diff_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)
|
||||
# echo nodes[i]
|
||||
i += 1
|
||||
scene.reorder_children()
|
||||
|
||||
if update_light_counts:
|
||||
scene.calculate_max_lights_and_cubemaps()
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
|
||||
import ../types
|
||||
import vmath except Quat
|
||||
import ../util
|
||||
|
||||
when defined(nimdoc):
|
||||
type TYPES* = LightType | Light
|
||||
|
@ -48,6 +49,7 @@ proc newLight*(engine: MyouEngine, name: string="",
|
|||
light_radius: float32 = 0.01,
|
||||
spot_size: float32 = 1.3,
|
||||
spot_blend: float32 = 0.15,
|
||||
use_shadow = false,
|
||||
): Light
|
||||
proc instance_physics*(this: Light)
|
||||
|
||||
|
@ -62,7 +64,11 @@ method get_light*(this: Light): Light =
|
|||
# End forward declarations and ob type methods
|
||||
|
||||
import ./gameobject
|
||||
import ./mesh
|
||||
import ../scene
|
||||
import ../shadows/shadow_common
|
||||
import ../shadows/simple_shadow
|
||||
import quickhull
|
||||
|
||||
proc newLight*(engine: MyouEngine, name: string="",
|
||||
scene: Scene = nil,
|
||||
|
@ -75,6 +81,7 @@ proc newLight*(engine: MyouEngine, name: string="",
|
|||
light_radius: float32 = 0.01,
|
||||
spot_size: float32 = 1.3,
|
||||
spot_blend: float32 = 0.15,
|
||||
use_shadow = false,
|
||||
): Light =
|
||||
var this = new Light
|
||||
discard procCall(this.GameObject.initGameObject(engine, name))
|
||||
|
@ -88,10 +95,81 @@ proc newLight*(engine: MyouEngine, name: string="",
|
|||
this.cutoff_distance = cutoff_distance
|
||||
this.diffuse_factor = diffuse_factor
|
||||
this.specular_factor = specular_factor
|
||||
this.use_shadow = use_shadow
|
||||
if scene != nil:
|
||||
scene.add_object(this, name=name)
|
||||
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) =
|
||||
discard
|
||||
|
||||
|
|
|
@ -605,22 +605,16 @@ proc update_bounding_box*(self: Mesh) =
|
|||
raise newException(Defect, "not implemented")
|
||||
let varray = self.data.varrays[0]
|
||||
let stride = self.data.stride div 4
|
||||
var minx, miny, minz = Inf.float32
|
||||
var maxx, maxy, maxz = -Inf.float32
|
||||
var minv = Vec3.high
|
||||
var maxv = Vec3.low
|
||||
var i = 0
|
||||
while i < varray.len:
|
||||
var v = varray[i]
|
||||
minx = min(minx, v)
|
||||
maxx = max(maxx, 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)
|
||||
var v = cast[ptr Vec3](varray[i].addr)
|
||||
minv = min(minv, v[])
|
||||
maxv = max(maxv, v[])
|
||||
i += stride
|
||||
self.bound_box[0] = vec3(minx, miny, minz)
|
||||
self.bound_box[1] = vec3(maxx, maxy, maxz)
|
||||
self.bound_box = (minv, maxv)
|
||||
self.center = mix(minv, maxv, 0.5)
|
||||
|
||||
proc debug_print_vertices*(self: Mesh, starts: int = 0, ends: int = 10) =
|
||||
if self.data.varrays[0].len == 0:
|
||||
|
|
|
@ -73,6 +73,7 @@ import std/strutils
|
|||
import std/tables
|
||||
import ./graphics/framebuffer
|
||||
import ./graphics/material
|
||||
import ./graphics/texture
|
||||
import ./graphics/ubo
|
||||
import ./incomplete
|
||||
import ./objects/camera
|
||||
|
@ -80,7 +81,7 @@ import ./objects/cubemap_probe
|
|||
import ./objects/gameobject
|
||||
import ./objects/light
|
||||
import ./objects/mesh
|
||||
import ./shadows/simple_shadow
|
||||
import ./shadows/shadow_common
|
||||
import ./screen
|
||||
import ./util
|
||||
|
||||
|
@ -100,6 +101,7 @@ proc initScene*(self: Scene, engine: MyouEngine, name: string = "Scene",
|
|||
self.world = newWorld(self)
|
||||
self.background_color = vec4(0, 0, 0, 1)
|
||||
self.cubemap_resolution = 128
|
||||
self.shadow_map_resolution = 1024
|
||||
self.max_point_lights = 8
|
||||
self.max_sun_lights = 1
|
||||
self.max_spot_lights = 0
|
||||
|
@ -331,24 +333,26 @@ proc update_all_matrices*(self: Scene) =
|
|||
# if c.visible:
|
||||
# ob.recalculate_bone_matrices()
|
||||
# break
|
||||
# TODO: why can't I mutate in for loops???
|
||||
for i in 0 .. self.auto_updated_children.high:
|
||||
self.auto_updated_children[i].update_matrices()
|
||||
return
|
||||
for ob in self.auto_updated_children:
|
||||
ob.update_matrices()
|
||||
|
||||
proc set_objects_auto_update_matrix*(self: Scene, objects: seq[GameObject], auto_update: bool) =
|
||||
for ob in objects:
|
||||
ob.auto_update_matrix = auto_update
|
||||
self.children_are_ordered = false
|
||||
return
|
||||
|
||||
proc destroy*(self: Scene) =
|
||||
for ob in reversed(self.children):
|
||||
var ob = ob
|
||||
ob.destroy(recursive=false)
|
||||
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)
|
||||
return
|
||||
|
||||
proc enable_render*(self: Scene) =
|
||||
self.enabled = true
|
||||
|
@ -401,8 +405,8 @@ proc calculate_max_lights_and_cubemaps*(self: Scene) =
|
|||
of SunLight: self.max_sun_lights += 1
|
||||
of SpotLight: self.max_spot_lights += 1
|
||||
of AreaLight: self.max_area_lights += 1
|
||||
if light.shadows.len != 0:
|
||||
self.max_shadow_maps += 1
|
||||
if self.shadow_maps != nil:
|
||||
self.max_shadow_maps = self.shadow_maps.layer_count.int32
|
||||
self.max_cubemaps = self.cubemap_probes.len.int32
|
||||
if self.world_material != nil:
|
||||
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 shadow maps {self.max_shadow_maps}"
|
||||
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] =
|
||||
# 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_CUBE_MAPS " & $(if self.isNil: 0 else: self.max_cubemaps),
|
||||
"#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 =
|
||||
|
@ -468,6 +470,7 @@ proc get_lighting_code*(self: Scene): string =
|
|||
#define light_specular_power powers.g
|
||||
#define light_inv_squared_cutoff powers.a
|
||||
#define light_direction dir.xyz
|
||||
#define light_shadow_index color.w
|
||||
struct PointLight {
|
||||
vec4 pos;
|
||||
vec4 color;
|
||||
|
@ -491,16 +494,17 @@ proc get_lighting_code*(self: Scene): string =
|
|||
struct ShadowMapInfo {
|
||||
mat4 depth_matrix;
|
||||
float size;
|
||||
float pad0, pad1, pad2;
|
||||
float bias;
|
||||
float pad0, pad1;
|
||||
};
|
||||
#if MAX_SHADOW_MAPS
|
||||
layout(std140) uniform ShadowMapInfos {
|
||||
ShadowMapInfo shadow_map_infos[MAX_SHADOW_MAPS];
|
||||
};
|
||||
#if USE_SHADOW_SAMPLERS
|
||||
uniform sampler2DShadow shadow_maps[MAX_SHADOW_MAPS];
|
||||
uniform sampler2DArrayShadow shadow_maps;
|
||||
#else
|
||||
uniform sampler2D shadow_maps[MAX_SHADOW_MAPS];
|
||||
uniform sampler2DArray shadow_maps;
|
||||
#endif
|
||||
#endif
|
||||
struct CubemapInfo {
|
||||
|
@ -563,11 +567,17 @@ proc update_lights*(self: Scene) =
|
|||
point += 1
|
||||
of SunLight:
|
||||
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(
|
||||
direction: ob.get_world_Z_vector.normalize,
|
||||
color: ob.color.xyz,
|
||||
diffuse_power: ob.energy * ob.diffuse_factor,
|
||||
specular_power: ob.energy * ob.specular_factor,
|
||||
shadow_index: shadow_index.float32,
|
||||
)
|
||||
sun += 1
|
||||
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;
|
||||
spec *= ld.light_specular_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 += debug_shadow(pos);
|
||||
}
|
||||
#endif
|
||||
// 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
|
||||
float get_shadow(vec3 position, int index) {
|
||||
vec3 co = vec3(shadow_map_infos[0].depth_matrix * vec4(position, 1.0));
|
||||
if(co.x < 0.0 || co.x >= 1.0 || co.y < 0.0 || co.y >= 1.0) return 1.0;
|
||||
ShadowMapInfo info = shadow_map_infos[int(findex)];
|
||||
float px_size = 1.5/info.size;
|
||||
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);
|
||||
#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 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 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;
|
||||
}
|
||||
}
|
||||
// return mix(vis/count, 1.0, 0.14);
|
||||
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/material
|
||||
import ../graphics/render
|
||||
import ../graphics/texture
|
||||
import ../graphics/ubo
|
||||
# import ../objects/light
|
||||
import ../objects/mesh
|
||||
import ../objects/gameobject
|
||||
import ../quat
|
||||
import ../scene
|
||||
import ../util
|
||||
|
||||
# TODO: Move these base methods
|
||||
method renderShadow*(self: ShadowManager, camera: Camera) {.base.} =
|
||||
discard
|
||||
method destroy*(self: ShadowManager) {.base.} =
|
||||
discard
|
||||
import ./shadow_common
|
||||
|
||||
when defined(nimdoc):
|
||||
# TODO: ShadowMapUniform goes elsewhere
|
||||
type TYPES* = SimpleShadowManager | ShadowMapUniform
|
||||
type TYPES* = SimpleShadowManager
|
||||
|
||||
# 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 USE_SHADOW_SAMPLERS:
|
||||
const LIGHT_PROJ_TO_DEPTH = mat4(0.5, 0, 0, 0, 0, 0.5, 0, 0,
|
||||
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
|
||||
|
||||
proc newSimpleShadowManager*(light: Light; fb_size: int;
|
||||
proc newSimpleShadowManager*(light: Light;
|
||||
use_camera: bool;
|
||||
near, far: float32 = 0.0;
|
||||
bias: float32 = 0.001;
|
||||
): SimpleShadowManager =
|
||||
): SimpleShadowManager {.discardable.} =
|
||||
|
||||
new(result)
|
||||
result.engine = light.engine
|
||||
result.light = light
|
||||
result.depth_range = (near, far)
|
||||
result.bias = bias
|
||||
result.auto_render = true
|
||||
result.required_texture_count = 1
|
||||
result.shadow_index = -1
|
||||
result.use_camera = use_camera
|
||||
light.shadows.add result
|
||||
let self = result
|
||||
result.engine.renderer.enqueue proc()=
|
||||
# TODO: allow configuring use of a depth-only pass with this code
|
||||
# and with sampler2DShadow support in texture.nim
|
||||
when USE_SHADOW_SAMPLERS:
|
||||
# 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,
|
||||
# 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):
|
||||
when defined(myouDebugShadows):
|
||||
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
|
||||
# TODO: why the FUCK do I need this here?
|
||||
self.engine.renderer.enqueue proc()=
|
||||
light.scene.add_object debug_mesh
|
||||
light.scene.add_object debug_mesh
|
||||
|
||||
method destroy*(self: SimpleShadowManager) =
|
||||
method destroy*(self: SimpleShadowManager) {.locks:"unknown".} =
|
||||
if self.material != nil:
|
||||
self.material.destroy()
|
||||
if self.framebuffer != nil:
|
||||
self.framebuffer.destroy()
|
||||
|
||||
when not defined(release):
|
||||
when defined(myouDebugShadows):
|
||||
proc show_mat(mat: Mat4) =
|
||||
var points = newSeqOfCap[Vec3](8)
|
||||
for z in [-1'f32,1'f32]:
|
||||
|
@ -125,9 +119,64 @@ when not defined(release):
|
|||
debug_mesh.data.update_varray
|
||||
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
|
||||
|
||||
when defined(myouDebugShadows):
|
||||
show_mat inverse(proj * inverse(light_matrix))
|
||||
|
||||
method renderShadow*(self: SimpleShadowManager, camera: Camera, index: int) =
|
||||
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
|
||||
# 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),
|
||||
]
|
||||
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)
|
||||
for z in screen_depth:
|
||||
for y in [-1'f32,1'f32]:
|
||||
for x in [-1'f32,1'f32]:
|
||||
let v = mat * vec4(x,y,z,1)
|
||||
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]:
|
||||
# debug_mesh.add_vertex points[i], vec4(1)
|
||||
when defined(myouDebugShadows):
|
||||
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]
|
||||
if zrange == 0.0:
|
||||
|
@ -166,58 +217,23 @@ method renderShadow*(self: SimpleShadowManager, camera: Camera, index: int) =
|
|||
# extend z range an arbitrary amount
|
||||
# TODO: make it configurable
|
||||
# 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) =
|
||||
# Find the projection matrix that fits all bounding points
|
||||
# TODO: find bounds of bounding sphere instead
|
||||
# TODO: option 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)
|
||||
for p in bounding_points:
|
||||
let p = rotmi * p
|
||||
pmin = min(pmin, p)
|
||||
pmax = max(pmax, p)
|
||||
# TODO: extend pmin.z so it never falls inside the complete frustum
|
||||
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))
|
||||
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()
|
||||
method renderShadow*(self: SimpleShadowManager, camera: Camera): bool {.locks:"unknown".} =
|
||||
if self.use_camera:
|
||||
self.renderShadowWithCamera(
|
||||
camera = camera)
|
||||
return true
|
||||
else:
|
||||
let v = get_world_Z_vector(self.light)
|
||||
let angle = angle(v, self.last_light_dir)
|
||||
# We use < and not <= so it always renders when min delta is 0
|
||||
if angle < self.min_light_angle_delta:
|
||||
return
|
||||
self.last_light_dir = v
|
||||
self.renderShadow(
|
||||
scene = camera.scene,
|
||||
bounding_points = self.caster_bounding_points,
|
||||
min_z = -Inf)
|
||||
return true
|
||||
|
||||
|
|
|
@ -269,9 +269,7 @@ type
|
|||
Light* = ref object of GameObject
|
||||
light_type*: LightType
|
||||
shadows*: seq[ShadowManager]
|
||||
# shadow_fb*: Framebuffer
|
||||
# shadow_texture*: Texture
|
||||
# render_shadow*: bool
|
||||
use_shadow*: bool
|
||||
color*: Vec3
|
||||
energy*: float32
|
||||
diffuse_factor*: float32
|
||||
|
@ -292,21 +290,27 @@ type
|
|||
ShadowManager* = ref object of RootObj
|
||||
engine* {.cursor.}: MyouEngine
|
||||
light* {.cursor.}: Light
|
||||
framebuffer*: Framebuffer
|
||||
texture*: Texture
|
||||
required_texture_count*: int32
|
||||
uses_color_channels*: int8
|
||||
uses_depth*: bool
|
||||
shadow_index*: int32
|
||||
sampler_type*: string
|
||||
material*: Material
|
||||
auto_render*: bool
|
||||
min_light_angle_delta*: float32
|
||||
last_light_dir*: Vec3
|
||||
|
||||
SimpleShadowManager* = ref object of ShadowManager
|
||||
depth_range*: (float32, float32)
|
||||
bias*: float32
|
||||
caster_bounding_points*: seq[Vec3]
|
||||
use_camera*: bool
|
||||
|
||||
# NOTE: layout must match struct ShadowMapInfo in scene.nim
|
||||
ShadowMapUniform* = object
|
||||
depth_matrix*: Mat4
|
||||
tex_size*: float32
|
||||
pad0, pad1, pad2: float32
|
||||
bias*: float32
|
||||
pad0, pad1: float32
|
||||
|
||||
# scene.nim
|
||||
|
||||
|
@ -326,11 +330,11 @@ type
|
|||
direction*: Vec3
|
||||
padding0: float32
|
||||
color*: Vec3
|
||||
padding1: float32
|
||||
shadow_index*: float32
|
||||
diffuse_power*: float32
|
||||
specular_power*: float32
|
||||
padding1: float32
|
||||
padding2: float32
|
||||
padding3: float32
|
||||
|
||||
Scene* = ref object
|
||||
engine* {.cursor.}: MyouEngine
|
||||
|
@ -361,6 +365,7 @@ type
|
|||
|
||||
# render options
|
||||
background_color*: Vec4
|
||||
shadow_map_resolution*: int32
|
||||
cubemap_resolution*: int32
|
||||
max_point_lights*: int32
|
||||
max_sun_lights*: int32
|
||||
|
@ -370,7 +375,7 @@ type
|
|||
max_cubemaps*: int32
|
||||
max_spherical_harmonics*: int32
|
||||
|
||||
shadow_maps*: seq[Texture]
|
||||
shadow_maps*: Framebuffer
|
||||
world_material*: Material
|
||||
background_cubemap*: Framebuffer
|
||||
cubemaps*: seq[Framebuffer]
|
||||
|
@ -576,7 +581,7 @@ type
|
|||
object_ubo_index*: GLuint
|
||||
|
||||
texture_locations*: seq[GLint]
|
||||
shadowmap_locations*: seq[GLint]
|
||||
shadowmap_location*: GLint
|
||||
cubemap_locations*: seq[GLint]
|
||||
|
||||
when not defined(release):
|
||||
|
@ -666,7 +671,7 @@ type
|
|||
|
||||
Framebuffer* = ref object of RootObj
|
||||
engine* {.cursor.}: MyouEngine
|
||||
width*, height*: int
|
||||
width*, height*, layer_count*: int
|
||||
format*: TextureFormat
|
||||
depth_type*: FramebufferDepthType
|
||||
texture*, depth_texture*: Texture
|
||||
|
|
26
src/util.nim
26
src/util.nim
|
@ -232,3 +232,29 @@ template staticOrDebugRead*(path: string): string =
|
|||
staticRead path
|
||||
|
||||
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