
저번 시간에 풀 하나 그리기를 성공 했으니, 여러개의 풀을 한꺼번에 그려보도록 해보자.
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

'공부 일지 > 그래픽스' 카테고리의 다른 글
| CPU와 GPU 동작 구조 (0) | 2025.10.28 |
|---|---|
| GPU로 풀 그리기 #6 (完) (0) | 2025.10.14 |
| GPU로 풀 그리기 #4 (0) | 2025.10.11 |
| GPU로 풀 그리기 #3 (0) | 2025.10.05 |
| GPU로 풀 그리기 #2 (0) | 2025.10.05 |