Infinite Lands vegetation requires a custom configuration to allow trees, rocks, and grass to appear via GPU Instancing. This means that there are some steps required to make a shader work.
Generally, it is recommended to use the preexisting shaders, or as a starting point, use the Minimal Shader.
However, if you’d like to create your own shader or modify an existing one, here’s the workflow required for them to work.
With Shader Graph
To make any shader at least render on screen you need the basic Vegetation Data node. Connecting Object Position to the Vertex/Position input will automatically make the vegetation load with the Vegetation Renderer.
Minimal Node
The other fields give information about the asset such as:
- Lod_Transition returns a value between 0 to 1 informing how transparent should the mesh be.
- Texture_Index is the exact texture index that should be sampled to get the ground color
Next you will have to create a boolean keyword and set it as shown in the picture below. Note the reference set to PROCEDURAL_INSTANCING_ON.
Keyword
With those two additions, and creating the proper Vegetation Asset and adding it to the graph, we will start seeing our new asset across the terrain.
Vegetation on screen
Painting the asset with the ground texture
All the textures used in the shaders will follow the name defined by the texture asset in code. You can check out which type of textures are available by going into the inspector of the graph.
Textures setup
Therefore, to load the ground albedo texture we will have to create a Texture Array variable with the correct name, and connect the Texture Index to it:


Creating the basic structure for a sampling the ground color
Final result with asset painted
With Code
First we will need to add the next lines to include the necessary files for the shader to work with Infinite Lands.
#pragma multi_compile_instancing
#include_with_pragmas "Packages/com.sapra.infinitelands/Runtime/Resources/Include/GPUInstancing.hlsl"
#pragma instancing_options procedural:IL_Initialize
Then, inside the appdata struct, we will add the Instance_ID input.
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
And finally, inside our vertex shader, we will transfer that Instance ID via the method UNITY_SETUP_INSTANCE_ID(v);
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
Here is a default Unlit Shader for Built-in pipeline that with this basic modifications.
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#pragma multi_compile_instancing
#include_with_pragmas "Packages/com.sapra.infinitelands/Runtime/Resources/Include/GPUInstancing.hlsl"
#pragma instancing_options procedural:IL_Initialize
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}