239 lines
9.5 KiB
Nim
239 lines
9.5 KiB
Nim
# The contents of this file are subject to the Common Public Attribution License
|
|
# Version 1.0 (the “License”); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
# https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla
|
|
# Public License Version 1.1 but Sections 14 and 15 have been added to cover use
|
|
# of software over a computer network and provide for limited attribution for
|
|
# the Original Developer. In addition, Exhibit A has been modified to be
|
|
# consistent with Exhibit B.
|
|
#
|
|
# Software distributed under the License is distributed on an “AS IS” basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
# the specific language governing rights and limitations under the License.
|
|
#
|
|
# The Original Code is Myou Engine.
|
|
#
|
|
# the Original Developer is the Initial Developer.
|
|
#
|
|
# The Initial Developer of the Original Code is the Myou Engine developers.
|
|
# All portions of the code written by the Myou Engine developers are Copyright
|
|
# (c) 2024. All Rights Reserved.
|
|
#
|
|
# Alternatively, the contents of this file may be used under the terms of the
|
|
# GNU Affero General Public License version 3 (the [AGPL-3] License), in which
|
|
# case the provisions of [AGPL-3] License are applicable instead of those above.
|
|
#
|
|
# If you wish to allow use of your version of this file only under the terms of
|
|
# the [AGPL-3] License and not to allow others to use your version of this file
|
|
# under the CPAL, indicate your decision by deleting the provisions above and
|
|
# replace them with the notice and other provisions required by the [AGPL-3]
|
|
# License. If you do not delete the provisions above, a recipient may use your
|
|
# version of this file under either the CPAL or the [AGPL-3] License.
|
|
|
|
import std/strutils
|
|
import ../types
|
|
import vmath except Quat, quat
|
|
import ../graphics/framebuffer
|
|
import ../graphics/material
|
|
import ../graphics/render
|
|
import ../graphics/ubo
|
|
# import ../objects/light
|
|
import ../objects/mesh
|
|
import ../objects/gameobject
|
|
import ../quat
|
|
import ../util
|
|
import ./shadow_common
|
|
|
|
when defined(nimdoc):
|
|
type TYPES* = SimpleShadowManager
|
|
|
|
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 defined(myouDebugShadows):
|
|
var debug_mesh: Mesh
|
|
|
|
proc newSimpleShadowManager*(light: Light;
|
|
use_camera: bool;
|
|
near, far: float32 = 0.0;
|
|
): SimpleShadowManager {.discardable.} =
|
|
|
|
new(result)
|
|
result.engine = light.engine
|
|
result.light = light
|
|
result.depth_range = (near, far)
|
|
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()=
|
|
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
|
|
|
|
when defined(myouDebugShadows):
|
|
if debug_mesh != nil:
|
|
debug_mesh.destroy()
|
|
debug_mesh = self.engine.newMesh(vertex_count = 100, draw_method = Lines)
|
|
debug_mesh.materials.add self.engine.newSolidMaterial("green", vec4(0,1,0,1))
|
|
debug_mesh.visible = false
|
|
light.scene.add_object debug_mesh
|
|
|
|
method destroy*(self: SimpleShadowManager) {.locks:"unknown".} =
|
|
if self.material != nil:
|
|
self.material.destroy()
|
|
when defined(myouDebugShadows):
|
|
debug_mesh.destroy()
|
|
|
|
when defined(myouDebugShadows):
|
|
proc show_mat(mat: Mat4) =
|
|
var points = newSeqOfCap[Vec3](8)
|
|
for z in [-1'f32,1'f32]:
|
|
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)
|
|
debug_mesh.data.update_varray
|
|
debug_mesh.visible = true
|
|
|
|
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?
|
|
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))
|
|
|
|
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
|
|
|
|
# it assumes that all world matrices are already up to date
|
|
# TODO: accept a cameradata instead?
|
|
|
|
template depth_to_screen(d: float32, proj: Mat4): float32 =
|
|
# TODO: make more efficient and put in Camera
|
|
let v = proj * vec4(0,0,-d,1)
|
|
clamp(v.z/v.w,-1.0, 1.0)
|
|
|
|
# get frustum points, in world space
|
|
let screen_depth = if self.depth_range == (0'f32,0'f32):
|
|
[-1'f32, 1'f32]
|
|
else: [
|
|
depth_to_screen(self.depth_range[0], 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
|
|
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
|
|
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:
|
|
zrange = camera.far_plane - camera.near_plane
|
|
# 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)
|
|
|
|
method renderShadow*(self: SimpleShadowManager, camera: Camera): bool {.locks:"unknown".} =
|
|
when defined(myouDebugShadows):
|
|
debug_mesh.clear_vertices()
|
|
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
|
|
|