

ํธ์๋ฅผ ์ํด ์๋์ ๊ฐ์ด Assets ์๋์ Scripts ํด๋์ Shaders ํด๋๋ฅผ ๋ง๋ค์ด ๊ด๋ฆฌํฉ๋๋ค.
Scirpts ํด๋๋ ํ์์ ๋ฐ๋ผ ํ์ ํด๋๋ฅผ ๋ง๋ค์ด์ C# ์คํฌ๋ฆฝํธ๋ฅผ ๋ฃ์ด ๋์ต๋๋ค.
Assets
โโ Scripts
โ โโ Artworks
โ โโ Art_015_NoiseFieldSphere.cs
โ
โโ Shaders
โ โโ GPUInstancedParticles.shader
Hierarchy > Create Empty (Ctrl + Shift + N)
์ด๋ ๊ฒ ๋ง๋ GameObject์ ์ด๋ฆ์ ๋ฐ๊พธ๊ฑฐ๋ ๊ทธ๋๋ก ์ฌ์ฉํด๋ ๋ฌด๋ฐฉํฉ๋๋ค.
์ฌ๊ธฐ์ C# ์คํฌ๋ฆฝํธ๋ฅผ ์ปดํฌ๋ํธ๋ก ๋ถ์ฐฉํ๋ฉด ๋ฉ๋๋ค.
์๋์ ์ฝ๋ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ฉด
GameObject ์ = 1
โ
Sphere Mesh ํ๋๋ง ์ฌ์ฉ
โ
Graphics.DrawMeshInstanced()
โ
GPU์์ ์์ฒ ๊ฐ ์ธ์คํด์ค ๋ ๋๋ง
์ด๋ ๊ฒ ์ํธ์ํฌ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค.
์์์ ๋ง๋
Shadersํด๋๋ก ์ด๋
Project Window > Create > Shader > URP Unlit Shader
์๋ก์ด ์
ฐ์ด๋ ํ์ผ์ด NewUnlitUniversalRenderPipelineShader๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ง๋ค์ด์ง๋๋ค.
์ด๋ฆ์ ์ํธ์ํฌ์ ์ด์ธ๋ฆฌ๊ฒ GPUInstancedParticles๋ก ๋ฐ๊พธ์ด ์ค๋๋ค. ์ดํ ์ด ํ์ผ์ C# ์คํฌ๋ฆฝํธ์์ ์ฐ๊ฒฐ๋ฉ๋๋ค. ๊ธฐ๋ณธ์ผ๋ก ์์ฑ๋ ์ฝ๋๋ฅผ ์ง์ฐ๊ณ ์๋์ ์
ฐ์ด๋ ์คํฌ๋ฆฝํธ๋ก ๊ต์ฒดํฉ๋๋ค.
using UnityEngine;
[ExecuteAlways]
public class Art_015_NoiseFieldSphere : MonoBehaviour
{
[Header("Particle Count")]
public int particleCount = 100000;
[Header("Field Radius")]
public float radius = 15f;
[Header("Motion")]
public float flowScale = 0.35f;
public float flowStrength = 6f;
public float curlStrength = 2f;
public float motionSpeed = 0.3f;
[Header("Particle Size")]
public float sizeMin = 0.05f;
public float sizeMax = 0.25f;
[Header("Color Field")]
public Gradient gradient;
public Mesh mesh;
public Material material;
ComputeBuffer particleBuffer;
ComputeBuffer argsBuffer;
struct Particle
{
public Vector3 pos;
public float size;
public Vector4 color;
}
Particle[] particles;
uint[] args = new uint[5];
bool initialized = false;
void OnEnable()
{
Initialize();
}
void OnDisable()
{
Release();
}
void Initialize()
{
if (initialized) return;
CreateGradient();
CreateMesh();
CreateMaterial();
particles = new Particle[particleCount];
for (int i = 0; i < particleCount; i++)
{
Vector3 p = Random.insideUnitSphere * radius;
float size = Random.Range(sizeMin, sizeMax);
float t = p.magnitude / radius;
Color col = gradient.Evaluate(t);
particles[i].pos = p;
particles[i].size = size;
particles[i].color = col;
}
particleBuffer = new ComputeBuffer(
particleCount,
sizeof(float) * 8
);
particleBuffer.SetData(particles);
args[0] = mesh.GetIndexCount(0);
args[1] = (uint)particleCount;
args[2] = mesh.GetIndexStart(0);
args[3] = mesh.GetBaseVertex(0);
argsBuffer = new ComputeBuffer(
1,
args.Length * sizeof(uint),
ComputeBufferType.IndirectArguments
);
argsBuffer.SetData(args);
material.SetBuffer("_Particles", particleBuffer);
initialized = true;
}
void CreateGradient()
{
if (gradient != null) return;
gradient = new Gradient();
GradientColorKey[] ck = new GradientColorKey[3];
ck[0] = new GradientColorKey(
new Color(0.2f,0.3f,1f),
0f
);
ck[1] = new GradientColorKey(
new Color(0f,1f,0.8f),
0.5f
);
ck[2] = new GradientColorKey(
new Color(1f,0.2f,1f),
1f
);
GradientAlphaKey[] ak = new GradientAlphaKey[2];
ak[0] = new GradientAlphaKey(1,0);
ak[1] = new GradientAlphaKey(1,1);
gradient.SetKeys(ck,ak);
}
void CreateMesh()
{
if (mesh != null) return;
GameObject g =
GameObject.CreatePrimitive(
PrimitiveType.Sphere
);
mesh =
g.GetComponent<MeshFilter>()
.sharedMesh;
DestroyImmediate(g);
}
void CreateMaterial()
{
if (material != null) return;
Shader shader =
Shader.Find("Custom/GPUInstancedParticles");
material = new Material(shader);
material.enableInstancing = true;
}
void Update()
{
if (!initialized) return;
if (!Application.isPlaying)
{
Render();
return;
}
AnimateParticles();
particleBuffer.SetData(particles);
Render();
}
void AnimateParticles()
{
float t = Time.time * motionSpeed;
for (int i = 0; i < particleCount; i++)
{
Vector3 p = particles[i].pos;
Vector3 flow =
FlowNoise(
p * flowScale +
Vector3.one * t
);
Vector3 curl =
CurlNoise(
p * flowScale +
Vector3.one * t
);
p += flow * flowStrength * Time.deltaTime;
p += curl * curlStrength * Time.deltaTime;
if (p.magnitude > radius)
p = p.normalized * radius;
particles[i].pos = p;
float d = p.magnitude / radius;
particles[i].color =
gradient.Evaluate(d);
}
}
Vector3 FlowNoise(Vector3 p)
{
float nx = Mathf.PerlinNoise(p.y,p.z);
float ny = Mathf.PerlinNoise(p.z,p.x);
float nz = Mathf.PerlinNoise(p.x,p.y);
return new Vector3(nx,ny,nz) - Vector3.one*0.5f;
}
Vector3 CurlNoise(Vector3 p)
{
float e = 0.1f;
float nx1 = Mathf.PerlinNoise(p.y,p.z+e);
float nx2 = Mathf.PerlinNoise(p.y,p.z-e);
float ny1 = Mathf.PerlinNoise(p.z,p.x+e);
float ny2 = Mathf.PerlinNoise(p.z,p.x-e);
float nz1 = Mathf.PerlinNoise(p.x,p.y+e);
float nz2 = Mathf.PerlinNoise(p.x,p.y-e);
float x = ny1-ny2 - (nz1-nz2);
float y = nz1-nz2 - (nx1-nx2);
float z = nx1-nx2 - (ny1-ny2);
return new Vector3(x,y,z);
}
void Render()
{
Graphics.DrawMeshInstancedIndirect(
mesh,
0,
material,
new Bounds(
Vector3.zero,
Vector3.one * radius * 4
),
argsBuffer
);
}
void Release()
{
if (particleBuffer != null)
particleBuffer.Release();
if (argsBuffer != null)
argsBuffer.Release();
}
}
Shader "Custom/GPUInstancedParticles"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct Particle
{
float3 pos;
float size;
float4 color;
};
StructuredBuffer<Particle> _Particles;
struct appdata
{
float3 vertex : POSITION;
uint instanceID : SV_InstanceID;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 col : COLOR;
};
v2f vert(appdata v)
{
v2f o;
Particle p =
_Particles[v.instanceID];
float3 world =
v.vertex * p.size + p.pos;
o.pos =
UnityObjectToClipPos(
float4(world,1)
);
o.col = p.color;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return i.col;
}
ENDCG
}
}
}
ํํฐํด์ด ์์ฑ๋ Scene์ Bloom, Color Adjustment, Depth of Field ๋ฑ์ ์ ์ฉํ์ฌ ๊พธ๋ฐ ์ ์์ต๋๋ค.
๋ ๋๋ง์ด ๋๋ ํ ํ๋ฉด์ ํ์ฒ๋ฆฌ๋ฅผ ํ๋ ๊ฒ์ด๊ณ ์ฝ๋๋ก๋ ์์ฑ์ด ๊ฐ๋ฅํ๋ GUI์์ ์กฐ์ ํ๋ฉฐ ๋ฐ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ํธ์ด ํจ์จ์ ์
๋๋ค.
๋จผ์ Global Volume์ ์์ฑํฉ๋๋ค.
Hierarchy > ์ฐํด๋ฆญ > Volume > Global Volume
Inspector ์ฐฝ์์ ๊ฐ์ข
์์น ์กฐ์ ์ ํ๋ฉฐ, ์ถ๊ฐ๋ก ํ์ํ ๊ธฐ๋ฅ์ด ์์ผ๋ฉด Add Override์ ๋๋ฌ Post-processing ํญ๋ชฉ ์ค์์ ๊ณ ๋ฅด๋ฉด ๋ฉ๋๋ค.

์ฐธ๊ณ ๋ก ์ด Volume ์ค์ ์ Camera์ Inspector์์ Rendering > Post Processing์ด ์ฒดํฌ๋์ด์ผ์ง ์ ์ฉ๋ฉ๋๋ค.
