
저번 시간에 풀 하나 그리기를 성공 했으니, 여러개의 풀을 한꺼번에 그려보도록 해보자.
GPUGrass.compute
#pragma kernel Init
#pragma kernel Tick
//추가
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#define VERTEX_PER_BLADE 15
cbuffer Constants
{
    uint Capacity;
}
RWByteAddressBuffer GrassBuffer;
RWByteAddressBuffer CommandBuffer;
[numthreads(1, 1, 1)]
void Init()
{
    CommandBuffer.Store4(0, uint4(VERTEX_PER_BLADE * Capacity, 1, 0, 0));
}
[numthreads(64, 1, 1)]
void Tick(const uint id : SV_DispatchThreadID)
{
    if (id >= Capacity) { return; }
// jitter생성
    const float jitter0 = GenerateHashedRandomFloat(id + 0);
    const float jitter1 = GenerateHashedRandomFloat(id + 1);
    const float jitter2 = GenerateHashedRandomFloat(id + 2);
    GrassBuffer.Store3(mad(id, 3, 0)<<2, asuint(float3(jitter0, jitter1, jitter2)));
}
Capacity값을 증가 시키면 랜덤으로 여러개가 추가로 생성 된다.


여러개가 생성되나 겹쳐서 생성되므로, scatterRange를 주어 일정 간격(y축고정)으로 퍼져서 풀을 그리도록 해보자.
GPUGrass.compute
#pragma kernel Init
#pragma kernel Tick
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#define VERTEX_PER_BLADE 15
cbuffer Constants
{
    uint Capacity;
    float ScatterRange;
}
RWByteAddressBuffer GrassBuffer;
RWByteAddressBuffer CommandBuffer;
[numthreads(1, 1, 1)]
void Init()
{
    CommandBuffer.Store4(0, uint4(VERTEX_PER_BLADE * Capacity, 1, 0, 0));
}
//BLADE 수 만큼 Tick
[numthreads(64, 1, 1)]
void Tick(const uint id : SV_DispatchThreadID)
{
    if (id >= Capacity) { return; }
    const float jitter0 = mad(GenerateHashedRandomFloat(id + 0), ScatterRange, -ScatterRange * 5.0f);
    // const float jitter1 = GenerateHashedRandomFloat((id + 1)*5.0f);   y축
    const float jitter2 = mad(GenerateHashedRandomFloat(id + 2), ScatterRange, -ScatterRange * 5.0f);
    GrassBuffer.Store3(mad(id, 3, 0)<<2, asuint(float3(jitter0, 0, jitter2)));
}
GPUGrass.cs
public struct Constants
    {
       public static readonly int Size = Marshal.SizeOf<Constants>();
       public uint Capacity;
       public float ScatterRange;
    }
    
  
  public class GPUGrass : MonoBehaviour
    {
        //////////
        
        [SerializeField][Range(10.0f, 32.0f)] private float scatterRange;
        
        /////////
    }
    
    
    private void Init()
        {
            //////////
            
            _constants[0].Capacity = (uint)capacity;
            
            _constants[0].ScatterRange = scatterRange; //추가
            
            _constBuffer.SetData(_constants);
            _compute.SetConstantBuffer(Constants, _constBuffer, 0, global::GPUGrass.Constants.Size);
            
            /////////


풀잎 방향을 바꿔보자
GPUGrass.shader
Varyings Vert(const Attribute input)
            {
                const uint globalVertexID = input.vertexID;
                const uint bladeID = uint(floor(globalVertexID / VERTEX_PER_BLADE));
                const uint localVertex = globalVertexID % VERTEX_PER_BLADE;
                const float4 bladeData = asfloat(GrassBuffer.Load4(mad(bladeID , GRASS_BUFFER_SIZE, 0) << 2));
                const float3 bladePosition = bladeData.xyz;
                const float bladeRotation = bladeData.w;
                
                float3 vertexPosition = grassMesh[localVertex];
                //풀 회전
                float s = sin(bladeRotation);
                float c = cos(bladeRotation);
                float2x2 rotationMatrix = float2x2(s, c, -c, s);
                vertexPosition.xz = mul(rotationMatrix, vertexPosition.xz);
                
                Varyings output;
                output.positionCS = TransformWorldToHClip(bladeData.xyz + vertexPosition);
                return output;
            }

풀잎들의 크기들도 각각 다르게 해보자. scale 관련 기능을 넣어본다. 전체 크기를 조절하는 scaleRange와 최소 크기를 지정하는 minimumScale을 추가하였다.
GPUGrass.cs
public struct Constants
    {
       public static readonly int Size = Marshal.SizeOf<Constants>();
       public uint Capacity;
       public float ScatterRange;
       public float ScaleRange;
       public float MinimumScale;
    }
    
    
 public struct Grass
    {
        public static readonly int Size = Marshal.SizeOf<Grass>();
        public float3 Position;
        public float Rotation;
        public float Scale;
    }
 
 ////////////////
 
        [SerializeField] private float scaleRange;
        [SerializeField] private float minimumScale;
 
 //////////
 
        _constants[0].ScaleRange = scaleRange;
        _constants[0].MinimumScale = minimumScale;
GPUGrass.shader
Varyings Vert(const Attribute input)
            {
            //////////////
                
                const float bladeScale = asfloat(GrassBuffer.Load(mad(bladeID, GRASS_BUFFER_SIZE, 4)<<2));
                ////////////
                
                float3 vertexPosition = grassMesh[localVertex] * bladeScale;
                ////////////////
              
            }
GPUGrass.compute
#define GRASS_BUFFER_SIZE 5
#define VERTEX_PER_BLADE 15
cbuffer Constants
{
    uint Capacity;
    float ScatterRange;
    float ScaleRange;
    float MinimumScale;
}
void Tick(const uint id : SV_DispatchThreadID)
{
    
    const float scale = mad(GenerateHashedRandomFloat(id + 4), ScaleRange, MinimumScale);
    /////////////
    
    GrassBuffer.Store(mad(id, GRASS_BUFFER_SIZE, 4)<<2, asuint(scale));
}

가로 세로 길이를 자유롭게 조절하여 원하는 크기의 풀을 만들기 위해, 기존의 있던 scaleRange와 MinimumScale을 합쳐서 float2 타입의 xScaleParm과 yScaleParm으로 구분하도록 수정하였다.
GPUGrass.cs
ublic struct Constants
    {
       public static readonly int Size = Marshal.SizeOf<Constants>();
       public uint Capacity;
       public float ScatterRange;
       public float2 XScaleParam;
       public float2 YScaleParam;
    }
    
    ///////
    
    [SerializeField] private float2 xScaleParam;
    [SerializeField] private float2 yScaleParam;
    
    ////////
    
    _constants[0].XScaleParam = xScaleParam;
    _constants[0].YScaleParam = yScaleParam;
    
    ///////
GPUGrass.shader
Varyings Vert(const Attribute input)
            {
                
                const float2 bladeScale = asfloat(GrassBuffer.Load2(mad(bladeID, GRASS_BUFFER_SIZE, 4)<<2));
GPUGrass.compute
#define GRASS_BUFFER_SIZE 6
#define VERTEX_PER_BLADE 15
cbuffer Constants
{
    uint Capacity;
    float ScatterRange;
    float2 XScaleParam;
    float2 YScaleParam;
}
void Tick(const uint id : SV_DispatchThreadID)
{
    /////////
    
    const float scaleX = mad(GenerateHashedRandomFloat(id + 4), XScaleParam.x, XScaleParam.y);
    const float scaleY = mad(GenerateHashedRandomFloat(id + 5), YScaleParam.x, YScaleParam.y);
    
    GrassBuffer.Store4(mad(id, GRASS_BUFFER_SIZE, 0)<<2, asuint(float4(x, 0.0f, z, rotation)));
    GrassBuffer.Store2(mad(id, GRASS_BUFFER_SIZE, 4)<<2, asuint(float2(scaleX, scaleY)));
}

풀잎이 여러방향으로 솟은 모습으로 만들어보자.
y가 높이값이긴 하지만 uv를 만든다는 느낌으로 0.0에서 1.0까지 유니폼하게 만들거다. 기존의 y값들의 따라서 +1.0f 녀석들은 +0.3f로 주고, +2.0f 녀석들은 +0.67f를 주고, 3.0f 녀석들은 1.0f로 4번째 인수를 할당한다.
GPUGrass.shader
static float4 grassMesh[VERTEX_PER_BLADE] =
            {
                float4(-1.0f, +0.0f, 0.0f, 0.0f), float4(-0.9f,+1.0f, 0.0f, 0.33f), float4(+0.9f, +1.0f, 0.0f, 0.33f),
                float4(+0.9f, +1.0f, +0.0f, 0.33f), float4(+1.0f, +0.0f, 0.0f, 0.0f), float4(-1.0f, +0.0f, +0.0, 0.00f),
                float4(-0.9f, +1.0f, +0.0f, 0.33f), float4(-0.6f, +2.0f, 0.0f, 0.67f), float4(+0.6f, +2.0f, +0.0f, 0.67f),
                float4(+0.6f, +2.0f, +0.0f, 0.67f), float4(+0.9f, +1.0f, 0.0f, 0.33f), float4(-0.9f, +1.0f, +0.0f, 0.33f),
                float4(-0.6f, +2.0f, +0.0f, 0.67f), float4(+0.0f, +3.0f, 0.0f, 1.0f), float4(+0.6f, +2.0f, +0.0f, 0.67f),
            };
기울이게 할 Bend 변수 추가
GPUGrass.cs
public struct Grass
    {
        public static readonly int Size = Marshal.SizeOf<Grass>();
        public float3 Position;
        public float Rotation;
        public float2 Scale;
        public float Bend;
    }
GPUGrass.shader
Varyings Vert(const Attribute input)
            {
                /////////
                
                const float bladeBend = asfloat(GrassBuffer.Load(mad(bladeID, GRASS_BUFFER_SIZE, 5)<<2));
                /////////
                
                float4 vertexAttribute = grassMesh[localVertex];
                vertexAttribute.xy *= bladeScale;
                //풀잎 꺽임
                vertexAttribute.z += pow(vertexAttribute.w, 3.0f) * bladeBend;
                //풀 회전
                float s = sin(bladeRotation);
                float c = cos(bladeRotation);
                float2x2 rotationMatrix = float2x2(s, c, -c, s);
                vertexAttribute.xz = mul(rotationMatrix, vertexAttribute.xz);
                
                Varyings output;
                output.positionCS = TransformWorldToHClip(bladePositionRotation + vertexAttribute.xyz);
                return output;
            }
GPUGrass.compute
void Tick(const uint id : SV_DispatchThreadID)
{
    ////////
    
    const float bend = GenerateHashedRandomFloat(id + 6);
    
    
    ////////
    GrassBuffer.Store(mad(id, GRASS_BUFFER_SIZE, 5)<<2, asuint(bend));
}

vertexAttribute의 w값을 uv좌표로 만들어서 적용하면
GPUGrass.shader
struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float uv0 : TEXCOORD0;
            };
            
            
Varyings Vert(const Attribute input)
            {
            
            
                ///////////
                Varyings output;
                output.positionCS = TransformWorldToHClip(bladePositionRotation + vertexAttribute.xyz);
                output.uv0 = vertexAttribute.w;
                return output;
            }
            half4 Frag(const Varyings input) : SV_TARGET
            {
                return half4(input.uv0.xxx, 1.0h);
            }
            ENDHLSL
'공부 일지 > 그래픽스' 카테고리의 다른 글
| GPU로 풀 그리기 #6 (完) (0) | 2025.10.14 | 
|---|---|
| GPU로 풀 그리기 #4 (0) | 2025.10.11 | 
| GPU로 풀 그리기 #3 (0) | 2025.10.05 | 
| GPU로 풀 그리기 #2 (0) | 2025.10.05 | 
| GPU로 풀 그리기 #1 (0) | 2025.10.05 | 
