# 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