텍스처를 사용하여 복잡한 색상 패턴을 가진 머티리얼을 생성 할 수 있다.
단일 mesh의 하나의 표면은 항상 매끄럽다. 법선벡터 3개를 이용하여 보간하기 때문이다. 거칠거나 다양한 표면을 표현할 수 없다.
예를 들기 위해 Quad를 만들것이다.
씬에 Quad하나를 추가하고 X 축을 중심으로 90 ° 회전하여 위쪽을 향하게 만들어보자.
질감이없고 완전히 흰색의 lighting을 사용하면된다.
완전히 평평한 상태의 Quad.
기본 sky box는 매우 밝기 때문에 다른 조명의 영향을보기가 어렵다. 이 기능은 해제하도록 한다.
조명 설정에서 Ambient Intensity 를 0 으로 낮추면 된다. 그런 다음 주 방향 지시등 만 활성화하고. 씬 뷰에서 시점변경을 통해 차이를 느끼게 만들어보자.
주 방향 조명만 있는 상태의 Quad.
텍스처에 음영을 베이킹하여 거칠기를 베이킹을 통해 만들 수 있지만 정적인 표현이라 실시간으로 바뀌지 않는다. 정반사의 경우 카메라조차 움직일 수 없다.
위 Quad에는 4개의 법선이 있고 꼭지점마다 있다. 다양하고 거친 표면을 위해선 더 많은 법선이 필요하다.
Quad를 더 작게 세분화 할 수있고 많은 법선을 이용해 움직이고 활용하여 거친표면을 만들 수 있다. 하지만 이것은 연산이 많이 필요하여 실시간계산이 어려워 적합하지 않다.
1.1 Height maps (높이 맵)
거친 표면은 평평한 표면과 비교할 때 고르지않는 높이를가진다. 이 높이 데이터를 텍스처에 저장하면 정점대신 fragment당 법선 벡터를 생성 할 수 있다. 이 방법은 범프 매핑으로 알려져 있으며 James Blinn이 처음 공식화했다.
대리석 질감과 같이쓰이는 높이map이 있다. 각 채널이 동일한 값으로 설정된 RGB 텍스처이다. 기본 가져오기 설정으로 프로젝트로 가져올수 있다.
Height map for marble.
My First Lighting Shader에_HeightMap 텍스처 속성을 추가합시다.
텍스처와 동일한 UV를 사용하기 때문에 자체 스케일 및 오프셋 매개 변수가 필요하지 않다.
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} [NoScaleOffset] _HeightMap ("Heights", 2D) = "gray" {} [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0 _Smoothness ("Smoothness", Range(0, 1)) = 0.1 }
Material with height map.
일치하는 변수를 lighting 포함 파일에 추가하면 텍스처에 액세스 할 수 있다.
float4 _Tint; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _HeightMap; … float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; albedo *= tex2D(_HeightMap, i.uv); … }
Using heights as colors.
조각의 법선이 더 복잡해지기 때문에 초기화를 별도의 함수로 옮겨 보자. 그리고 height map 테스트 코드를 제거하자.
void InitializeFragmentNormal(inout Interpolators i) { i.normal = normalize(i.normal); }float4 MyFragmentProgram (Interpolators i) : SV_TARGET { InitializeFragmentNormal(i); float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; // albedo *= tex2D(_HeightMap, i.uv); … }
현재 XZ 평면에있는 Quad로 작업하기 때문에 법선 벡터는 항상 (0, 1, 0)이다. 따라서 정점 데이터를 무시하고 상수를 사용할 수 있다. 일단 이렇게하고 나중에 다른 방향에 대해 생각해보자.
void InitializeFragmentNormal(inout Interpolators i) { i.normal = float3(0, 1, 0); i.normal = normalize(i.normal); }
이 높이 데이터는 정규화하기 전에 높이를 표준의 Y 구성 요소로 사용하면 포함시킬수 있다.
void InitializeFragmentNormal(inout Interpolators i) { float h = tex2D(_HeightMap, i.uv); i.normal = float3(0, h, 0); i.normal = normalize(i.normal); }
높이를 법선으로 사용.
정규화는 모든 벡터를 다시 (0, 1, 0)로 변환하기 때문에 작동하지 않는다. 이 경우 검은 색 선은 높이가 0 인 위치에 나타나므로 다른 방법이 필요하다.
텍스처 데이터로 작업하기 때문에 2 차원 데이터인 U와 V 를 사용한다. 높이는 3 차원으로 올라가는 것으로 상상할 수 있다. 텍스처가 함수
f
(
)
h
. 를 나타낼 수 있다. U 차원만으로 제한하면함수는
f(u,v)=h
u
,
v
=
f
(
u
)
=
h
f(u)=h
. 로 축소된다 . 이 함수를 이용해 법선 벡터를 구할수있다. 함수의 기울기로 어느 지점에서든지 법선을 계산할 수 있다. 기울기는 h 의 변화율로 정의된다 . 그리고 함수의 값은 미분 함수처럼 다음과 같이 표기한다.
f
'
(
u
)
=
h
'
에프'(
이 값을 정확히 알지는 못하지만 근사값을 얻을수 있다. 우리는 텍스처의 두 지점에서 높이를 비교할 수 있다. 예를 들어 극단 끝에 U 좌표 0과 1을 사용한다. 두 샘플 간의 차이는 해당 좌표 간의 변경 비율이다. 함수로 표현하면
f
(
1
)
−
f
(
0
)
f(1)-f(0)
이고, 우리는 접선 벡터를 구성하기 위해 다음을 사용한다.
⎡
⎢
⎣
f
(
1
)
1
−
0
f
(
0
)
⎤
⎥
⎦
[1f(1)-f(0)0]
.
탄젠트값 [
f
0
]
to [
1
]
[0f(0)]
(
0
)
[1f(1)]
f
(
1
)
..
그것은 실제 접선 벡터의 매우 조잡한 근사값이다. 전체 텍스처를 선형 슬로프로 처리한다. 더 가깝게있는 두 점을 샘플링하면 더 잘 수행 할 수 있다. 예를 들어 U 좌표는 0과 1/2이다. 이 두 점 사이의 변화율 은 s
f
(
1
2
)
−
f
(
0
)
f(12)-f(0)
,절반당 U 단위이다. 전체 단위당 변화율을 다루는 것이 더 쉽기 때문에, 점들 사이의 거리로 나누면
f
(
1
2
)
−
f
(
0
)
(
(
1
2
)
)
1
2
=
2
f
−
f
(
0
)
f(12)-f(0)12=2(f(12)-f(0))
접선벡터가 나온다.
⎡
⎢ ⎢
⎣
(
(
1
2
1
⎤
⎥ ⎥
⎦
2
f
)
−
f
(
0
)
)
0
[12(f(12)-f(0))0]
.
일반적으로 렌더링 할 모든 조각의 U 좌표를 기준으로이 작업을 수행해야한다. 다음 점까지의 거리는 상수 델타에 의해 정의된다. 따라서 미분 함수에 의해 근사화 된다.
f
′
(
u
)
≈
f
(
u
+
δ
δ
)
−
f
(
u
)
f′(u)≈f(u+δ)-f(u)δ
.
δ가 작을수록 실제 미분 함수에 가까워진다. 물론이 0이 될 수는 없지만, 그 이론적 인 한계에 가지고 갈 때, 당신이 얻을 다음 방법은
f
′
(
u
)
=
lim
f
(
u
+
δ
δ
)
−
f
(
u
)
f′(u)=limδ→0f(u+δ)-f(u)δ
δ
→
0
0F → F'(U) = limδ (U + δ) -f (u) δ
미분을 근사하는이 방법을 유한 차분 방법이라고한다. 즉, 우리는 임의의 지점에서의 접선 벡터를 생성 할 수
⎡
⎢
⎣
f
1
⎤
⎥
⎦
′
(
u
)
0
[1f′(u)0]
.
셰이더의 δ에 어떤 값을 사용할 수 있을까? 가장 작은 감각적 인 차이는 텍스처의 단일 텍셀을 포함한다. 우리는 float4 변수를 통해 셰이더에서이 정보를 검색 할 수 있다.
_TexelSize. Unity는 변수와 비슷한 변수를 설정 _ST 한다.
sampler2D _HeightMap; float4 _HeightMap_TexelSize;
이제 텍스쳐를 두 번 샘플링하고, 높이 미분을 계산하고, 접선 벡터를 구성 할 수 있다. 이것으로 우리의 법선 벡터로 직접 사용할수있다.
float2 delta = float2(_HeightMap_TexelSize.x, 0); float h1 = tex2D(_HeightMap, i.uv); float h2 = tex2D(_HeightMap, i.uv + delta); i.normal = float3(1, (h2 - h1) / delta.x, 0); i.normal = normalize(i.normal);
실제로, 우리는 어쨌든 정규화를하기 때문에 접선 벡터를 δ만큼 스케일 할 수 있다. 이것은 분할을 제거하고 정밀도를 향상시킨다.
i.normal = float3(delta.x, h2 - h1, 0);
접선을 법선으로 사용.
우리는 확연한 결과를 얻을수 있다. 높이가 한 단위의 범위를 가지기 때문에 아주 가파른 경사가 생기기 때문이다. 교란 된 법선이 실제로 표면을 변경하지 않기 때문에 이러한 큰 차이점을 원하지 않는다. 우리는 임의의 요인으로 높이를 조절할 수 있다. 범위를 단일 텍셀로 줄이자. 우리는 높이 차이를 δ로 곱하거나 단순히 탄젠트에서 1을 δ로 대체함으로써이를 수행 할 수 있다.
i.normal = float3(1, h2 - h1, 0);
스케일 된 높이.
이것은보기 좋게 보이기 시작했지만 조명이 잘못되었다. 그것은 너무 어둡다. 왜냐하면 우리는 접선을 법선으로 직접 사용하기 때문이다. 상향 법선 벡터로 바꾸려면 접선을 Z 축 주위로 90도 회전해야한다.
i.normal = float3(h1 - h2, 1, 0);
실제 법선을 사용.
우리는 법선 벡터를 만들기 위해 유한 차분 근사법을 사용했다. 특히, 순방향 차이 법을 사용한다. 우리는 한 점을 취해 기울기를 결정하기 위해 한 방향으로 본다. 결과적으로 법선은 그 방향으로 편향된다. 법선의 더 나은 근사를 얻기 위해, 우리는 대신 양방향으로 샘플 포인트를 오프셋 할 수 있다. 이는 현재 점에 대한 선형 근사를 중심으로하며 중앙 차분 법으로 알려져 있다. 이것은 파생 함수를 다음과 같이 표현한다.
f
′
(
u
)
=
lim
f
(
u
+
δ
2
)
−
δ
f
(
u
−
δ
2
)
δ
→
0
f′(u)=limδ→0f(u+δ2)-f(u-δ2)δ
우리가 만든 법선은 U에 따른 변화만을 고려한다. 우리는 함수의 편미분 사용하고있어
f
(
u
,
v
)
에 대해 U
U
f(u,v)
.
그것은
f
u
′
(
u
,
v
)
또는 간단히 f
'
이다. f
'
fu'(u, v)
fu'
u
fv'를
v
사용하여 V를 따라 법선을 생성 할 수도 있다 .
그 경우, 접선 벡터이다
⎡
⎢
⎣
0
⎤
⎥
⎦
f
v
′
1
[0fv′1]
및 법선 벡터는
⎡
⎢
⎣
0
1
⎤
⎥
⎦
[01-fv′]
−
f
v
′
.
이다.
float2 du = float2(_HeightMap_TexelSize.x * 0.5, 0); float u1 = tex2D(_HeightMap, i.uv - du); float u2 = tex2D(_HeightMap, i.uv + du); i.normal = float3(u1 - u2, 1, 0); float2 dv = float2(0, _HeightMap_TexelSize.y * 0.5); float v1 = tex2D(_HeightMap, i.uv - dv); float v2 = tex2D(_HeightMap, i.uv + dv); i.normal = float3(0, 1, v1 - v2); i.normal = normalize(i.normal);
V 에 따른 법선들.
이제 U와 V 접선에 액세스 할 수 있다. 함께이 벡터들은 우리 조각에서 높이 필드의 표면을 묘사한다. 교차 곱을 계산하여 2D 높이 필드의 법선 벡터를 찾는다.
float2 du = float2(_HeightMap_TexelSize.x * 0.5, 0); float u1 = tex2D(_HeightMap, i.uv - du); float u2 = tex2D(_HeightMap, i.uv + du); float3 tu = float3(1, u2 - u1, 0); float2 dv = float2(0, _HeightMap_TexelSize.y * 0.5); float v1 = tex2D(_HeightMap, i.uv - dv); float v2 = tex2D(_HeightMap, i.uv + dv); float3 tv = float3(0, v2 - v1, 1); i.normal = cross(tv, tu); i.normal = normalize(i.normal);
완전한 법선.
당신이 접선 벡터와 외적을 계산하면, 법선을 볼수있다.
⎡
⎢
⎣
0
⎤
⎥
⎦
⎡
⎢
⎣
1
⎤
⎥
⎦
⎡
⎢
⎣
−
f
′
⎤
⎥
⎦
u
f
v
′
×
f
′
=
1
u
1
0
−
f
′
v
[0fv′1]×[1fu′0]=[-fu′1-fv′]
[0fv'1] × [1fu'0] = [- fu'1-FV']
.
. 그래서 우리는 cross 함수 에 의존하지 않고 벡터를 직접 생성 할 수 있다 .
void InitializeFragmentNormal(inout Interpolators i) { float2 du = float2(_HeightMap_TexelSize.x * 0.5, 0); float u1 = tex2D(_HeightMap, i.uv - du); float u2 = tex2D(_HeightMap, i.uv + du); // float3 tu = float3(1, u2 - u1, 0); float2 dv = float2(0, _HeightMap_TexelSize.y * 0.5); float v1 = tex2D(_HeightMap, i.uv - dv); float v2 = tex2D(_HeightMap, i.uv + dv); // float3 tv = float3(0, v2 - v1, 1);// i.normal = cross(tv, tu); i.normal = float3(u1 - u2, 1, v1 - v2); i.normal = normalize(i.normal); }
범프 매핑이 작동하는 동안 우리는 여러 텍스처 샘플과 유한 차이 계산을 수행해야한다. 이 결과는 항상 동일해야하므로 낭비처럼 보인다. 왜 모든 것이 모든 프레임에서 작동하는가? 우리는 한번 그것을 할 수 있고 텍스쳐에 법선을 저장할 수 있다.
이것은 우리가 노멀 맵을 필요로한다는 것을 의미한다. 나는 하나를 제공 할 수는 있지만 Unity가 우리를 위해 일하게 할 수 있다. 높이 맵 의 Texture Type 을 Normal Map으로 변경하자 . Unity는 자동으로 삼선 형 필터링을 사용하도록 텍스처를 전환하고 그레이 스케일 이미지 데이터를 사용하여 노멀 맵을 생성한다고 가정한다. 이것은 정확히 우리가 원하는 것이지만, Bumpiness 는 0.05와 같이 훨씬 낮은 값으로 변경된다.
가져 오기 설정을 적용한 후 Unity는 법선 맵을 계산한다. 원래의 높이 맵은 여전히 존재하지만 Unity는 내부적으로 생성 된 맵을 사용한다.
법선을 색상으로 시각화 할 때와 마찬가지로 0-1 범위에 맞게 조정해야한다. 그래서 그들은
.로 저장된다 . 이것은 평평한 지역이 연한 초록색으로 보일 것이라고 제안한다. 그러나 대신에 밝은 파란색으로 표시된다. 왜냐하면 노멀 맵에 대한 가장 일반적인 관례는 Z 방향에서 위쪽 방향을 저장하기 때문이다. 유니티의 관점에서 Y 좌표와 Z 좌표가 서로 바뀐다.
노멀 맵은 높이 맵과 완전히 다르기 때문에 셰이더 속성의 이름을 적절하게 바꾼다.
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} [NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}// [NoScaleOffset] _HeightMap ("Heights", 2D) = "gray" {} [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0 _Smoothness ("Smoothness", Range(0, 1)) = 0.1 }
N+12
N
2
+
1
노멀 맵을 사용함.
모든 높이 맵 코드를 제거하고 단일 텍스처 샘플로 대체 한 다음 정규화를 수행 할 수 있다.
sampler2D _NormalMap;//sampler2D _HeightMap;//float4 _HeightMap_TexelSize; … void InitializeFragmentNormal(inout Interpolators i) { i.normal = tex2D(_NormalMap, i.uv).rgb; i.normal = normalize(i.normal); }
물론, 우리는 계산하여, 다시 원래 -1-1 범위로 법선을 변환해야
2
N을
2N-1
-
1
노멀 맵 사용.
확실히 우리의 법선에 문제가 있다. Unity가 우리가 예상했던 것과 다른 방식으로 법선을 인코딩하기 시작했기 때문이다. 텍스쳐 프리뷰가 RGB 인코딩을 보여 주더라도, Unity는 실제로 DXT5nm을 사용한다.
DXT5nm 형식은 법선의 X 및 Y 구성 요소 만 저장한다. 그 Z 구성 요소는 버려진다. Y 구성 요소는 예상대로 G 채널에 저장된다. 그러나 X 구성 요소는 A 채널에 저장된다. R 및 B 채널은 사용되지 않는다.
따라서 DXT5nm을 사용할 때 우리는 정상의 처음 두 구성 요소 만 검색 할 수 있다.
i.normal.xy = tex2D(_NormalMap, i.uv).wy * 2 - 1;
우리는 나머지 두 개로부터 세 번째 구성 요소를 추론해야한다. 법선은 단위 벡터이기 때문에
|
|
N
|
|
=
|
|
N
|
|
2
=
N
x
2
+
N
y
2
+
N
z
2
=
1
||N||=||N||2=Nx2+Ny2+Nz2=1
.이고, 따라서 다음식이 나온다.
N
z
=
√
1
−
N
x
2
−
N
2
y
Nz
=1-Nx2-Ny2
N
x
+
N
y
Nx2 + Ny2
디코딩 된 DXT5nm 법선.
법선을 텍스처로 베이킹하므로 조각 쉐이더에서 법선을 스케일 할 수 없다.
Z를 계산하기 전에 법선의 X와 Y 성분을 스케일 할 수 있다. X와 Y를 줄이면 Z가 커져서 표면이 더 평평 해진다. 우리가 그들을 늘리면 반대가 일어날 것이다. 그래서 우리는 그런 방식으로 울퉁불퉁 함을 조정할 수 있다. 우리가 이미 X와 Y의 제곱을 클램프하고 있기 때문에, 우리는 결코 유효하지 않은 법선으로 끝나지 않을 것이다.
Unity의 표준 셰이더처럼 범프 스케일 속성을 셰이더에 추가하자.
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} [NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {} _BumpScale ("Bump Scale", Float) = 1 [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0 _Smoothness ("Smoothness", Range(0, 1)) = 0.1 }
이 척도를 일반적인 계산에 통합하자.
sampler2D _NormalMap; float _BumpScale; … void InitializeFragmentNormal(inout Interpolators i) { i.normal.xy = tex2D(_NormalMap, i.uv).wy * 2 - 1; i.normal.xy *= _BumpScale; i.normal.z = sqrt(1 - saturate(dot(i.normal.xy, i.normal.xy))); i.normal = i.normal.xzy; i.normal = normalize(i.normal); }
높이지도를 사용하면서 얻은 강도와 거의 같은 강도의 융기를 얻으려면 배율을 0.25와 같이 줄이자.
확장 범프.
UnityStandardUtils 에는 이 UnpackScaleNormal 함수 가 포함되어 있다. 자동으로 노멀 맵에 대해 올바른 디코딩을 사용하고 노멀을 스케일한다. 이러한 편리한 기능을 이용하자.
void InitializeFragmentNormal(inout Interpolators i) { // i.normal.xy = tex2D(_NormalMap, i.uv).wy * 2 - 1;// i.normal.xy *= _BumpScale;// i.normal.z = sqrt(1 - saturate(dot(i.normal.xy, i.normal.xy))); i.normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv), _BumpScale); i.normal = i.normal.xzy; i.normal = normalize(i.normal); }
이제는 기능적 노멀 맵이 생겼으므로, 맵의 차이점을 확인할 수 있다. 대리석의 알베도 질감 만 사용하면 쿼드는 완벽하게 연마 된 돌처럼 보인다. 노멀 맵을 추가하면 더 재미있는 표면이 가능하다.
범프없음.
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3부에서는 Textures를 결합하여 세부 질감이 있는 Shader를 만들었다. 알베도 (albedo)로 이것을 했지만, 범프로도 할 수 있다. 먼저 My First Lighting Shader 에 디테일 알베도에 대한 지원을 추가한다.
[[My First Lighting Shader.shader ]]
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} [NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {} _BumpScale ("Bump Scale", Float) = 1 [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0 _Smoothness ("Smoothness", Range(0, 1)) = 0.1 _DetailTex ("Detail Texture", 2D) = "gray" {} }
디테일 알베도 텍스처
세부 UV에 보간을 추가하는 대신, 주 UV와 상세 UV를 수동으로 하나의 보간 회로에 채운다. 주 UV는 XY로 들어가고 세부 UV는 ZW로 들어간다.
[[My First Shader.shader]]
struct Interpolators { float4 position : SV_POSITION; // float2 uv : TEXCOORD0; float4 uv : TEXCOORD0; float3 normal : TEXCOORD1; float3 worldPos : TEXCOORD2; #if defined(VERTEXLIGHT_ON) float3 vertexLightColor : TEXCOORD3; #endif };
필요한 변수를 추가하고 정점 프로그램에서 보간을 채운다.
[[My Lighting.cginc]]
sampler2D _MainTex, _DetailTex; sampler2D _MainTex, _DetailTex; float4 _MainTex_ST, _DetailTex_ST; … Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.position = mul(UNITY_MATRIX_MVP, v.position); i.worldPos = mul(unity_ObjectToWorld, v.position); i.normal = UnityObjectToWorldNormal(v.normal); i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex); i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex); ComputeVertexLightColor(i); return i; }
이제 주 UV가 필요할 때 i.uv대신에 i.uv.xy 을 사용한다.
[[My Lighting.cginc]]
void InitializeFragmentNormal(inout Interpolators i) { i.normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale); i.normal = i.normal.xzy; i.normal = normalize(i.normal); } float4 MyFragmentProgram (Interpolators i) : SV_TARGET { InitializeFragmentNormal(i); float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb; … }
알베도(albedo)의 인자에 Detail Texture를 넣어준다.
[[My Lighting.cginc]]
float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb; albedo *= tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;
Detail Albedo 만 있는것 / Detail Albedo와 Bump를 사용한것
우리의 대리석 재질의 Detail Texture는 GrayScale이기 때문에 ,이를 사용하여 노멀 맵을 생성 할 수 있다.
텍스처을 복제하고 Import Type 을 노멀 맵으로 변경
Bumpiness를 0.1정도로 감소시키고 다른설정은 그대로 둔다.
우리가 밉맵을 페이드 아웃하는 동안, 색은 회색으로 희미해진다.결과적으로 Unity가 생성하는 디테일 노멀 맵은 평면으로 사라지게 된다.
디테일 노멀 텍스처.
디테일 노멀 맵을 셰이더에 추가해 해준다.그리고 범프 스케일 또한 추가한다.
[[My First Lighting Shader.shader ]]
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} [NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {} _BumpScale ("Bump Scale", Float) = 1 [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0 _Smoothness ("Smoothness", Range(0, 1)) = 0.1 _DetailTex ("Detail Texture", 2D) = "gray" {} [NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {} _DetailBumpScale ("Detail Bump Scale", Float) = 1 }
디테일 노멀 맵과 스케일.
기본 노멀 맵과 마찬가지로 필요한 변수를 추가하고 디테일 노멀 맵을 가져온다. 결합하기 전엔 디테일 노멀만 보여진다.
[[My Lighting.cginc]]
sampler2D _NormalMap, _DetailNormalMap; float _BumpScale, _DetailBumpScale; … void InitializeFragmentNormal(inout Interpolators i) { i.normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale); i.normal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale); i.normal = i.normal.xzy; i.normal = normalize(i.normal); }
디테일 범프.
우리는 기본 텍스처와 디테일 알베도 텍스처를 함께 곱하여 결합했다.그것은 벡터로 되어있기 때문에 노멀로 표현 할 수 없다. 하지만 정규화하기 전에 그들을 평균을 구해 표현 할 수 있다.
[[My Lighting.cginc]]
void InitializeFragmentNormal(inout Interpolators i) { float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale); float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale); i.normal = (mainNormal + detailNormal) * 0.5; i.normal = i.normal.xzy; i.normal = normalize(i.normal); }
평균화 된 노멀.
주 요 범프와 디테일 범프가 모두 평평 해진다. 이상적으로는, 그 중 하나가 평평 할 때, 그것은 다른 노멀에게 전혀 영향을 미치면 안된다.
여기서 우리가 효과적으로하려고하는 것은 두 개의 높이 필드를 결합하는 것 이다. 노멀을 평균을 구하는 것 보다, 노멀을 추가하는 것이 훨씬 더 합리적이다.
두 개의 높이 함수를 추가 할 때 슬로프, 즉 파생 함수가 추가 된다.
앞서 우리는 정규화를 통해 우리의 노멀 벡터를 생성 했다
⎡
⎢
⎣
−
f
u
′
⎤
⎥
⎦
1
[-fu′1-fv11′]
−
f
v
′
. 우리의 노멀 맵은 Y와Z컴포넌트가 서로 바낀거외에는 같은 타입의 노멀들을 포함하고 있다.
따라서 그것은 형태 이다.
−
f
′
⎤
⎥
⎦
u
⎢
⎣
−
f
v
′
1
[-fu′-fv′1]
그러나 이러한 노멀은 정규화 과정을 통해 확장되었다. 아래가 표현된 형태.
⎡
⎢
⎣
−
−
s
f
f
′
u
⎤
⎥ s는 임의의scale Factor
s
′
v
s
Z 컴포넌트는 s와 같습니다. 이것은 X와 Y를 Z로 나눔으로써 편미분을 찾을 수 있음을 의미 한다. 이것은 Z가 0 일 때만 실패하며 이는 수직면에 해당 한다.
파생물을 얻은 후 합계 높이 필드의 파생물을 찾기 위해 얻을 파생물을 추가 할 수 있다. 그런 다음 노멀벡터로 다시 변환 한다.
정규화되기 전 결과벡터
[MxMz + DxDzMyMz DyDz1 +]
⎢ ⎢ ⎢
⎣
M
M
M
z
x
+
D
D
z
x
⎤
⎥ ⎥ ⎥
⎦
M
z
y
+
D
D
y
z
1
[MxMz+DxDzMyMz+DyDz1]
. [[My Lighting.cginc]]
void InitializeFragmentNormal(inout Interpolators i) { float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale); float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale); i.normal = float3(mainNormal.xy / mainNormal.z + detailNormal.xy / detailNormal.z, 1); i.normal = i.normal.xzy; i.normal = normalize(i.normal); }
파생물 추가.
거의 평평한 것 을 결합 할 때 아주 잘 작동 된다. 그러나 가파른 경사면을 결합하면 여전히 세부 사항을 잃게된다. 그에 대한 대안 방법은 화이트 아웃 블렌딩이다.
먼저, 새로운 노멀에
M
z
D
z
MzDz를
곱한다 . 이 방법은 나중에 정규화 하기때문에 가능하다.
그결과의 벡터
⎡
⎢
⎣
M
M
x
D
D
z
z
+
+
D
D
x
y
M
z
⎤
⎥
⎦
y
M
z
M
z
D
z
. [MxDz + DxMzMyDz + DyMzMzDz]
[MxDz+DxMzMyDz+DyMzMzDz]
.
그리고 X와 Y의 스케일링 제거한 벡터를 유도한다.
⎡
⎢
⎣
M
M
y
x
+
+
D
D
y
x
⎤
⎥
⎦
M
z
D
z
[Mx+DxMy+DyMzDz]
[MX + + DxMy DyMzDz]가
.
이 과정을 통해 X 및 Y 컴포넌트를 과장하여 가파른 경사면을 따라 더 두드러진 범프를 생성한다. 그러나 노멀 중 하나가 평평하면 다른 노멀은 변경되지 않는다.
[[My Lighting.cginc]]
i.normal = float3(mainNormal.xy + detailNormal.xy, mainNormal.z * detailNormal.z);// float3(mainNormal.xy / mainNormal.z + detailNormal.xy / detailNormal.z, 1);
알베도와 혼합된 화이트 아웃 블렌딩 노멀.
UnityStandardUtils 에는 화이트 아웃 블렌딩을 사용하는 블렌딩 노멀 함수를 포함되어있다.
그함수를 사용해 보자. 결과를 정규화까지 해주므로, 더 이상 우리가 해줄 필요가 없다.
[[My Lighting.cginc]]
i.normal = BlendNormals(mainNormal, detailNormal);// float3(mainNormal.xy + detailNormal.xy, mainNormal.z * detailNormal.z); i.normal = i.normal.xzy; // i.normal = normalize(i.normal);
큐브의면 중 하나는 우리의 가정과 일치하도록 정렬 될 수 있다. 그리고 차원을 바꾸고 뒤집어서 다른면을 지원할 수 있다. 그러나 이것은 축 정렬 된 큐브를 기준으로 한다.
큐브가 임의의 회전을하면 더 복잡해진다. 범프 매핑 코드의 결과를 변환시켜 면의 실제 방향과 일치하도록 해야한다.
면의 방향을 알기위해서 U,V 축을 정의하는 벡터가 필요하다. 이 두 개가 노멀벡터에 더해지면 우리의 가정과 일치하는 3D 공간을 정의 한다. 일단 우리가 그 공간을 가지면 범프를 월드공간으로 변환시키는 데 사용할 수 있다.
우리가 이미 노멀벡터
를 가지고 있으므로 추가 벡터가 하나만 필요하다. 이 두 벡터의 외적은 세 번째 벡터를 정의 한다.
추가 벡터는 메쉬의 정점 데이터의 일부로 제공된다. 표면 노멀에 의해 정의 된 평면에 있기 때문에 접선 벡터
라고 한다. 규칙에 따라 이 벡터는 오른쪽을 가리키는 U 축과 일치 한다.
세 번째 벡터는
이루어진다. 그러나 이것은 앞이 아닌 뒤를 가리키는 벡터를 생성한다. 이를 수정하려면 결과에 -1을 곱해야 한다. 이 인자는
의 제 4 성분으로서 저장된다 .
그래서 정점 노멀과 접선을 사용하여 메쉬 표면과 일치하는 3D 공간을 만들 수 있다. 이 공간은 탄젠트 공간, 접선 기반 또는 TBN 공간으로 알려져 있다. 큐브의 경우 접선 공간은 면당 균일하고,구의 경우 접선 공간이 표면을 감싸고 있다.
이 공간을 구성하기 위해서는 메쉬에 접선 벡터가 있어야 한다. 다행히 Unity의 기본 메쉬에는이 데이터가 포함되어 있다. 메쉬를 Unity로 임포트 할 때, 자신의 접선을 가져 오거나 유니티에서 생성 할 수 있다.
TangentSpaceVisualizer 컴포넌트와 , OnDrawGizmos메소드를 만든다.
[[TangentSpaceVisualizer.cs]]
using UnityEngine;public class TangentSpaceVisualizer : MonoBehaviour { void OnDrawGizmos () { }}
기즈모가 그려 질 때마다 게임 객체의 메쉬 필터에서 메쉬를 가져 와서 접선 공간을 표시한다. 메쉬가있는 경우에만 작동한다. shadedMesh만 사용한다.첫 번째는 메쉬시트에 대한 참조를 제공하고 두 번째는 사본을 생성한다.
[[TangentSpaceVisualizer.cs]]
void OnDrawGizmos () { MeshFilter filter = GetComponent<MeshFilter>(); if (filter) { Mesh mesh = filter.sharedMesh; if (mesh) { ShowTangentSpace(mesh); } } } void ShowTangentSpace (Mesh mesh) { }
먼저, 노멀 벡터를 표현하겠다. 메쉬에서 정점 위치와 노멀을 가져 와서 선을 그린다. 우리는 그것을 월드공간으로 변환시켜 기하학과 일치하도록 해야 한다. 노멀이 접선 공간에서 위쪽 방향과 일치해야한다.노멀의 색상은 녹색 색상을 지정한다.
[[TangentSpaceVisualizer.cs]]
void ShowTangentSpace (Mesh mesh) { Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; for (int i = 0; i < vertices.Length; i++) { ShowTangentSpace( transform.TransformPoint(vertices[i]), transform.TransformDirection(normals[i]) ); } } void ShowTangentSpace (Vector3 vertex, Vector3 normal) { Gizmos.color = Color.green; Gizmos.DrawLine(vertex, vertex + normal); }
메쉬가있는 객체에 컴포넌트를 추가하여 정점 노멀을 확인한다.
B
B
N
N을
T
T
T
T
, 비트 탄젠트 또는 바이 노멀이라고합니다.이 벡터는 V 축을 정의하여 앞방향을 가리킨다. 비트 탄젠트를 유도하는 표준 방법은 B
B = N × T를 통해
=
N
×
T
노멀이 보여진다.
선의 길이는 어느정도인가? 그것은 기하학에 따라 달라진다. 이제 구성 가능한 척도를 추가 한다. 또한 구성 가능한 오프셋을 지원하여 선을 표면에서 밀어냅니다.겹치는 정점을 더 쉽게 검사 할 수 있습니다.
[[TangentSpaceVisualizer.cs]]
public float offset = 0.01f; public float scale = 0.1f; void ShowTangentSpace (Vector3 vertex, Vector3 normal) { vertex += normal * offset; Gizmos.color = Color.green; Gizmos.DrawLine(vertex, vertex + normal * scale); }
오프셋 및 크기 조정.
이제 접선 벡터를 포함한다. 그것들은 4D 벡터라는 것을 제외하고는 노멀벡터와 똑같이 작동한다. 그들이 로컬에서 오른쪽을 가리킬 때, 그들에게 붉은색을 준다.
[[TangentSpaceVisualizer.cs]]
void ShowTangentSpace (Mesh mesh) { Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; Vector4[] tangents = mesh.tangents; for (int i = 0; i < vertices.Length; i++) { ShowTangentSpace( transform.TransformPoint(vertices[i]), transform.TransformDirection(normals[i]), transform.TransformDirection(tangents[i]) ); } } void ShowTangentSpace (Vector3 vertex, Vector3 normal, Vector3 tangent) { vertex += normal * offset; Gizmos.color = Color.green; Gizmos.DrawLine(vertex, vertex + normal * scale); Gizmos.color = Color.red; Gizmos.DrawLine(vertex, vertex + tangent * scale); }
법선과 접선을 표시.
마지막으로, 바이 노멀 벡터를 파란색 선으로 구성하여 보여준다.
[[TangentSpaceVisualizer.cs]]
void ShowTangentSpace (Mesh mesh) { … for (int i = 0; i < vertices.Length; i++) { ShowTangentSpace( transform.TransformPoint(vertices[i]), transform.TransformDirection(normals[i]), transform.TransformDirection(tangents[i]), tangents[i].w ); } } void ShowTangentSpace ( Vector3 vertex, Vector3 normal, Vector3 tangent, float binormalSign ) { … Vector3 binormal = Vector3.Cross(normal, tangent) * binormalSign; Gizmos.color = Color.blue; Gizmos.DrawLine(vertex, vertex + binormal * scale); }
완전한 탄젠트 공간을 표시.
탄젠트 공간은 기본 큐브의 각면마다 차이는 있지만 일정하다는 것을 알 수 있다. 기본 구의 경우 탄젠트 공간은 각 정점마다 다르다. 결과적으로, 탄젠트 공간은 삼각형을 가로 질러 보간 될 것이고, 결과적으로 곡선 공간이 된다.
기본 구 주위의 접선 공간.
구 주위에 접선 공간을 배치하는 것은 문제가 있다. Unity의 기본 구는 경도 - 위도 텍스처 레이아웃을 사용합니다. 마치 공 주위에 종이를 감아 실린더를 만드는 것과 같다. 그런 다음 실린더의 상단과 하단이 구체와 일치 할 때까지 구겨진다.유니티의 기본 영역은 큐빅 버텍스 레이아웃과 결합하여 문제를 악화시킨다. 실물 크기 모형의 경우에는 괜찮지 만, 기본 메쉬가 고품질의 결과를 나올것을 기대하지는 마라.
셰이더의 접선에 접근하려면 VertexData 구조체 에 추가해야 한다 .
[[My Lighting.cginc]]
struct VertexData { float4 position : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 uv : TEXCOORD0; };
추가 interpolator를 포함시켜야합니다. interpolator의 순서는 중요하지 않지만 노멀과 접선을 유지해야 한다.
[[My Lighting.cginc]]
struct Interpolators { float4 position : SV_POSITION; float4 uv : TEXCOORD0; float3 normal : TEXCOORD1; float4 tangent : TEXCOORD2; float3 worldPos : TEXCOORD3; #if defined(VERTEXLIGHT_ON) float3 vertexLightColor : TEXCOORD4; #endif };
UnityCG의 UnityObjectToWorldDir를 사용하여 정점 프로그램의 월드공간으로 접선을 변환한다. 물론 이것은 접선의 XYZ 부분에만 적용된다. W 컴포넌트는 수정되지 않은 상태로 전달되어야 한다.
[[My Lighting.cginc]]
Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.position = mul(UNITY_MATRIX_MVP, v.position); i.worldPos = mul(unity_ObjectToWorld, v.position); i.normal = UnityObjectToWorldNormal(v.normal); i.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w); i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex); i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex); ComputeVertexLightColor(i); return i;
이제 프래그먼트 쉐이더에서 노멀 및 접선에 접근 할 수 있다. 그래서 우리는 바이노멀을 만들 수 있다. 그러나 원래의 노멀을 범프 노멀로 대체하지 않도록 주의해야한다. 범프 노멀은 접선 공간에 존재하므로 분리되어 있다.
[[My Lighting.cginc]]
void InitializeFragmentNormal(inout Interpolators i) { float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale); float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale); float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal); tangentSpaceNormal = tangentSpaceNormal.xzy; float binormal = cross(i.normal, i.tangent.xyz) * i.tangent.w; }
범프노멀을 접선 공간에서 월드 공간으로 변환 할 수 있다.
[[My Lighting.cginc]]
float binormal = cross(i.normal, i.tangent.xyz) * i.tangent.w; i.normal = normalize( tangentSpaceNormal.x * i.tangent + tangentSpaceNormal.y * i.normal + tangentSpaceNormal.z * binormal );
우리는 공간 변환과 결합하여 명시적 YZ 스왑을 제거 할 수 있다.
[[My Lighting.cginc]]
// tangentSpaceNormal = tangentSpaceNormal.xzy; float binormal = cross(i.normal, i.tangent.xyz) * i.tangent.w; i.normal = normalize( tangentSpaceNormal.x * i.tangent + tangentSpaceNormal.y * binormal + tangentSpaceNormal.z * i.normal ;
변환 된 법선.
바이 노멀을 구성 할 때 하나의 추가 세부 사항이 있습니다. 객체의 스케일이 (-1, 1, 1)로 설정되어 있다고 가정합니다. 그것은 거울에 비춰지는것 처럼 된다. 이 경우 바이노멀을 뒤집어 접선 공간을 정확하게 반영해야한다. 사실 홀수차원이 음수 일 때이 작업을 수행해야한다.
UnityShaderVariables 는 float4 unity_WorldTransformParams 변수 를 정의함으로서 이를 도와준다 . 네 번째 구성 요소는 바이노멀을 뒤집을 필요가있을 때 -1을 포함하고 그렇지 않으면 1을 포함한다.
[[My Lighting.cginc]]
float binormal = cross(i.normal, i.tangent.xyz) * (i.tangent.w * unity_WorldTransformParams.w);
3D 아티스트가 자세한 모델을 만들 때 일반적인 방법은 매우 고해상도 모델을 만드는 것이다. 모든 디테일은 실제 3D 기하학이다. 게임에서이 작업을 수행하려면 모델의 저해상도 버전이 생성된다. 디테일는이 모델의 텍스처로 구워진다.
고해상도 모델의 노멀은 노멀 맵으로 구워진다. 이것은 노멀을 월드 공간에서 접선 공간으로 변환하여 수행된다. 게임에서 저해상도 모델을 렌더링 할 땐 변환이 취소된다.
두 프로세스가 동일한 알고리즘과 탄젠트 공간을 사용하는 한이 프로세스는 정상적으로 작동한다. 그렇지 않은 경우 게임 내 결과가 잘못된다. 이로 인해 3D 아티스트에게 큰 슬픔이 생길 수 있다. 따라서 노멀 맵 생성기, Unity의 메시 가져 오기 프로세스 및 쉐이더가 모두 동기화되었는지 확인해야한다. 이를 동기화 된 탄젠트 스페이스 워크 플로우라고한다.
버전 5.3부터 Unity는 mikktspace를 사용한다. 그러므로 일반 맵을 생성 할 때도 mikktspace를 사용하고 있는지 확인해야한다. 메쉬를 임포트 할 때, 그것에 대한 mikktspace 알고리즘을 사용하기 때문에 Unity가 접선 벡터를 생성하도록 허용 할 수 있다.
mikktspace를 사용할 때 선택할 수있는 옵션이 하나 있다. 바이노멀은 프레그먼트 프로그램에서 만들 수 있다. 두 접근법 모두 약간 다른 바이노멀을 생성한다.
[[My Lighting.cginc]]
struct Interpolators { float4 position : SV_POSITION; float4 uv : TEXCOORD0; float3 normal : TEXCOORD1; #if defined(BINORMAL_PER_FRAGMENT) float4 tangent : TEXCOORD2; #else float3 tangent : TEXCOORD2; float3 binormal : TEXCOORD3; #endif float3 worldPos : TEXCOORD4; #if defined(VERTEXLIGHT_ON) float3 vertexLightColor : TEXCOORD5; #endif };
바이 노멀 계산을 자체 함수에 넣자. 그런 다음 정점 또는 프레그먼트 쉐이더에서 사용할 수 있다.
[[My Lighting.cginc]]
float3 CreateBinormal (float3 normal, float3 tangent, float binormalSign) { return cross(normal, tangent.xyz) * (binormalSign * unity_WorldTransformParams.w);}Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.position = mul(UNITY_MATRIX_MVP, v.position); i.worldPos = mul(unity_ObjectToWorld, v.position); i.normal = UnityObjectToWorldNormal(v.normal); #if defined(BINORMAL_PER_FRAGMENT) i.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w); #else i.tangent = UnityObjectToWorldDir(v.tangent.xyz); i.binormal = CreateBinormal(i.normal, i.tangent, v.tangent.w); #endif i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex); i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex); ComputeVertexLightColor(i); return i; } … void InitializeFragmentNormal(inout Interpolators i) { float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale); float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale); float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal); #if defined(BINORMAL_PER_FRAGMENT) float3 binormal = CreateBinormal(i.normal, i.tangent.xyz, i.tangent.w); #else float3 binormal = i.binormal; #endif i.normal = normalize( tangentSpaceNormal.x * i.tangent + tangentSpaceNormal.y * binormal + tangentSpaceNormal.z * i.normal ); }
BINORMAL_PER_FRAGMENT는 어디에도 정의되지 않으므로 셰이더는 이제 정점 당 바이노멀을 계산한다.픽셀마다 계산하려는 경우 BINORMAL_PER_FRAGMENT어딘가에 정의해야한다 . 이 파일은 include 파일의 구성 옵션으로 간주 할 수 있다. 따라서 My Lighting을 포함하기 전에 My First Lighting Shader 내 에서 정의하는 것이 좋다 .
모든 패스에 대해 동일한 설정을 사용하는 것이 합리적이므로 기본 패스와 추가 패스 모두에서 이를 정의해야한다. 그러나 우리는 CGINCLUDE 쉐이더 맨 위에 있는 블록 안에 넣을 수도 있다 . 해당 블록의 내용은 모든 CGPROGRAM 블록 안에 포함된다 .
[[My First Lighting Shader.shader]]
Properties { … } CGINCLUDE #define BINORMAL_PER_FRAGMENT ENDCG SubShader { … }
컴파일 된 셰이더 코드를 검사하여 이것이 작동하는지 확인할 수 있다. 예를 들어, BINORMAL_PER_FRAGMENT 정의 되지 않은 D3D11에 의해 사용 된 보간 기는 다음과 같다.
// Output signature: // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ // SV_POSITION 0 xyzw 0 POS float xyzw // TEXCOORD 0 xyzw 1 NONE float xyzw // TEXCOORD 1 xyz 2 NONE float xyz // TEXCOORD 2 xyz 3 NONE float xyz // TEXCOORD 3 xyz 4 NONE float xyz // TEXCOORD 4 xyz 5 NONE float xyz
그리고 여기에 그것들 BINORMAL_PER_FRAGMENT 이 정의된다.
// Output signature: // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ // SV_POSITION 0 xyzw 0 POS float xyzw // TEXCOORD 0 xyzw 1 NONE float xyzw // TEXCOORD 1 xyz 2 NONE float xyz // TEXCOORD 2 xyzw 3 NONE float xyzw // TEXCOORD 4 xyz 4 NONE float xyz