summaryrefslogtreecommitdiffstats
path: root/Scripts/InstanceGrass.cs
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-02-16 16:32:00 -0800
committeryum <yum.food.vr@gmail.com>2026-02-16 16:36:24 -0800
commit864c2ba12dc864d9cb55cb797ba8919bee5b5913 (patch)
treeaa6cd98a71e4ef05d23f762127d3759a4a3e3e21 /Scripts/InstanceGrass.cs
parent6504b2c4631bab477838548167b88c1052eac263 (diff)
Add instancing distance culling, scale deformation
* GPU instance distance culling now takes a min/max range * Fold recovers ops from material, allowing state to persist across editor restarts * Add scale node to vertex deformation framework * Remove fold presets - dumb LLM idea, unused * Drop more "undeform" code; unused, was for ray marching, which does not work well * Fix reflection energy compensation; was using cloth math, which makes things too bright
Diffstat (limited to 'Scripts/InstanceGrass.cs')
-rwxr-xr-xScripts/InstanceGrass.cs70
1 files changed, 52 insertions, 18 deletions
diff --git a/Scripts/InstanceGrass.cs b/Scripts/InstanceGrass.cs
index 3c0d8f6..d6a1eab 100755
--- a/Scripts/InstanceGrass.cs
+++ b/Scripts/InstanceGrass.cs
@@ -15,6 +15,7 @@ public class InstanceGrass : UdonSharpBehaviour
// 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 float min_distance_;
[SerializeField] public Vector3 angle_randomization_;
[SerializeField] public float scale_randomization_;
@@ -34,12 +35,15 @@ public class InstanceGrass : UdonSharpBehaviour
// Track fields to detect runtime changes.
private float density_live_;
private Vector3 extent_meters_live_;
+ private float min_distance_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 float[] instance_ids_;
+ private MaterialPropertyBlock instance_properties_;
private int tex_width_;
private int tex_height_;
@@ -67,6 +71,7 @@ public class InstanceGrass : UdonSharpBehaviour
Material[] materials = mesh_renderer.sharedMaterials;
if (materials != null && materials.Length > 0) {
instance_material_ = materials[0];
+ instance_material_.enableInstancing = true;
} else {
Debug.LogError("[Grass::Debug] MeshRenderer has no materials.");
}
@@ -117,26 +122,29 @@ public class InstanceGrass : UdonSharpBehaviour
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;
+ // Create transform array and per-instance IDs.
+ if (identity_transforms_ == null || identity_transforms_.Length != total_instances_) {
+ identity_transforms_ = new Matrix4x4[total_instances_];
+ for (int i = 0; i < total_instances_; i++) {
+ identity_transforms_[i] = Matrix4x4.identity;
+ }
+ }
+
+ if (instance_ids_ == null || instance_ids_.Length != total_instances_) {
+ instance_ids_ = new float[total_instances_];
+ for (int i = 0; i < total_instances_; i++) {
+ instance_ids_[i] = i;
}
}
if (mesh_ != null) {
- // Set huge bounds so culling doesn't interfere
- mesh_.bounds = new Bounds(Vector3.zero, Vector3.one * 10000f);
+ // Prevent frustum culling from clipping instanced draws that move in-shader.
+ mesh_.bounds = new Bounds(Vector3.zero, Vector3.one * 100000f);
}
density_live_ = density_;
extent_meters_live_ = extent_meters_;
+ min_distance_live_ = min_distance_;
angle_randomization_live_ = angle_randomization_;
scale_randomization_live_ = scale_randomization_;
@@ -182,6 +190,7 @@ public class InstanceGrass : UdonSharpBehaviour
// Reinitialize if any config changed at runtime.
if (density_ != density_live_ ||
extent_meters_ != extent_meters_live_ ||
+ min_distance_ != min_distance_live_ ||
angle_randomization_ != angle_randomization_live_ ||
scale_randomization_ != scale_randomization_live_) {
Init();
@@ -194,9 +203,12 @@ public class InstanceGrass : UdonSharpBehaviour
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);
+
+ // Only update grid position on axes with extent >= 1cm
+ // For axes with smaller extent, keep grid at 0 (don't move with player)
+ int grid_x = extent_meters_.x >= 0.01f ? Mathf.FloorToInt(player_pos.x * inv_cell_dim_.x) : 0;
+ int grid_y = extent_meters_.y >= 0.01f ? Mathf.FloorToInt(player_pos.y * inv_cell_dim_.y) : 0;
+ int grid_z = extent_meters_.z >= 0.01f ? Mathf.FloorToInt(player_pos.z * inv_cell_dim_.z) : 0;
int half_x = count_.x / 2;
int half_y = count_.y / 2;
@@ -222,20 +234,24 @@ public class InstanceGrass : UdonSharpBehaviour
Quaternion rot = base_transform_.localRotation;
instance_material_.SetVector("_Instance_Texture_Offset_Base_Rotation", new Vector4(rot.x, rot.y, rot.z, rot.w));
+ // Distance culling parameters
+ instance_material_.SetFloat("_Instance_Distance_Culling_Min_Distance", min_distance_);
+ float max_distance = Mathf.Max(extent_meters_.x, Mathf.Max(extent_meters_.y, extent_meters_.z));
+ instance_material_.SetFloat("_Instance_Distance_Culling_Max_Distance", max_distance * 0.5f);
+
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,
+ GetInstanceProperties(),
UnityEngine.Rendering.ShadowCastingMode.Off,
true,
0,
@@ -246,6 +262,15 @@ public class InstanceGrass : UdonSharpBehaviour
initialized_ = true;
}
+ private MaterialPropertyBlock GetInstanceProperties() {
+ if (instance_properties_ == null) {
+ instance_properties_ = new MaterialPropertyBlock();
+ }
+
+ instance_properties_.SetFloatArray("_Instance_ID", instance_ids_);
+ return instance_properties_;
+ }
+
void OnDestroy() {
if (instance_data_tex_ != null) {
instance_data_tex_.Release();
@@ -288,7 +313,16 @@ public class InstanceGrassEditor : Editor
return;
}
- string assetPath = "Assets/yum_food/3ner/Grass_Generated/Grass_generated.mat";
+ // Get hierarchy path
+ Transform current = script.transform;
+ string hierarchyPath = current.name;
+ while (current.parent != null)
+ {
+ current = current.parent;
+ hierarchyPath = current.name + "/" + hierarchyPath;
+ }
+
+ string assetPath = $"Assets/yum_food/3ner/Grass_Generated/{hierarchyPath}/Grass_generated.mat";
// Create or load existing material
Material blitMat = AssetDatabase.LoadAssetAtPath<Material>(assetPath);