summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-18 14:59:59 -0800
committeryum <yum.food.vr@gmail.com>2026-01-18 14:59:59 -0800
commit6504b2c4631bab477838548167b88c1052eac263 (patch)
treee7d3aa4d19a21e624927a6771e2aeb4815af9393
parentfbe7ed126883b0c4a1d5115e5c953bc244bc0214 (diff)
Grass: add crude instancing code
-rwxr-xr-x3ner.cginc7
-rwxr-xr-x3ner.shader13
-rw-r--r--Scripts/GrassGridBlit.shader77
-rwxr-xr-xScripts/InstanceGrass.asset1265
-rwxr-xr-xScripts/InstanceGrass.cs343
-rwxr-xr-xScripts/InstanceGrass.cs.meta11
-rwxr-xr-xfeatures.cginc4
-rwxr-xr-xglobals.cginc10
-rwxr-xr-xinstancing.cginc150
9 files changed, 1879 insertions, 1 deletions
diff --git a/3ner.cginc b/3ner.cginc
index 02081a9..e675bc2 100755
--- a/3ner.cginc
+++ b/3ner.cginc
@@ -15,6 +15,7 @@
#include "lighting.cginc"
#include "globals.cginc"
#include "interpolators.cginc"
+#include "instancing.cginc"
#include "ray_marching.cginc"
#include "vertex.cginc"
#include "impostor.cginc"
@@ -30,6 +31,9 @@ v2f vert(appdata v) {
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
+ // Apply instance texture offset transformation if enabled
+ instancing_vert(v);
+
#if defined(_IMPOSTORS)
impostor_vert(v.vertex.xyz);
#endif
@@ -262,6 +266,9 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace
return 0;
#endif
+ // Apply instancing effects (e.g., distance culling)
+ instancing_frag(i);
+
#if defined(_RAY_MARCHING)
const bool is_fragment = true;
ray_march(i, is_fragment);
diff --git a/3ner.shader b/3ner.shader
index dce0e18..e411ada 100755
--- a/3ner.shader
+++ b/3ner.shader
@@ -426,12 +426,23 @@ Shader "yum_food/3ner"
[DoNotAnimate][HideInInspector] Instancing ("Instancing", Float) = 0
//ifex Instancing==0
[HideInInspector] m_start_Instancing("Instancing", Float) = 0
- //ifex _Instance_Distance_Culling_Enabled
+ //ifex _Instance_Distance_Culling_Enabled==0
[HideInInspector] m_start_Instance_Distance_Culling("Distance Culling", Float) = 0
[ThryToggle(_INSTANCE_DISTANCE_CULLING)] _Instance_Distance_Culling_Enabled("Enable", Float) = 0
_Instance_Distance_Culling_Distance("Distance (m)", Float) = 10
[HideInInspector] m_end_Instance_Distance_Culling("Instance Distance Culling", Float) = 0
//endex
+ //ifex _Instance_Texture_Offset_Enabled==0
+ [HideInInspector] m_start_Instance_Texture_Offset("Texture Offset", Float) = 0
+ [ThryToggle(_INSTANCE_TEXTURE_OFFSET)] _Instance_Texture_Offset_Enabled("Enable", Float) = 0
+ _Instance_Texture_Offset_Data_Tex("Instance Data Texture", 2D) = "black" {}
+ _Instance_Texture_Offset_Cell_Dimensions("Cell Dimensions", Vector) = (1, 1, 1, 0)
+ _Instance_Texture_Offset_Angle_Randomization("Angle Randomization", Vector) = (0, 0, 0, 0)
+ _Instance_Texture_Offset_Scale_Randomization("Scale Randomization", Float) = 0
+ _Instance_Texture_Offset_Base_Scale("Base Scale", Vector) = (1, 1, 1, 0)
+ _Instance_Texture_Offset_Base_Rotation("Base Rotation (Quaternion)", Vector) = (0, 0, 0, 1)
+ [HideInInspector] m_end_Instance_Texture_Offset("Instance Texture Offset", Float) = 0
+ //endex
[HideInInspector] m_end_Instancing("Instancing", Float) = 0
//endex
diff --git a/Scripts/GrassGridBlit.shader b/Scripts/GrassGridBlit.shader
new file mode 100644
index 0000000..f4dc872
--- /dev/null
+++ b/Scripts/GrassGridBlit.shader
@@ -0,0 +1,77 @@
+Shader "yum_food/GrassGridBlit"
+{
+ Properties
+ {
+ }
+ SubShader
+ {
+ Tags { "RenderType"="Opaque" }
+ LOD 100
+
+ Pass
+ {
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+
+ #include "UnityCG.cginc"
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ float2 uv : TEXCOORD0;
+ };
+
+ struct v2f
+ {
+ float2 uv : TEXCOORD0;
+ float4 vertex : SV_POSITION;
+ };
+
+ // Grid parameters
+ float3 _PlayerGridPos; // Player's grid cell position (x, y, z)
+ int3 _GridCount; // Number of instances along each axis
+ int3 _GridHalf; // Half dimensions for centering around player
+ int2 _TexDimensions; // Texture width and height
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.vertex = UnityObjectToClipPos(v.vertex);
+ o.uv = v.uv;
+ return o;
+ }
+
+ float4 frag (v2f i) : SV_Target
+ {
+ // Convert UV to pixel coordinates
+ int2 pixelCoord = int2(i.uv.x * _TexDimensions.x, i.uv.y * _TexDimensions.y);
+
+ // Convert pixel coordinate to instance index
+ int instanceID = pixelCoord.x + pixelCoord.y * _TexDimensions.x;
+
+ // Calculate total valid instances
+ int totalInstances = _GridCount.x * _GridCount.y * _GridCount.z;
+
+ // If this pixel is beyond valid instances, output invalid marker
+ if (instanceID >= totalInstances) {
+ return float4(0, 0, 0, -1); // Alpha = -1 marks invalid
+ }
+
+ // Convert instance index to local grid coordinates (0 to GridCount-1)
+ int localX = instanceID % _GridCount.x;
+ int localY = (instanceID / _GridCount.x) % _GridCount.y;
+ int localZ = instanceID / (_GridCount.x * _GridCount.y);
+
+ // Convert to world grid coordinates centered around player
+ int worldX = _PlayerGridPos.x - _GridHalf.x + localX;
+ int worldY = _PlayerGridPos.y - _GridHalf.y + localY;
+ int worldZ = _PlayerGridPos.z - _GridHalf.z + localZ;
+
+ // Store world grid coordinates in RGB channels, alpha = 0 marks valid
+ return float4(worldX, worldY, worldZ, 0);
+ }
+ ENDCG
+ }
+ }
+}
diff --git a/Scripts/InstanceGrass.asset b/Scripts/InstanceGrass.asset
new file mode 100755
index 0000000..3dd3bc4
--- /dev/null
+++ b/Scripts/InstanceGrass.asset
@@ -0,0 +1,1265 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: c333ccfdd0cbdbc4ca30cef2dd6e6b9b, type: 3}
+ m_Name: InstanceGrass
+ m_EditorClassIdentifier:
+ serializedUdonProgramAsset: {fileID: 11400000, guid: db135bbe94c59ac45866d438c8479426,
+ type: 2}
+ udonAssembly:
+ assemblyError:
+ sourceCsScript: {fileID: 11500000, guid: 8dd03d28500e21640aad6c2650091d4c, type: 3}
+ scriptVersion: 2
+ compiledVersion: 2
+ behaviourSyncMode: 0
+ hasInteractEvent: 0
+ scriptID: -6812178109815128089
+ serializationData:
+ SerializedFormat: 2
+ SerializedBytes:
+ ReferencedUnityObjects: []
+ SerializedBytesString:
+ Prefab: {fileID: 0}
+ PrefabModificationsReferencedUnityObjects: []
+ PrefabModifications: []
+ SerializationNodes:
+ - Name: fieldDefinitions
+ Entry: 7
+ Data: 0|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[UdonSharp.Compiler.FieldDefinition,
+ UdonSharp.Editor]], mscorlib
+ - Name: comparer
+ Entry: 7
+ Data: 1|System.Collections.Generic.GenericEqualityComparer`1[[System.String,
+ mscorlib]], mscorlib
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 12
+ Data: 23
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: prefab_
+ - Name: $v
+ Entry: 7
+ Data: 2|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: prefab_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 3|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.GameObject, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 3
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: true
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 4|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 1
+ - Name:
+ Entry: 7
+ Data: 5|UnityEngine.SerializeField, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: density_
+ - Name: $v
+ Entry: 7
+ Data: 6|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: density_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 7|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: System.Single, mscorlib
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 7
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: true
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 8|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 1
+ - Name:
+ Entry: 7
+ Data: 9|UnityEngine.SerializeField, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: extent_meters_
+ - Name: $v
+ Entry: 7
+ Data: 10|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: extent_meters_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 11|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.Vector3, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: true
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 12|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 1
+ - Name:
+ Entry: 7
+ Data: 13|UnityEngine.SerializeField, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: angle_randomization_
+ - Name: $v
+ Entry: 7
+ Data: 14|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: angle_randomization_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: true
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 15|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 1
+ - Name:
+ Entry: 7
+ Data: 16|UnityEngine.SerializeField, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: scale_randomization_
+ - Name: $v
+ Entry: 7
+ Data: 17|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: scale_randomization_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 7
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 7
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: true
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 18|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 1
+ - Name:
+ Entry: 7
+ Data: 19|UnityEngine.SerializeField, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: max_grid_size_
+ - Name: $v
+ Entry: 7
+ Data: 20|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: max_grid_size_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 21|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.Vector3Int, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 21
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: true
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 22|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 1
+ - Name:
+ Entry: 7
+ Data: 23|UnityEngine.SerializeField, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: blit_material_
+ - Name: $v
+ Entry: 7
+ Data: 24|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: blit_material_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 25|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.Material, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 25
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: true
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 26|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 1
+ - Name:
+ Entry: 7
+ Data: 27|UnityEngine.SerializeField, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: mesh_
+ - Name: $v
+ Entry: 7
+ Data: 28|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: mesh_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 29|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.Mesh, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 29
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 30|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: instance_material_
+ - Name: $v
+ Entry: 7
+ Data: 31|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: instance_material_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 25
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 25
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 32|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: base_transform_
+ - Name: $v
+ Entry: 7
+ Data: 33|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: base_transform_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 34|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.Transform, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 34
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 35|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: cell_dim_
+ - Name: $v
+ Entry: 7
+ Data: 36|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: cell_dim_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 37|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: inv_cell_dim_
+ - Name: $v
+ Entry: 7
+ Data: 38|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: inv_cell_dim_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 39|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: count_
+ - Name: $v
+ Entry: 7
+ Data: 40|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: count_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 21
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 21
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 41|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: total_instances_
+ - Name: $v
+ Entry: 7
+ Data: 42|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: total_instances_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 43|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: System.Int32, mscorlib
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 43
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 44|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: initialized_
+ - Name: $v
+ Entry: 7
+ Data: 45|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: initialized_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 46|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: System.Boolean, mscorlib
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 46
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 47|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: density_live_
+ - Name: $v
+ Entry: 7
+ Data: 48|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: density_live_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 7
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 7
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 49|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: extent_meters_live_
+ - Name: $v
+ Entry: 7
+ Data: 50|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: extent_meters_live_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 51|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: angle_randomization_live_
+ - Name: $v
+ Entry: 7
+ Data: 52|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: angle_randomization_live_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 11
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 53|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: scale_randomization_live_
+ - Name: $v
+ Entry: 7
+ Data: 54|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: scale_randomization_live_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 7
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 7
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 55|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: instance_data_tex_
+ - Name: $v
+ Entry: 7
+ Data: 56|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: instance_data_tex_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 57|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.RenderTexture, UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 57
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 58|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: identity_transforms_
+ - Name: $v
+ Entry: 7
+ Data: 59|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: identity_transforms_
+ - Name: <UserType>k__BackingField
+ Entry: 7
+ Data: 60|System.RuntimeType, mscorlib
+ - Name:
+ Entry: 1
+ Data: UnityEngine.Matrix4x4[], UnityEngine.CoreModule
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 60
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 61|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: tex_width_
+ - Name: $v
+ Entry: 7
+ Data: 62|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: tex_width_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 43
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 43
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 63|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 7
+ Data:
+ - Name: $k
+ Entry: 1
+ Data: tex_height_
+ - Name: $v
+ Entry: 7
+ Data: 64|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
+ - Name: <Name>k__BackingField
+ Entry: 1
+ Data: tex_height_
+ - Name: <UserType>k__BackingField
+ Entry: 9
+ Data: 43
+ - Name: <SystemType>k__BackingField
+ Entry: 9
+ Data: 43
+ - Name: <SyncMode>k__BackingField
+ Entry: 7
+ Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
+ - Name:
+ Entry: 6
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name: <IsSerialized>k__BackingField
+ Entry: 5
+ Data: false
+ - Name: _fieldAttributes
+ Entry: 7
+ Data: 65|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
+ - Name:
+ Entry: 12
+ Data: 0
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 8
+ Data:
+ - Name:
+ Entry: 13
+ Data:
+ - Name:
+ Entry: 8
+ Data:
diff --git a/Scripts/InstanceGrass.cs b/Scripts/InstanceGrass.cs
new file mode 100755
index 0000000..3c0d8f6
--- /dev/null
+++ b/Scripts/InstanceGrass.cs
@@ -0,0 +1,343 @@
+using UdonSharp;
+using UnityEngine;
+using VRC.SDKBase;
+
+#if UNITY_EDITOR && !COMPILER_UDONSHARP
+using UnityEditor;
+using System.IO;
+#endif
+
+public class InstanceGrass : UdonSharpBehaviour
+{
+ [SerializeField] public GameObject prefab_;
+ // The density of instances, in instances per meter.
+ [SerializeField] public float density_;
+ // The extent along each cardinal axis where instances will be rendered, in
+ // meters. I.e. render inside a cube with edges this long.
+ [SerializeField] public Vector3 extent_meters_;
+ [SerializeField] public Vector3 angle_randomization_;
+ [SerializeField] public float scale_randomization_;
+
+ // GPU mode settings
+ [SerializeField] public Vector3Int max_grid_size_ = new Vector3Int(512, 1, 512); // Maximum grid dimensions for GPU mode
+ [SerializeField] public Material blit_material_; // Auto-generated in editor, set by custom editor
+
+ private Mesh mesh_;
+ private Material instance_material_; // Extracted from prefab
+ private Transform base_transform_;
+ private Vector3 cell_dim_;
+ private Vector3 inv_cell_dim_;
+ private Vector3Int count_;
+ private int total_instances_;
+ private bool initialized_;
+
+ // Track fields to detect runtime changes.
+ private float density_live_;
+ private Vector3 extent_meters_live_;
+ private Vector3 angle_randomization_live_;
+ private float scale_randomization_live_;
+
+ // GPU-specific resources
+ private RenderTexture instance_data_tex_;
+ private Matrix4x4[] identity_transforms_; // All identity matrices
+ private int tex_width_;
+ private int tex_height_;
+
+ private void Init() {
+ // Extract components from prefab.
+ if (prefab_ != null) {
+ MeshFilter mesh_filter = prefab_.GetComponent<MeshFilter>();
+ if (mesh_filter == null && prefab_.transform.childCount > 0) {
+ mesh_filter = prefab_.GetComponentInChildren<MeshFilter>();
+ }
+ if (mesh_filter != null) {
+ mesh_ = mesh_filter.sharedMesh;
+ if (mesh_ == null) {
+ mesh_ = mesh_filter.mesh;
+ }
+ } else {
+ Debug.LogError("[Grass::Debug] Could not find MeshFilter on prefab or children.");
+ }
+
+ MeshRenderer mesh_renderer = prefab_.GetComponent<MeshRenderer>();
+ if (mesh_renderer == null && prefab_.transform.childCount > 0) {
+ mesh_renderer = prefab_.GetComponentInChildren<MeshRenderer>();
+ }
+ if (mesh_renderer != null) {
+ Material[] materials = mesh_renderer.sharedMaterials;
+ if (materials != null && materials.Length > 0) {
+ instance_material_ = materials[0];
+ } else {
+ Debug.LogError("[Grass::Debug] MeshRenderer has no materials.");
+ }
+ } else {
+ Debug.LogError("[Grass::Debug] Could not find MeshRenderer on prefab.");
+ }
+
+ base_transform_ = prefab_.transform;
+ Debug.Log($"[Grass::Debug] Prefab transform - pos:{base_transform_.position}, rot:{base_transform_.rotation.eulerAngles}, scale:{base_transform_.localScale}");
+ } else {
+ Debug.LogError("[Grass::Debug] prefab is null in Init().");
+ }
+
+ density_ = Mathf.Max(1e-6f, density_);
+ extent_meters_ = Vector3.Max(Vector3.one * 1e-6f, extent_meters_);
+ angle_randomization_ = new Vector3(
+ Mathf.Clamp(angle_randomization_.x, 0f, 180f),
+ Mathf.Clamp(angle_randomization_.y, 0f, 180f),
+ Mathf.Clamp(angle_randomization_.z, 0f, 180f));
+
+ // Use max_grid_size_ directly for GPU mode
+ count_ = Vector3Int.Min(max_grid_size_, new Vector3Int(
+ Mathf.Max(1, Mathf.RoundToInt(density_ * extent_meters_.x)),
+ Mathf.Max(1, Mathf.RoundToInt(density_ * extent_meters_.y)),
+ Mathf.Max(1, Mathf.RoundToInt(density_ * extent_meters_.z))));
+
+ cell_dim_ = new Vector3(
+ extent_meters_.x / count_.x,
+ extent_meters_.y / count_.y,
+ extent_meters_.z / count_.z);
+ inv_cell_dim_ = new Vector3(
+ 1f / cell_dim_.x,
+ 1f / cell_dim_.y,
+ 1f / cell_dim_.z);
+
+ total_instances_ = count_.x * count_.y * count_.z;
+
+ // Calculate texture dimensions (power of 2, minimum 256x256)
+ int min_tex_size = Mathf.CeilToInt(Mathf.Sqrt(total_instances_));
+ tex_width_ = Mathf.Max(256, Mathf.NextPowerOfTwo(min_tex_size));
+ tex_height_ = tex_width_;
+
+ // Create instance data texture
+ if (instance_data_tex_ != null) {
+ instance_data_tex_.Release();
+ }
+ instance_data_tex_ = new RenderTexture(tex_width_, tex_height_, 0, RenderTextureFormat.ARGBFloat);
+ instance_data_tex_.filterMode = FilterMode.Point;
+ instance_data_tex_.Create();
+
+ // Create transform array with encoded instance IDs - must match actual draw count
+ int actual_draw_count = tex_width_ * tex_height_;
+ if (identity_transforms_ == null || identity_transforms_.Length != actual_draw_count) {
+ identity_transforms_ = new Matrix4x4[actual_draw_count];
+ for (int i = 0; i < actual_draw_count; i++) {
+ // Encode instance ID in the matrix translation component
+ // We'll put the ID in the X component, which the shader will read
+ Matrix4x4 m = Matrix4x4.identity;
+ m.m03 = i; // Store instance ID in translation.x
+ identity_transforms_[i] = m;
+ }
+ }
+
+ if (mesh_ != null) {
+ // Set huge bounds so culling doesn't interfere
+ mesh_.bounds = new Bounds(Vector3.zero, Vector3.one * 10000f);
+ }
+
+ density_live_ = density_;
+ extent_meters_live_ = extent_meters_;
+ angle_randomization_live_ = angle_randomization_;
+ scale_randomization_live_ = scale_randomization_;
+
+ Debug.Log($"[Grass::Debug] Init: density={density_}, extent={extent_meters_}, count={count_}, cell_dim={cell_dim_}, instances={total_instances_}, tex={tex_width_}x{tex_height_}, scale={base_transform_.localScale}, rot={base_transform_.localRotation.eulerAngles}");
+ Debug.Log($"[Grass::Debug] Init details: count_.x={count_.x}, count_.y={count_.y}, count_.z={count_.z}, extent.x={extent_meters_.x}, extent.z={extent_meters_.z}");
+ }
+
+ private bool Valid() {
+ if (prefab_ == null) {
+ Debug.LogError("[Grass::Debug] prefab is null.");
+ return false;
+ }
+ if (mesh_ == null) {
+ Debug.LogError("[Grass::Debug] mesh is null.");
+ return false;
+ }
+ if (instance_material_ == null) {
+ Debug.LogError("[Grass::Debug] instance_material is null.");
+ return false;
+ }
+ if (blit_material_ == null) {
+ Debug.LogError("[Grass::Debug] blit_material is null (failed to generate).");
+ return false;
+ }
+ if (instance_data_tex_ == null) {
+ Debug.LogError("[Grass::Debug] instance_data_tex is null.");
+ return false;
+ }
+ return true;
+ }
+
+ void Start() {
+ Init();
+ if (!Valid()) {
+ return;
+ }
+
+ initialized_ = true;
+ Debug.Log($"[Grass::Debug] GPU mode initialized");
+ }
+
+ void Update() {
+ // Reinitialize if any config changed at runtime.
+ if (density_ != density_live_ ||
+ extent_meters_ != extent_meters_live_ ||
+ angle_randomization_ != angle_randomization_live_ ||
+ scale_randomization_ != scale_randomization_live_) {
+ Init();
+ initialized_ = false;
+ }
+
+ if (!Valid()) {
+ return;
+ }
+
+ VRCPlayerApi lcl_player = Networking.LocalPlayer;
+ Vector3 player_pos = lcl_player.GetPosition();
+ int grid_x = Mathf.FloorToInt(player_pos.x * inv_cell_dim_.x);
+ int grid_y = Mathf.FloorToInt(player_pos.y * inv_cell_dim_.y);
+ int grid_z = Mathf.FloorToInt(player_pos.z * inv_cell_dim_.z);
+
+ int half_x = count_.x / 2;
+ int half_y = count_.y / 2;
+ int half_z = count_.z / 2;
+
+ // Update blit material properties
+ blit_material_.SetVector("_PlayerGridPos", new Vector3(grid_x, grid_y, grid_z));
+ blit_material_.SetVector("_GridCount", new Vector3(count_.x, count_.y, count_.z));
+ blit_material_.SetVector("_GridHalf", new Vector3(half_x, half_y, half_z));
+ blit_material_.SetVector("_TexDimensions", new Vector2(tex_width_, tex_height_));
+
+ // Blit to generate instance data texture
+ VRCGraphics.Blit(null, instance_data_tex_, blit_material_);
+
+ // Update instance material properties
+ instance_material_.SetTexture("_Instance_Texture_Offset_Data_Tex", instance_data_tex_);
+ instance_material_.SetVector("_Instance_Texture_Offset_Cell_Dimensions", cell_dim_);
+ instance_material_.SetVector("_Instance_Texture_Offset_Angle_Randomization", angle_randomization_);
+ instance_material_.SetFloat("_Instance_Texture_Offset_Scale_Randomization", scale_randomization_);
+ instance_material_.SetVector("_Instance_Texture_Offset_Base_Scale", base_transform_.localScale);
+
+ // Pass rotation as quaternion (x, y, z, w)
+ Quaternion rot = base_transform_.localRotation;
+ instance_material_.SetVector("_Instance_Texture_Offset_Base_Rotation", new Vector4(rot.x, rot.y, rot.z, rot.w));
+
+ if (Time.frameCount % 300 == 0) {
+ Debug.Log($"[Grass::Debug] Drawing {total_instances_} GPU instances");
+ Debug.Log($"[Grass::Debug] GridCount={count_}, TexDim={tex_width_}x{tex_height_}, CellDim={cell_dim_}");
+ }
+
+ // Draw instances with identity transforms - GPU handles everything
+ // Must draw tex_width * tex_height instances to match texture
+ VRCGraphics.DrawMeshInstanced(
+ mesh_,
+ 0,
+ instance_material_,
+ identity_transforms_,
+ total_instances_,
+ null,
+ UnityEngine.Rendering.ShadowCastingMode.Off,
+ true,
+ 0,
+ null,
+ UnityEngine.Rendering.LightProbeUsage.Off,
+ null);
+
+ initialized_ = true;
+ }
+
+ void OnDestroy() {
+ if (instance_data_tex_ != null) {
+ instance_data_tex_.Release();
+ }
+ }
+}
+
+#if UNITY_EDITOR && !COMPILER_UDONSHARP
+[CustomEditor(typeof(InstanceGrass))]
+public class InstanceGrassEditor : Editor
+{
+ public override void OnInspectorGUI()
+ {
+ DrawDefaultInspector();
+
+ InstanceGrass script = (InstanceGrass)target;
+
+ // Auto-generate blit material if missing
+ if (script.blit_material_ == null)
+ {
+ if (GUILayout.Button("Generate Blit Material"))
+ {
+ GenerateBlitMaterial(script);
+ }
+
+ EditorGUILayout.HelpBox("Blit material is missing. Click the button above to auto-generate it.", MessageType.Warning);
+ }
+ else
+ {
+ EditorGUILayout.HelpBox("Blit material is set. GPU instancing ready.", MessageType.Info);
+ }
+ }
+
+ private void GenerateBlitMaterial(InstanceGrass script)
+ {
+ Shader blitShader = Shader.Find("yum_food/GrassGridBlit");
+ if (blitShader == null)
+ {
+ EditorUtility.DisplayDialog("Error", "Could not find shader 'yum_food/GrassGridBlit'. Make sure GrassGridBlit.shader is imported.", "OK");
+ return;
+ }
+
+ string assetPath = "Assets/yum_food/3ner/Grass_Generated/Grass_generated.mat";
+
+ // Create or load existing material
+ Material blitMat = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
+ if (blitMat == null)
+ {
+ // Create new material
+ blitMat = new Material(blitShader);
+ blitMat.name = "Grass_generated";
+
+ // Ensure directory exists
+ string directory = Path.GetDirectoryName(assetPath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ AssetDatabase.CreateAsset(blitMat, assetPath);
+ Debug.Log($"[Grass::Editor] Created blit material at {assetPath}");
+ }
+ else
+ {
+ // Update existing material shader
+ blitMat.shader = blitShader;
+ EditorUtility.SetDirty(blitMat);
+ Debug.Log($"[Grass::Editor] Updated existing blit material at {assetPath}");
+ }
+
+ script.blit_material_ = blitMat;
+ EditorUtility.SetDirty(script);
+ AssetDatabase.SaveAssets();
+ }
+
+ [UnityEditor.Callbacks.DidReloadScripts]
+ private static void OnScriptsReloaded()
+ {
+ // Auto-generate blit material for all InstanceGrass components when scripts reload
+ InstanceGrass[] allGrass = FindObjectsOfType<InstanceGrass>();
+ foreach (var grass in allGrass)
+ {
+ if (grass.blit_material_ == null)
+ {
+ var editor = CreateEditor(grass) as InstanceGrassEditor;
+ if (editor != null)
+ {
+ editor.GenerateBlitMaterial(grass);
+ DestroyImmediate(editor);
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/Scripts/InstanceGrass.cs.meta b/Scripts/InstanceGrass.cs.meta
new file mode 100755
index 0000000..58e03e6
--- /dev/null
+++ b/Scripts/InstanceGrass.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8dd03d28500e21640aad6c2650091d4c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/features.cginc b/features.cginc
index ebcf8a7..292e03c 100755
--- a/features.cginc
+++ b/features.cginc
@@ -61,6 +61,10 @@
#pragma shader_feature_local _INSTANCE_DISTANCE_CULLING
//endex
+//ifex _Instance_Texture_Offset_Enabled==0
+#pragma shader_feature_local _INSTANCE_TEXTURE_OFFSET
+//endex
+
//ifex _Parallax_Heightmap_Enabled==0
#pragma shader_feature_local _PARALLAX_HEIGHTMAP
//endex
diff --git a/globals.cginc b/globals.cginc
index 0341146..9bc4330 100755
--- a/globals.cginc
+++ b/globals.cginc
@@ -170,6 +170,16 @@ float _Ray_Marching_Hexagon_Height;
float _Instance_Distance_Culling_Distance;
#endif // _INSTANCE_DISTANCE_CULLING
+#if defined(_INSTANCE_TEXTURE_OFFSET)
+texture2D _Instance_Texture_Offset_Data_Tex;
+float4 _Instance_Texture_Offset_Data_Tex_TexelSize;
+float3 _Instance_Texture_Offset_Cell_Dimensions;
+float3 _Instance_Texture_Offset_Angle_Randomization;
+float _Instance_Texture_Offset_Scale_Randomization;
+float3 _Instance_Texture_Offset_Base_Scale;
+float4 _Instance_Texture_Offset_Base_Rotation;
+#endif // _INSTANCE_TEXTURE_OFFSET
+
#if defined(_PARALLAX_HEIGHTMAP)
texture2D _Parallax_Heightmap;
float4 _Parallax_Heightmap_ST;
diff --git a/instancing.cginc b/instancing.cginc
index eb8e25b..6672db4 100755
--- a/instancing.cginc
+++ b/instancing.cginc
@@ -4,6 +4,87 @@
#include "globals.cginc"
#include "interpolators.cginc"
+#if defined(_INSTANCE_TEXTURE_OFFSET)
+
+// Hash function for deterministic pseudo-random values
+// Returns 5 pseudo-random floats in [0,1) from a single hash computation
+void Hash5(int x, int z, out float r0, out float r1, out float r2, out float r3, out float r4) {
+ int h = (x * 73856093) ^ (z * 83492791);
+
+ h = h ^ (h >> 13);
+ h = h * 1274126177;
+ r0 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+
+ h = h ^ (h >> 13);
+ h = h * 1274126177;
+ r1 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+
+ h = h ^ (h >> 13);
+ h = h * 1274126177;
+ r2 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+
+ h = h ^ (h >> 13);
+ h = h * 1274126177;
+ r3 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+
+ h = h ^ (h >> 13);
+ h = h * 1274126177;
+ r4 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+}
+
+// Quaternion multiplication
+float4 qmul(float4 q1, float4 q2) {
+ return float4(
+ q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
+ q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,
+ q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w,
+ q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
+ );
+}
+
+// Rotate vector by quaternion
+float3 qrotate(float4 q, float3 v) {
+ float3 t = 2 * cross(q.xyz, v);
+ return v + q.w * t + cross(q.xyz, t);
+}
+
+// Create quaternion from Euler angles (in degrees)
+float4 euler_to_quat(float3 euler) {
+ float3 rad = euler * 0.0174533; // deg to rad
+ float3 c = cos(rad * 0.5);
+ float3 s = sin(rad * 0.5);
+
+ return float4(
+ s.x * c.y * c.z - c.x * s.y * s.z,
+ c.x * s.y * c.z + s.x * c.y * s.z,
+ c.x * c.y * s.z - s.x * s.y * c.z,
+ c.x * c.y * c.z + s.x * s.y * s.z
+ );
+}
+
+// Compute rotation quaternion from hash
+float4 compute_rotation(int x, int z) {
+ int h = (x * 73856093) ^ (z * 83492791);
+
+ float r0, r1, r2;
+ h = h ^ (h >> 13); h = h * 1274126177;
+ r0 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+ h = h ^ (h >> 13); h = h * 1274126177;
+ r1 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+ h = h ^ (h >> 13); h = h * 1274126177;
+ r2 = (h & 0x7FFFFFFF) / float(0x7FFFFFFF);
+
+ float3 euler = float3(
+ _Instance_Texture_Offset_Angle_Randomization.x * (r0 * 2.0 - 1.0),
+ _Instance_Texture_Offset_Angle_Randomization.y * (r1 * 2.0 - 1.0),
+ _Instance_Texture_Offset_Angle_Randomization.z * (r2 * 2.0 - 1.0)
+ );
+
+ return euler_to_quat(euler);
+}
+
+#endif // _INSTANCE_TEXTURE_OFFSET
+
void instance_distance_culling(inout v2f i) {
#if defined(_INSTANCE_DISTANCE_CULLING)
// We want to measure the distance from the instance's transform to the camera.
@@ -15,6 +96,75 @@ void instance_distance_culling(inout v2f i) {
#endif // _INSTANCE_DISTANCE_CULLING
}
+void instancing_vert(inout appdata v) {
+#if defined(_INSTANCE_TEXTURE_OFFSET) && defined(UNITY_INSTANCING_ENABLED)
+ // Extract instance ID from the matrix translation component
+ // We encoded it in the X translation (m03) on the CPU side
+ float encodedID = unity_ObjectToWorld[0][3];
+ uint instanceID = (uint)encodedID;
+
+ // Read grid coordinates from instance data texture
+ // _TexelSize.zw contains (width, height)
+ float2 texDimensions = _Instance_Texture_Offset_Data_Tex_TexelSize.zw;
+ int2 texCoord = int2(instanceID % (uint)texDimensions.x,
+ instanceID / (uint)texDimensions.x);
+ float2 uv = (float2(texCoord) + 0.5) / texDimensions;
+ float4 instanceData = _Instance_Texture_Offset_Data_Tex.SampleLevel(point_repeat_s, uv, 0);
+
+ // Check if this is a valid instance (alpha >= 0)
+ if (instanceData.w < 0) {
+ // Invalid instance - collapse to degenerate triangle far away
+ v.vertex.xyz = float3(0, -100000, 0);
+ return;
+ }
+
+ int3 gridCoord = int3(instanceData.xyz);
+
+ // Compute hash values for randomization
+ float h0, h1, h2, h3, h4;
+ Hash5(gridCoord.x, gridCoord.z, h0, h1, h2, h3, h4);
+
+ // Base grid position
+ float3 basePosition = float3(
+ gridCoord.x * _Instance_Texture_Offset_Cell_Dimensions.x,
+ gridCoord.y * _Instance_Texture_Offset_Cell_Dimensions.y,
+ gridCoord.z * _Instance_Texture_Offset_Cell_Dimensions.z
+ );
+
+ // Add randomized offset within cell
+ float3 positionOffset = float3(
+ _Instance_Texture_Offset_Cell_Dimensions.x * h0,
+ _Instance_Texture_Offset_Cell_Dimensions.y * h1,
+ _Instance_Texture_Offset_Cell_Dimensions.z * h2
+ );
+
+ // Compute random rotation from hash (in world space)
+ float4 randomRotation = compute_rotation(gridCoord.x, gridCoord.z);
+
+ // Combine with base rotation: apply base rotation first, then world-space randomization
+ float4 finalRotation = qmul(randomRotation, _Instance_Texture_Offset_Base_Rotation);
+
+ // Compute scale
+ float3 scale = _Instance_Texture_Offset_Base_Scale * (1.0 + h4 * _Instance_Texture_Offset_Scale_Randomization);
+
+ // Apply transform to vertex in object space
+ float3 scaledVertex = v.vertex.xyz * scale;
+ float3 rotatedVertex = qrotate(finalRotation, scaledVertex);
+ float3 transformedVertex = rotatedVertex + basePosition + positionOffset;
+
+ // Compensate for the encoded offset that Unity will apply via the matrix
+ // Unity will add encodedID to X, so we subtract it here
+ transformedVertex.x -= encodedID;
+
+ // Update vertex position
+ v.vertex.xyz = transformedVertex;
+
+ // Transform normal and tangent (only rotation, no scale - they're direction vectors)
+ v.normal = qrotate(finalRotation, v.normal);
+ v.tangent.xyz = qrotate(finalRotation, v.tangent.xyz);
+#endif // _INSTANCE_TEXTURE_OFFSET
+}
+
void instancing_frag(v2f i) {
instance_distance_culling(i);
}