Line anti-aliasing at long distances

Avatar
  • updated
  • Answered

I'm working on a space game and using Shapes to generate the UI. At close distances the lines look amazing, but at longer distances, depending on the angle of the camera, the lines become very jagged. The lines are generated using immediate mode with GPU instancing, polyline density of 128 and bezier curve accuracy of 2. I've followed all the guidelines in the documentation and I'm using SMAA 8x, output precision float 4, high local AA quality and high quad interpolation quality. The lines are volumetric3d and 1px in thickness. 


Ideally, I would like the lines to render without jagged edges or as close to smooth lines as possible. Any recommendations or advice would be greatly appreciated. A video is attached below as a sample.

Video: https://webmshare.com/play/aB40g

Reporting a bug? please specify Unity version:
Reporting a bug? please specify Shapes version:
Reporting a bug? please specify Render Pipeline:
URP
Avatar
richard_roth

Can I please get a response?

Avatar
Freya Holmér creator

For these orbital circles, I would recommend using billboarded polylines with pixel based thickness and the transparent (or additive) blend mode, if the camera is supposed to support viewing them edge-on. You should probably also generate them using sine/cosine rather than with béziers, that way they're more accurate! Assuming they are circles of course

The opaque blend mode (which you have to use with volumetric 3D) doesn't support the thin line fading type of anti-aliasing that Transparent blend mode lines can do.

Avatar
richard_roth

Thanks, I'll try to implement these suggestions. 

Avatar
richard_roth

You wouldn't happen to have some sample code to create a polyline circle?

The code I use for the Draw.Disc is:

Draw.Ring(yAxisPoint, Quaternion.Euler(90f, 0f, 0f), (size.x + size.z) / 2f, lineColour);

How would I convert that into a polyline (size can just be a radius)?

Edit: I did find the color picker example, but I'm having trouble decoupling the hue to vector stuff from the simple line drawing.

Avatar
Freya Holmér creator
  • Answered

something like this should do

void PolylineCircle( float radius, int detail = 64 ) {
    using( Draw.StyleScope ) {
        using( var path = new PolylinePath() ) {
            for( int i = 0; i < detail; i++ ) {
                float angle = (ShapesMath.TAU * i) / detail;
                Vector2 point = ShapesMath.AngToDir( angle ) * radius;
                path.AddPoint( point );
            }
            Draw.PolylineGeometry = PolylineGeometry.Billboard;
            Draw.PolylineJoins = PolylineJoins.Simple;
            Draw.Polyline( path, closed: true );
        }
    }
}

Avatar
richard_roth

And I just dispose of them in the normal way (like in the example?)

Actually, a few follow-up questions.


How do I rotate the polylines to the xz axis?

How do I centralise the polylines on the specific object it is attached to?

Avatar
Freya Holmér creator

Sure, or manage lifetime with OnEnable/OnDisable. There's info on that in the immediate mode docs if you want to know more about disposing polylines.

Rotating to XZ: https://shapes.userecho.com/knowledge-bases/2/articles/549-drawing-in-the-xz-plane


Centering on an object's transform is part of the first example in the immediate mode docs: https://acegikmo.com/shapes/docs/#immediate-mode

Avatar
richard_roth

So, I'm trying to use the example in the documentation to generate a static polyline path, but it doesn't seem to render. Am I missing something?

        private PolylinePath polylinePath;

private void Awake()
{
Vector3 yAxisPoint = new Vector3(objectTransform.position.x, 0f, objectTransform.position.z);
polylinePath = GeneratePolylinePath((size.x + size.z) / 2f, yAxisPoint);
}
        private PolylinePath GeneratePolylinePath(float radius, Vector3 position, int detail = 64)
{
PolylinePath path = new PolylinePath();
for (int i = 0; i < detail; i++)
{
float angle = (ShapesMath.TAU * i) / detail;
Vector2 point = ShapesMath.AngToDir(angle) * radius;
path.AddPoint(new Vector3(point.x + position.x, 0f, point.y + position.z));
}
return path;
}
        public override void DrawShapes(UnityEngine.Camera cam)
{
using (Draw.Command(cam))
{ PolylineCircle(polylinePath, lineColour);         }         }
        void PolylineCircle(PolylinePath path, Color color)
{
using (Draw.StyleScope)
{
Draw.Color = color;
Draw.PolylineGeometry = PolylineGeometry.Billboard;
Draw.PolylineJoins = PolylineJoins.Simple;
Draw.Polyline(path, closed: true);
}
}
Avatar
Freya Holmér creator

Your script works fine for me, but there are a few pitfalls

1. Note that since you only generate the polyline in Awake(), it won't respond live to any changes you make to the size vector3

2. if your size initializes to (0,0,0), and then you go to unity, it will be invisible/of 0 size, and it won't update if you change editor parameters either

3. if your color variable initializes to (0,0,0,0), it will be invisible

this version should work fine:

using Shapes;
using UnityEngine;

[ExecuteAlways] // this is needed to make these run and draw in-editor as well
public class RingTest : ImmediateModeShapeDrawer {

// make sure they initialize to sensible values
public Vector3 size = Vector3.one;
public Color color = Color.white;

private PolylinePath polylinePath;

public override void OnEnable() {
base.OnEnable();
polylinePath = new PolylinePath();
RegeneratePath();
}

public override void OnDisable() {
base.OnDisable();
// it's important to dispose of your paths, otherwise you will get memory leaks
polylinePath.Dispose();
}

// this is called every time a variable in the inspector changes
void OnValidate() {
RegeneratePath();
}

void RegeneratePath() {
const int DETAIL = 64;
polylinePath.ClearAllPoints();
float radius = ( size.x + size.z ) / 2f;
Vector3 position = new Vector3( transform.position.x, 0f, transform.position.z );
for( int i = 0; i < DETAIL; i++ ) {
float angle = ( ShapesMath.TAU * i ) / DETAIL;
Vector2 point = ShapesMath.AngToDir( angle ) * radius;
polylinePath.AddPoint( new Vector3( point.x + position.x, 0f, point.y + position.z ) );
}
}

public override void DrawShapes( Camera cam ) {
using( Draw.Command( cam ) ) {
PolylineCircle( polylinePath, Color.white );
}
}

void PolylineCircle( PolylinePath path, Color color ) {
using( Draw.StyleScope ) {
Draw.Color = color;
Draw.PolylineGeometry = PolylineGeometry.Billboard;
Draw.PolylineJoins = PolylineJoins.Simple;
Draw.Polyline( path, closed: true );
}
}

}
Avatar
richard_roth

Ah, that makes a lot of sense. I was looking at the code and couldn't figure out why it wasn't rendering. Thank you for all your help, it is greatly appreciated. I've reimplemented the code with polylines and they look much cleaner.

Video: Updated overworld UI

I also want to say that your shapes package is amazing and that I use it in all my projects. I hope you continue to develop it in the future. It is invaluable to my current project.

Avatar
Freya Holmér creator
Quote from richard_roth

Ah, that makes a lot of sense. I was looking at the code and couldn't figure out why it wasn't rendering. Thank you for all your help, it is greatly appreciated. I've reimplemented the code with polylines and they look much cleaner.

Video: Updated overworld UI

I also want to say that your shapes package is amazing and that I use it in all my projects. I hope you continue to develop it in the future. It is invaluable to my current project.

I'm happy you like it!