5. 3D Lighting and Materials – Moving from Unity to Godot: An In-Depth Handbook to Godot for Unity Users

© Alan Thorn 2020
A. ThornMoving from Unity to Godothttps://doi.org/10.1007/978-1-4842-5908-5_5

5. 3D Lighting and Materials

Alan Thorn1 
(1)
High Wycombe, UK
 

This chapter focuses on Lighting and Materials in Godot, specifically for 3D. Here, we’ll focus on Godot’s standard material and light types and then explore its two major lighting systems: Global Illumination and Light Baking . By understanding and using these techniques, you’ll effectively transition from Unity to Godot’s lighting system, and you’ll be able to configure Godot scenes to achieve high-quality visual results in 3D. It must first be admitted, however, that Godot’s lighting system is rudimentary compared to the newer HDRP (High Definition Render Pipeline ) in Unity. In the Unity HDRP, we see an extensive, real-time PBR system for easily creating high-quality scenes, by tweaking just a few settings and materials. Godot, by contrast, is more limited in its lighting features out of the box. But still, that being said, Godot is capable of delivering high-quality lighting that makes a 3D scene compelling. Although a PBR workflow is supported and features exist to achieve a photo-realistic finish, the results are often performance intensive for many systems. This means that Godot may not be the right choice, right now, for a “photo-realistic” game. But that still leaves us many options and styles. We’ll explore lighting in this context throughout the chapter.

Lighting Fundamentals

Let’s explore the Godot Light types together. We’ll do this using a simple scene to see the lighting results in isolation, in a stand-alone scene, specifically a cube on a plane. This is created easily. Just make a new scene, and then add two MeshInstance nodes: one created as a Cube Mesh and the other as a Plane Mesh. See Figure 5-1.
Figure 5-1

Creating a Basic Cube/Plane Scene Setup for Exploring Lights

You’ll notice that every newly created Godot 3D scene features lighting by default. Even though there’s no light listed in the scene tree, we still see a sky in the viewport, and both the cube and plane meshes are illuminated. In a scene truly absent of light, we’d see only black or darkness. But in our scene – as with all new 3D scenes – we have lighting already. This is because every new 3D scene is automatically associated with a default WorldEnvironment, defining the lighting properties of the “world” – the natural, environment light from the sun or the moon, or surrounding areas –including the sky and fog. For our purpose, let’s completely switch off the environment lighting. To do this, we’ll override the default world environment. There are multiple ways to do this. We’ll add a new node, namely, a WorldEnvironment Node . See Figure 5-2.
Figure 5-2

A World Environment Node Overrides Ambient Light

After creating the WorldEnvironment node, select New Environment for the Environment field in the Inspector. This creates a new environment and the world turns dark, overriding the original settings. See Figure 5-3.
Figure 5-3

Configuring the World Environment Node…

Now expand the Background and Ambient Light sections, and then turn their Energy field to 0. This removes all ambient light from the scene. See Figure 5-4.
Figure 5-4

Removing All Ambient Light

Exploring Light Types

We’ve now succeeded in removing any and all ambient light from the scene by overriding the WorldEnvironment lighting data using our own newly created WorldEnvironment Node . This turns the scene – and indeed, any scene without lights – completely black. Doing this is useful for exploring lights, because we can create lights, edit their properties, and see them in isolation. Let’s start by creating a Directional Light. To do this, add a Directional Light node. See Figure 5-5.
Figure 5-5

Creating a Directional Light

Directional Lights are truly “positionless” insofar as their location within the scene makes no difference to their lighting impact. It’s only their orientation – their direction – that determines what they illuminate and how. Directional Lights are useful for simulating the sun, the moon, and other huge natural light sources. They are also used to simulate bounced light and ambient light. You can enable Light Shadows – for all standard light types – by expanding the Shadow tab in the inspector and setting Enabled to On. By default, Shadows will appear completely black. You can change their intensity by changing the Shadow Color, for example, from black to gray. See Figure 5-6 for Directional Light Shadows.
Figure 5-6

Controlling Directional Light Shadows

Next, let’s delete the Directional Light and try a Spot Light. The Spot Light is a computationally expensive Light Source, which has a position, a range, and a cone of influence. It’s useful for creating car headlights, ceiling lights, and other artificial Light Sources. The Light tab can be used to control a light’s color and brightness. See Figure 5-7.
Figure 5-7

Adding Spot Lights

The third and final main light type is the Omni Light, sometimes referred to as a Point Light. This light is defined by a location and a range. From its position, it casts light in all direction for a specified range. This light simulates lamps, bulbs, and other artificial sources. See Figure 5-8.
Figure 5-8

Creating an Omni Light

Together the Directional, Spot, and Omni lights are the three main light types for multi-purpose lighting in a Godot seen. As we’ll see shortly, there are more types that are not immediately obvious and which can be used to great effect.

Materials

Lights are an important aspect of making scenes look great, but so are materials. A material defines how the surface of an object will appear, when lighting and environmental conditions are all considered. Materials let you apply textures, color, and other effects to your objects. Normally, your object needs correct UVs – added inside your 3D modeling software – for textures to appear correctly on its surface. In this section, we’ll create a simple material and apply it to a cube in our sample scene. To do this, right-click the Res// path inside the FileSystem Window, and then choose New Resource from the context menu. See Figure 5-9.
Figure 5-9

Making a New Resource

Afterward, a New Resource Window appears. From here, search and select a SpatialMaterial . These are materials that are created and added to 3D objects. It is Godot’s equivalent to the Unity Standard Shader, or to the HDRPLit material, or the URPLit material. It aims to provide a diversity of shader features needed to illuminate most 3D objects. See Figure 5-10.
Figure 5-10

Creating a New Spatial Material

Name your material matCube; and it will appear as a Resource in the FileSystem panel. You can select and view the material properties by double-clicking it. When selected, the material properties display for editing inside the Inspector. To change the main material color, expand the Albedo section, and click the Color swatch. This lets you select a color for the material. For our example, I’ll make the material red, and it will soon be applied to the cube. See Figure 5-11.
Figure 5-11

Building a Red Material

You can use the Texture slot to select an image texture for applying to the mesh. This option only works as intended if the 3D mesh has appropriate UVs.

To assign the material to the cube, select the cube in the scene, and then expand the Material section from the Inspector. Click the Empty drop-down, and then choose Load from the context menu. See Figure 5-12.
Figure 5-12

Assigning a Spatial Material

From the Resource Selection Window , choose the newly created Red Material. Once assigned, the cube will turn red to reflect the material assignment. Now double-click the material from the File System panel to view its properties in the Inspector again. Expand the Parameters section and check the Cull Mode field. This should be set to Back. Back means that Backface Culling is enabled. This is a performance optimization that saves the Renderer from having to render the “back faces” of a mesh, that is, the reverse side of a surface that is normally facing away from the camera. For most meshes in a 3D scene, you’ll want this enabled. See Figure 5-13.
Figure 5-13

Enabling Backface Culling on a Material

You can make a material partially transparent by expanding the Flags Section, enabling the Transparent check box, and then by assigning a Alpha Value to the Albedo Color.

Global Illumination – Light Baking

We’ve surveyed Godot’s most basic and fundamental tools for building effective 3D scenes, complete with lighting. But we’re missing Indirect Illumination, sometimes known as Global Illumination (GI), that is, light that strikes a surface – such as a wall – and then bounces off and continues. Perhaps it bounces off directly, as it does with a mirror (Specular Reflections), or perhaps it gets absorbed into the object and then reflected back after passing through it (a Diffuse Interreflection ), or sometimes it even passes into an object and exits at a point quite distant from the entry (a Subsurface Scatter ). In all of these cases, light is reflected after making contact with an object; and this results in the continued transport of light through the scene, potentially illuminating areas that are not directly in view of the original light source. By default, when using Godot’s three major light types, no indirect illumination is calculated because it’s computationally expensive to do so in real time. This is evident whenever we add a light to the scene and observe the results in the viewport. Consider Figure 5-14. In this figure, light directly hits the cube from a point light and then illuminates its faces; but any faces turned away from the light will simply render completely black.
Figure 5-14

By Default, Godot Only Calculates Direct Illumination

Now, such Direct Illumination behavior is simply not physically accurate. Polygons facing away from a light should normally receive illumination from that light. We can solve this problem by using either one of Godot’s GI lighting methods. The first method, considered here, is Light Baking. The other, considered in the next section, is the GIProbe system. Both systems simulate the transport of light through scene, beyond direct illumination. So, both systems are capable of achieving great levels of realism. Light Baking should be preferred when creating games for a diversity of hardware, new and old. It’s for when you need good-looking lighting at a low-performance cost. Light Baking saves most or all lighting data to textures ahead of runtime to simulate the effect of bounced lighting. Let’s see how to set up Light Baking. We’ll start by opening the LightBox Godot project included in the book companion files for this chapter. It features a basic scene and a mesh with all lighting removed using a WorldEnvironment node, as we’ve seen previously. See Figure 5-15.
Figure 5-15

Lightbox Room with Lighting Removed

Next, let’s add an Omni Light to the scene and position it close to the ceiling for simulating a ceiling light. By default, indirect illumination will not be calculated, as we’ve seen already. Don’t worry, we’ll take care of that shortly! See Figure 5-16.
Figure 5-16

Adding an Omni Light to the Scene

Now let’s add some shadows. Select the Omni Light from the Scene Tree and enable the Shadow field via the Inspector. From there, select a mid-tone color to create a believable look and feel. Also make sure that Shadow Casting is enabled on the world mesh instance too. See Figure 5-17.
Figure 5-17

Adding Shadows

Wow. What a difference shadows can already make to a scene. Now, we’ll configure the Baked Lighting. To do this, start by selecting the environment mesh instance. In our sample scene, we have only one mesh. But in larger scenes, you’d select all environment instances – walls, floors, ceilings, trees, and so on. Then choose MeshUnwrap UV2 for Lightmap/AO from the viewport context menu. See Figure 5-18. This creates a new second set of UVs for the selected mesh. In this set, no UV islands will overlap within the UV space, ensuring that lighting and pixels can be rendered safely to a Lightmap texture and encode all scene lighting, resulting in no overlaps, conflicts, or aberrant effects.
Figure 5-18

Creating Lightmap UVs for the World Environment

Now, right-click the scene root from the Scene Tree, and then add a new Baked Lightmap node using the Add Node dialog. The newly created node will eventually encode all lighting information. See Figure 5-19.
Figure 5-19

Adding a Baked Lightmap Node

The newly created object surrounds the scene with a highlighted cube gizmo, representing the total volume of the scene to be included in the Baked Lighting. Ideally, this box should be as small as possible while containing all areas of the scene. You can resize the box by clicking and dragging the dotted handles at the edges of the box or by adjusting the X, Y, and Z extents field from the Bake section of the Inspector. See Figure 5-20.
Figure 5-20

Defining a Baking Volume…

Next, define the light quality for capturing by adjusting the Default Texels Per Unit field from the Inspector. This determines the ratio between linear meters to pixels in the Lightmap texture. The default value is 20, meaning 20 Lightmap pixels will be produced for every “meter of surface” in the scene. This means that a winding, wrapping, and contorted surface will generate more lightmap pixels than a simple, flat plane extending across the same region. For our example, I’ll reduce the Texels to 10. See Figure 5-21.
Figure 5-21

Defining a Texel Ratio…

Before starting the Baking Process, you’ll need to mark each mesh instance to be included. Select the environment mesh. From the Inspector, expand the Geometry section, and then enable the Use in Baked Light field. See Figure 5-22.
Figure 5-22

Marking Baked Meshes

In Unity, you mark geometry for light baking by enabling the Static check box, available on all Game Objects from the top-right hand side of Object Inspector. However, in Unity, static objects cannot move. In Godot, this limitation does not apply. Baked Objects can move. Moving objects automatically revert to an internal Light Probe system for calculating Indirect Illumination.

Finally, and optionally, let’s disable the Gizmo Visibility for the Light Bake volume. Now that we’ve sized it appropriately for our scene, we don’t need to continue viewing the volume itself. To do that, select ViewGizmosBakedLightmap from the viewport context menu. See Figure 5-23.
Figure 5-23

Toggling BakedLightmap Visibility…

Finally, you can bake the Lighting by choosing Bake Lightmaps from the viewport menu. Save the scene before doing so. This process may take a while, depending on the scene size. See Figure 5-24.
Figure 5-24

Baking Lightmaps

Notice the dramatic improvement on your scene lighting. What a difference indirect illumination makes to the scene realism. See Figure 5-25.
Figure 5-25

Baking Lightmaps Increase the Believability of a Scene

We can improve the lighting quality even further by using Post-Processing from the WorldEnvironment node. Let’s try that now. Select the WorldEnvironment node from the Scene Tree and then expand the Environment section in the Inspector. This features lots of post-processing options. See Figure 5-26.
Figure 5-26

Exploring Post-Processing

First, expand the Tonemap group in the Inspector, and change the Mode to Aces. The different Tonemapping options adjust the brightness and saturation of your image dynamically, based on specific properties, to make your colors look punchier. See Figure 5-27.
Figure 5-27

Adding Tone Mapping

Next, let’s add Contact Shadows, also known as Ambient Occlusion. This refers to the darkening of objects at their edges, as they come into contact with other surfaces. To do this, expand the SSAO section (Screen Space Ambient Occlusion) and enable it. Then reduce the Intensity slider to 0.8. See Figure 5-28.
Figure 5-28

Adding Contact Shadows

Now let’s add some Bloom. Bloom adds a subtle, but dreamy, blur to the render’s highlight values. To do this, expand the Glow section. Enable the effect. Increase the Intensity and Strength fields to strengthen the effect overall, and then reduce the HDR Threshold to increase the range of elements affected. See Figure 5-29.
Figure 5-29

Adding a Glow Effect

This is looking good. Now, to finish things off, let’s enable Anti-Aliasing to smooth off any sharp, jagged edges on the meshes. To enable this, select ProjectProject Settings from the application main menu. From there, display the Rendering ➤ Quality section, enable 4x for the MSAA setting (Multi Sampling Anti-Aliasing). See Figure 5-30.
Figure 5-30

Adding a Glow Effect

Great! And now you’ve successfully illuminated a 3D scene, complete with Light Baking and global illumination. Godot makes it easy. See Figure 5-31. Now, in the next section, we’ll try it again, but using the GIProbe system!
Figure 5-31

A Light Baked Scene

Global Illumination – GI Probes

In this section, we’ll illuminate our Light Box room, the same as from the previous section. We’ll begin again from the point shown in Figure 5-32. This room is included in the book companion files too, in the Scene GIProbes. This scene features only a Lightbox Room mesh, an Omni Light, and a default WorldEnvironment node. You’ll notice again that our scene features only Direct Illumination. Throughout this section, we’ll add Indirect Illumination, but by using the GIProbe system. Unlike Light Baking, which uses Image Textures to save scene lighting, the GIProbe system is more sophisticated and can bake a greater variety of lighting data to create realistic effects quickly and easily. However, the GIProbe is computationally expensive and is suitable for high-end PCs, consoles, and other more recent powerful devices.
Figure 5-32

Getting Ready for GIProbes

As before, we must select all meshes and enable the Use in Baked Lighting option from the Geometry section of the Inspector. This ensures all meshes will be included in, and effected by, the GIProbe system. See Figure 5-33.
Figure 5-33

Enabling Baked Lighting for Mesh Instances

Next, add a GIProbe node to the scene from the Node dialog. Your scene needs only one GIProbe node for calculating indirect illumination in the scene. See Figure 5-34.
Figure 5-34

Adding a GIProbe Node

Initially, your GIProbe will probably be larger than your scene and will appear as a subdivided cube gizmo. The idea is to resize the gizmo to completely contain your scene, being as tightly sized as possible. You can resize the Gizmo by clicking and dragging the edge handles, inside the viewport, or by adjusting the X, Y, and Z fields for Extents group in the Inspector. See Figure 5-35.
Figure 5-35

Resizing the GI Probe

Make sure the GIProbe node is selected in the Scene Tree, and then click Bake GI Probe from the viewport menu. When you do this, Godot calculates scene lighting and automatically adds indirect illumination. See Figure 5-36.
Figure 5-36

Baked Scene with GI

The GI will probably need tweaking if you’re using the default settings. For example, by viewing the backside of the sphere in our scene (as featured in Figure 5-37), you’ll notice some aberrant shadows or shading. See Figure 5-37. You may notice similar problems in other places too.
Figure 5-37

Baked “Errors

Let’s address these issues. First, mark our current scene as an “Interior,” as opposed to an exterior scene, like a forest or a beach. This ensures that the sky or any skyboxes don’t get baked into the scene lighting. You can do this by selecting the GIProbe node and then enabling the Interior check box from the Inspector. See Figure 5-38.
Figure 5-38

Marking a Scene As Interior

Next, let’s adjust the Normal Bias field from the Inspector. This adjusts the fidelity of shadows and light bounces. Usually, 0 results in lower-quality shadows for smaller objects. Try adjusting the value to 0.16. This will instantly have an effect in the viewport, and already the lighting will look much better. Great work! See Figure 5-39.
Figure 5-39

Adjusting the Normal Bias for Better Shadows

The Subdiv field at the top of the Inspector (when the GI Probe is selected) can be raised to higher values to improve lighting quality at the cost of performance. Here, we’ll leave the GI Probe set to the default value of 128. Ideally, this value should be as low as possible while still maintaining the results you need. See Figure 5-40.
Figure 5-40

Setting the GIProbe Subdivisions

Great. This scene is looking good. Now let’s add Reflections. To do this, add a new Node, a Reflection Probe Node . This node acts like a camera; it captures images of the surrounding environment as a HDR map, and the captured data is assigned as a reflection to reflective objects. See Figure 5-41 for adding a Reflection Probe.
Figure 5-41

Adding a GI Probe to the Scene…

Next, use the Extents field of the Reflection Probe (from the inspector) or use the viewport gizmo handles to resize the reflection volume so it contains the scene completely. Again, this should be resized as tightly as possible. See Figure 5-42.
Figure 5-42

Setting the Reflection Probe Extents to Contain the Scene

And that’s it! The scene is set up and ready to use Reflections. The Update Mode is specified as Once by default. This means the scene will be captured once and the image continually used as a reflection map. If your scene changes dramatically often – or its lighting changes often – you many need to change the mode to Always. Be careful, this is highly intensive. See Figure 5-43 for the complete scene with reflections.
Figure 5-43

Looking Good with Reflections

Finally, let’s enable our Post-Process effects from the WorldEnvironment node, as demonstrated in the preceding section. This includes Ambient Occlusion, Tone mapping, and Glows. See Figure 5-44.
Figure 5-44

Completed Scene with Post-Processing

Summary

This chapter completes our analysis of lighting in 3D using Godot’s two major lighting systems, Baking and GI Probes. For your projects, you’ll need to choose the right one. Both are capable of great results: Baking trades quality for performance, and GI Probes trades in the opposite direction.