pixel perfect 2d Sprite in 3d world with isometric camera, how to do it right?

I’m trying to use 2d Sprites inside a 3d world and having some problems getting them just right… I already tried 2 aproaches to have the Sprite inside the world correctly but both have their own problems.

  1. I tried to rotate the Sprite on the x-axis just into the angle my camera is, this works nice and results in a pixel perfect sprite if camera is setup correctly… until you set something behind the character like in the picutre below…


    I tried to use the sortinglayer on the ground and sprite without any effects… so I went to another solution

  2. Instead of rotating the sprite on the x-axis I used a scale of 1.1657f (dimetric angle…) on the y-axis resulting in everythin working as expected, expect the sprite starts to jiggle while moving the camera and its far away from beeing pixel perfect…

so i want to stick to methode 1. but I don’t know how to prevent the sprite begin inside the stuff that is “behind” it…

Btw i know on both screenshots the red rectangular is not pixel perfect ^^

What you want to achieve is doable using a custom projection matrix. The one we used must be parallelepipedoidal, as mentioned in my comment earlier. To picture it, first look at an orthogonal camera frustrum in Unity (that white box that appears in front of the camera when you select it and its projection setting is set to “orthogonal”). Now, imagine you take the far side of that box and drag it down. Here is a quick drawing illustrating the concept:

92100-projection.png

You have a 3d object (blue square) and a sprite (blue line) illustrated. The dotted grey lines are how vertices are projected upon the screen depending on if you use an orthogonal or custom projection matrix. As you can see, with an ortogonal matrix, you wouldn’t see the floor at all, or any flat surface parallel with the X-Z plane. With the custom matrix, not only you can see those surfaces (notice how the 3d object is projected) but every vertical surface is still seen as vertical. This means that your world can be as it is supposed to be, without strange rotation hacks, and you can still see stuff as you want to see it.

The kind of result I got with three planes and a sprite (purposefully going through planes to show it has normal rotation values):

92101-projection-test.png

And this would be my projection matrix for the above screenshot:

0.18    0       0      0
0       0.1     0      0
0      -0.04   -0.02   0
0       0       0      1

You can instantiate a Matrix4x4, use SetRow(), SetColumn() or give your matrix the [SerializeField] attribute and assign it these values, then tweak them to get the intended result. Then you can assign that matrix to the projectionMatrix property of your camera.

The key value in the matrix is that -0.04. This is what allows for an isometric view with a perfectly horizontal camera. It’s a way of using the Z value of a given point to affect the Y value where it will be projected.

[EDIT]

I made these two simple files you could use when you are ready.
The first one is to be put on your camera game object as a component. It exposes a matrix and updates the camera’s projection matrix every frame.

ProjectionMatrixTester.cs

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class ProjectionMatrixTester : MonoBehaviour
{
    Camera camera;
    [SerializeField] Matrix4x4 matrix;

    void Start()
    {
        camera = GetComponent<Camera>();
        InitializeMatrix();
    }

    void Update()
    {
        camera.projectionMatrix = matrix;
    }

    void InitializeMatrix()
    {
        matrix.SetRow(0, new Vector4(0.09f, 0, 0, 0));
        matrix.SetRow(1, new Vector4(0, 0.05f, -0.02f, 0));
        matrix.SetRow(2, new Vector4(0, 0, -0.02f, -1));
        matrix.SetRow(3, new Vector4(0, 0, 0, 1));
    }
}

This second file changes the way the matrix is shown in the inspector. It shows it as it is supposed to be. It has to be placed in a folder named Editor.

MatrixPropertyDrawer.cs

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(Matrix4x4))]
public class MatrixPropertyDrawer : PropertyDrawer
{
    const float CELL_WIDTH = 48;
    const float CELL_HEIGHT = 16;

    Rect position;
    SerializedProperty property;
    GUIContent label;

    public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent lab)
    {
        position = pos;
        property = prop;
        label = lab;

        EditorGUI.BeginProperty(position, label, property);

        DrawLabel();
        DrawMatrix();

        EditorGUI.EndProperty();
    }

    public override float GetPropertyHeight(SerializedProperty p, GUIContent l)
    {
        return 5 * CELL_HEIGHT;
    }

    void DrawLabel()
    {
        EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
        position.y += CELL_HEIGHT;
        EditorGUI.indentLevel = 0;
    }

    void DrawMatrix()
    {
        for (int r = 0; r < 4; r++)
        {
            for (int c = 0; c < 4; c++)
            {
                DrawCell(c, r);
            }
        }
    }

    void DrawCell(int column, int row)
    {
        Vector2 cellPos = position.position;
        cellPos.x += CELL_WIDTH * column;
        cellPos.y += CELL_HEIGHT * row;
        EditorGUI.PropertyField(
            new Rect(cellPos, new Vector2(CELL_WIDTH, CELL_HEIGHT)),
            property.FindPropertyRelative("e" + column + row),
            GUIContent.none
        );
    }
}