@@ -50,7 +50,7 @@ static const float grass_cull_radius = 1.5f;
5050// values[0] = (cell_size, ring_radius, lod_base_in_instances, max_instances_per_lod)
5151// values[1] = (height_min, height_max, max_slope_cos, inner_radius)
5252// values[2] = (map_origin_x, map_origin_z, map_inv_size_x, map_inv_size_z)
53- // values[3] = (patch_size_m, patch_coverage, patch_edge, patch_scar )
53+ // values[3] = (patch_size_m, patch_coverage, patch_edge, ground_mask bits )
5454// a patch size of zero spreads the slot evenly, which is what this pass did before patches existed,
5555// and a negative one inverts the field so a slot takes the ground the others left bare
5656// every float is taken, so the rest travels in the bits of draw_index:
@@ -64,6 +64,8 @@ static const float grass_cull_radius = 1.5f;
6464// terrain height is r32 local y bound to tex (t7), material_index bitcast is the entity y plus the
6565// layer's seating offset, a negative offset is what pushes an instance down into the ground
6666// biome_min arrives asfloat(is_transparent), negative disables the gate
67+ // the prop mask is rgb weights plus the dominant surface layer index in alpha, the index needs a
68+ // point tap because a bilinear one between two layers averages to a value that names a third
6769
6870// 32-bit integer hash, takes the cell's integer world coords and returns a uniform 32-bit value
6971// keyed off coordinates that do not move with the camera, so blade placement is stable
@@ -131,6 +133,11 @@ static const float grass_max_boost = 12.0f;
131133// how far above its own gate the biome mask has to climb before a slot runs at full density, a
132134// narrow band here is what keeps meadow cores thick while the mask edges still fade out
133135static const float grass_biome_gain = 0.25f ;
136+ // share of the edge fringe amount that also punches bare scars through a pocket interior, a clean
137+ // edged pocket with a pockmarked middle would read as two unrelated effects. mirrored on the cpu
138+ static const float grass_patch_scar_ratio = 0.3f ;
139+ // terrain_layer_max, the dominant layer index in the mask alpha can never exceed this
140+ static const uint grass_ground_layer_max = 8u;
134141
135142// four octaves, the fewest that still reads as an organic outline rather than a blob
136143float grass_fbm (float2 p, uint seed)
@@ -381,7 +388,10 @@ void main_cs(uint3 dispatch_thread_id : SV_DispatchThreadID)
381388 bool patch_invert = patch_size_signed < 0.0f ;
382389 float patch_coverage = saturate (buffer_pass.values[3 ].y);
383390 float patch_edge = saturate (buffer_pass.values[3 ].z);
384- float patch_scar = saturate (buffer_pass.values[3 ].w);
391+ // the knob that frays the outline also breaks the interior, one is a fixed fraction of the other
392+ // so it is derived here rather than pushed, which frees the float for the ground type bits
393+ float patch_scar = patch_edge * grass_patch_scar_ratio;
394+ uint ground_mask = (uint )buffer_pass.values[3 ].w;
385395
386396 uint packed_index = buffer_pass.draw_index;
387397 uint lod_index = packed_index & 0xFu;
@@ -589,7 +599,7 @@ void main_cs(uint3 dispatch_thread_id : SV_DispatchThreadID)
589599 // biome mask, same uv as the heightfield, the slot picks the channel it is gated on
590600 // is_transparent carries biome_min as float bits, negative disables the gate
591601 float biome_min = asfloat (buffer_pass.is_transparent);
592- if (biome_min >= 0.0f )
602+ if (biome_min >= 0.0f || ground_mask != 0u )
593603 {
594604 // the mask is baked at its own resolution, so it needs its own texel centre rebase
595605 uint mask_w;
@@ -600,22 +610,44 @@ void main_cs(uint3 dispatch_thread_id : SV_DispatchThreadID)
600610 terrain_world_to_normalized (world_xz),
601611 float2 (mask_w, mask_h)
602612 );
603- float4 mask = tex3.SampleLevel (samplers[sampler_bilinear_clamp], mask_uv, 0 );
604- float grass_w = mask_channel == 0u ? mask.r : (mask_channel == 1u ? mask.g : mask.b);
605- float slope_fit = saturate ((surface_normal.y - max_slope_cos) / max (1.0f - max_slope_cos, 1e-4f ));
606- if (grass_w < biome_min)
613+
614+ // ground type gate, the same bitfield the mesh placer reads. the dominant surface layer rides
615+ // in alpha as an index rather than a weight, so it takes a point tap, a bilinear one across the
616+ // boundary between two layers averages out to a value that names a third
617+ if (ground_mask != 0u)
607618 {
608- return ;
619+ float index_a = tex3.SampleLevel (samplers[sampler_point_clamp], mask_uv, 0 ).a;
620+ uint dominant = min (
621+ (uint )(index_a * 255.0f + 0.5f ),
622+ grass_ground_layer_max - 1u
623+ );
624+ if ((ground_mask & (1u << dominant)) == 0u)
625+ {
626+ return ;
627+ }
609628 }
610629
611- // the mask decides where grass can live, the patch field above decides where it did, so the
612- // mask is remapped to saturate just past its own gate instead of being used as the density
613- // directly. a meadow core reading 0.6 used to throw away four blades in ten for nothing
614- float ground = saturate ((grass_w - biome_min) / grass_biome_gain) * slope_fit;
615- float biome_roll = hash_unit (hash_mix (h0 ^ 0x27d4eb2du));
616- if (biome_roll > ground)
630+ // a slot with the biome set to ignore still wants the ground gate above, so this is nested
631+ // rather than being the condition on the whole block
632+ if (biome_min >= 0.0f )
617633 {
618- return ;
634+ float4 mask = tex3.SampleLevel (samplers[sampler_bilinear_clamp], mask_uv, 0 );
635+ float grass_w = mask_channel == 0u ? mask.r : (mask_channel == 1u ? mask.g : mask.b);
636+ float slope_fit = saturate ((surface_normal.y - max_slope_cos) / max (1.0f - max_slope_cos, 1e-4f ));
637+ if (grass_w < biome_min)
638+ {
639+ return ;
640+ }
641+
642+ // the mask decides where grass can live, the patch field above decides where it did, so the
643+ // mask is remapped to saturate just past its own gate instead of being used as the density
644+ // directly. a meadow core reading 0.6 used to throw away four blades in ten for nothing
645+ float ground = saturate ((grass_w - biome_min) / grass_biome_gain) * slope_fit;
646+ float biome_roll = hash_unit (hash_mix (h0 ^ 0x27d4eb2du));
647+ if (biome_roll > ground)
648+ {
649+ return ;
650+ }
619651 }
620652 }
621653
0 commit comments