[SHADER] URP Shader 1편
함께 회사에 다니는 친구를 위하여 기록합니다.
URP 전에 흔하게 사용되던 레거시 셰이더의 경우, 유니티 홈페이지에서 따로 받을 수 있고, URP 프로젝트에서 셰이더를 생성하더라도 레거시 셰이더(CGPROGRAM)으로 생성이 됩니다. 그렇기 때문에 URP 셰이더를 처음 시작할 경우 어렵게 느낄 수 있습니다.
이와 같은 이유로 직접 Shader Library에서 Unlit.shader 파일을 열어 시작하는 방법과, 유니티 URP 매뉴얼에 샘플로 제공하는 기본형 셰이더로 시작할 수도 있습니다.
우선 Shader Library의 경우, 경로가 조금씩 다를 수 있지만 보통 프로젝트 폴더 아래, "Library\PackageCache\com.unity.render-pipelines.core@14.0.8\ShaderLibrary"에 존재합니다.
윈도우 파일 탐색기에서 Unlit.shader로 검색하면 쉽게 찾을 수 있습니다.
그리고 유니티의 URP 메뉴얼에서 샘플 코드를 열람할 수 있습니다. 이 글에서 그 코드를 가지고 Unity Shader에 대하여 설명을 진행하겠습니다.
유니티의 렌더링 파이프라인은 크게 두 가지로 나눌 수 있는데, 기존의 내장 렌더링 파이프라인 built-in render pipeline(BRP)와 개발자가 프로그래밍 할 수 있는 렌더링 파이프라인인 srciptable render pipeline(SRP)가 있다. 여기서 SRP의 경우 다시 URP와 HDRP로 나눌 수 있습니다.
URP는 BRP와 달리 개발자가 직접 필요 없는 부분을 배제하고, 최적화 등을 직접 진행할 수 있기 때문에 렌더링 성능이 좋아지고, 확장성이 더 좋아졌습니다.
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@17.0/manual/writing-shaders-urp-basic-unlit-structure.html : URP 17.0.1 메뉴얼의 URP unlit basic shader 코드를 열람 할 수 있습니다.
직접 코드를 보며 각 부분이 무엇이고, 어떤 역할을 하는지 이야기하겠습니다.
먼저 샘플 코드 전체를 봅시다. 하단의 코드는 메뉴얼에서 제공하는 샘플 코드에서 주석을 지우고, 열을 정리하였습니다.
Shader "Example/URPUnlitShaderBasic"
{
Properties
{ }
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
};
Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
return OUT;
}
half4 frag() : SV_Target
{
half4 customColor = half4(0.5, 0, 0, 1);
return customColor;
}
ENDHLSL
}
}
}
Shader "Example/URPUnlitShaderBasic" { ... }
유니티는 ShaderLab이라는 셰이더 언어로 작성됩니다. 그리고 위의 코드는 유니티에서 셰이더의 경로와 이름을 보여주는 코드입니다. 이런 형식이라면 뒤도 보지 않고 유니티에서 사용하는 셰이더라고 생각하면 좋을 것 같습니다.
Properties { ... }
속성(프로퍼티) : 개발자가 의도적으로 셰이더에 접근할 수 있는 변수들을 메테리얼의 인스팩터에서 노출시키는데 사용됩니다. 특정 키워드를 이용하여 메테리얼의 인스팩터에서 보이지 않도록 할 수 있으며, 스크립트로도 제어할 수 있습니다.
SubShader { ... }
SubShader : 실질적으로 최종 픽셀의 색을 정하는 버텍스 셰이더와 프레그먼트 셰이더가 작동하기 전, 셰이더 모델과 GPU 기능을 타겟팅 합니다.
예를 들어서 #pragma target 4.0 이라는 지시어를 통하여 셰이더 모델을 지정합니다. 자세한 정보는 유니티 메뉴얼 https://docs.unity3d.com/Manual/SL-ShaderCompileTargets.html 에 있습니다.
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
Tags : 렌더파이프라인, 큐, 렌더 타입 등 여러가지를 설정 할 수 있는 부분입니다. 자세한 정보는 유니티 메뉴얼 https://docs.unity3d.com/2023.3/Documentation/Manual/SL-SubShaderTags.html 에 있습니다.
Pass { ... }
본격적으로 셰이더가 계산되는 부분이라고 생각하면 편할 것 같습니다.
위 샘플 코드에서는 나오지 않았지만, Pass의 이름과, Tags에 라이트 모드를 설정할 수 있는데, 이것들은 예를 들어 "Name "Forward" Tags {"LightMode" = "UniversalForward"}"로 작성됩니다.
라이트 모드의 경우 유니티에서 해당 패스를 사용하는 방법을 설명하기도 하며, 랜더 피처에서 타겟팅 할 수 있습니다.
URP용 SimpleLit.shader를 살펴 보면,
Name "ForwardLit" "LightMode" = "UniversalForward" : 포워드 렌더링 경로에서 오브젝트들을 렌더링하는데 사용됩니다.
Name "ShadowCaster" "LightMode" = "ShadowCaster" : 그림자를 그리는데 사용됩니다.
Name "GBuffer" "LightMode" = "UniversalGBuffer" : 디퍼드 랜더링의 G 버퍼 생성에 사용됩니다.
Name "DepthOnly" "LightMode" = "DepthOnly" : 뎁스 버퍼 생성에 사용됩니다.
Name "Meta" "LightMode" = "Meta" : 라이트맵 베이킹에 사용됩니다.
Cull : 면을 추려내는 지시문입니다.
ZTest : 뎁스 버퍼와 비교하여 렌더링 유무를 결정합니다.
ZWrite : 뎁스 버퍼를 사용하는 유무를 결정합니다.
Offset : 뎁스버퍼에서 실제 값보다 뒤나 앞에 오브젝트를 그릴 수 있도록 보정해줄 때 사용됩니다.
관련 유니티 메뉴얼 : https://docs.unity3d.com/kr/2022.1/Manual/SL-Offset.html
블랜드 모드 정의 관련 유니티 메뉴얼 : https://docs.unity3d.com/2023.3/Documentation/Manual/SL-Blend.html
하나의 SubShader 안에 여러 개의 Pass를 만들 수 있습니다. 기본적으로 각 Pass는 오브젝트의 색, 그림자 등을 Pass 별로 나누어서 렌더링 하게 됩니다. 그리고 이런 일련의 과정들을 프레임 디버거나 렌더독으로 확인할 수 있습니다.
HLSLPROGRAM
URP 전의 레거시 셰이더의 경우 CGPROGRAM을 사용하였고, #include "UnityCG.cginc"를 통하여 셰이더 제작에 도움을 주었습니다.
URP는 HLSLPROGRAM을 사용하며, #include "~Core.hlsl"를 통하여 셰이더 제작에 도움을 주고 있습니다.
ENDHLSL로 HLSLPROGRAM의 끝을 알립니다.
#pragma vertex vert
#pragma fragment frag
사용할 버텍스 셰이더(함수)와 프래그먼트 셰이더(함수)를 설정합니다.
#pragma vertex vert : vert라는 이름의 버텍스 셰이더(함수)를 사용할 것이라는 뜻입니다.
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
Core.hlsl를 포함할 것이라는 뜻입니다. 이 hlsl파일 안에는 유니티에서 제공하는 기본적인 설정이나, 도우미 함수들이 존재하고, 또한 많은 다른 hlsl파일들을 포함할 수 있도록 구조가 짜져 있습니다.
struct Attributes { ... }
버텍스 셰이더에 정점 데이터를 입력하는 구조체입니다.
struct Varyings { ... }
버텍스 셰이더의 출력과 프래그먼트 셰이더의 입력을 담당하는 구조체입니다.
Attributes와, Varyings는 1:1 매칭이 아닙니다.
예를 들어서 아래의 코드를 확인해 보면, 입력은 2개, 출력은 4개임을 볼 수 있습니다.
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
}
struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD1;
half4 normalWS : TEXCOORD2;
}
몇 가지 시멘틱스(POSITION, TEXCOORD0 등 대문자로 표기된 것 들...)는 이것이 무엇인지 의미를 전달해 주는 것입니다. TEXCOORD를 제외하고 몇 가지 정해진 이름이 있습니다.
POSITION : 버텍스의 위치
COLOR : 버텍스 칼라
NORMAL : 버텍스의 노말
TANGENT : 버텍스 탄젠트
TEXCOORD의 경우 0부터 7까지 존재하는데, 사실 이것은 DX9 Shader Model 2.0에서는 8개까지, DX 3.0은 10개, OpenGL ES 3.0, 메탈은 16개, DX10 Shader Model 4.0은 32개까지 사용할 수 있지만, 보통의 노말 한 디바이스를 모두 지원하기 위하여 최대 8개 사용을 권장합니다.
Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
return OUT;
}
버텍스 셰이더입니다.
구조체 Attributes를 매개변수로 작동하게 됩니다.
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); : 각 버텍스의 위치를 정합니다.
TransformObjectToHClip : 렌더파이프라인에서 버텍스의 위치는 우리가 보는 3D 공간에서 몇 개의 단계를 거쳐야 합니다. 로컬(모델) 스페이스 > 월드 스페이스 > 뷰(카메라) 스페이스 > 클립 스페이스 > 스크린(모니터) 스페이스, 이런 공간변환을 맡고 있는 함수입니다.
이 함수의 경우 SpaceTransform.hlsl에 들어있습니다.
half4 frag() : SV_Target
{
half4 customColor = half4(0.5, 0, 0, 1);
return customColor;
}
프래그먼트 셰이더입니다.
입력받는 값이 없기 때문에 frag()로 매개변수 부분이 비어 있습니다.
단순하게 색만 출력합니다.
이다음부터는 부분별로 더욱 상세히 이야기할 것이며, 우선 여기서 한번 끊고, 다음 장에서 이어서 진행하겠습니다.
'Unity > Rendering&Shader' 카테고리의 다른 글
[SHADER] 재미있는 UV의 세계 (0) | 2023.11.09 |
---|---|
[LIGHT][SHADER] Fake Light - LARA CROFT GO (0) | 2023.11.09 |
[SHADER] Object의 Scale 값을 이용한 UV Shader (0) | 2023.11.08 |
[SHADER] URP Shader를 뜯어보자 - 기본형 (0) | 2023.11.07 |
[RENDERING] Planar Reflection, Render (0) | 2023.10.27 |