diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-18 14:59:59 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-18 14:59:59 -0800 |
| commit | 6504b2c4631bab477838548167b88c1052eac263 (patch) | |
| tree | e7d3aa4d19a21e624927a6771e2aeb4815af9393 | |
| parent | fbe7ed126883b0c4a1d5115e5c953bc244bc0214 (diff) | |
Grass: add crude instancing code
| -rwxr-xr-x | 3ner.cginc | 7 | ||||
| -rwxr-xr-x | 3ner.shader | 13 | ||||
| -rw-r--r-- | Scripts/GrassGridBlit.shader | 77 | ||||
| -rwxr-xr-x | Scripts/InstanceGrass.asset | 1265 | ||||
| -rwxr-xr-x | Scripts/InstanceGrass.cs | 343 | ||||
| -rwxr-xr-x | Scripts/InstanceGrass.cs.meta | 11 | ||||
| -rwxr-xr-x | features.cginc | 4 | ||||
| -rwxr-xr-x | globals.cginc | 10 | ||||
| -rwxr-xr-x | instancing.cginc | 150 |
9 files changed, 1879 insertions, 1 deletions
@@ -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); } |
