간혹 서로 다른 그래픽스 API에서 그래픽스 렌더링이 다르게 작동할 수 있습니다. 대부분의 경우 Unity 에디터는 그에 따른 차이점을 숨기지만, 그렇게 되지 않는 경우가 일부 존재합니다. 이러한 상황이 발생할 때 취해야 할 조치는 다음과 같습니다.
Direct3D 유형 플랫폼과 OpenGL 유형 플랫폼의 수직 텍스처 좌표 규칙은 서로 다릅니다.
이 차이는 렌더 텍스처로 렌더링하는 경우를 제외하면 프로젝트에 영향을 미치지 않는 편입니다. Direct3D 타입 플랫폼에서 텍스처로 렌더링하는 경우 Unity는 내부적으로 렌더링을 플립합니다. 그러면 규칙이 플랫폼 간에 일치하게 되며 OpenGL 타입 플랫폼 규칙이 표준으로 사용됩니다.
셰이더에서 다른 좌표 규칙으로 인해 프로젝트에 문제가 발생하지 않도록 조치를 취해야 하는 두 가지의 일반적인 사례로는 이미지 효과와 UV 공간에서 렌더링이 있습니다.
이미지 효과가 렌더 텍스처를 한 번에 하나씩 처리하는 간단한 효과인 경우, 일치하지 않는 좌표는 Graphics.Blit으로 처리됩니다. 그러나 이미지 효과에서 두 개 이상의 렌더 텍스처를 함께 처리하는 경우, 안티앨리어싱을 쓰거나 Direct3D 타입에서 작업하면 렌더 텍스처들의 수직 방향이 다르게 출력될 수 있습니다. 좌표를 표준화하려면 버텍스 셰이더에서 화면 텍스처를 위아래로 플립하여 OpenGL 타입 좌표 표준과 일치시켜야 합니다.
다음 코드 샘플은 작업을 수행하는 방법입니다.
// Flip sampling of the Texture:
// The main Texture
// texel size will have negative Y).
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
uv.y = 1-uv.y;
#endif
비슷한 상황은 GrabPass에서도 발생합니다. Direct3D 타입(비OpenGL 타입) 플랫폼에서 실제로는 렌더 텍스처의 위아래가 뒤집히지 않을 수 있습니다. 셰이더 코드에서 GrabPass 텍스처를 샘플링하는 경우 UnityCG include 파일의 ComputeGrabScreenPos 함수를 사용하십시오.
빌트인 변수인 ProjectionParams.x의 값은 +1 또는 –1입니다. -1은 OpenGL 타입 투사 좌표와 일치하도록 투사가 위 아래로 플립되었음을 나타내는 한편, +1은 플립되지 않았음을 나타냅니다. 셰이더에서 이 값을 확인한 다음 다양한 작업을 수행할 수 있습니다. 아래 예시는 투사가 플립되었는지 확인하고, 플립된 경우 일치하는 UV 좌표를 반환합니다.
float4 vert(float2 uv : TEXCOORD0) : SV_POSITION
{
float4 pos;
pos.xy = uv;
// This example is rendering with upside-down flipped projection,
// so flip the vertical UV coordinate too
if (_ProjectionParams.x < 0)
pos.y = 1 - pos.y;
pos.z = 0;
pos.w = 1;
return pos;
}
Direct3D 타입: 클립 공간 뎁스의 범위는 근접 평면의 +1.0부터 원거리 평면의 0.0까지입니다. Direct3D, Metal 및 콘솔에 해당합니다.
OpenGL 타입: 클립 공간 뎁스의 범위는 근접 평면의 –1.0부터 원거리 평면의 +1.0까지입니다. OpenGL 및 OpenGL ES에 해당합니다.
셰이더 코드 안에서 UNITY_NEAR_CLIP_VALUE 빌트인 매크로를 사용하여 플랫폼별 근접 평면 값을 얻을 수 있습니다.
스크립트 코드 안에서는 GL.GetGPUProjectionMatrix를 사용해 (OpenGL 타입 규칙을 따르는) Unity 좌표계에서 Direct3D 타입 좌표로 전환할 수 있습니다(플랫폼에서 이 좌표를 요구하는 경우).
자세한 내용은 셰이더에서 16비트 정밀도 사용을 참고하십시오.
Unity에서 셰이더를 작성할 때 Unity는 half를 float 또는 min16float로 정의한다는 점에 유의해야 합니다. Shader Precision Model 이 Platform Default 로 설정된 경우 half는 macOS에서는 float이고 iOS/tvOS에서는 min16float입니다. Shader Precision Model 이 Unified 로 설정된 경우 macOS/tvOS/iOS에서 half는 min16float입니다.
Unity 6 이상에서 버텍스 셰이더 입력, 상수 버퍼, 구조화된 버퍼와 같은 모든 CPU 가시 버퍼에서 Metal의 min16float 크기와 정렬은 4바이트입니다. 따라서 플랫폼이나 프로젝트 설정에 관계없이 half의 크기와 정렬은 항상 동일합니다. 이전 버전의 Unity에서는 min16float의 크기와 정렬이 2바이트이기 때문에 플랫폼과 선택한 Shader Precision Model 설정에 따라 half가 포함된 버퍼의 레이아웃이 달라졌습니다. 이 문제로 인해 iOS 및 tvOS 사용자는 iOS/tvOS의 GPU 버퍼에 데이터를 업로드할 때 Unity 6에는 더 이상 적용되지 않는 C# 코드를 추가해야 했습니다. Unity 6에서 FXC로 컴파일할 때 이전 동작을 활성화하려면 셰이더에 #pragma metal_fxc_allow_float16_in_cpu_visible_buffers를 포함하면 됩니다.
const의 사용 방식은 Microsoft HLSL(msdn.microsoft.com 참조)과 OpenGL의 GLSL(Wikipedia 참조) 셰이더 언어 간에 다릅니다.Microsoft의 HLSL const의 의미는 선언되는 변수의 범위 안에서 읽기 전용이지만 어떤 식으로든 초기화될 수 있다는 면에서 C#와 C++에서 사용되는 의미와 거의 같습니다.
OpenGL의 GLSL const는 변수가 사실상 컴파일 시간 상수이므로 컴파일 시간 제약(다른 const의 리터럴 값 또는 계산)을 사용하여 초기화되어야 함을 의미합니다.
OpenGL의 GLSL 시맨틱을 따르고 변수가 실제로 불변인 경우에만 변수를 const로 선언하는 것이 좋습니다. const 변수를 변할 수 있는 다른 값으로(함수의 로컬 변수로 등) 초기화하지 않는 것이 좋습니다. 이 방식은 Microsoft의 HLSL에서도 유효합니다. 따라서 이러한 방식으로 const를 사용하면 일부 플랫폼에서 혼동을 방지할 수 있습니다.
버퍼를 사용하여 셰이더에서 변수를 선언한 다음 GPU 컴퓨터 버퍼 또는 그래픽스 버퍼의 데이터를 사용하여 값을 설정하는 경우, 그래픽스 API에 따라 데이터 레이아웃이 일치하지 않을 수 있습니다. 즉, 데이터를 덮어쓰거나 변수를 잘못된 값으로 설정할 수 있습니다.
예를 들어 cbuffer 또는 Unity의 상수 버퍼 매크로를 사용하는 경우, 상수 버퍼의 데이터 레이아웃과 그래픽스 API에 따라 float3이 float4가 되거나 float가 float2가 될 수 있습니다.
다음을 수행하여 모든 그래픽스 API가 동일한 데이터 레이아웃으로 버퍼를 컴파일하도록 할 수 있습니다.
float3과 float3x3 대신 float4와 float4x4를 사용합니다. float4 변수는 모든 그래픽스 API에서 크기가 동일한 반면 float3 변수는 일부 그래픽스 API에서 크기가 달라질 수 있기 때문입니다.float4, float2, float 순)으로 선언합니다.예시:
cbuffer myConstantBuffer {
float4x4 matWorld;
float4 vObjectPosition; // Uses a float4 instead of a float3
float arrayIndex;
}
버텍스 셰이더 출력(클립 공간) 위치: SV_POSITION. 때때로 셰이더는 셰이더가 모든 플랫폼에서 작동하도록 POSITION 시맨틱을 사용합니다. Sony PS4 또는 테셀레이션에서는 이렇게 할 수 없습니다.
프래그먼트 셰이더 출력 컬러: SV_Target. 때때로 셰이더는 셰이더가 모든 플랫폼에서 작동하도록 COLOR 또는 COLOR0을 사용합니다. Sony PS4에서는 이렇게 할 수 없습니다.
메시를 포인트로 렌더링하는 경우 버텍스 셰이더에서 PSIZE 시맨틱을 출력합니다(예: 1로 설정). OpenGL ES 또는 Metal과 같은 일부 플랫폼은 포인트 크기를 셰이더에서 작성하지 않는 경우 ‘정의되지 않은’ 것으로 간주합니다.
자세한 내용은 셰이더 시맨틱 기술 자료를 참조하십시오.
구문을 사용할 때 발생할 수 있는 가장 일반적인 문제는 다음과 같습니다.
out 파라미터가 있는 표면 셰이더 버텍스 수정자입니다. 다음과 같이 출력을 초기화합니다. void vert (inout appdata_full v, out Input o)
{
**UNITY_INITIALIZE_OUTPUT(Input,o);**
// ...
}
부분적으로 초기화된 값입니다. 예를 들어 함수는 float4를 반환하지만 코드는 함수의 .xyz 값만 설정합니다. 세 개의 값만 필요한 경우 모든 값을 설정하거나 float3으로 변경합니다.
버텍스 셰이더에서 tex2D 사용. UV 파생물이 버텍스 셰이더에 없기 때문에 유효하지 않습니다. 대신 명시적 밉맵 레벨을 샘플링해야 합니다. 예를 들어 tex2Dlod(tex, float4(uv,0,0))를 사용할 수 있습니다. tex2Dlod는 셰이더 모델 3.0 기능이므로 #pragma target 3.0도 추가해야 합니다.
표면 셰이더 컴파일 파이프라인의 일부분은 DirectX 11 전용 HLSL(Microsoft의 셰이더 언어) 구문을 이해하지 않습니다.
StructuredBuffers, RWTextures와 기타 비DirectX 9 구문 같은 HLSL 기능을 사용하는 경우 아래 예에 나와 있는 것처럼 DirectX X11 전용 프리프로세서에 래핑해야 합니다.
#ifdef SHADER_API_D3D11
// DirectX11-specific code, for example
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif
EXT_shader_framebuffer_fetch 참조).프레임버퍼 페치 기능을 사용하는 셰이더를 Unity에서 작성할 수 있습니다. 이렇게 하려면 프래그먼트 셰이더를 HLSL(Microsoft의 셰이딩 언어 - msdn.microsoft.com 참조) 또는 Cg(Nvidia의 셰이딩 언어 - nvidia.co.uk 참조)로 작성할 때 inout 컬러 인수를 사용해야 합니다.
아래 예제는 Cg를 따릅니다.
CGPROGRAM
// only compile Shader for platforms that can potentially
// do it (currently gles,gles3,metal)
#pragma only_renderers framebufferfetch
void frag (v2f i, inout half4 ocol : SV_Target)
{
// ocol can be read (current framebuffer color)
// and written into (will change color to that one)
// ...
}
ENDCG
뎁스(Z) 방향은 셰이더 플랫폼마다 다릅니다.
DirectX 11, DirectX 12, Metal: 반대 방향
뎁스(Z) 버퍼는 근접 평면에서 1.0이고 원거리 평면에서 0.0으로 감소합니다.
클립 공간 범위는 [near,0]입니다(원거리 평면에서 0.0으로 감소하는 근접 평면에서의 근접 평면 거리 의미).
기타 플랫폼: 기존 방향
뎁스(Z) 버퍼 값은 근접 평면에서 0.0이고 원거리 평면에서 1.0입니다.
반대 방향 뎁스(Z)를 부동 소수점 뎁스 버퍼와 함께 사용하면 기존 방향에 비해 뎁스 버퍼 정밀도가 크게 개선됩니다. 특히 작은 근접 평면과 큰 원거리 평면을 사용하는 경우 Z 좌표 충돌이 감소하고 그림자가 개선되는 장점이 있습니다.
따라서 뎁스(Z)가 반대인 플랫폼에서 셰이더를 사용하면
_CameraDepth 텍스처의 텍스처 범위는 1(근접)부터 0(원거리)까지입니다.하지만 다음 매크로와 함수는 뎁스(Z) 방향의 차이를 자동으로 해결합니다.
Linear01Depth(float z)LinearEyeDepth(float z)float z = tex2D(_CameraDepthTexture, uv);
#if defined(UNITY_REVERSED_Z)
z = 1.0f - z;
#endif
float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace);
참고: 매크로는 OpenGL 또는 OpenGL ES 플랫폼에서 클립 공간을 변경하지 않으므로, 이런 플랫폼에서는 \“-near\”1(근접)에서 far(원거리) 이내의 값을 반환합니다.
예제는 아래와 같습니다.
var shadowProjection = Matrix4x4.Ortho(...); //shadow camera projection matrix
var shadowViewMat = ... //shadow camera view matrix
var shadowSpaceMatrix = ... //from clip to shadowMap texture space
//'m_shadowCamera.projectionMatrix' is implicitly reversed
//when the engine calculates device projection matrix from the camera projection
m_shadowCamera.projectionMatrix = shadowProjection;
//'shadowProjection' is manually flipped before being concatenated to 'm_shadowMatrix'
//because it is seen as any other matrix to a Shader.
if(SystemInfo.usesReversedZBuffer)
{
shadowProjection[2, 0] = -shadowProjection[2, 0];
shadowProjection[2, 1] = -shadowProjection[2, 1];
shadowProjection[2, 2] = -shadowProjection[2, 2];
shadowProjection[2, 3] = -shadowProjection[2, 3];
}
m_shadowMatrix = shadowSpaceMatrix * shadowProjection * shadowViewMat;