We made a game called Two Opposites for the Brackeys Game Jam 2021.1. The game was made in a week and out of the 10k+ people participated worldwide, it ranked #22 in the innovation category. You can play it in your browser here.
1.0 2D Lighting System
Upon initial analysis, we decided that the atmosphere should be given the most priority while developing this game. And the visual appeal of the game played a significant role in that. Back when we started working on this project, Unity didn’t have any rendering pipeline that supported 2D lighting. So my task was to develop a 2D lighting system for the game.
1.1 2D Raycaster (GL) - 1st iteration
- The basic idea was to draw transparent lines originating radially outwards from a sprite with negligible separation to give a sense of light coming out.
- I used the Unity’s low level Graphics Library (gl) to draw lines between two points.
- The raycast loop formulated--
for (float i = 0; i < theta; i += steps)
{
GL.Begin(GL.LINES);
GL.Color(col); // Initializing GL Library with white color as input
GL.Vertex3(player.transform.position.x, player.transform.position.y, 0); //strating point
GL.Vertex3(player.transform.position.x +
Mathf.Cos(i * Mathf.Deg2Rad) * maxVisiblityDistance,
player.transform.position.y +
Mathf.Sin(i * Mathf.Deg2Rad) * maxVisiblityDistance,
0); //ending point
GL.End(); //clearing garbage
}
Adjusting steps Adjusting theta
- This loop draws rays from the player’s position to equally spaced points around the player.
- The angle that light covers is governed by
theta
. - The spacing between each ray is governed by
steps
. - The color of the rays is governed by
col
. - The length of light ray is governed by
maxVisiblityDistance
. - All of these variables were serialized in the inspector.
- This script is attached to the MainCamera and the loop is called in
OnPostRender()
method so that the lines are rendered as soon as the camera finishes rendering the scene.
1.2 Ray Material
- The next task to make the light rays feel more natural by introducing transparency.
- While pondering I found that GL library by default uses the Unlit material provided by Unity to create the lines.
- As the Unlit Material doesn’t support transparency by default, I wrote an Unlit Shader that supported both transparency and vertex colors.
- The RGBA values of the colors of the material based upon this shader was passed as an input.
GL.Begin(GL.LINES);
lineMat.SetPass(0);
GL.Color(new Color(lineMat.color.r,
lineMat.color.g,
lineMat.color.b,
lineMat.color.a));
With transparency controls, the rays looked more natural
GL.Begin(GL.LINES);
lineMat.SetPass(0);
GL.Color(new Color(lineMat.color.r,
lineMat.color.g,
lineMat.color.b,
lineMat.color.a));
With transparency controls, the rays looked more natural
1.3 Environment Lighting
- The next step would be to make the scene react to the light rays emitted by the player.
- This involved two steps--
- Making the light ray stop when it hits an object. This is done by Raycasting along the light rays that GL draws and checking if we’ve hit something.
RaycastHit2D hit = Physics2D.Raycast(player.transform.position,
new Vector2(Mathf.Cos(i * Mathf.Deg2Rad),
Mathf.Sin(i * Mathf.Deg2Rad)),
maxVisiblityDistance);
if (hit)
{
GL.Vertex3(player.transform.position.x, player.transform.position.y, 0);
GL.Vertex3(hit.point.x, hit.point.y, 0);
}
else
{
GL.Vertex3(player.transform.position.x, player.transform.position.y, 0);
GL.Vertex3(player.transform.position.x +
Mathf.Cos(i * Mathf.Deg2Rad)* maxVisiblityDistance,
player.transform.position.y +
Mathf.Sin(i * Mathf.Deg2Rad)* maxVisiblityDistance,
0);
}
- Making the sprite color of the object depend upon its distance from the light source.
SpriteRenderer sp = hit.transform.GetComponent<SpriteRenderer>();
if (sp != null)
{
sp.color = new Color(1 / Mathf.Pow(Vector3.Distance(
hit.transform.position, player.transform.position),
1.5f),
1 / Mathf.Pow(Vector3.Distance(hit.transform.position, player.transform.position),
1.5f),
1 / Mathf.Pow(Vector3.Distance(hit.transform. position, player.transform. position),
1.5f));
}
RaycastHit2D hit = Physics2D.Raycast(player.transform.position,
new Vector2(Mathf.Cos(i * Mathf.Deg2Rad),
Mathf.Sin(i * Mathf.Deg2Rad)),
maxVisiblityDistance);
if (hit)
{
GL.Vertex3(player.transform.position.x, player.transform.position.y, 0);
GL.Vertex3(hit.point.x, hit.point.y, 0);
}
else
{
GL.Vertex3(player.transform.position.x, player.transform.position.y, 0);
GL.Vertex3(player.transform.position.x +
Mathf.Cos(i * Mathf.Deg2Rad)* maxVisiblityDistance,
player.transform.position.y +
Mathf.Sin(i * Mathf.Deg2Rad)* maxVisiblityDistance,
0);
}
SpriteRenderer sp = hit.transform.GetComponent<SpriteRenderer>();
if (sp != null)
{
sp.color = new Color(1 / Mathf.Pow(Vector3.Distance(
hit.transform.position, player.transform.position),
1.5f),
1 / Mathf.Pow(Vector3.Distance(hit.transform.position, player.transform.position),
1.5f),
1 / Mathf.Pow(Vector3.Distance(hit.transform. position, player.transform. position),
1.5f));
}
1.4 2D Raycaster (GL) - 2nd iteration
- The previous method for generating rays was very inefficient with time complexity of O(n) as the loop had to run 3600 times every frame with a step size of 0.1.
- This issue was solved by detecting the edges of nearby objects and casting rays at them and then filling the space by generating mesh between them.
int numRays = Caster.LightContour.Count - 1;
LightMesh.mesh.vertices = Caster. LightContour. ToArray();
int[] triangles = new int[numRays * 3];
for (int i = 0; i < numRays * 3; i += 3)
{
triangles[i] = (i / 3 + 1) % numRays;
triangles[i + 1] = numRays;
triangles[i + 2] = (i7 3 + 2) % numRays;
}
LightMesh.mesh.triangles = triangles;
This 2D lighting system that I incorporated in the project was implemented by Unity in the later versions as a Package in URP Render Pipeline.
int numRays = Caster.LightContour.Count - 1;
LightMesh.mesh.vertices = Caster. LightContour. ToArray();
int[] triangles = new int[numRays * 3];
for (int i = 0; i < numRays * 3; i += 3)
{
triangles[i] = (i / 3 + 1) % numRays;
triangles[i + 1] = numRays;
triangles[i + 2] = (i7 3 + 2) % numRays;
}
LightMesh.mesh.triangles = triangles;
This 2D lighting system that I incorporated in the project was implemented by Unity in the later versions as a Package in URP Render Pipeline.
No comments:
Post a Comment