Lighting pass not working in split screen mode [SOLVED]

I have the following loop in the update function of my render script. Only the last window renders correctly with the other three drawing the ambient light but not the lights themselves.

Is there something that works asynchronously in the render script? Seems like something is being reset by the next run through the loop before being used.

	for k,v in pairs(self.cam) do
		render.set_viewport(v.vp.x, v.vp.y, v.vp.z, v.vp.w)

		-- draw to render target
		render.set_render_target(self.render_target_world)
		render.set_view(cam.get(k))
		render.set_projection(vmath.matrix4_orthographic(0, v.proj.x, 0, v.proj.y, -1, 1))
		set_default_renderer_state()
		render.draw(self.tile_pred)
		render.draw_debug3d()
		render.set_render_target(render.RENDER_TARGET_DEFAULT)

		-- lighting pass
		render.set_render_target(self.render_target_lights)
		clear_with_color(ambient_color)
		set_default_renderer_state()
		render.draw(self.light_pred)
		render.set_render_target(render.RENDER_TARGET_DEFAULT)

		-- draw to screen
		render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})
		render.set_viewport(math.ceil(sx), math.ceil(sy), math.ceil(w), math.ceil(h))
		render.set_view(IDENTITY_MATRIX)
		render.set_projection(IDENTITY_MATRIX)
		set_default_renderer_state()
		render.enable_material(hash("lighting_quad"))
		render.enable_texture(0, self.render_target_world, render.BUFFER_COLOR0_BIT)
		render.enable_texture(1, self.render_target_lights, render.BUFFER_COLOR0_BIT)
		render.draw(self.quad_pred)
		render.disable_texture(0)
		render.disable_texture(1)
		render.disable_material()
	end

My guess is that you’re clearing the first three on loop. You might want to try clearing only once every four iterations. But that’s just a guess, I could be wrong. I’ve never done something quite like this before.

1 Like

Yeah, that clear is needed each time to reset the lighting pass.

I’m guessing the "lighting_quad" is fullscreen and not individual quads per player?

What seems to be issue here is that you are clearing the frame with ambient color then drawing the lighting then on the next view/loop iteration the same thing so the one before is removed to ambient color, until all you have is the last player with the correct lighting. One way you can fix this is in the for loop only clear to color for the first view/camera.

Edit* I realize after re-reading that you may be doing this because you are drawing the lighting to the whole quad from each view overlapping the lighting data. I will test for a possible solution.

1 Like

But why do you clear the buffer every for loop iteration instead of doing it once on start update function?

It’s probably because he’s drawing inside a loop. Wouldn’t it be better to draw to the screen just once, outside the loop? ( Hard to say without actually doing it or knowing how he handles.)

Something like this:

render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})

...
-- Draw cameras to render target
for k,v in pairs(self.cam) do
		render.set_viewport(v.vp.x, v.vp.y, v.vp.z, v.vp.w)

		-- draw to render target
		render.set_render_target(self.render_target_world)
		render.set_view(cam.get(k))
		render.set_projection(vmath.matrix4_orthographic(0, v.proj.x, 0, v.proj.y, -1, 1))
		set_default_renderer_state()
		render.draw(self.tile_pred)
	end

-- if it is a single quad ->
        render.set_viewport(math.ceil(sx), math.ceil(sy), math.ceil(w), math.ceil(h))
		render.set_view(IDENTITY_MATRIX)
		render.set_projection(IDENTITY_MATRIX)

-- lighting pass
		render.set_render_target(self.render_target_lights)
		clear_with_color(ambient_color)
		set_default_renderer_state()
		render.draw(self.light_pred)

-- draw to screen
        render.set_render_target(render.RENDER_TARGET_DEFAULT)
		set_default_renderer_state()
		render.enable_material(hash("lighting_quad"))
		render.enable_texture(0, self.render_target_world, render.BUFFER_COLOR0_BIT)
		render.enable_texture(1, self.render_target_lights, render.BUFFER_COLOR0_BIT)
		render.draw(self.quad_pred)
		render.disable_texture(0)
		render.disable_texture(1)
		render.disable_material()

Lazy version of the idea:

2 Likes

Yes! This was key to the answer. I had the same thought last night in bed falling asleep (as you do). Moving the ‘draw to screen’ section after the loop and the two ‘clear render target’ parts before the loop was needed.

@selimanac @MasterMind @Dragosha - thank you all. I’ll be sending you free Steam keys for the game when it’s released first half of June.

9 Likes

:tada: Glad you figured that out :slight_smile:

Just out of curiosity, I came this far. I’m not happy with the code, but… I had to loop twice (not thrilled about that). I’m pretty sure there’s a better way.

 local cameras = camera.get_cameras()

    -- World
    render.set_render_target(self.render_target_world)
    render.clear(state.clear_buffers)

    for i, camera_id in ipairs(cameras) do
        local view = camera.get_view(camera_id)
        local projection = camera.get_projection(camera_id)

        local col = (i - 1) % cols
        local row = math.floor((i - 1) / cols)

        local x = col * rect_width
        local y = row * rect_height

        render.set_viewport(x, y, rect_width, rect_height)
        render.set_view(view)
        render.set_projection(projection)
        local frustum = projection * view
        render.draw(predicates.tile, { frustum = frustum })
    end

    -- Lights
    render.set_render_target(self.render_target_lights)
    render.clear(state.clear_buffers)
    self.constants.time = vmath.vector4(math.random(90, 100) / 100)

    for i, camera_id in ipairs(cameras) do
        local view = camera.get_view(camera_id)
        local projection = camera.get_projection(camera_id)

        local col = (i - 1) % cols
        local row = math.floor((i - 1) / cols)

        local x = col * rect_width
        local y = row * rect_height

        render.set_viewport(x, y, rect_width, rect_height)
        render.set_view(view)
        render.set_projection(projection)
        local frustum = projection * view
        render.draw(predicates.light, { frustum = frustum, constants = self.constants })
    end

    -- Render to quad
    render.set_viewport(0, 0, state.window_width, state.window_height)
    render.set_view(IDENTITY)
    render.set_projection(IDENTITY)

    render.set_render_target(render.RENDER_TARGET_DEFAULT)
    render.enable_state(graphics.STATE_CULL_FACE)
    render.enable_material(hash("multiply_quad"))
    render.enable_texture("tex0", self.render_target_world, graphics.BUFFER_TYPE_COLOR0_BIT)
    render.enable_texture("tex1", self.render_target_lights, graphics.BUFFER_TYPE_COLOR0_BIT)
    render.draw(predicates.quad)
    render.disable_texture("tex0")
    render.disable_texture("tex1")
    render.disable_material()
1 Like

So conceptually I have something like this:

  • clear render targets (world + lights)
  • loop through all four viewport:
    – setup viewport, projection and view
    – draw world to render target
    – draw lighting to second render target
  • clear screen
  • set viewport, projection and view to whole screen
  • combine world render target x lighting render target onto screen
  • clean up

And the render script looks like this:

	render.set_render_target(self.render_target_world)
	clear_with_color(self.clear_color)
	render.set_render_target(self.render_target_lights)
	clear_with_color(ambient_color)
	
	for k,v in pairs(self.cam) do
		render.set_viewport(v.vp.x, v.vp.y, v.vp.z, v.vp.w)

		-- draw to render target
		render.set_render_target(self.render_target_world)
		render.set_view(cam.get(k))
		render.set_projection(vmath.matrix4_orthographic(0, v.proj.x, 0, v.proj.y, -1, 1))
		set_default_renderer_state()
		render.draw(self.tile_pred)
		render.draw_debug3d()
		render.set_render_target(render.RENDER_TARGET_DEFAULT)

		-- lighting pass
		render.set_render_target(self.render_target_lights)
		set_default_renderer_state()
		render.draw(self.light_pred)
		render.set_render_target(render.RENDER_TARGET_DEFAULT)
	end

	-- draw to screen
	render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})
	render.set_viewport(math.ceil(sx), math.ceil(sy), math.ceil(w), math.ceil(h))
	render.set_view(IDENTITY_MATRIX)
	render.set_projection(IDENTITY_MATRIX)
	set_default_renderer_state()
	render.enable_material(hash("lighting_quad"))
	render.enable_texture(0, self.render_target_world, render.BUFFER_COLOR0_BIT)
	render.enable_texture(1, self.render_target_lights, render.BUFFER_COLOR0_BIT)
	render.draw(self.quad_pred)
	render.disable_texture(0)
	render.disable_texture(1)
	render.disable_material()
3 Likes

May I ask why you’re not using a frustum? It might help reduce a few draw calls.

Not super important, but I think you don’t need to call render.set_render_target(render.RENDER_TARGET_DEFAULT) in a loop. You can just set it once before the -- draw to screen.

1 Like

Yeah you’re right, I don’t need to keep calling that - I’m still figuring this stuff out, working from multiple examples, combining them together. There’s probably further refinements that could be made.

edit: I also moved set_default_renderer_state() to just before the loop starts. No need to call that multiple times.

4 Likes