How to procedurally draw a FILLED polygon in 2D?

I am using UILineRenderer to draw some polygons, but there is no way to fill in the polygon with a color. There is no (For example) UIPolygonRenderer. It doesn’t seem like a task which should require a ton of extra coding/implementation/math. What is the easiest way to fill in the polygon once I already know the edges of the polygons I want to draw?

The GPU only knows triangles. That’s it.

A LineRenderer (or UILineRenderer) creates a mesh with two new vertices per point along your list of lines. With each new pair of vertices, it makes a pair of triangles. That’s all it is doing.

For anything more fancy than that, you will need to decide how to fill in your polygon as a set of triangles, and use the Mesh API to provide that information to a UIRenderer.

This is an example that lists how to use the Mesh API and UIRenderer. You will have to decide how YOUR polygon is broken down into triangles. Unity - Manual: Use Mesh API to create a radial progress indicator

3 Likes

In classical OpenGL it’s possible to draw arbitrary polygons as the API will take care of splitting it up in triangles. Apart from that the low level APIs usually also allow to draw “triangle fans” or “triangle strips”. However Unity has a limited number or topologies it supports as they have to find the least common denominator that works on all target platforms.

Unity only supports triangles and quads when it comes to “filled” surfaces. The other topologies are only rendering lines or points which you rarely ever need.

So if you want an arbitrary polygon, you need to split it up into individual triangles as halley already explained. Splitting an arbitrary convex polygon into triangles is quite simple. However if the polygon is concave it’s not that trivial anymore. There are many different algorithms to solve this (ear clipping for example).

1 Like

Let me share my experience.
I faced the same task: “Draw a FILLED polygon in 2D.”

The first thing I tried was implementing the Ear Clipping triangulation algorithm.
If you want to have some fun, I recommend starting with the Ear Clipping method. It’s simple and easy to understand. Later, you can switch to the Delaunay triangulation method, which is more complex but faster and tends to avoid very small triangles.

However, if you just need a filled polygon, I recommend another approach:
Use PolygonCollider2D to generate a mesh with Collider2D.CreateMesh(). This already uses a Delaunay triangulation method internally.

Advantages of this method:

  1. Highly optimized — no need to implement triangulation yourself.
  2. Unity automatically provides a polygon editor tool in the editor, with serialization and convenient visual controls.

Important considerations:

  1. The vertices of the mesh are in world space. You need to manually transform them to local space.
  2. The PolygonCollider2D must be enabled during the CreateMesh() call. If it’s disabled, the function returns null.
  3. You need to handle mesh deletion to avoid memory leaks.
  4. You must use MeshRenderer and MeshFilter components.
  5. You need to manually set material, color, and UV coordinates (if needed).

Here is an example that draws a polygon with a solid color in local space:

using UnityEngine;

namespace View
{
    public class PolygonDrawer : MonoBehaviour
    {
        [SerializeField] private PolygonCollider2D _polygon;
        [SerializeField] private MeshFilter _meshFilter;
        [SerializeField] private Color _color;

        private Mesh _mesh;
        
        void Awake()
        {
            _mesh = _polygon.CreateMesh(false, false, true);
            var colors = new Color[_mesh.vertexCount];
            var vertices = _mesh.vertices;
            for (int i = 0; i < _mesh.vertexCount; i++)
            {
                colors[i] = _color;
                vertices[i] = transform.InverseTransformPoint(vertices[i]);
            }

            _mesh.vertices = vertices;
            _mesh.colors = colors;
            
            _meshFilter.mesh = _mesh;
            _polygon.enabled = false;
        }

        private void OnDestroy()
        {
            if (_mesh != null)
            {
                Destroy(_mesh);
                _mesh = null;
            }
        }
    }
}

To note, this isn’t technically true. The first two arguments to Collider2D.CreateMesh allow you to select if the mesh is in body-space or not. If false then the mesh is returned in local-space. The problem you’ll have in your example is that you’re likely not using a Rigidbody2D so the mesh local-space is identical to world-space but it is local-space.

To solve that, add a Rigidbody2D and set its body-type to Static and ensure the PolygonCollider2D is attached to it. Presumably this is just sitting on a GO somewhere in your project but it means if you modify the Transform, it moves the Rigidbody2D rather than recalculating the collider geometry (bad). You’ll then find that when asking for the local-space mesh, it’ll be local to where the body is.

Understand that collider geometry is always created in body-space because it’s only a body that can move in physics, colliders are just along for the ride and don’t change; they have no concept of position.

Hope that helps.

2 Likes