How to set up 2D lighting on default render pipeline?

With this game I’m working on, trying to add some 2D lighting. I am novice when it comes to lighting and not sure where to start - this is complicated by the fact that, unfortunately, I cannot seem to get URP working with this project, and all the tutorials I can find are for URP. When I try URP, this happens: Universal Render Pipeline screen tear / ghost image help

Does anyone know any good sources for the default render pipeline?

Here’s where I’m at now:

Ideally, I’d like to put some point lights on points of interest, like the base of the lava falls.

I’ve tried playing around with the material and getting weird results. In the above video, the middle of the image seems to get the most lit, with a point light, but the edges less so, even when I move the light directly overhead of the edge of the tilemap. It still gets lit, but less intensely. Is this some lighting setting? If you look at the trees, you can see the middle ones seem brighter. Here’s another example:

If you focus on the colour of the red lava, you can see it’s brighter when the light is on the left, and not on the right. Unless that’s the reflected lighting off the yellow sand?

Oddly, the lava walls do not get lit either, even though I am using the same material. Any ideas why that could be? Everything else is a tile on a tilemap, while the lava falls are game objects with animations / animators - but using the same material.

I’m using “Sprites > Diffuse” as the material / shader.

I tried some other materials to experiment with my loading screen, ie, Mobile > Vertex Lit, for instance, seems to light the corners of my test loading screen sprite:

But if I move the light over the center of the image, it goes dark:

I’m kind of shooting in the dark here. These are my lighting settings:

Also, I may need to look at optimizations as well, as this will be going on console.

9833241--1414428--upload_2024-5-14_13-44-31.png

Also here’s the settings on the point light:

As the name implies, “Vertex Lit” only lits vertices. There’s 4 vertices in a rectangular image (one at each corner) so only the corners will get lit. Placing the light in the middle of the image where there’s no vertices will add no lighting to it.

This is mostly useful for very weak hardware, where lighting every fragment (roughly speaking, every pixel) is too expensive, in situations where vertices are very close together and there’s no discernible difference between per-fragment and per-vertex lighting.

This could be due to lots of things. Do you have any kind of image post-process active? it looks to me like the light is being post-multiplied by a darker color, kinda like a vignette.

As a suggestion, in case you want your lighting to be more 3D-ish you could add normal maps to your sprites to make them pop up a little bit more.

Lighting settings in this context are mostly useless since they only apply to baked lighting, and yours is fully realtime.

Oh yes, you’re right, I forgot I had a little vignette over the top still, thanks.

Oh yes I had heard someone mention that before some time back, forgot all about it, but I have no idea how to do that. Did you know of a good link? Otherwise I’ll try and have a bit of a search, thanks for the suggestion.

Considering I want to go for console (Xbox One and Switch included), and they are weaker hardware, are there any recommended lighting settings I should be aware of? I’m not entirely sure how baked lighting works, I’m just using realtime because it’s the default. Aside from lava / water animating, the background is pretty static. The main thing is the character and puzzle pieces moving around. So, would baking the light make much difference in regards to load times with loading scenes, or performance? Or negligible if I just have a few point lights around? I’m not sure how CPU / GPU intensive it is.

I also noted that, if I have multiple point lights in the scene, and I modify one, the others seem to flicker and change in brightness? Is that anything to be worried about? I vaguely remember when I was studying in the past, there were different settings with point lights, and that depending on what you had it set to, would affect how many lights were active in a scene and how many objects were affected, but I can’t remember where to modify that or if I need to worry.

Xbox One and Switch aren’t weak enough to require per-vertex lighting. I’m talking low-tier mobile devices here.

Baked lighting basically “draws” the lighting to a big texture (a lightmap). So your lights can be a lot more detailed, using shadows with varying penumbra softness, color bleeding and what not, but you lose the ability to change them dynamically since they’re essentially drawn on surfaces. You can mix it with realtime lighting, making some lights dynamic. There’s also several types of baked lighting (baking direct light, baking only indirect light, mixing realtime and baked light based on distance to camera, etc).

Overall it results in longer load times, but faster performance since there’s less lighting to calculate at runtime. Entirely depends on what you want to do / what your goals are. I’d recommend experimenting and reading tutorials to become a bit more familiar with it.

Yes, forward rendering has a limit of lights per object. Say your limit is set to 6, then objects will only be lit by the 6 lights that contribute the most to their look. So as you move lights around, the lights affecting each object change.

Deferred rendering doesn’t have this limit, but consumes more video memory as it requires building a G-buffer. See: https://docs.unity3d.com/Manual/RenderingPaths.html

That’s helpful, thanks. My game runs fine, the biggest issue performance wise I had was taking longer to load on older hardware, which was greatly reduced by using addressables on things like loading audio files. So for now I’ll stick to real-time and see how that goes.

Ah ok that’s very helpful also, thanks. Is deferred rendering the only way to change this limit? Hard to tell if it’s affecting it in a way that’s necessary to modify. There’s 1 large point light in the middle, and 1 per lava fall base (as well as particles on each base). All the lava falls are the same intensity and range.

9835782--1414956--upload_2024-5-15_15-42-41.png+
9835782--1414959--upload_2024-5-15_15-44-48.png

No, you can change the amount of per-pixel lights and per-vertex lights in the Quality Settings. The link I shared explains both forward and deferred rendering in detail, along with how to change the amount of lights per object on forward rendering. I’m pasting here the relevant page:

https://docs.unity3d.com/Manual/RenderTech-ForwardRendering.html

A common workaround when using forward rendering is to split large objects that need to be lit by many lights into multiple smaller objects. So if your lava floor is a single large object, just split it into several smaller ones, ideally so that each lava floor object is within range of a couple lava falls at most. This will help not only with lighting but culling as well.

Rough explanation: the reason forward rendering limits the amount of lights affecting each object is that when an object is affected by more than 1 light it must render the object multiple times. So it lights objects as it renders them, hence the name “forward” rendering. In pseudocode:

for each object
{
     for each light affecting this object
     {
        render object as lit by current light, and accumulate its contribution to final image.
     }
}

So if you have a large object that fills the entire screen, and you have to render it many times because there’s multiple lights affecting it, you can become fillrate bound (in layman terms fillrate is the amount of pixels a GPU can draw per second).

On the other hand, deferred rendering renders objects only once to a group of offscreen textures, known as the G-Buffer. These textures hold all necessary information to lit the image: albedo, specular, normal, etc. Then lights are calculated as a post process. This makes each light a lot cheaper to calculate (specially small lights, that cover less % of the screen) and you can have many more lights, at the cost of extra memory to store the GBuffer textures. It renders all objects first, then applies lighting to them on a second process, hence the name “deferred” rendering. In pseudocode:

for each object
{
   render it on GBuffer
}
for each light in scene
{
   read data from GBuffer, calculate lighting and accumulate its contribution to final image
}

Thanks for giving such a detailed response.

Hm definitely having trouble getting the lighting to work as intended. I tried adding some more lights, over each of the rocks in the lava. So, 1 directional light for ambience, and 8 point lights. In editor (scene view), it looks great - this is how I want it to look:
9837720--1415364--upload_2024-5-16_12-31-38.png

In build, with a pixel light count of 1000, and all point lights set to “Auto”, I get this:

9837720--1415367--upload_2024-5-16_12-33-54.png

Not bad, but several lights appear to not be working at all, and the two lower lava falls I think aren’t lighting up the surrounding area as much as they should be.

I tried putting all of the point lights to important, which looks like this:
9837720--1415358--upload_2024-5-16_12-30-47.png

I have looked very little into things like quality settings. Looking at them, I saw that it was defaulted to “Very Low” for each platform. I assume that means each platform would then use “Very Low”? I can see there’s other ticks in black though, so not sure if they get used if they’re not green?

I did have a bit of a read of the manual: https://docs.unity3d.com/Manual/class-QualitySettings.html
But, knowing little about what the different settings do, I am unsure if I should simply crank the pixel light count, or raise any other quality settings too. I mean the game looked fine before, so assuming it used the “Very Low” default which looked fine pre-lighting, perhaps I should keep it on that but crank the pixel light count?

I can’t see anti aliasing being very useful to pure pixel art, although maybe it’s more useful with the lighting? Any advice on best recommended settings for a pixel art game with lighting? Below is “Very Low” settings, and just cranked the pixel light count to 10,000:

9837720--1415370--upload_2024-5-16_12-53-20.png

To my eyes, simply cranking the pixel art count up looks good:

9837720--1415373--upload_2024-5-16_12-54-0.png

The earlier screenshots were on “High”, but I think “very low” looks good too. I tried taking a screenshot with 10,000 pixel light in both settings, and I could see no difference. The only place I can imagine seeing it would be the colour banding, and I can’t see a difference on my screen, but it could just be my screen.

In regards to large or small objects, being pixel art, I’m using a spritesheet and Unity Tilemaps, so everything including the lava is split into 12x12 sprites on a tilemap, so I guess it’s already split? Thankfully in the case of this game, as it’s a puzzle game, each level is just 1 screen. Might have to look a lot more into culling for my next RPG project tho.

I appreciate your explanation about memory and rendering. Any estimate of what kind of costs you are looking at? Would you assume this is much of a cost, or a relatively small one? I imagine it’s dependent on the size of textures, and how much is on screen at once? Are we talking MB’s, maybe 100’s of MB’s, or maybe GB’s? Just wondering how much of an issue it would be for things like XBox One / Switch, or, if it ever happens, a port to Evercade (which I assume is quite weak).

Most quality settings won’t affect much in case of a 2D game with no shadows, particles, reflections, etc. Rendering 2D pixel art is as simple as it gets, so using “Very Low” will work well.

Be mindful of VSync Count (disabling it can lead to tearing, which is when half your screen shows one frame and the other half shows the next frame) and Texture Quality. These are the only ones that will have an impact on your game, besides Pixel Light Count.

I’m not 100% sure about how tile maps work, but my guess is it depends on the TilemapRenderer’s mode: if set to “Chunks”, then each chunk would be considered a separate mesh. If using “Individual”, each sprite would instead. In Chunk mode you also have options for expanding the bounds of each chunk for culling, so this kind of confirms my guess (as you need chunks to be independent meshes to be able to cull them!)

If you’re always rendering your entire game world, then culling is not an issue indeed.

I assume you’re talking about the memory overhead of deferred rendering? It depends on the screen size. The GBuffer in Unity has 4 full-screen textures plus a depth buffer according to the manual:

So 160 bits/pixel in a 1024x768 screen would require around 1024x768x160 / (8x1024x1024) = 15 Mbs of video memory (the /8 converts from bits to bytes, /1024 from bytes to kilobytes, another /1024 from kilobytes to megabytes). This cost is independent of how many things are on screen at once, since that would change the content of the GBuffer textures but not their size.

Whether you can assume this cost or not depends on how painful it would be for you to work within the constraints imposed by forward rendering, how much video memory you plan on spending for other purposes, and how much video memory your potential target hardware has.

Haven’t talked about this, but there’s a third method that may be worth exploring in your case.

Since most of your lights are uniform point lights that cast no shadow and your objects have no surface direction information (no normal maps), you could get away with just additively blended sprites: like small vignettes that instead of darkening the area around them, brighten it up. You will need to use a custom material for these sprites, that uses additive blending instead of the default alpha blending.

This way you could keep the benefits of both forward and deferred rendering: low memory cost and unlimited amount of lights.

Well my game is designed at 480 x 270, then scaled up. So, would you do the math on the native size, or the scaled up size, that would depend on the screen size?

Assuming it’s the native size, then I’m guessing that would make it 480 x 270 x 160 / (8x1024x1024) = 2.47 MB of video memory I think? If I did that right. That actually sounds quite low. So perhaps deferred rendering could be helpful performance-wise and relatively cheap. But, I remember doing it once years ago for study. It’s a bit of work learning how to set up, right? On the other hand it does allow cool effects. Would it help more in scene load times, or in running performance during a level?

Working on adding lights to other worlds now. Really happy how it’s working so thanks very much. Still need to work out if it’s optimized enough or not. If the waterfall is animating, is that going to matter with baked lighting, do you think?



I assume the thing you’re animating is the sprite / color palette, to create the illusion of water falling - not actually moving the waterfall geometry around right?

In that case it doesn’t matter, as baked lighting is sort of a second texture on top of your objects. Your primary texture can still be animated.

You’d do the math on the native size, 480 x 270 in your case.

Yes for your case it is, since you’re using a very low resolution.

Compared to forward rendering I wouldn’t expect much of a difference on either load times or performance. Compared to a fully baked lighting setup, you’d get slightly worse performance and slightly faster load times.

It’s just animation frames. Probably if I knew how to do it, I could have used a shader to move the texture, and then maybe even apply water effects to it, but no idea how to do that. Would have loved to do reflections on the water too.

Ohhh okay, I just assumed baked lighting was more optimized all round, but it’s slower to load? I’m guessing because when the game boots, it has to load all the lighting data? Or longer when loading a scene? Or both?

You could look into using ShaderGraph for shader-based effects, without the need to manually write your shaders.

As with pretty much anything, it’s a tradeoff: you’re sacrificing disk space, memory, loading time, and dynamic lighting for faster rendering and better quality lighting.

Baked lighting is just another set of textures (lightmaps) that are applied on top of your normal textures. Unity has to store these somewhere and load them when you load the scene using them, hence the extra loading time (which for small enough lightmaps, won’t be much).

Just going by this: https://docs.unity3d.com/Manual/shader-graph.html
That sounds amazing, thanks!

Are you saying that baked lighting tends to have better quality? Why is that?

As it’s related but sort of different, put up a separate topic about quality settings: Best "Quality Settings" for a 2D, pixel art project with lights?