Illumination — Ready-to-use forward shading lighting for 3D games

Overview

This extension contains ready-to-use forward shading lighting for 3D games made with Defold. Just set the provided material to your mesh and place light sources on the scene.

Technically it supports about ~200 light sources, but the performance limit is about 20-30 sources at the moment. Need to implement clustered forward shading to get a valuable performance boost.

All the lighting data passed to the shader program as the texture, so it doesn’t use a render script.

:video_game: Play HTML5 demo with :flashlight: on the E key.

:bulb: Look at Operator, Kinematic Walker and TrenchBroom used in the demo.

Features

  • Linear and radial fog.
  • Ambient lighting.
  • Directional lighting.
  • Light points and spots.
  • Everything can be animated with go.animate().
  • Support for baked light maps (also can be used as emission maps).
  • Support for specular maps.
  • Support for normal maps.

Media



Documentation

Full documentation is available in GitHub repository.

29 Likes

The next things would be great to do are:

9 Likes

How wonderful is this then? :star_struck: Genius!

5 Likes

This is SOOOOO COOOOOL! :heart_eyes:

Exactly the look I was looking for, like Quake 2/3.

With a space skybox it would perfect.

5 Likes

:star_struck:

4 Likes

This is a completely new level for Defold, thank you for driving it @astrochili and sharing your journey with us! :heart:

7 Likes

Looks awesome! This will help many people!

5x

10 Likes

Well done! Looks fantastic :slight_smile:

7 Likes

Skybox is a great idea, as far as I know there are already examples of implementation, but I honestly have not tried it yet. Added the issue #8.

7 Likes

this example have skybox implementation

6 Likes

It would be perfect if SSAO would be possible to add here! :heart_eyes:
There were already experiments here: SSAO Example and here SSAO/SSLR/Deferred shading - #4 by dlannan

2 Likes

At this time, any intervention in the render script turns the external library into an example project. This is one of the reasons why all the data is passed with the texture here.

But the development of libraries with additional render passes will be possible after adding the modularity of the rendering pipeline that has been rumored :+1:

4 Likes

This is on an experimentation stage at the moment. I’m not sure if it will ever become a single prescribed solution to modular render scripts or if it is better to have it as an opt-in thing much the same way as camera or screen management solutions are made available today.

2 Likes

Unfortunately, the problem is that rendering modularity does not solve the problem of effect conflict.
For example, you have some extension with deferred rendering pipeline.
And you need to insert your custom pass there instead of the “final build” pass, which implements cluster lighting.
And other user also want to add Bloom - so he need a couple more passes after all the existing ones.
And now other user want to add SSLR - where does he stick them?
It all makes sense if you make one big extension with all the effects, and then you can just turn them off if you want to.

But making individual effects modular is quite inconvenient. And the problem is not manually changing the render script, but that it’s rather problematic to make a universal effect that will easily connect and combine with all variations of user projects.

7 Likes

I agree with you @morgerion . You make a good point with the challenges of efficiently combining different modules into something useful.

I think we will see complete and configurable rendering pipelines as single assets going forward.

2 Likes

Well, very strong points! I don’t have enough experience to tell how to make it better, other than in terms of “the easier it is to plug and run, the better”.

If the best way is to make one big extension with switchable options, some kind of consolidation is needed for such a solution. At first look it’s too unrealistic to make this usable and efficient without consolidated development.

4 Likes

This is kinda what I’m doing with my render pipeline project / asset:

dfp.configure({
		[dfp.config.LIGHTING_HDR]            = true,
		[dfp.config.POST_PROCESSING]         = true,
		[dfp.config.POST_PROCESSING_BLOOM]   = true,
		[dfp.config.SHADOWS]                 = true,
		[dfp.config.SHADOWS_SHADOW_MAP_SIZE] = 2048
	})

	self.pass_shadows_debug = dfp.add_custom_pass({
		predicate = {"postprocess_shadow_debug"},
		material  = "postprocess_shadow_debug",
		before    = dfp.node_keys.FRAMEBUFFER,
		textures  = {
			dfp.node_keys.SHADOWS
		}
	})

My idea is that you probably want to insert custom passes into the graph, but only for certain scenarios (mostly debugging) but not a completely modular approach because that is probably a lot bigger and more complex undertaking… On the whole the purpose of at least my project is that I’d rather develop it as a fixed set of features into an asset that can be dropped into any project to get a good looking game without too much hassle.

8 Likes

I happen to have seen this example before and found it very promising. :slight_smile: )
I was thinking along the same lines - create a custom pipeline where only post-processing effects or shadow renders can be plugged in. And nothing else.
With that limitation, it’s easier to make an good architecture.

7 Likes

I do have a completely modular approach for GMAI, that I hope to open source once I get the time. It’s more rather optimised with post-processing filters in mind, but it looks somewhat like this:

local pipeline = require "main.render.pipeline"
local dispatcher = require "crit.dispatcher"
local save_file = require "lib.save_file"

local h_filter_set_enabled = hash("filter_set_enabled")
local h_config_change = hash("config_change")

go.property("enabled", true)
go.property("hook", hash("gui"))
go.property("alpha", true)

local function update_enabled(self)
  pipeline.set_module_enabled(self.module, self.enabled and not save_file.config.reduce_vfx)
end

function init(self)
  self.module = pipeline.register_module(self.hook, {
    sort_order = 10,
    output = 1,
    render_passes_before = {{
      output = pipeline.INPUT,
      clear = true,
      clear_color = vmath.vector4(0),
    }},
    render_passes = {{
      inputs = {pipeline.INPUT},
      output = 1,
      settings = self.alpha and "filter_alpha" or "filter",
      predicate = "chromatic_abberation",
      -- Optimise away clears done to pipeline.OUTPUT
      -- since all the pixels will be overwritten anyway
      is_clear_invariant = not self.alpha,
    }},
  })

  self.sub_id = dispatcher.subscribe({ h_config_change })
  update_enabled(self)
end

function on_message(self, message_id, message)
  if message_id == h_filter_set_enabled then
    self.enabled = message.enabled
    update_enabled(self)

  elseif message_id == h_config_change then
    if message.key == "reduce_vfx" then
      update_enabled(self)
    end

  end
end

function final(self)
  pipeline.unregister_module(self.module)
  dispatcher.unsubscribe(self.sub_id)
end
6 Likes

Interesting! I also started on a basic post processing pipeline. Focused only on full screen post processing effects. Very basic.

Good to see that there are alternatives!

6 Likes