2D lighting in Defold(SOLVED)

Hello everyone,
Just wanted to know about how do we implement 2d lighting in Defold.
So, basically I want to create dynamic lightened scenes, with a sort of night like environment and just torches to light the way.
This could be done easily if we had lights, but as we don’t, how can we implement this in Defold.?
Thanks in advance.

Are we talking about dynamic 2d light where the sprites have both a diffuse texture and normal map and shadows are applied on the sprite based on the direction of the light (like SpriteLamp) or do you mean simple “fake” lights where areas outside the light radius is dark/black while within the light it gets rendered using it’s normal texture, optionally gradually darker the further from the lightsource?

For the latter I think something like this would work:

  • The torches should have a sprite with an image in a circular shape with increasing transparency the further from the center you go
  • The sprite should have another material and predicate so that it doesn’t get rendered with normal sprites
  • Render the game to a render target first
  • Next render the lights to a separate render target
  • Finally combine the two render targets into a single image with the end result

For the former we need to wait a little while longer since we have an almost finished solution that is awaiting some additional work before we can release it.

6 Likes

@britzl think the latter should do the job.
Thanks for clearing my doubt
. BTW where can I learn using materials, render targets and render scripts?

Also there is something here, why is the reply button in Defold forums working weirdly?
I mean that whenever I reply to someone, it shows
Youre not allowed to view the requested resource.

Take a look at this thread:

2018-10-06%2018_23_08-How%20do%20I%20make%20a%20platformer_%20-%20Questions%20-%20Defold%20game%20engine%20forum

Like this you mean? I’ve been seeing it too every time I post… logging out / clearing cache didn’t help.

2 Likes

Let’s ping @samuel.nystedt and @axel.hammarback , perhaps they know more…

3 Likes

We’re investigating the issue now.

3 Likes

+1 I have the some problem

1 Like

@ross.grams the thread exactly elaborates upon what I need. But still this is the first time I am delving in render scripts, render targets, and therefore, I don’t know what they are… And how to use them( I was evading this part for long, but bad luck :frowning_face: )
So, would you be kind to setup a small tutorial for me on how can I do this.

(Sorry if I have asked for something which I shouldn’t have)
BTW @britzl making changes to your orthographic camera render script won’t disturb the core functionality, isn’t it?

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