본문 바로가기
게임 그래픽 리서치

NVIDIA 표면하산란(Subsurface Scattering)

by Raypop 2012. 1. 14.


제16장. 실시간에 가까운 표면하산란(Subsurface Scattering)


사이먼 그린
NVIDIA


시간 그래픽에 사용 되는 대부분의 쉐이딩 모델은 물체 표면에서 빛의 상호 작용을 고려 하여야 합니다. 그러나, 현실 세계에서의 많은 개체는 약간 반투명 : 빛이 표면으로 들어가고, 재질의 내부에서 흩어져 들어간 다음 표면 밖으로 나오게 됩니다. 잠재적으로 들어 간 곳과 나온곳이 달라지게 됩니다.


많은 연구로 효율적이고 정확한 subsurface light transport 모델을 생산을 해오고 있다. 비록 표면하산란이 완전한 물리로 정확이 시뮬레이션되는건 현재 그래픽 하드웨어의 범위 밖이지만, 실시간으로 이 효과의 어느정도 형태가 가능합니다. 이 장에서 피부 등 대리석, 프로그래밍 가능한 그래픽 하드웨어를 사용하여 반투명 물질의 모습에 가까운 여러가지 방법을 설명 합니다.



16.1 표면하산란의 시각효과

모든 시각적 효과를 재현하려고 할 때, 종종 효과의 이미지를 검사하고 그 구성 부분에서 시각적 모양을 타파 하려고 하면 편리합니다.


사진, 반투명 오브젝트의 렌더링 된 이미지를 살펴보면, 우리는 몇 가지를 알 수 있습니다. 첫째, 표면하산란 조명의 전반적인 효과를 완화하는 경향이있다. 한 영역에서 빛이 표면에 인접한 지역으로 출혈하는 경향이 작은 표면의 세부 사항이 보이지 않게 된다.  멀리 있는 빛이 물체에 침투 할수록 감쇄되고 확산된다. 피부에 산란도 그림자 속에 있다는 조명 뒤 표면에 전이, 붉은색으로 약간의 색상 변화를 유발하는 경향이 있다. 이 라이트는 조명쪽 면에 들어가는 피부 아래에 혈액과 조직에 의해 산란 및 흡수 되고, 조명이 있는 쪽에서 들어 오는 빛에 의해 발생합니다. 피부는 콧 구멍과 귀 주위로 얇은 곳에 산란의 효과는 가장 분명 합니다.



16.2 간단한 산란근사

산란의 간단한 트릭이 랩 라이팅입니다. 표면은 빛 방향에 수직 일 때 일반적으로 확산 (램버트) 조명 제로 빛을 기여하고있다. 조명은 일반적으로 어두운 될 것 점 이상 개체 주위에 래핑 있도록 랩 라이트 확산 함수를 수정합니다. 이것은 필요로 하는 주위온도와 충분한 조명의 양을 감소시키는 확산 조명의 명암을 줄일 수 있습니다. 랩 라이트 보다 정확하게 거친 무광택 표면 (나야 르와 오렌 1995) 시뮬레이션을 시도, 오렌 - 나야르 조명 모델에 비슷한 근사값입니다.


코드는 여기에 표시하고 그림 16-1의 그래프는 랩 효과를 포함하는 확산 조명 기능을 변경하는 방법을 보여줍니다. 라이팅이 개체 주위에 래핑하는 방법까지 제어하는 0과 1 사이의 부동 소수점 숫자입니다.


fig16-01.JPG


그림 16-1 랩 라이트 기능의 그래프


float diffuse = max(0, dot(L, N));
float wrap_diffuse = max(0, (dot(L, N) + wrap) / (1 + wrap));

프래그먼트 프로그램에서 효율적으로 이를 계산하기 위해 함수는 빛의 벡터와 법선과 내적에 의해 텍스처를 인코딩 할 수 있습니다. 이 질감은 조명이 0에 접근 할수록 붉은색에 가깝게 약간의 색상 변화를 포함하도록 만들 수 있습니다. 이 스킨 쉐이더에 대한 산란을 시뮬레이션하는 쉬운 방법입니다. 같은 텍스처가 알파 채널의 반사 조명의 전력 기능을 포함 할 수 있습니다. 목록 16-1에있는 FX 코드는 이 기술을 사용하는 방법을 보여줍니다. 예는 그림 16-2을 참조하십시오.


fig16-02a.jpg

16-2 그림 분야에 랩 라이트를 적용


예 16-1. 랩 라이트를 통합 스킨 셰이더 효과에서 발췌


// Generate 2D lookup table for skin shading

   float4 GenerateSkinLUT(float2 P : POSITION) : COLOR
{
  float wrap = 0.2;
  float scatterWidth = 0.3;
  float4 scatterColor = float4(0.15, 0.0, 0.0, 1.0);
  float shininess = 40.0;

  float NdotL = P.x * 2 - 1;  // remap from [0, 1] to [-1, 1]

  
   float NdotH = P.y * 2 - 1;

  float NdotL_wrap = (NdotL + wrap) / (1 + wrap); // wrap lighting
  
   float diffuse = max(NdotL_wrap, 0.0);

  // add color tint at transition from light to dark
  
   float scatter = smoothstep(0.0, scatterWidth, NdotL_wrap) *
                    smoothstep(scatterWidth * 2.0, scatterWidth,
                               NdotL_wrap);

  float specular = pow(NdotH, shininess);
  if (NdotL_wrap <= 0) specular = 0;
  float4 C;
  C.rgb = diffuse + scatter * scatterColor;
  C.a = specular;
  return C;
}

// Shade skin using lookup table

   half3 ShadeSkin(sampler2D skinLUT,
                half3 N,
                half3 L,
                half3 H,
                half3 diffuseColor,
                half3 specularColor) : COLOR
{
  half2 s;
  s.x = dot(N, L);
  s.y = dot(N, H);
  half4 light = tex2D(skinLUT, s * 0.5 + 0.5);
  return diffuseColor * light.rgb + specularColor * light.a;
}

16.3 Depth Map을 이용한 시뮬레이션


흡수는 반투명 재질을 시뮬레이션 하는 가장 중요한 요소 중 하나 입니다. 빛 재질을 통과 하면서 더 많이 산란하고 흡수 된다. 이 효과를 시뮬레이션하기 위해, 빛이 재질을 통해 나아가는 거리가 필요합니다.

이 거리를 추정하는 방법 중 하나는 뎁스맵(Hery 2002)을 사용하는 것입니다. 이 기술은 쉐도우 맵핑과 매우 유사합니다. 그리고 실시간 렌더링에 실용적입니다. 첫 번째 단꼐는 빛에서 텍스처 까지 거리를 저장하고 빛의 관점에서 장면을 렌더링 합니다. 이 이미지는 표준 투영 텍스처 매핑을 사용 하여 장면에 다시 투영 됩니다. 렌더링 패스에서 그림자가 되는 점을 감안 할때, 우리는 빛이 표면에 들어간 점 (di )의 빛에서의 거리를 얻으려면 텍스처에서 확인 할 수 있습니다. 빛이 재질 밖으로 나오는 표면 (do )을 거리에서 값을 빼서, 우리 빛 개체 (s)를 통해 이동한 거리를 추정치를 얻을수 있습니다. 그림 16-3을 참조 하십시오.


fig16-03.JPG

그림 16-3는 빛의 거리 계산은 뎁스맵을 사용하여 개체를 통해 알수있다


이 기술 명백한 문제는 볼록한 객체에서만 사용 가능 하다는 것입니다. 개체내 구멍에서는 제대로 구현되지 않습니다. 실제로, 이것은 큰 문제가 아니지만, 그것은 하나의 (Everitt 2003)에 의해 객체 하나의 레이어를 제거하는 depth peeling 을 사용하여 문제를 해결하는 것이 가능하다.


당신은 움직이지 않는 오브젝트에 대해, 그것은 각 지점에서 표면의 대략적인 두께를 나타내는 맵를 그리거나 미리 계산 할 수 있을 것이라고 생각 할 수 있을지도 모른다. 뎁스맵 을 사용하는 장점은 그들이 들어오는 빛의 방향을 가지고, 그들은 또한 (당신은 뎁스맵의 각 프레임을 다시 생성한다고 가정) 애니메이션 모델에 대해 작동합니다.


목록 16-2과 16-3에있는 프로그램은 빛과 텍스처 까지의 거리를 렌더링하는 방법을 보여줍니다. 그들은 모델뷰와 modelViewProj 행렬 빛 뷰 응용 프로그램에서 설정 되었다고 가정합니다.


예 16-2. 뎁스 패스의 정점 프로그램


struct a2v {
  float4 pos    : POSITION;
  float3 normal : NORMAL;
};
struct v2f {
  float4 hpos : POSITION;
  float  dist : TEXCOORD0; // distance from light
};

v2f main(a2v IN,
         uniform float4x4 modelViewProj,
         uniform float4x4 modelView,
         uniform float    grow)
{
  v2f OUT;
  float4 P = IN.pos;
  P.xyz += IN.normal * grow;  // scale vertex along normal
  OUT.hpos = mul(modelViewProj, P);
  OUT.dist = length(mul(modelView, IN.pos));
  return OUT;
}

예 16-3.뎁스 패스의 프로그먼트 프로그램


  float4 main(float dist : TEX0) : COLOR
{
  return dist;  // return distance
}

목록 16-4의 프래그먼트 프로그램에서 추출한 깊이를 계산하기 위해, 빛의 거리 질감에서 조회하는 방법을 보여줍니다. 유연성을 높이기 위해 이 코드에서는 프래그먼트 프로그램에 투영합니다. 당신은 몇 가지 샘플을 가지고 있다면, 그것은 정점 프로그램에서 이러한 변환을 계산하는 것이 더 효율적입니다.


예 16-4. 뎁스맵 을 사용하여 침투 깊이를 계산하기 위한 프래그먼트 프로그램 기능


  // Given a point in object space, lookup into depth textures

   // returns depth

   float trace(float3 P,
            uniform float4x4  lightTexMatrix, // to light texture space
            
   uniform float4x4  lightMatrix,    // to light space
            
   uniform sampler2D lightDepthTex,
            )
{
  // transform point into light texture space
  
   float4 texCoord = mul(lightTexMatrix, float4(P, 1.0));

  // get distance from light at entry point
  
   float d_i = tex2Dproj(lightDepthTex, texCoord.xyw);

  // transform position to light space
  
   float4 Plight = mul(lightMatrix, float4(P, 1.0));

  // distance of this pixel from light (exit)
  
   float d_o = length(Plight);

  // calculate depth
  
   float s = d_o - d_i;
  return s;
}

빛이 오브젝트를 통과한 거리가 측정이 되면, 우리는 그것을 사용할 수있는 여러 가지 방법이 있습니다. 간단한 방법 중 하나는 아티스트가 만든 1D 텍스처에 색상을 거리를 맵핑하여 직접 인덱스를 사용하는 것입니다. 색상은 거리에 따라 기하 급수적으로 떨어질 것이다. 이 색상 맵을 변경하고 다른 전통적인 라이팅 모델 효과를 결합하여, 우리는 대리석이나 옥 같은 다른 재료의 이미지를 생성 할 수 있습니다.


float si = trace(IN.objCoord, lightTexMatrix, lightMatrix, lightDepthTex);   return tex1D(scatterTex, si);


또한, 우리가 직접 지수 함수를 평가할 수 있습니다 :


  return exp(-si * sigma_t) * lightColor;

이 기술의 문제는 그것이 오브젝트를 통과하는 빛이 확산되는 방식을 시뮬레이션하지 않는다는 것입니다. 빛이 오브젝트의 뒤에 있는 경우, 종종 명확하게 전면에 투명 물체의 뒤편에서의 특징을 볼 수 있습니다. 이 문제에 대한 해결책은 표면에 다른 지점에서 여러 개의 샘플을 채취하거나 다음 섹션에서 설명하는 서로 다른 확산 근사를 사용하는 것입니다.



16.3.1 구현 세부 사항


지포스 FX 하드웨어, 뎁스 텍스쳐에서 읽을 때, 깊이 값은 가장 중요한 8 비트가 포함 되어 있습니다. 이것은 정밀도 충분 하지 않습니다. 대신, 우리는 부동 소수점 텍스처를 사용하거나 일반 8 비트 RGBA 텍스처에 32 비트 부동 소수점 값을 저장하기 위해 NVIDIA 프래그먼트 프로그램 확장자로 압축 및 풀기 명령을 사용할 수 있습니다. 부동 소수점 텍스처는 현재 필터링을 지원하지 않으므로 투영 텍스처가 확대되는 블록 아티팩트는 종종 볼 수 있습니다. 필요한 경우, 바이리니어 필터링은 약간의 성능 비용, 쉐이더에서 수행 할 수 있습니다.


투영 된 뎁스맵 의 또 다른 문제는 아티펙트가 종종 투영 주위에 나타나는 것입니다. 이 쉐도우 매핑 은 셀프 쉐도우 아트펙트와 비슷합니다. 그들은 주로 객체의 가장자리에 투영되도록 배경에서 픽셀을 일으키는 텍스처 맵의 한정된 해상도에서 주로 발생한다. 샘플 코드는 약한 뎁스맵 을 통과하는 동안 정점 법선을 따라 객체를 확장하여 문제를 피할 수 있습니다.


보다 정확한 시뮬레이션을 위해, 우리는 빛이 오브젝트를 입력하는 시점에서 법선 및 잠재적으로 표면의 색상을 알 필요가 있습니다. 우리는 텍스처에 추가 정보를 렌더링 할 추가 경로를 렌더링하여 이 작업을 수행 할 수 있습니다. 우리는 뎁스 텍스처와 유사한 방법으로 이러한 텍스처를 찾아 볼 수 있습니다. 복수의 렌더링 타겟을 지원하는 시스템에서는 여러 개의 값을 출력하는 단일 패스에 깊이는 일반적으로 다른 경로를 축소 하는 것이 가능하다. 그림 16-4을 참조하십시오.


fig16-04.JPG

그림 16-4 대략적인 산란에 깊이 맵을 사용


16.3.2 더 복잡한 산란 모델


보다 정교한 모델은 정확하게 재질의 산란의 누적 효과를 시뮬레이션 하려고 합니다.

하나의 모델은 빛이 물질 내에서 한 번만 반사 한다고 가정하고 단일 산란 근사치입니다. 재료에 굴절 라인을 따라 단계별로, 하나의 카메라를 향해 흩어지는지 많은 광자 추정 할 수 있습니다. 위상 함수는 그것이 입자에 닿으면 빛을 산란시키는 방향의 분포를 설명하는 데 사용됩니다. 그것은 또한 입구와 출구 지점에서 프레넬 효과를 고려 하는 것이 중요하다.


다른 모델 확산 근사 는 피부와 같은 고도로 분산 매체에 다중 산란의 효과를 시뮬레이트 합니다.


불행히도, 이러한 기술은이 장의 범위를 벗어난다.

SIGGRAPH 2003 렌더맨 코스 (Hery 2003)의 크리스토프 Hery의 장 피부 쉐이더 단일 및 확산 산란 정보입니다.


16.4 텍스처 공간 확산


앞에서 언급 한 바와 같이, 표면하산란의 가장 확실한 시각적 징후 중 하나는 조명 효과의 일반적인 블러 입니다. 사실, 3D 아티스트들은 종종 어도비 포토샵에서 렌더링의 가우시안 블러를 실행하여 화면 공간이 현상을 모방하고 원래의 상단에 이미지를 흐리게 한뒤 작은 추가로 할 수 있습니다. 이 "glow" 기술은 조명을 부드럽게하고 이미지는 컴퓨터에 의해 만들어 집니다.


텍스쳐 공간 (Borshukov와 루이스 2003)의 확산을 시뮬레이션 할 수 있습니다. 우리는 정점 화면 위치로 UV 텍스처 좌표를 사용하여 버텍스 프로그램과 오브젝트의 메쉬를 랩핑 해제 하 수 있습니다. 이 프로그램은 단순히 다시 매핑 텍스처의 [0, 1] 범위 [-1, 1] 정규화 장치 좌표의 범위를 조정합니다. 우리는 객체가 좋은 UV 매핑을 가지고 주의 해야 합니다, 즉, 텍스처의 각 점은 중복되지 않게, 객체의 한 점에 매핑해야합니다. 정상적인 방법으로 개봉된 메쉬를 조명함으로써, 개체의 조명을 나타내는 2D 이미지를 얻을 수 있습니다. 이 이미지를 처리​​하고 일반적인 텍스처와 같은 3D 모델에 다시 적용 할 수 있습니다.

목록 16-5에 있는 정점 프로그램은 UV 공간에서 모델을 렌더링 확산 조명을 수행하는 방법을 보여줍니다.


화면 해상도에서 음영의 복잡성을 불리하는 이 기법은 다른 용도로 유용하다 : 음영, 텍스처 맵의 각 텍셀 뿐만 아니라 개체의 모든 화소에 대해서 이루어진다. 예를 들어, 회선 등의 많은 문제는 3차원 표면보다 이미지 공간에서보다 효율적으로 할 수있다. 표면의 UV 매개 변수가 비교적 균일 한 경우는 월드 공간에서 가까운 도트도 텍스처 공간에 자리 잡고 있는 점에 매핑됩니다.


이미지 공간에서 빛의 확산을 시뮬레이션하기 위해, 우리는 단순히 라이트 맵 텍스처를 흐리게 할 수 있습니다. 우리는 분리 필터를 사용하여 이중 선형 필터링 하드웨어를 활용하는 등 모든 일반적인 GPU 이미지 처리 기법을 활용 할 수 있습니다. 상대적으로 낮은 해상도 텍스처에 라이팅 을 렌더링하는 것은 이미 블러의 일정 공간을 제공합니다. 그림 16-5은 개봉 된 머리 메쉬와 라이트 맵 텍스처를 블러의 결과를 보여줍니다.


fig16-05a.jpg

16-5 그림 언랩 머리 메쉬


예 16-5. 언렙 모델 과 확산 조명을 수행하는 버텍스 프로그램


  struct a2v {
  float4 pos     : POSITION;
  float3 normal  : NORMAL;
  float2 texture : TEXCOORD0;
};

struct v2f {
  float4 hpos     : POSITION;
  float2 texcoord : TEXCOORD0;
  float4 col      : COLOR0;
};

v2f main(a2v IN,
         uniform float4x4 lightMatrix)
{
  v2f OUT;

  // convert texture coordinates to NDC position [-1, 1]
  OUT.hpos.xy = IN.texture * 2 - 1;
  OUT.hpos.z = 0.0;
  OUT.hpos.w = 1.0;

  // diffuse lighting
  
   float3 N = normalize(mul((float3x3) lightMatrix, IN.normal));
  float3 L = normalize(-mul(lightMatrix, IN.pos).xyz);
  float diffuse = max(dot(N, L), 0);
  OUT.col = diffuse;

  OUT.texcoord = IN.texture;
  return OUT;
}


확산 컬러 맵은 라이트 맵 텍스처에 포함 할 수 있으며, 그 컬러맵에서 세부 사항도 확산 될 것입니다. 쉐도우 텍스처에 포함 된 경우, 블러 프로세스는 부드러운 그림자가 발생합니다.

흡수 및 산란은 파장에 의존한다는 사실을 시뮬레이션하기 위해, 우리는 각 색상 채널에 대해 개별적으로 필터 가중치를 변경할 수 있습니다. 목록 16-6과 16-7에 표시된 샘플 쉐이더는, 피부를 시뮬레이션 하려고 합니다. 그것은 가우시안 가중치 7 텍스처 샘플을 사용합니다. 필터의 폭은 빨간색이 다른 채널보다 확산 될 수 있도록, 녹색 및 파랑 채널에 비해,  빨강 채널 크다. 목록 16-6에 있는 정점 프로그램은 X 방향의 흐림에 대한 샘플의 위치를 계산, y 방향에 대한 프로그램은 거의 동일합니다. 샘플은 하드웨어의 선형 필터링 기능을 활용하기 위해 두 개의 텍셀이 떨어져 있습니다.


예 16-6. 확산 블러 효과를 위한 정점 프로그램


  v2f main(float2 tex : TEXCOORD0)
{
  v2f OUT;
  // 7 samples, 2 texel spacing
  OUT.tex0 = tex + float2(-5.5, 0);
  OUT.tex1 = tex + float2(-3.5, 0);
  OUT.tex2 = tex + float2(-1.5, 0);
  OUT.tex3 = tex + float2(0, 0);
  OUT.tex4 = tex + float2(1.5, 0);
  OUT.tex5 = tex + float2(3.5, 0);
  OUT.tex6 = tex + float2(5.5, 0);
  return OUT;
}


예 16-7. 확산 블러 프래그먼트 프로그램


  half4 main(v2fConnector v2f,
           uniform sampler2D lightTex
           ) : COLOR
{
  // weights to blur red channel more than green and blue
  
   const float4 weight[7] = {
    { 0.006, 0.0,  0.0,  0.0 },
    { 0.061, 0.0,  0.0,  0.0 },
    { 0.242, 0.25, 0.25, 0.0 },
    { 0.383, 0.5,  0.5,  0.0 },
    { 0.242, 0.25, 0.20, 0.0 },
    { 0.061, 0.0,  0.0,  0.0 },
    { 0.006, 0.0,  0.0,  0.0 },
  };

  half4 a;
  a  = tex2D(lightTex, v2f.tex0) * weight[0];
  a += tex2D(lightTex, v2f.tex1) * weight[1];
  a += tex2D(lightTex, v2f.tex2) * weight[2];
  a += tex2D(lightTex, v2f.tex3) * weight[3];
  a += tex2D(lightTex, v2f.tex4) * weight[4];
  a += tex2D(lightTex, v2f.tex5) * weight[5];
  a += tex2D(lightTex, v2f.tex6) * weight[6];
  return a;
}


넓게 블러를 달성하기 위해, 어떤 블러 쉐이더를 여러 번 적용하거나 프래그먼트 프로그램의 샘플 위치를 계산하여 더 많은 샘플을 취하는 쉐이더를 작성할 수 있습니다. 그림 16-6는 3D 머리 모형에 다시 적용 흐리게 라이트맵 질감을 보여줍니다.


fig16-06b.jpg

16-6 그림 머리 모델에 텍스처 공간 확산


그림 16-7에서와 같이 최종 쉐이더는, 최종 효과를 얻을 위해 원본 고해상도 컬러맵을 함께 확산 조명 텍스처를 혼합합니다.


fig16-07.jpg를

그림 16-7 컬러맵과 함께 최종 모델,


16.4.1 향후 작업


뎁스맵 기술을 하나의 가능성의 확장은 추가 깊이와 같은 신체 내 뼈와 같은 객체 내의 밀도가 높은 개체에 대해 고려 하여 전달합니다. 문제는 우리가 표면 기반 표현을 사용하여 볼륨 효과를 담당 하려는 것입니다. 볼륨 렌더링은 이러한 제한을 가지고 있지 않고, 누구의 밀도 변화 개체를 훨씬 더 정확 렌더링 생성 할 수 있습니다. 볼륨 렌더링에 대한 자세한 내용은, "볼륨 렌더링 기법을."이 책의 39 장을 참조


이 기술에 또 다른 확장은 여러 가지 컬러 맵, 피부의 다른 층의 색을 나타내는 각을 제공하는 것입니다. 예를 들어, 당신은 피부 아래 혈관 및 모세 혈관의 표면의 색과 다른 하나의 맵을 제공 할 수 있습니다.


그렉 제임스 (2003)은 먼저 배면의 거리를 추가하고 다음 모든 표면 거리를 뺀 임의의 다각형 오브젝트를 처리하는 방법을 설명합니다. 볼륨메트릭 안개 효과에서 거리를 계산하지만, 그것은 더 일반적인 상황으로 확장 할 수 있습니다.

향후 연구의 흥미로운 영역은 양쪽의 장점을 얻기 위해 뎁스맵과 텍스쳐 공간 기술을 결합하고 있습니다..


16.5 결론


표면하산란의 효과는 피부와 다른 반투명 재질의 설득력 있는 이미지 생산에 중요한 요소이다. 여러 가지 비슷한것을 사용하여, 우리는 오늘 실시간으로 표면하산란의 모습을 달성하는 방법을 보여 주었다. 그래픽 하드웨어가 더욱 강력 해짐에 따라, 표면 빛 전송이 점점 정확한 모델이 가능합니다.

우리는 이 장에서 설명하는 기술은 실시간 게임 캐릭터의 현실감을 향상시키기 위해 영감을 수 있기를 바랍니다. 그러나 기억하세요, 좋은 쉐이더라도  잘못된 아트를 도울 수 없다!



출처 : http://http.developer.nvidia.com/GPUGems/gpugems_ch16.html

오역이 많습니다 -ㅅ-;; 출처도 같이 참고해 주세요