간혹 서로 다른 그래픽스 API에서 그래픽스 렌더링이 다르게 작동할 수 있습니다. 대부분 Unity 에디터는 이러한 차이점을 숨기지만, 일부 경우 에디터가 이를 수행하지 못할 수 있습니다. 이러한 경우 및 이러한 상황이 발생하면 취해야 할 조치는 다음과 같습니다.
두 가지의 Direct3D 타입 및 OpenGL 타입 플랫폼의 수직 텍스처 좌표 규칙은 서로 다릅니다.
이 차이는 렌더 텍스처로 렌더링하는 경우를 제외하고 프로젝트에 영향을 미치지 않는 경향이 있습니다. Direct3D 타입 플랫폼에서 텍스처로 렌더링하는 경우 Unity는 내부에서 위 아래로 뒤집어서 렌더링합니다. 그러면 규칙이 플랫폼 간에 일치하고 OpenGL 타입 플랫폼 규칙이 표준으로 사용됩니다.
셰이더에서 다른 좌표 규칙으로 인해 프로젝트에 문제가 발생하지 않도록 조치를 취해야 하는 두 가지의 일반적인 사례로는 이미지 이펙트와 UV 공간에서 렌더링이 있습니다.
이미지 효과와 안티앨리어싱을 사용하면 이미지 효과의 소스 텍스처가 OpenGL 타입 플랫폼 규칙과 일치하도록 플립되지 않습니다. 이 경우 Unity는 화면에 안티앨리어싱이 적용되도록 렌더링한 다음 렌더링을 이미지 효과로 더 처리하기 위해 렌더 텍스처로 분석합니다.
이미지 효과가 렌더 텍스처를 한 번에 하나씩 처리하는 간단한 효과인 경우, 일치하지 않는 좌표는 Graphics.Blit로 처리됩니다. 하지만 이미지 효과에서 2개 이상의 렌더 텍스처를 한꺼번에 처리하는 경우 렌더 텍스처가 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 포함 파일의 ComputeGrabScreenPos
함수를 사용해야 합니다.
특수 효과 또는 툴을 위해 텍스처 좌표(UV) 공간에서 렌더링하는 경우 Direct3D 타입 및 OpenGL 타입 시스템 간 렌더링이 일관되도록 셰이더를 조정해야 할 수 있습니다. 화면으로의 렌더링과 텍스처로의 렌더링 간에 렌더링을 조정해야 할 수도 있습니다. Direct3D 타입 투사 좌표가 OpenGL 타입 투사 좌표와 일치하도록 Direct3D 타입 투사를 위 아래로 플립하여 조정해야 합니다.
빌트인 변수 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타입 및 OpenGL타입 플랫폼 간에 다릅니다.
Direct3D 타입: 클립 공간 뎁스의 범위는 근접 평면의 +1.0부터 원거리 평면의 0.0까지입니다. Direct3D, Metal 및 콘솔에 해당합니다.
OpenGL 타입: 클립 공간 뎁스의 범위는 근접 평면의 –1.0부터 원거리 평면의 +1.0까지입니다. OpenGL 및 OpenGL ES에 해당합니다.
셰이더 코드 안에서 UNITY_NEAR_CLIP_VALUE
빌트인 매크로를 사용하여 플랫폼별 근접 평면 값을 얻을 수 있습니다.
스크립트 코드 안에서는 GL.GetGPUProjectionMatrix를 사용해 (OpenGL 타입 규칙을 따르는)Unity 좌표계에서 Direct3D 타입 좌표로 전환할 수 있습니다(플랫폼에서 이 좌표를 요구하는 경우).
정밀도 문제를 방지하려면 셰이더를 타겟 플랫폼에서 테스트해야 합니다. 모바일 디바이스와 PC의 GPU는 플로팅 포인트 타입을 처리하는 방법이 서로 다릅니다. PC GPU는 모든 연산을 전체 32비트 정밀도를 사용하여 수행하는 방법으로 모든 플로팅 타입(플로트, 반 및 고정)을 동일하게 처리하지만, 많은 모바일 디바이스 GPU가 이렇게 하지 않습니다.
자세한 내용은 데이터 타입과 정밀도 문서를 참조하십시오.
const
의 용도는 Microsoft HLSL(msdn.microsoft.com 참조)과 OpenGL의 GLSL(위키피디아 참조) 셰이더 언어 간에 서로 다릅니다.
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;
}
셰이더가 모든 플랫폼에서 작동하도록 하려면 일부 셰이더 값에서 다음 시맨틱을 사용해야 합니다.
Vertex Shader 출력(클립 공간) 포지션: SV_POSITION
. 때때로 셰이더는 모든 플랫폼에서 작동하도록 하기 위해 POSITION 시맨틱을 사용합니다. Sony PS4 또는 테셀레이션에서는 이렇게 할 수 없습니다.
Fragment Shader 출력 컬러: SV_Target
. 때로는 셰이더가 모든 플랫폼에서 작동하도록 COLOR
또는 COLOR0
을 사용합니다. Sony PS4에서는 이렇게 할 수 없습니다.
메시를 포인트로 렌더링하는 경우 버텍스 셰이더에서 PSIZE
시맨틱을 출력합니다(예: 1로 설정). OpenGL ES 또는 Metal 같은 일부 플랫폼은 포인트 크기를 셰이더에서 작성하지 않는 경우 “정의되지 않음”으로 처리합니다.
자세한 내용은 셰이더 시맨틱 문서를 참조하십시오.
Direct3D 플랫폼에서는 Microsoft의 HLSL 셰이더 컴파일러를 사용합니다. HLSL 컴파일러는 여러 미묘한 셰이더 오류에 대해 다른 컴파일러보다 더 엄격합니다. 예를 들어, 올바르게 초기화되지 않은 함수 출력 값을 허용하지 않습니다.
구문을 사용할 때 발생할 수 있는 가장 일반적인 문제는 다음과 같습니다.
out
파라미터가 있는 표면 셰이더 버텍스 한정자. 출력을 다음과 같이 초기화해야 합니다. void vert (inout appdata_full v, out Input o)
{
**UNITY_INITIALIZE_OUTPUT(Input,o);**
// ...
}
부분적으로 초기화된 값. 예를 들어, 함수에서 float4
를 반환하지만 코드에서 함수의 .xyz
값만 설정할 수 있습니다. 모든 값을 설정하거나 값이 3개만 필요한 경우 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
(특히 iOS의 PowerVR 기반 GPU를 비롯한)일부 GPU는 현재 프래그먼트 컬러를 프래그먼트 셰이더 입력으로 제공하여 일종의 프로그램 가능 블렌딩을 지원합니다(khronos.org에서 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)
뎁스(Z) 버퍼 값을 수동으로 페치하는 경우 버퍼 방향을 확인하는 것이 좋습니다. 예제는 다음과 같습니다.
float z = tex2D(_CameraDepthTexture, uv);
#if defined(UNITY_REVERSED_Z)
z = 1.0f - z;
#endif
클립 공간(Z) 뎁스를 수동으로 사용하는 경우 다음 매크로를 사용하여 플랫폼 차이를 추상화할 수도 있습니다
float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace);
참고: 매크로는 OpenGL 또는 OpenGL ES 플랫폼에서 클립 공간을 변경하지 않으므로, 이런 플랫폼에서는 “-near”1(근접)에서 far(원거리) 이내의 값을 반환합니다.
GL.GetGPUProjectionMatrix()는 뎁스(Z)가 반전된 플랫폼에서 a z 반전 매트릭스를 반환합니다. 하지만 투사 매트릭스에서 (예를 들어, 커스텀 섀도우 또는 뎁스 렌더링을 위해)수동으로 작성하는 경우 스크립트를 통해 뎁스(Z) 방향을 직접 반전해야 합니다(해당되는 경우).
예제는 아래와 같습니다.
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;
Unity는 뎁스(Z) 바이어스를 자동으로 처리하여 Unity의 뎁스(Z) 방향과 일치하도록 합니다. 하지만 네이티브 코드 렌더링 플러그인을 사용하는 경우 C 또는 C++ 코드에서 (리버스) 뎁스(Z) 바이어스를 상쇄해야 합니다.