Skip to content

Instantly share code, notes, and snippets.

@Cyanilux
Last active September 5, 2025 15:01
Show Gist options
  • Save Cyanilux/4046e7bf3725b8f64761bf6cf54a16eb to your computer and use it in GitHub Desktop.
Save Cyanilux/4046e7bf3725b8f64761bf6cf54a16eb to your computer and use it in GitHub Desktop.

Revisions

  1. Cyanilux revised this gist Jun 2, 2021. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions GrassInstanced.hlsl
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // https://twitter.com/Cyanilux/status/1396848736022802435?s=20

    #ifndef GRASS_INSTANCED_INCLUDED
    #define GRASS_INSTANCED_INCLUDED

  2. Cyanilux created this gist May 24, 2021.
    99 changes: 99 additions & 0 deletions GrassInstanced.hlsl
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,99 @@
    #ifndef GRASS_INSTANCED_INCLUDED
    #define GRASS_INSTANCED_INCLUDED

    // ----------------------------------------------------------------------------------

    // Graph should contain Boolean Keyword, "PROCEDURAL_INSTANCING_ON", Global, Multi-Compile.
    // Must have two Custom Functions in vertex stage. One is used to attach this file (see Instancing_float below),
    // and another to set #pragma instancing_options :

    // It must use the String mode as this cannot be defined in includes.
    // Without this, you will get "UNITY_INSTANCING_PROCEDURAL_FUNC must be defined" Shader Error.
    /*
    Out = In;
    #pragma instancing_options procedural:vertInstancingSetup
    */
    // I've found this works fine, but it might make sense for the pragma to be defined outside of a function,
    // so could also use this slightly hacky method too
    /*
    Out = In;
    }
    #pragma instancing_options procedural:vertInstancingSetup
    void dummy(){
    */

    // ----------------------------------------------------------------------------------

    struct InstanceData {
    float4x4 m;
    };

    StructuredBuffer<InstanceData> _PerInstanceData;
    // Stores the matrices (and possibly other data) sent from the C# side via material.SetBuffer, in Start/OnEnable.
    // See : https://gist.github.com/Cyanilux/e7afdc5c65094bfd0827467f8e4c3c54

    #if UNITY_ANY_INSTANCING_ENABLED

    // Updates the unity_ObjectToWorld / unity_WorldToObject matrices so our matrix is taken into account

    // Based on :
    // https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.shadergraph/Editor/Generation/Targets/BuiltIn/ShaderLibrary/ParticlesInstancing.hlsl
    // and/or
    // https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/CGIncludes/UnityStandardParticleInstancing.cginc

    void vertInstancingMatrices(inout float4x4 objectToWorld, out float4x4 worldToObject) {
    InstanceData data = _PerInstanceData[unity_InstanceID];

    objectToWorld = mul(objectToWorld, data.m);

    // Transform matrix (override current)
    // I prefer keeping positions relative to the bounds passed into DrawMeshInstancedIndirect so use the above instead
    //objectToWorld._11_21_31_41 = float4(data.m._11_21_31, 0.0f);
    //objectToWorld._12_22_32_42 = float4(data.m._12_22_32, 0.0f);
    //objectToWorld._13_23_33_43 = float4(data.m._13_23_33, 0.0f);
    //objectToWorld._14_24_34_44 = float4(data.m._14_24_34, 1.0f);

    // Inverse transform matrix
    float3x3 w2oRotation;
    w2oRotation[0] = objectToWorld[1].yzx * objectToWorld[2].zxy - objectToWorld[1].zxy * objectToWorld[2].yzx;
    w2oRotation[1] = objectToWorld[0].zxy * objectToWorld[2].yzx - objectToWorld[0].yzx * objectToWorld[2].zxy;
    w2oRotation[2] = objectToWorld[0].yzx * objectToWorld[1].zxy - objectToWorld[0].zxy * objectToWorld[1].yzx;

    float det = dot(objectToWorld[0].xyz, w2oRotation[0]);
    w2oRotation = transpose(w2oRotation);
    w2oRotation *= rcp(det);
    float3 w2oPosition = mul(w2oRotation, -objectToWorld._14_24_34);

    worldToObject._11_21_31_41 = float4(w2oRotation._11_21_31, 0.0f);
    worldToObject._12_22_32_42 = float4(w2oRotation._12_22_32, 0.0f);
    worldToObject._13_23_33_43 = float4(w2oRotation._13_23_33, 0.0f);
    worldToObject._14_24_34_44 = float4(w2oPosition, 1.0f);
    }

    void vertInstancingSetup() {
    vertInstancingMatrices(unity_ObjectToWorld, unity_WorldToObject);
    }

    #endif

    // Shader Graph Functions

    // Obtain InstanceID. e.g. Can be used as a Seed into Random Range node to generate random data per instance
    void GetInstanceID_float(out float Out){
    Out = 0;
    #ifndef SHADERGRAPH_PREVIEW
    #if UNITY_ANY_INSTANCING_ENABLED
    Out = unity_InstanceID;
    #endif
    #endif
    }

    // Just passes the position through, allows us to actually attach this file to the graph.
    // Should be placed somewhere in the vertex stage, e.g. right before connecting the object space position.
    void Instancing_float(float3 Position, out float3 Out){
    Out = Position;
    }

    #endif