
URP 레시피: 컴퓨트 셰이더
Tutorial
intermediate
+0XP
0 mins
Unity Technologies

이 튜토리얼은 유니버설 렌더 파이프라인으로 자주 사용되는 셰이더와 시각 효과 만들기(Unity 6 에디션) 기술 전자책에 수록된 URP(유니버설 렌더 파이프라인) 레시피를 기반으로 합니다.
이 튜토리얼을 마치면 파티클 효과를 생성하는 컴퓨트 셰이더를 직접 만들 수 있게 됩니다. 이 튜토리얼은 Unity에서 프로젝트를 개발해 본 경험이 있고 URP 기능을 사용할 줄 알며, HLSL 기반 셰이더 작성에 대한 지식을 갖춘 중급 Unity 사용자를 대상으로 합니다.
컴퓨트 셰이더가 처음이라면 기술 자료의 컴퓨트 셰이더 섹션을 확인하세요.
Languages available:
1. 컴퓨트 셰이더 소개
컴퓨트 셰이더는 일반 렌더링 파이프라인과는 별도로 기기의 GPU에서 실행되는 셰이더 프로그램입니다. 컴퓨트 셰이더는 여러 엔티티에 동일한 계산을 적용하기 위해 많은 양의 계산을 해야 하는 모든 작업에 적합합니다.
Unity는 GPU 기반 파티클 효과를 구현하기 위한 VFX Graph를 제공하지만, 이 튜토리얼에서는 여러분이 직접 셰이더를 만들어 봅니다. 이 과정을 마치면 인스턴싱된 메시에 적용할 수 있는 셰이더 제작 기법을 배워 수만 개의 메시를 활용하는 시각 효과를 구현할 수 있게 됩니다. 사용 중인 GPU에 따라 백만 개의 로우폴리곤 메시도 처리할 수 있으며, 이 기법은 풀과 머리카락, 물, 군중을 만들 때 유용합니다.
2. ParticleFun 레시피
ParticleFun 레시피는 마우스 커서를 따라 이동하는 파티클을 인스턴스화하는 방법을 다룹니다.
이 섹션에서는 먼저 샘플 프로젝트를 다운로드하고 튜토리얼을 통해 만들게 될 스크립트가 실제로 어떻게 작동하는지 확인해 보겠습니다.
참고: 이 프로젝트에는 Unity 6가 필요합니다.
다음 단계를 따라 샘플 프로젝트를 다운로드하고 완성된 셰이더를 살펴보세요.
1. GitHub 프로젝트를 다운로드한 다음 Scenes > Compute Shaders > Particles 폴더로 이동하여 ParticleFun 씬을 엽니다.
2. 에디터에서 플레이 모드를 시작합니다.
파티클이 움직이는데, 시간이 지나면서 점점 느려지는 것을 볼 수 있습니다. 게임(Game) 뷰에서 커서를 움직이면 씬에 있는 파티클이 커서 위치를 따라 움직이다가 점차 컬러가 변하는 것도 볼 수 있습니다.
3. 스크립팅 시작
이제 셰이더가 어떤 역할을 하는지 감이 잡혔다면 스크립팅을 시작할 차례입니다. 먼저 MonoBehaviour 스크립트를 만들어 파티클 설정을 시작해야 합니다. 다음 단계를 따라 스크립트 작성을 시작합니다.
1. Assets 폴더를 오른쪽 클릭한 다음, Create > Folder를 차례대로 선택하고 새 폴더의 이름을 ‘Particles’로 지정합니다.
2. Particles 폴더를 오른쪽 클릭하고 Create > Shader > Compute Shader를 차례대로 선택합니다.
3. 새로 생성된 .cs 파일의 이름을 ‘ParticleFun’으로 변경하고 IDE에서 엽니다.
4. ParticleFun.cs 파일에 다음 코드를 추가합니다.
public class ParticleFun : MonoBehaviour
{
private Vector2 cursorPos;
// 구조체
struct Particle
{
public Vector3 position;
public Vector3 velocity;
public float life;
}
const int SIZE_PARTICLE = 7 * sizeof(float);
public int particleCount = 1000000;
public Material material;
public ComputeShader shader;
int kernelID;
ComputeBuffer particleBuffer;
int groupSizeX;
RenderParams rp;
}
방금 추가한 코드를 좀 더 자세히 살펴보겠습니다.
- 파티클은 position, velocity, life 값을 가집니다. 파티클 1개의 데이터에는 플로트 7개가 사용되므로, 파티클 1개의 크기는 플로트 1개 크기의 7배가 됩니다.
- 그런 다음, 인스펙터(Inspector) 창에서 조정할 수 있는 여러 개의 public 변수를 선언합니다. 머티리얼에는 ParticleFun.shader를 사용하는 파티클 머티리얼이 할당되고, 셰이더에는 ParticleFun.compute가 할당됩니다.
5. Start 메서드에서 각 파티클의 초기 설정을 만드는 Init 메서드를 만들고 호출합니다.
파티클의 위치는 Homogeneous Clip Space로 설정되며, 각 축에 -w와 w 사이의 값을 가집니다. 여기서 w는 좌표의 네 번째 구성 요소입니다. w가 1이라고 가정하면 -1, -1, -1은 근거리 절두체의 왼쪽 하단을, 1, 1, 1은 원거리 절두체의 오른쪽 상단을 나타냅니다. 각 파티클의 position 값은 이 범위 내에서 무작위로 지정됩니다.
void Init()
{
// 파티클 초기 설정
Particle[] particleArray = new Particle[particleCount];
for (int i = 0; i < particleCount; i++)
{
float x = Random.value * 2 - 1.0f;
float y = Random.value * 2 - 1.0f;
float z = Random.value * 2 - 1.0f;
Vector3 xyz = new Vector3(x, y, z);
xyz.Normalize();
xyz *= Random.value * 5;
particleArray[i].position.x = xyz.x;
particleArray[i].position.y = xyz.y;
particleArray[i].position.z = xyz.z;
particleArray[i].velocity.x = 0;
particleArray[i].velocity.y = 0;
particleArray[i].velocity.z = 0;
// 초기 life 값 설정
particleArray[i].life = Random.value * 5.0f + 1.0f;
}
}
6. x, y, z 값이 각각 -1에서 1 사이의 무작위 값으로 설정된 Vector3를 생성한 다음, 해당 벡터를 정규화합니다.
이때 벡터의 길이가 1이 되도록 설정해야 합니다.
7. 벡터의 길이를 5까지 늘립니다. 이 벡터를 사용하여 파티클 배열에서 개별 파티클의 위치를 설정합니다. velocity는 0으로 설정하고, life는 1에서 6 사이의 무작위 값으로 지정합니다.
다음 단계에서는 컴퓨트 버퍼를 설정하고 컴퓨트 셰이더에 바인드합니다.
4. ComputeBuffer 생성
이 단계에서는 초기화 메서드에 사용할 컴퓨트 버퍼를 생성합니다.
컴퓨트 셰이더는 마우스 커서 위치에 따라 파티클의 위치를 지정하는 데 사용되며, 버텍스-프래그먼트 셰이더는 렌더링을 수행합니다. 파티클의 위치 정보는 컴퓨트 버퍼를 통해 버텍스-프래그먼트 셰이더로 전달됩니다.
다음 단계를 따라 컴퓨트 버퍼에 데이터를 채우고 컴퓨트 셰이더의 코드를 호출하는 방법을 확인합니다. ComputeBuffer는 스크립트 코드에서 생성하고 데이터를 채운 다음 컴퓨트 셰이더나 일반 셰이더에서 사용할 수 있는 클래스입니다.
ComputeBuffers에는 두 가지 파라미터가 있습니다.
- 요소의 개수: particleCount
- 각 요소의 크기: SIZE_PARTICLE
ComputeBuffer를 생성한 후에는 SetData 메서드를 사용해 버퍼에 데이터를 채워야 합니다. 이 작업을 통해 RAM에 있던 데이터가 GPU 메모리로 전송됩니다. ComputeShader에서 데이터에 액세스하려면 모든 데이터가 반드시 GPU 메모리에 있어야 합니다. 그런 다음 ComputeShader 코드 내에 kernel(커널)이라는 특수한 유형의 함수를 사용해 ComputeShader 코드를 호출합니다. 각 커널에는 고유한 ID가 있습니다. 함수 이름을 파라미터로 사용해 FindKernel 메서드를 호출하면 해당 커널의 ID를 확인할 수 있습니다.
// 컴퓨트 버퍼 생성
particleBuffer = new ComputeBuffer(particleCount,SIZE_PARTICLE);
particleBuffer.SetData(particleArray);
// 커널 ID 검색
kernelID = shader.FindKernel("CSParticle");각 커널에는 x, y, z라는 3가지 스레드 파라미터가 있습니다. 컴퓨트 셰이더의 놀라운 점은 이 스레드들이 병렬로 작동하는 방식에 있습니다. 이 예제에서는 스레드 그룹 크기를 각각 256, 1, 1로 설정합니다. GPU 성능을 최적화하려면 사용하는 기기의 아키텍처를 알아야 합니다.
C# 스크립트에서는 GetKernelThreadGroupSizes 컴퓨트 셰이더 메서드를 사용해 스레드 그룹 크기에 액세스할 수 있습니다. 모든 파티클에 스레드가 할당되도록 하려면 아래 코드에 표시된 횟수만큼 커널을 디스패치해야 하며, 이 코드는 초기화 과정에서 앞서 작성한 코드 다음에 위치해야 합니다.
uint threadsX;
shader.GetKernelThreadGroupSizes(kernelID, out threadsX, out _, out _);
groupSizeX = Mathf.CeilToInt((float)particleCount / (float)threadsX);이 예제에서는 모든 작업이 x 스레드에서 처리됩니다. particleBuffer는 컴퓨트 셰이더뿐만 아니라 머티리얼에도 함께 전달되어야 합니다.
// 컴퓨트 버퍼를 셰이더와 컴퓨트 셰이더에 바인드
shader.SetBuffer(kernelID, "particleBuffer", particleBuffer);
material.SetBuffer("particleBuffer", particleBuffer);이 예제의 핵심은 GPU에 상주하는 ComputeBuffer 하나를 컴퓨트 셰이더와 버텍스-프래그먼트 셰이더에서 함께 사용할 수 있다는 점입니다. 이렇게 하면 컴퓨트 셰이더에서 버퍼의 내용을 조작한 다음, 버텍스-프래그먼트 셰이더로 오브젝트를 렌더링할 때 동일한 버퍼를 그대로 사용할 수 있습니다.
RenderParams 인스턴스를 새로 구성해야 하며, 이때는 단순히 큰 Bounds 인스턴스를 지정하면 됩니다. 이 인스턴스는 Graphics.RenderPrimitives를 사용해 실제로 파티클을 렌더링할 때 필요합니다.
rp = new RenderParams(material);
rp.worldBounds = new Bounds(Vector3.zero, 10000*Vector3.one);5. Update 메서드
이 단계에서는 커서 정보를 스크립트와 셰이더에 전달하는 Update 로직을 스크립트에 추가합니다.
다음 단계를 따라 컴퓨트 셰이더에 데이터를 전달하는 방법을 알아보세요.
1. Update 메서드에서 아래와 같이 컴퓨트 셰이더에 deltaTime과 mousePosition을 설정합니다.
float[] mousePosition2D = { cursorPos.x, cursorPos.y };
// 컴퓨트 셰이더에 데이터 전송
shader.SetFloat("deltaTime", Time.deltaTime);
shader.SetFloats("mousePosition", mousePosition2D);2. 앞서 ‘ComputeBuffer 생성’에서 확인한 kernelID를 디스패치(Dispatch)합니다.
Dispatch에는 x, y, z 각 차원에 작업 그룹 수를 설정해야 합니다.
shader.Dispatch(kernelID, groupSizeX, 1, 1);커널을 particleCount의 수만큼 실행하려면 x 스레드 그룹 크기가 256이므로 groupSizeX를 particleCount/256의 정수 올림값으로 미리 계산해 두어야 합니다. 여기서 정수 올림값이란, 예를 들어 7을 2로 나누면 부동 소수점 값은 3.5가 되며, 이 값을 정수로 올림하면 그다음 정수인 4가 된다는 의미입니다.
x 차원에 groupSizeX를 사용하면 particleCount가 정확히 256의 배수가 아니더라도 x 값을 0부터 particleCount-1 이상까지의 각 인덱스 값으로 두고 커널이 실행됩니다. Dispatch 작업이 완료되면 particleBuffer에는 각 파티클의 새로운 positoin 값이 저장됩니다.
여기서부터는 Graphics 인터페이스의 메서드를 사용합니다.
6. RenderPrimitives 메서드 및 셰이더
이 단계에서는 스크립트에서 파티클을 표시하기 위한 RenderPrimitive 메서드를 만들고, 셰이더도 생성합니다.
RenderPrimitive 메서드를 만드는 방법은 다음과 같습니다.
Update 메서드에 마지막으로 RenderPrimitive 메서드를 생성합니다. RenderPrimitives 메서드는 다음 4가지 파라미터를 사용합니다.
- 최소한 Bounds 영역이 정의된 RenderParams 인스턴스
- 메시 토폴로지 유형(이 예제에서는 점을 렌더링하지만, 삼각형의 선도 렌더링할 수 있음)
- 단일 인스턴스에서 사용할 버텍스 수(점의 경우 항상 1)
- 인스턴스 수(이 예제에서는 파티클 수에 해당)
...
Graphics.RenderPrimitives(rp, MeshTopology.Points, 1, particleCount );
실제 렌더링은 머티리얼에 연결된 셰이더가 처리합니다. 직접 살펴보겠습니다.
1. Create > Shader > Standard Surface Shader를 차례대로 선택하고 이름을 ‘ParticleFun’으로 지정합니다.
형식상 포함된 코드는 이 섹션 마지막에 나오는 코드로 덮어쓸 예정입니다.
ParticleFun.shader 파일에 버퍼에 대한 레퍼런스를 추가해야 합니다. 또한 파티클 구조체도 정의해야 합니다.
struct Particle{
float3 position;
float3 velocity;
float life;
};
StructuredBuffer<Particle> particleBuffer;2. 버퍼를 RWStructuredBuffer가 아닌 StructuredBuffer로 설정합니다.
셰이더에서는 이 버퍼에 데이터를 쓰지 않으며, 해당 작업은 컴퓨트 셰이더에서 수행합니다.
셰이더 코드에는 _PointSize 프로퍼티가 포함됩니다. 이때 vert 함수로 전달되는 Attributes 구조체 인스턴스에는 instanceID 프로퍼티가 있습니다. 포인트 셰이더에서는 이 인스턴스 ID가 0부터 파티클 수 – 1까지 할당되며, 이 값을 버퍼의 인덱스로 사용하게 됩니다. 컴퓨트 셰이더가 이 position 값을 업데이트하므로, 위치 지정은 컴퓨트 셰이더가 담당하고 렌더링은 버텍스-프래그먼트 셰이더가 수행하는 구조가 갖춰집니다. MeshTopology 점을 사용할 경우, 셰이더에서 입력 파라미터에 PSIZE 시맨틱으로 점의 픽셀 크기를 설정해야 합니다. 이 예제에서는 스크립트에 의해 전달된 PointSize 변수를 해당 값으로 설정합니다.
3. ParticleFun 셰이더의 HLSL 코드를 다음 코드로 교체합니다.
Shader "Custom/ParticleFun"
{
Properties
{
_PointSize("Point size", Float) = 5.0
}
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
struct Particle{
float3 position;
float3 velocity;
float life;
};
StructuredBuffer<Particle> particleBuffer;
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float _PointSize;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
uint instanceID : SV_InstanceID;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float4 color : COLOR;
float size: PSIZE;
};
Varyings vert(Attributes IN)
{
Varyings OUT;
Particle particle = particleBuffer[IN.instanceID];
// 컬러
float lerpVal = particle.life * 0.25f;
OUT.color = half4(1.0f - lerpVal+0.1, lerpVal+0.1, 1.0f, lerpVal);
// 위치
OUT.positionHCS = TransformObjectToHClip(particle.position);
OUT.size = _PointSize;
return OUT;
}
half4 frag(Varyings IN) : SV_Target
{
return IN.color;
}
ENDHLSL
}
}
}
이 셰이더는 각 파티클 메시에 컬러를 채우며, 일반적인 HLSL 프로그램의 구조를 따릅니다. 자세한 내용은 URP에서 언릿 기본 셰이더 작성 기술 자료 페이지(영문)에서 확인할 수 있습니다.
7. 컴퓨트 셰이더 생성
이 단계에서는 컴퓨트 셰이더를 생성합니다.
컴퓨트 셰이더를 생성하고 설정하는 방법은 다음과 같습니다.
1. 에디터의 프로젝트(Project) 창에서 오른쪽 클릭한 다음, Create > Shaders > Compute Shader를 차례대로 선택합니다.
이 셰이더에도 형식상 포함된 코드가 있습니다.
2. 함수 또는 커널의 이름을 ‘CSParticle’로 지정합니다.
파티클의 버퍼를 정의해야 합니다. 스크립트에 있는 것과 동일한 구조체가 필요하며, 이 셰이더는 버퍼에 데이터를 쓸 예정이므로 RWStructuredBuffer를 선언해야 합니다.
3. 컴퓨트 셰이더에 다음 코드를 추가합니다.
// 파티클 데이터
struct Particle
{
float3 position;
float3 velocity;
float life;
};
// 셰이더와 공유되는 파티클 데이터
RWStructuredBuffer<Particle> particleBuffer;4. CPU에서 전달받을 변수와 다음 단계에서 사용할 고속 난수 생성 알고리즘 Xorshift를 설정합니다.
// CPU의 변수 세트
float deltaTime;
float2 mousePosition;
uint rng_state;
uint rand_xorshift()
{
// 조지 마르살리아의 논문에서 차용한 Xorshift 알고리즘
rng_state ^= (rng_state << 13);
rng_state ^= (rng_state >> 17);
rng_state ^= (rng_state << 5);
return rng_state;
}
이제 파티클을 마우스 커서의 위치로부터 빠르게 움직이는 데 사용할 수 있는 벡터가 준비되었습니다. 이번에는 deltaTime으로 파티클 속도를 조정해 보겠습니다.
파티클의 수명이 0보다 작아지면 해당 파티클을 다시 생성하고, 고속 난수 함수를 사용해 필요한 무작위 값을 생성합니다.
void respawn(uint id)
{
rng_state = id;
float tmp = (1.0 / 4294967296.0);
float f0 = float(rand_xorshift()) * tmp - 0.5;
float f1 = float(rand_xorshift()) * tmp - 0.5;
//float f2 = float(rand_xorshift()) * tmp - 0.5;
float3 normalF3 = normalize(float3(f0, f1, 0.0)) * 0.8f;
normalF3 *= float(rand_xorshift()) * tmp;
particleBuffer[id].position = float3(normalF3.x + mousePosition.x, normalF3.y + mousePosition.y, 0.0);
// 이 파티클의 수명 초기화
particleBuffer[id].life = 4;
particleBuffer[id].velocity = float3(0,0,0);
}
파티클은 마우스 커서의 위치를 중심으로 평면 위 반지름이 0.8인 구 내부에 위치하며, z = 0입니다.
5. 새로 생성된 파티클의 life는 4초로, velocity는 0으로 초기화됩니다.
6. CSParticle 커널을 생성하고 다음 코드를 추가합니다.
[numthreads(256, 1, 1)]
void CSParticle(uint3 id : SV_DispatchThreadID)
{
Particle particle = particleBuffer[id.x];
particle.life -= deltaTime;
float3 delta = float3(mousePosition.xy, 0) - particle.position;
float3 dir = normalize(delta);
particle.velocity += dir;
particle.position += particle.velocity * deltaTime;
particleBuffer[id.x] = particle;
if (particle.life < 0) respawn(id.x);
}
7. 버퍼에서 인덱스가 id.x인 파티클을 가져온 다음, 화면이 업데이트될 때마다 전달되는 deltaTime 프로퍼티를 사용해 life 값을 감소시킵니다.
이 메서드에서는 마우스 커서 위치의 델타값과 속도를 합산하지만, life가 0보다 작아지면 이전 단계의 함수를 사용해 파티클을 다시 생성합니다.
8. ParticleFun.cs 파일에 다음 코드를 추가하여 OnGUI 이벤트를 사용합니다.
void OnGUI()
{
Vector3 p = new Vector3();
Camera c = Camera.main;
Event e = Event.current;
Vector2 mousePos = new Vector2();
// Event에서 마우스 커서 위치 수신
// Event의 y 값이 반전되어 있음
mousePos.x = e.mousePosition.x;
mousePos.y = c.pixelHeight - e.mousePosition.y;
p = c.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y,
c.nearClipPlane ));
cursorPos.x = p.x;
cursorPos.y = p.y;
}이 코드는 마우스 클릭을 감지하고 간단한 코드로 클릭 위치를 화면상 위치로 변환합니다. 화면 좌표계에서는 화면 아래쪽이 0이지만, Event.mousePosition은 화면 위쪽을 0으로 사용합니다.
y 값을 반전하려면 Camera pixelHeight에서 mousePosition.y를 빼면 됩니다. 그런 다음 Camera에 ScreenToWorldPoint 메서드(z 값 필요)를 사용합니다. 마지막으로 nearClipPlane을 함께 사용하면 정확한 결과를 얻을 수 있습니다.
최종적으로 계산된 커서 위치는 Update 함수에서 컴퓨트 셰이더로 값을 전달하는 데 사용됩니다.
8. 파티클 동작 확인
이 단계에서는 셰이더에서 컬러 페이드 효과를 적용하고 씬이 제대로 작동하도록 설정합니다. 다음 단계를 따라 파티클 컬러를 변경합니다.
파티클에 멋진 효과를 주기 위해 life 프로퍼티로 파티클의 컬러를 제어하겠습니다.
버텍스-프래그먼트 셰이더의 vert 함수로 돌아가서 컬러 할당에 관여하는 다음 코드를 확인하세요.
Particle particle = particleBuffer[IN.instanceID];
// 컬러
float lerpVal = particle.life * 0.25f;
OUT.color = half4(1.0f - lerpVal+0.1, lerpVal+0.1, 1.0f, lerpVal);이제 파티클의 컬러가 변합니다. respawn 함수에서 life를 4로 설정한 다음 여기에 0.25를 곱하기 때문에, lerp 값은 항상 1에서 0 사이가 됩니다. 적색 채널은 life가 줄어듦에 따라 0.1에서 1.1까지 증가하고, 녹색 채널은 반대로 1.1에서 0.1까지 감소합니다. 청색 채널은 항상 1로 유지되며, 알파 채널은 시간이 지남에 따라 줄어들어 픽셀이 점점 사라지게 됩니다.
이 예제는 동일한 에셋을 여러 인스턴스로 렌더링할 때 컴퓨트 셰이더와 버텍스-프래그먼트 셰이더를 함께 사용하는 방식이 얼마나 유용한지를 보여줍니다. 여기서는 가장 단순한 에셋인 단일 픽셀 컬러 값을 사용합니다.
이제 파티클이 작동할 씬을 준비해 보겠습니다.
1. 새 게임 오브젝트를 만들고 ParticleFun.cs 컴포넌트를 추가합니다.
셰이더에 대한 레퍼런스를 추가하려면 해당 셰이더의 머티리얼을 생성하고 컴퓨트 셰이더에 대한 레퍼런스도 포함해야 합니다.
최종 결과에서는 머티리얼로 파티클 크기를 조정하거나, 컴포넌트로 파티클 수를 변경할 수 있습니다.
완성된 셰이더를 확인하고 여러분이 직접 작성한 코드와 비교하려면 GitHub 저장소에서 코드를 다운로드하세요.
동영상과 함께 이 튜토리얼을 따라 하고 주제를 더 깊이 이해하고 싶다면, 다음 섹션에 소개된 동영상 튜토리얼을 시청하시기 바랍니다.
9. 추가 리소스
셰이더와 시각 효과에 대한 더 많은 레시피를 확인하려면 유니버설 렌더 파이프라인으로 자주 사용되는 셰이더와 시각 효과 만들기(Unity 6 에디션) 기술 전자책을 다운로드하세요. Unity 고급 사용자를 위한 유니버설 렌더 파이프라인 소개(Unity 6 에디션)에서는 Unity 6에서 URP(유니버설 렌더 파이프라인)를 통해 최대한 효율적으로 개발하는 방법을 종합적으로 안내합니다.
다음 동영상 튜토리얼도 확인해 보세요.