2D lighting in Defold(SOLVED)

Yes I am getting this too , every time I reply.

I think I’ve only encountered the issue when using Firefox on a work computer. Posting this from Chrome so I should see…

Edit: Dang, happens in Chrome too. Disregard this.

No, it won’t disturb anything. You can quite easily integrate the camera with any render script.

Hello again @britzl and @ross.grams
This is what I did today with your ideas

  1. Created a new material, namely lights.
  2. Assigned a new predicate to the material
  3. Assigned the lights material to the light sprite.
  4. Edited the render script to house the new predicate.

But I am still stuck with somethings.

  1. Do I need to create a new shader for my lights sprite?
  2. How do I do this?
  1. How do I color everything else black.?

Plz guide me with this.
Thanks in advance

BTW I watched the render scripts video and it was so informative that I can’t put it in a line :slight_smile:

No, you don’t necessarily need a new shader for your lights. Just set the blend mode on the sprites to “Add” mode.

To draw things to a render target, first you need to create one with render.render_target() (you can copy-paste most of the parameters from the API ref or somewhere). Then, in update(), use render.enable_render_target() to enable it, and then render.clear() to clear it. After that everything you draw will be drawn to that render target, so just use render.draw() as usual. After that, call render.disable_render_target() to disable it.

That just draws stuff to the render target texture, instead of drawing things directly to the screen. So you won’t see anything yet. The next step is to add a simple quad (flat square plane) model to your scene with a material on it that mixes together both of your render target textures. You’ll want a separate predicate just for this quad so you can draw it separately. Also set the view and projection matrices so this quad fills the whole screen. In your render script you’ll need to render.enable_texture() both textures, then draw the quad, then disable both textures. For the material you need to give it a texture sampler for each render target. Then a custom fragment shader that combines them. The most basic, easy way to do it is just multiply them together. That way if the lighting texture is zero (black) you’ll just get black, and if it’s 1 (white) you’ll see the base render clearly.

To make everything black by default, just use a (0, 0, 0, 0) color to clear the lighting render target with. You can change this color to have some “ambient” lighting.

Ages ago I put up a sample project. It’s a bit old and a little bit more complicated, but maybe it will help: Link Here

2 Likes

Yeah!! I cracked the code, and finally:::
Here is the Sunday’s surprise :point_down: .

I am just too much happy with the stuff I have created, :smile:

But still configuring it to work makes everything weird. The whole positioning, scaling, and stuff is messed up, and the orthographic camera script just refuses to work. How can I rectify it???

8 Likes

More errors incoming. The base tilemaps show weird scale, and now cover half of the screen rather than the usual full screen. Factory creates objects at random positions, and so on. This thing is kinda confusing, especially when using the render scripts for the first time.
Plz help me clear my doubts .

Post render script as is, probably something off!

If everything is showing at half size or offset by half the screen then probably your projection matrix doesn’t match up with the size and position of the quad you are rendering with.

1 Like

Here’s the entire render script.

local camera = require "orthographic.camera"

-- Check if 'shared_state' setting is on
-- From https://github.com/rgrams/rendercam/blob/master/rendercam/rendercam.lua#L4-L7
if sys.get_config("script.shared_state") ~= "1" then
	error("ERROR - camera - 'shared_state' setting in game.project must be enabled for camera to work.")
end

local high_dpi = sys.get_config("display.high_dpi", "0") == "1"

local WIDTH, HEIGHT = camera.get_window_size()

local CLEAR_COLOR = hash("clear_color")
local SET_VIEW_PROJECTION = hash("set_view_projection")
local SET_CAMERA_OFFSET = hash("set_camera_offset")
local IDENTITY_MATRIX = vmath.matrix4()


local DISPLAY_WIDTH = tonumber(sys.get_config("display.width"))
local DISPLAY_HEIGHT = tonumber(sys.get_config("display.height"))

local function create_render_targets(self)
	local color_params = {
		format = render.FORMAT_RGBA,
		width = WIDTH,
		height = (HEIGHT/1.3),
		min_filter = render.FILTER_LINEAR,
		mag_filter = render.FILTER_LINEAR,
		u_wrap = render.WRAP_CLAMP_TO_EDGE,
		v_wrap = render.WRAP_CLAMP_TO_EDGE
	}
	local depth_params = {
		format = render.FORMAT_DEPTH,
		width = WIDTH*2,
		height = (HEIGHT/1.3),
		u_wrap = render.WRAP_CLAMP_TO_EDGE,
		v_wrap = render.WRAP_CLAMP_TO_EDGE
	}

	self.base_target = render.render_target("base_target", {[render.BUFFER_COLOR_BIT] = color_params, [render.BUFFER_DEPTH_BIT] = depth_params })
	self.lights_target = render.render_target("lights_target", {[render.BUFFER_COLOR_BIT] = color_params, [render.BUFFER_DEPTH_BIT] = depth_params })
end

function init(self)
	self.tile_pred = render.predicate({"tile"})
	self.gui_pred = render.predicate({"gui"})
	self.text_pred = render.predicate({"text"})
	self.particle_pred = render.predicate({"particle"})
	self.lights_pred = render.predicate({"lights"})
	self.render_target_pred = render.predicate({"render target"})
	self.clear_pred = render.predicate({"clear"})

	self.ambient_light = vmath.vector4(0, 0, 0, 1)
	
	self.clear_color = vmath.vector4(1, 1, 1, 1)
	self.clear_color.x = sys.get_config("render.clear_color_red", 0)
	self.clear_color.y = sys.get_config("render.clear_color_green", 0)
	self.clear_color.z = sys.get_config("render.clear_color_blue", 0)
	self.clear_color.w = sys.get_config("render.clear_color_alpha", 0)

	self.world_view = vmath.matrix4()
	self.world_projection = vmath.matrix4()

	self.screen_view = vmath.matrix4()

	self.window_width = nil
	self.window_height = nil

	create_render_targets(self)
end

function update(self)
	local window_width = render.get_window_width()
	local window_height = render.get_window_height()
	if self.window_width ~= window_width or self.window_height ~= window_height then
		self.window_width = window_width
		self.window_height = window_height

		-- update window width/height for camera (used by the projections)
		if high_dpi then
			camera.set_window_size(window_width / 2, window_height /2)
		else
			camera.set_window_size(window_width, window_height)
		end
	end

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


	-- setup the viewport
	render.set_viewport(0, 0, window_width, window_height)


	-- draw world space using projection received from the camera in on_message
	render.set_view(self.world_view)
	render.set_projection(self.world_projection)

	render.set_depth_mask(false)
	render.disable_state(render.STATE_DEPTH_TEST)
	render.disable_state(render.STATE_STENCIL_TEST)
	render.enable_state(render.STATE_BLEND)
	render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
	render.disable_state(render.STATE_CULL_FACE)

	render.enable_render_target(self.base_target)
	render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})
	render.draw(self.tile_pred)
	render.draw(self.particle_pred)
	render.draw_debug3d()
	render.disable_render_target(self.base_target)


	--LIGHTING STUFF
	render.enable_render_target(self.lights_target)
	-- Clear with ambient light color
	render.clear({[render.BUFFER_COLOR_BIT] = self.ambient_light, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})

	render.draw(self.lights_pred)
	render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
	render.disable_render_target(self.lights_target)

	--PROJECTION
	render.set_view(IDENTITY_MATRIX) -- set view and projection so quad fills the viewport
	render.set_projection(IDENTITY_MATRIX)
	render.enable_texture(0, self.lights_target, render.BUFFER_COLOR_BIT)
	render.enable_texture(1, self.base_target, render.BUFFER_COLOR_BIT)
	render.draw(self.render_target_pred)
	render.disable_texture(0, self.lights_target)
	render.disable_texture(1, self.base_target)

	render.set_viewport(0, 0, window_width, window_height)


	-- draw world space using projection received from the camera in on_message
	render.set_view(self.world_view)
	render.set_projection(self.world_projection)
	
	-- draw gui in screen space using an orthographic projection
	render.set_view(self.screen_view)
	if self.camera_offset then
		local m4 = vmath.matrix4_orthographic(
		self.camera_offset.x, window_width + self.camera_offset.x,
		self.camera_offset.y, window_height + self.camera_offset.y,
		-1, 1)
		render.set_projection(m4)
	else
		local m4 = vmath.matrix4_orthographic(0, window_width, 0, window_height, -1, 1 )
		render.set_projection(m4)
	end
	render.enable_state(render.STATE_STENCIL_TEST)
	render.draw(self.gui_pred)
	render.draw(self.text_pred)
	render.draw(self.clear_pred)
	render.disable_state(render.STATE_STENCIL_TEST)

	render.set_depth_mask(false)
	render.draw_debug2d()
end

function on_message(self, message_id, message)
	if message_id == CLEAR_COLOR then
		self.clear_color = message.color
	elseif message_id == SET_VIEW_PROJECTION then
		self.world_view = message.view
		self.world_projection = message.projection
	elseif message_id == SET_CAMERA_OFFSET then
		self.camera_offset = message.offset
	end
end

this is basically just @britzl 's orhographic cam render script, in which I added the render lighting part. I just stretched the height part to fit the entire thing on the whole screen.

Yes, I think that is the case, but how can I properly set the matrix. Plz help…

2 Likes

OK, so the projection matrix defines what 3D area that your game will draw from. What you’re trying to do is set it so the render target quad will be drawn over the whole screen. Using an identity matrix for the projection will draw everything in a cube-shaped area from (-1, -1, -1) to (1, 1, 1), so if that’s what you’re using, your flat render quad needs to cover the area from (-1, -1) to (1, 1). You can zoom way in on your main collection to see if that is the case. Possibly you just moved it by a pixel, or you may be using a model that goes from (0, 0) to (1, 1)?

If you want to use a different matrix, the vmath.matrix4_orthographic() function makes it simple, you just give it one coordinate for each side of the cube. So you can use whatever position and size of quad model you want to draw your render targets on, just set the projection matrix so it’s covers the same area.

You can also actually use a sprite, you don’t need a quad model. But the sprite needs to be in its own atlas and should fill the atlas completely (a power of 2 size), so the UVs aren’t weird.

6 Likes

Well I don’t think so, but I have a small demo project here to showw the stuff :-
lighting.zip (1.3 MB)
Could it be the way I extract height and width from the camera ?, playing with wrong coordinates, as the position of objects, when called from factory.create, is also changed.
BTW: here are some ss to show what I mean:


The GUI is rendered properly, both here :point_up: n here :point_down:

(I colored the ambient light red to show everything clearly) But the screen here is scaled half(almost), and most of the sprites too. Moreover the enemies spawn, where they shouldn’t have.
WHy should this happen?

Is this problem only happening after you resize the window? Your example project works perfectly for me, except if I change the proportion of the window, then I get the same problem you have. The one thing that I forgot to mention, that is missing from your render script, is updating the size of the render target when the window changes. You should add the following lines to the part where you update the window size (the if block at the start of update()).

render.set_render_target_size(self.base_target, window_width, window_height)
render.set_render_target_size(self.lights_target, window_width, window_height)

1 Like

No, the problem occurs as soon as I hit ctrl-b on my keyboard

Hmm . . .

If adding those lines didn’t help . . . I think I’m out of ideas. The demo project you put up works fine for me. @britzl, any ideas?

I notice your earlier screenshot didn’t have this problem, do you know what changed between then and now?

Thanks @ross.grams This works as expected now. I was only missing those lines, updating the render(“sigh”).

Just divided the height by 1.3 to have it span the entire screen.

So, screenshot time again :slight_smile: :slight_smile:

4 Likes

Ah! Great!!!

Sorry to necro this thread; was the aforementioned solution regarding dynamic 2d light ever pursued or finished?

3 Likes

This sounds what I’m looking for. The only thing missing is specular mapping for an objects shininess. Would be great if there was a tutorial for it or example

I don’t know if it’s easily doable now without being able to have two textures passed to sprite’s material - or is it guys?