Transform point array in Geometry shader to respect transforms rotation and scale.

Hi,

I’ve got a geometry shader rendering a compute buffer full of points in local space.

I’m transforming their position into world space by just adding the position of the object to the local position. I’m just wondering if there are any built in functions that will handle the bulk of this work for me before I go implement it the long way.

For reference this is the shader so far: Next step is to add rotation and scale transformations in the Vert function. Then adding in the geometry function to render more than just a point at each point.

Shader "Geometry/PointGeometryShader"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma target 5.0

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct data
            {
                float3 pos;
            };

            StructuredBuffer<data> buf_Points;
            float3 _worldPos;

            struct ps_input
            {
                float4 pos : SV_POSITION;
                float3 color : COLOR0;
            };

            ps_input vert(uint id : SV_VertexID)
            {
                ps_input o;
                float3 worldPos = buf_Points[id].pos + _worldPos;
                o.pos = UnityObjectToClipPos(float4(worldPos, 1.0f));
                o.color = worldPos;
                return o;
            }

            float4 frag(ps_input i) : COLOR
            {
                float4 col = float4(i.color, 1);

                if (i.color.r <= 0 && i.color.g <= 0 && i.color.b <= 0)
                    col.rgb = float3(0, 1, 1);

                //col = float4(1,1,1,1);
                return col;
            }
            ENDCG
        }
    }
}

So I’ve got it working but I know its not optimal.

For some reason the translationMatrix is not applying which I why I’m resorting to

                transformedPos.xyz += _worldPos;

If anyone can point at a more consice or efficient way to write this or help me understand why my translation matrix isn’t applying even though the scale and rotation matrices are it would be much appreciated.

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Geometry/PointGeometryShader"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma target 5.0

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct data
            {
                float3 pos;
            };

            StructuredBuffer<data> buf_Points;
            float3 _worldPos;
            float3 _Scale;
            float3 _Rotation;

            struct ps_input
            {
                float4 pos : SV_POSITION;
                float3 color : COLOR0;
            };

            ps_input vert(uint id : SV_VertexID)
            {
                float4x4 translationMatrix =
                {
                    1, 0, 0, _worldPos.x,
                    0, 1, 0, _worldPos.y,
                    0, 0, 1, _worldPos.x,
                    0, 0, 0, 1
                };
               
                float4x4 scaleMatrix =
                {
                    _Scale.x, 0            , 0            , 0,
                    0        , _Scale.y    , 0            , 0,
                    0        , 0            , _Scale.z    , 0,
                    0        , 0            , 0            , 1
                };

                float radX = radians(_Rotation.x);
                float radY = radians(_Rotation.y);
                float radZ = radians(_Rotation.z);

                float sinX, cosX;
                sincos(radX, sinX, cosX);

                float sinY, cosY;
                sincos(radY, sinY, cosY);

                float sinZ, cosZ;
                sincos(radZ, sinZ, cosZ);

                float4x4 rotationMatrix =
                {
                    cosY * cosZ                        , -cosY * sinZ                        , sinY            ,        0,
                    cosX * sinZ + sinX * sinY * cosZ, cosX * cosZ - sinX * sinY * sinZ    , -sinX * cosY    ,        0,
                    sinX * sinZ - cosX * sinY * cosZ, sinX * cosZ + cosX * sinY * sinZ    , cosX * cosY    ,        0,
                    0                                , 0                                    , 0                ,        1,
                };

                float4x4 transformationMatrix = mul(scaleMatrix, rotationMatrix);
                transformationMatrix = mul(transformationMatrix, translationMatrix);

                float3 worldPos = buf_Points[id].pos;

                float4 localPos = float4(worldPos.x, worldPos.y, worldPos.z, 1);

                float4 transformedPos = mul(localPos, transformationMatrix);

                transformedPos.xyz += _worldPos;

                ps_input o;
                o.pos = UnityObjectToClipPos(transformedPos);
                o.color = transformedPos;
                return o;
            }

            float4 frag(ps_input i) : COLOR
            {
                float4 col = float4(i.color, 1);

                if (i.color.r <= 0 && i.color.g <= 0 && i.color.b <= 0)
                    col.rgb = float3(0, 1, 1);

                //col = float4(1,1,1,1);
                return col;
            }
            ENDCG
        }
    }
}

Hi!
First, why don’t you build your transform in C# and pass it to your shader?
Second, your matrix multiplications happen on the wrong side. It’s not the same to do (float4 * float4x4) and (float4x4 * float4).

Constructing the full transform in the shader is extremely inefficient. While applying the transform matrix itself to a position is fairly cheap, the transform matrix should be passed in as a Matrix4x4 from script like @aleksandrk suggested and not constructed from the scale, euler rotation, and translation. Otherwise you’d be better off not using a transform matrix at all and apply the scale, rotation, and translation separately. Also you should probably pass the rotation as a quaternion in that case too as any way you’d go about rotating something with Euler angles is going to require a lot of sines and cosines.

That makes perfect sense.

Reordering the matrix multiplication fixed my translation issue.

I’m going to go rewrite the matrix building in c#.

Thanks so much guys.

That worked perfectly. I’m still creating the rotation matrix from the euler angles rather than the quaternion.
I think I could use this to use the quaternion directly but that can be an efficiency for later if I need it.

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Geometry/PointGeometryShader"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma target 5.0

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct data
            {
                float4 col;
                float3 pos;
            };

            StructuredBuffer<data> buf_Points;

            float3 _worldPos;
            float3 _Scale;
            float3 _Rotation;

            float4x4 transformationMatrix;

            struct ps_input
            {
                float4 pos : SV_POSITION;
                float4 col : COLOR0;
            };

            ps_input vert(uint id : SV_VertexID)
            {
                float3 worldPos = buf_Points[id].pos;

                float4 localPos = float4(worldPos.x, worldPos.y, worldPos.z, 1);

                float4 transformedPos = mul( transformationMatrix, localPos);

                ps_input o;
                o.pos = UnityObjectToClipPos(transformedPos);
                o.col = buf_Points[id].col;
                return o;
            }

            float4 frag(ps_input i) : COLOR
            {
                return i.col;
            }
            ENDCG
        }
    }
}

And to calculate the matrix:

public static Matrix4x4 CalculateTransformMatrix(Vector3 position, Quaternion rotation, Vector3 scale)
    {

        Matrix4x4 translationMatrix = new Matrix4x4();
        translationMatrix.SetRow(0, new Vector4(1f, 0f, 0f, position.x));
        translationMatrix.SetRow(1, new Vector4(0f, 1f, 0f, position.y));
        translationMatrix.SetRow(2, new Vector4(0f, 0f, 1f, position.z));
        translationMatrix.SetRow(3, new Vector4(0f, 0f, 0f, 1f));

        Matrix4x4 scaleMatrix = new Matrix4x4();
        scaleMatrix.SetRow(0, new Vector4(scale.x, 0f, 0f, 0f));
        scaleMatrix.SetRow(1, new Vector4(0f, scale.y, 0f, 0f));
        scaleMatrix.SetRow(2, new Vector4(0f, 0f, scale.z, 0f));
        scaleMatrix.SetRow(3, new Vector4(0f, 0f, 0f, 1f));

        Vector3 eulerRotation = rotation.eulerAngles;

        float radX = eulerRotation.x * Mathf.Deg2Rad;
        float radY = eulerRotation.y * Mathf.Deg2Rad;
        float radZ = eulerRotation.z * Mathf.Deg2Rad;
        float sinX = Mathf.Sin(radX);
        float cosX = Mathf.Cos(radX);
        float sinY = Mathf.Sin(radY);
        float cosY = Mathf.Cos(radY);
        float sinZ = Mathf.Sin(radZ);
        float cosZ = Mathf.Cos(radZ);

        Matrix4x4 rotationMatrix = new Matrix4x4();
        rotationMatrix.SetColumn(0, new Vector4(
            cosY * cosZ,
            cosX * sinZ + sinX * sinY * cosZ,
            sinX * sinZ - cosX * sinY * cosZ,
            0f
        ));
        rotationMatrix.SetColumn(1, new Vector4(
            -cosY * sinZ,
            cosX * cosZ - sinX * sinY * sinZ,
            sinX * cosZ + cosX * sinY * sinZ,
            0f
        ));
        rotationMatrix.SetColumn(2, new Vector4(
            sinY,
            -sinX * cosY,
            cosX * cosY,
            0f
        ));
        rotationMatrix.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));

        Matrix4x4 transformationMatrix = scaleMatrix;

        transformationMatrix = rotationMatrix * transformationMatrix;
        transformationMatrix = translationMatrix * transformationMatrix;

        return transformationMatrix;
    }

There’s no need to construct the matrix manually like that from script. Unity provides functions to do that.

Oh, one other note. A Geometry Shader is a very specific thing. It is another shader stage that can optionally occur between the vertex shader stage and fragment shader stage. What you have is just a vertex and fragment shader.