Shadow map - how to properly create a render target? (SOLVED)

I’m trying to create a depth buffer for a shadow map. I created a render target:

local depth_params = {
    format     = render.FORMAT_DEPTH,
    width      = render.get_window_width(),
    height     = render.get_window_height(),
    min_filter = render.FILTER_NEAREST,
    mag_filter = render.FILTER_NEAREST,
    u_wrap     = render.WRAP_CLAMP_TO_EDGE,
    v_wrap     = render.WRAP_CLAMP_TO_EDGE
}

local parameters = {
    [render.BUFFER_DEPTH_BIT] = depth_params
}
self.render_target_shadow_map = render.render_target("shadow_map", parameters)

In render script update I draw in a Render Pass for Shadow Map the data to this render target.

-- Enable render target
HELPER.to_render_target(self.render_target_shadow_map, function()
    -- Clear screen
    HELPER.clear_all_buffers(clear_buffers)

    -- Use virtual light camera (I'm trying now with a single light source)
    local camera_light = cameras.light
    render.set_view(camera_light.view)  -- Use the light camera's view matrix
    render.set_projection(camera_light.proj)  -- Use the light camera's projection matrix

    -- Calculate light space matrix for given light camera and set it for Lighting Pass constants
    self.constants.u_light_space_matrix = camera_light.proj * camera_light.view

    -- Set state
    render.set_depth_mask(true)
    render.set_depth_func(render.COMPARE_FUNC_LEQUAL)
    render.enable_state(render.STATE_DEPTH_TEST)
    render.disable_state(render.STATE_BLEND)

    -- Draw to render target
    render.draw(predicates.model)  -- Draw all objects that cast shadows
end)

HELPER here is just a simple stateless wrapper for frequent calls, e.g. clearing, enabling render targets and then disabling it after whole function in callback is finished, same for enabling textures.

And then there is G-Buffer pass and then I want to use it in the Lighting Pass like this:

HELPER.with_texture(0, gbuffer_rt, render.BUFFER_COLOR0_BIT, function()  -- POSITIONS RGBA32F
    HELPER.with_texture(1, gbuffer_rt, render.BUFFER_COLOR1_BIT, function()  -- NORMALS RGBA32F
        HELPER.with_texture(2, gbuffer_rt, render.BUFFER_COLOR2_BIT, function()  -- ALBEDO RGBA
            assert(self.render_target_shadow_map, "Shadow map render target is nil or not initialized.")
            HELPER.with_texture(3, self.render_target_shadow_map, render.BUFFER_DEPTH_BIT, function()  -- SHADOW MAP
                render.draw(self.predicates.light, { frustum = camera_world.frustum.frustum, constants = self.constants })
            end)
        end)
    end)
end)

But I have an error:

ERROR:SCRIPT: render/helper.lua:189: Render target '(asset 524294 type=ASSET_TYPE_RENDER_TARGET)' does not have a texture for the specified buffer type (type=BUFFER_TYPE_DEPTH_BIT).
stack traceback:
  [C]:-1: in function enable_texture
  render/helper.lua:189: in function with_texture
  render/cute.render_script:199: in function draw_function
  render/helper.lua:190: in function with_texture
  render/cute.render_script:197: in function draw_function
  render/helper.lua:190: in function with_texture
  render/cute.render_script:196: in function draw_function
  render/helper.lua:190: in function with_texture
  render/cute.render_script:195: in function <render/cute.render_script:74>

And the effect is like this:

Without shadows:

Assert is not triggered.
The data passed looks correct:

DEBUG:SCRIPT: 3,  <-- this is unit binding
8590458886,   <--- looks like a correct render target handle
16  <--- buffer type - DEPTH

What might be the cause of error?
I want to first solve the issue with the render target buffer (and then most probably with proper camera creation for this light, because from the output it “looks” like depth from the perspective of the light in the middle is incorrect, if it’s depth representation even)

Full project:
MWDJ_2024_3D.zip (323.4 KB)

Have you tried the RT resource instead?

Not sure, what do you mean?

And other example from @jhonny.goransson :smiley:

You can define RT resources to renderer and you can render buffer to atlas

1 Like

Ok, I’ll try. But in this example shouldn’t rendering to render target with depth bit work? I think it’s how it’s done in (yet another by Jhonny :sweat_smile:) example on shadow mapping.

At least your depth params are correct. :slight_smile:
This is how I’m using…

local depth_params                   = {
		format     = render.FORMAT_DEPTH,
		width      = w,
		height     = h,
		min_filter = render.FILTER_NEAREST,
		mag_filter = render.FILTER_NEAREST,
		u_wrap     = render.WRAP_CLAMP_TO_EDGE,
		v_wrap     = render.WRAP_CLAMP_TO_EDGE,
		flags      = render.TEXTURE_BIT -- this might not be necessary anymore
	}

self.pixelart_render_target          = render.render_target('pixelart_buffer', {
		[render.BUFFER_COLOR_BIT] = color_params,
		[render.BUFFER_DEPTH_BIT] = depth_params
	})
....

render.set_render_target(self.pixelart_render_target, { transient = { render.BUFFER_DEPTH_BIT } })

2 Likes

Which defold version are you using? I’ve refactored quite a lot in the recent beta (1.9.2) so perhaps try 1.9.1 if it still doesn’t work

1 Like

I developed shadow mapping by working backwards from jhonny’s public shadow mapping example, as I found it relatively straightforward to understand. It still works in 1.9.2 beta.

1 Like

@Wolfe2x7, yeah, precisely what I’m trying to do, but I have a rendering pipeline with deferred lighting there and I “just” want to add shadows from that example :sweat_smile: I attached the full project in the first post, I’m not even halfway there I believe, but this issue stops me.
Defold 1.9.1 alpha

Your project fails without this: flags = render.TEXTURE_BIT looks like it is necessary for depth

1 Like

When I add this flag, it work

1 Like

Thank you! :heart: It solves the issue :wink:

For future me:

local params = {
    format     = render.FORMAT_DEPTH,
    width      = render.get_window_width(),
    height     = render.get_window_height(),
    min_filter = render.FILTER_NEAREST,
    mag_filter = render.FILTER_NEAREST,
    u_wrap     = render.WRAP_CLAMP_TO_EDGE,
    v_wrap     = render.WRAP_CLAMP_TO_EDGE,
    flags      = render.TEXTURE_BIT
}

local parameters = {
    [render.BUFFER_DEPTH_BIT] = params
}
self.render_target_shadow_map = render.render_target("my_shadow_map", parameters)

I would love, if someone would additionally explain what that flag is for and why it’s necessary, it would help me and others learn :wink:

1 Like

Yeah I might have forgot to add an API entry for it :lying_face: it’s a good simple issue if someone wants to contribute!

1 Like

Strange thing is, when I start my project using 1.9.0 or 1.9.1. I have to use this flag for enable_texture. But now, with 1.9.2 using Vulkan and new pipeline, it works fine without it :slight_smile:

Hmmm well that’s not how it’s supposed to work :thinking:

It doesn’t complain :slight_smile: I’m using 3ede22e06920e3b6e2aa30f1d1170b6f9f597c99 and new pipeline
Top left looks good to me, right? or I’m doing something miraculously wrong :smiley:

3 Likes

I might try to create repo-case if needed?

Yeah it looks correct! It’s ok, but if you want you can open a ticket and I’ll take a look later when I’m back from vacation

1 Like

Let me extend the topic with a question: I realized that such shadow mapping allows to create a shadow only for directional lighting. If I want to have shadows from point lights there should be a different approach, the one with cubemaps, right? Or some other approach?

Second question is to you guys more experienced with adding shadows to your pipelines using the shadow mapping technique - how would you describe the steps for adding them for beginners to understand how the process is working? I want to truly understand it, not only copy-pasting.

1 Like