프로그래밍

Oren-Nayar Reflectance Lighting Model

알 수 없는 사용자 2012. 2. 11. 00:00

Oren-Nayar Reflectance Lighting Model


안녕하세요 11일, 26일 연재를 맡게 된 풍풍풍이라고 합니다 __)
 

저는 여기 계시는 대단한 분들과는 다르며 고렙이 되고 싶은 신입 프로그래머일뿐 제가 공부하며 정리한것을 좋게 보신
포프아저씨의 낚시(?)에 글 을 올리는것 뿐이라고 사전공지를 드리겠습니다 ( 베이스 깔기 스킬을 발동하였습니다 )
나눠서 연재하기 스킬을 시전하려 했으나.. 걍 한번에 올렸습니다.. 
 

그렇게 살지 않기 위해 열심히 하고 있습니다__)


여기에 계신 대략 쩌시는 프로그래머 분들 및 아티스트 기획자 분들이 더 난이도 있는 것을 설명해 주실것이고 -.-
난이도가 너무 데스윙만 나오면 재미 없으니 가끔씩 트롤도 나와줘야 한다는 결론하에 글을 올리게 되었습니다

물론!! 틀릴 수도 있습니다 __) 그때는 과감히!!!! 피드백을 주시진 마시고...

.....살며시 해주세요
 


오늘 할 것은 Oren-Nayer 조명 모델입니다
개념부터 차근차근 살펴 보겠습니다

 개념.

 

Oren-Nayer 조명 모델이란

Michael Oren 과  Shree K. Nayar가  Generalization of Lambert’s reflectance model”, Michael Oren and Shree K. Nayar  논문에서 발표한 모델로 거친 표면으로부터의 diffuse 를 표현하기 위한 reflectance model 이라고 합니다.

(거친 표면으로부터의 Specular를 고려한 모델은 Cook-Torrance Model입니다)

 

Lambert 의 diffuse model 은 모든 방향에서 같은 복사량을 취하게 된다는 가정을 깔고 있으며, 

플라스틱이나 모래와 같이 반들반들한 표면을 가진 재질 적합하다고 할 수 있습니다. 


하지만 현실 세계에서는 거친 표면을 가진 재질들도 많이 있지요
 





( 각 Specular Reflectance에 따른 Diffuse Model의 비교)

하이라이트에는 변화가 없으나 Diffuse가 Oren-Nayer 쪽이 약간 더 어두운 형태를 띄고 있습니다
 

 나이키 광고 인데..  하늘에 있는 달이  Oren-Nayer 방식을 사용해서 렌더링 되었습니다 @_@

 

 

Oren-Nayer Model의 공식을 유도할 때 사용되는 거친 표면(roughness Surface)이라는 것은

서로 다른 각도를 가진 Torrance 와 Sparrow에 의해 제안된 미세면(microfacet)들의 집합으로서 설명될 수 있습니다.

 

여기에서 미세면의 표면은 길고 대칭적인 V 자형태의 굴곡(cavity)으로 구성된다고 가정합니다.

또한, 각 cavity 는 두 개의 평면의 면으로 구성됩니다

 

면의 거친 정도는 경사진 면의 분산을 위한 확률 함수로 결정되는데, 보통  Gaussian distribution  이 사용됩니다

 


 

Oren-Nayar reflectance model 에서 각 면은 Lambetr reflectance model 을 따른다고 가정합니다.

 


 

여기에서  
 는 입사광선의 radiance이며, 
 은 반사 광선의 radiance입니다.

 

해당 표현은 구면 좌표계에서의 좌표 표현법으로 원래는 
와 같이 세 개의 정보를 사용해야 하지만

우리가 사용하는 것은 단위 vector 이기 때문에 원점으로부터의 거리를 의미하는 
 에 대한 정보는 필요로 하지 않습니다.

 

따라서 구하는 것은 
 이며 
 

는 양의 Z 축과 이루는 각도를 의미하고, 

 는 Z 축을 축으로 했을 때 양의 방향 X 축과 이루는 각을 의미합니다

 

또한 
 와 
의 vector 를 알고 있다면 vector의 성분을 이용해 아래와 같이 구할 수 있습니다

 




 

 

 공식.

  

Oren-Nayar model 에서는 미세면의 방향에 의한 빛에 반사량, 자기차폐, 자기반사의 효과가 포함되어 있으며
공식은 다음과 같이 표현됩니다

 

 

과정은 이렇게 되겠군요@_@
 

여기에서 A = B 는

 


,  

 

이고 

 

을 통해 아래의
 


 

 
를 구합니다

 

이때의 
 는 surface 의 조도계수(albedo) 이며, 
 는 [ 0.0, 1.0 ] 범위를 가지는 surface 의 거친 정도를 의미합니다.

만약 
가 0 이라면(즉, 매끄러운 표면이라면
  이 되어 다음과 같이 되며

 

 


 

N과 L의 두벡터를 normalize해서 계산하므로 길이가 전부 1이고



이 말은 
   일때의 값은  
와 같다는 말이므로

가 0일때는 Lambert 방식과 대충 비슷한 양상을 띠게 됩니다


 

그래도 계속 봅시다.... 0-.-0
 

 Lamber model 과의 비교.

 

 

아래 그림은 영문위키에 있는 Oren-Nayar 와 Lambertian 의 Brightness 를 비교한 것입니다

여기서 Measurements 는 실제 model 에서의 측정값을 나타내고 있습니다

 




 

이 graph 를 보면 Oren-Nayar 의 경우에는 면과 광원의 각이 거의 수직이 되어 갈 때 밝기가 급격하게 변하지만,
Lambertian 의 경우에는 완만하게 변하고 있다라는 것을 알 수 있지요 @_@

3개의 모델을 비교해 보겠습니다 



 

 

Lambertian model 에 익숙해져 있기 때문에 가운데 그림이 제일 자연스러워 보일 수도 있으나, 

실제 이미지는 더 많은 정보를 가지고 있기에 ( 거칠은 정도까지 나타내는 부분을 포함해서  )

실제 Real Image에 가까운 것은 Oren-Nayar Model 이라고 할 수 있습니다 ( 실제 결과도 그러함 )

 

아래 그림은  
 값에 따른 변화를 보여 줍니다.

 


 

Specular 는?

 

Oren-Nayar model 은 diffuse model 이기 때문에 specular 성분은 고려하고 있지 않습니다.

그러므로 다른 Specluar 모델과 혼합하여 표현해야 합니다

 

 specular model에는 다음과 같은 것들이 있는데요

 

  • Phong
  • Blinn - Phong
  • Gaussian Distribution
  • Beckmann Distribution
  • Ward anisotropic Distribution
  • Cook - torrance Model

 
저 부분에 대해서는 다른 훌륭하신 분들이 해주실거라고... 믿습니다...

 ........

 


구현

  

구현에 있어 특이한 사항은 원래 world 의 좌표계를 원점으로 하는 구면 좌표계를 사용하지 않고,

normal 을 up 으로 하는 구면 좌표계를 사용한다는 것인데요.


왜냐하면 어차피 
 
 가 관계를 표현하기 위한 요소여서 꼭 특정 좌표계에 종속될 필요가 없기 때문입니다.

 

그러므로 아래와 같은 표현이 가능합니다@_@ 

 

 




 

  

위의 공식들을 HLSL에서 표현해보면


float4 psOrenNayarSimple
        (
            in VS_OUTPUT f,
            uniform bool UseLookUpTexture
        ) : SV_TARGET
{
    // Make sure the interpolated inputs and
    // constant parameters are normalized
    float3 n = normalize( f.normal );
    float3 l = normalize( -vLightDirection );
    float3 v = normalize( pCameraPosition - f.world );

    // Compute the other aliases
    float gamma   = dot
                    (
                        v - n * dot( v, n ),
                        l - n * dot( l, n )
                    );

    float rough_sq = fRoughness * fRoughness;

    float A = 1.0f - 0.5f * (rough_sq / (rough_sq + 0.33f));

    float B = 0.45f * (rough_sq / (rough_sq + 0.09));

    float C;
    if( UseLookUpTexture )
    {
        // The two dot-products will be in the range of
        // 0.0 to 1.0 which is perfect for a texture lookup:
        float tc = float2
                    (
                        (VdotN + 1.0f) / 2.0f,
                        (LdotN + 1.0f) / 2.0f
                    );
    C = texSinTanLookup.Sample( DefaultSampler, tc ).r;
    }
    else
    {
        float alpha = max( acos( dot( v, n ) ), acos( dot( l, n ) ) );
        float beta  = min( acos( dot( v, n ) ), acos( dot( l, n ) ) );

        C = sin(alpha) * tan(beta);
    }

    float3 final = (A + B * max( 0.0f, gamma ) * C);

    return float4( cDiffuse * max( 0.0f, dot( n, l ) ) * final, 1.0f );
}

 

 

이때 cDiffuse = ( Albedo / PI ) 입니다.

 

잘보면 UseLookUpTexture 부분이 있는데 sin()과 tan() 함수에 여전히 종속적인 부분을

Texture Fetch를 통하여 대체 하는 방식을 사용하는 부분을 말합니다.

 

위와같은 sin()과 tan()함수를 계산한 텍스처를 참조해서 사용합니다

 

아래는 HLSL에서 참조되는 룩업텍스처를 만드는 방법을 나타내고 있습니다.

 

HRESULT CreateSinTanLookupTexture( ID3D10Device* pDevice )
{
    HRESULT hr = S_OK;

    // The incoming dot-product will be between 0.0 and 1.0
    // covering 180 degrees thus a look-up of 512 pixels
    // gives roughly 1/3rd of a degree accuracy which
    // should be enough...
    const UINT LOOKUP_DIMENSION = 512;

    // Describe the texture
    D3D10_TEXTURE2D_DESC texDesc;
    texDesc.ArraySize            = 1;
    texDesc.BindFlags            = D3D10_BIND_SHADER_RESOURCE;
    texDesc.CPUAccessFlags       = 0;
    texDesc.Format               = DXGI_FORMAT_R32_FLOAT;
    texDesc.Height               = LOOKUP_DIMENSION;
    texDesc.Width                = LOOKUP_DIMENSION;
    texDesc.MipLevels            = 1;
    texDesc.MiscFlags            = 0;
    texDesc.SampleDesc.Count     = 1;
    texDesc.SampleDesc.Quality   = 0;
    texDesc.Usage                = D3D10_USAGE_IMMUTABLE;

    // Generate the initial data
    float* fLookup = new float[ LOOKUP_DIMENSION * LOOKUP_DIMENSION ];

    for( UINT x = 0; x < LOOKUP_DIMENSION; ++x )
    {
        for( UINT y = 0; y < LOOKUP_DIMENSION; ++y )
        {
            // This following fragment is a direct conversion of
            // the code that appears in the HLSL shader
            float VdotN = static_cast< float >( x )
                        / static_cast< float >( LOOKUP_DIMENSION );
            float LdotN = static_cast< float >( y )
                        / static_cast< float >( LOOKUP_DIMENSION );

            // Convert the 0.0..1.0 ranges to be -1.0..+1.0
            VdotN *= 2.0f;
            VdotN -= 1.0f;

            LdotN *= 2.0f;
            LdotN -= 1.0f;

            float alpha = max( acosf( VdotN ), acosf( LdotN ) );
            float beta  = min( acosf( VdotN ), acosf( LdotN ) );

            fLookup[ x + y * LOOKUP_DIMENSION ]
                    = sinf( alpha ) * tanf( beta );
        }
    }

    D3D10_SUBRESOURCE_DATA initialData;
    initialData.pSysMem             = fLookup;
    initialData.SysMemPitch         = sizeof(float) * LOOKUP_DIMENSION;
    initialData.SysMemSlicePitch    = 0;

    // Create the actual texture
    hr = pDevice->CreateTexture2D
                        (
                            &texDesc,
                            &initialData,
                            &g_pSinTanTexture
                        );
    if( FAILED( hr ) )
    {
        SAFE_DELETE_ARRAY( fLookup );

        return hr;
    }

    // Create a view onto the texture
    ID3D10ShaderResourceView* pLookupRV = NULL;

    hr = pDevice->CreateShaderResourceView
                                    (
                                        g_pSinTanTexture,
                                        NULL,
                                        &pLookupRV
                                    );
    if( FAILED( hr ) )
    {
        SAFE_RELEASE( pLookupRV );
        SAFE_RELEASE( g_pSinTanTexture );
        SAFE_DELETE_ARRAY( fLookup );

        return hr;
    }

    // Bind it to the effect variable
    ID3D10EffectShaderResourceVariable *pFXVar
    = g_pEffect->GetVariableByName("texSinTanLookup")->AsShaderResource( );
    if( !pFXVar->IsValid() )
    {
        SAFE_RELEASE( pLookupRV );
        SAFE_RELEASE( g_pSinTanTexture );
        SAFE_DELETE_ARRAY( fLookup );

        return hr;
    }

    pFXVar->SetResource( pLookupRV );

    // Clear up any intermediary resources
    SAFE_RELEASE( pLookupRV );
    SAFE_DELETE_ARRAY( fLookup );

    return hr;
}

 

 

일부 작업들 중에는 룩업텍스처 사용이 오히려 보틀넥의 주된 원인이 될수 있으므로

이 부분은 
상황에 맞게 합리적인 생각을 하여 처리를 하는것이 좋습니다

 여기까지 오느라 수고하셨습니다..__)

 

 


구현은 위의 파일을 다운받아 렌더몽키로 돌리면 되시겠구요 ㅇ_ㅇ
 
각 값들을 이리저리 조절해보면서 이런거구나 하시면 되겠습니다 __)

 

구현한 것을 보시면  
 를 0 으로 해도 실제 Lambert 방식과 같은 밝기는 나오지 않는데 이유는 공식을 보면 알 수있습니다.


다시 위에 있던 공식을 보시죠...

 



위의 공식에서  
이면 괄호안이 소거가 되므로 아래와 같은 식이 되며

Lambert Model과 일치하다고 말씀드렸습니다

 




구현에서는 
와 
를 둘다 1 로 잡았으니, 최종결과는 
 에 의해 결정됩니다.

따라서 
를 조절하여 비교하면 대충 비슷한 밝기가 나옵니다. (저는 1.0 정도가 비슷한밝기 였다능...)

( 주의 : 비교를 위한 조절일뿐, 정확하게는
 를 곱하는 것이 맞으며, 필요에 따라 조절하는것은 재량입니다)

  

 

 

Lambert


Oren-Nayer  
= 0

 

Oren-Nayer  
 = 0.5
 


 Oren-Nayer 
= 1.0


블라인드 렌더러님에 블로그에는 디퍼드 렌더링에서도 사용가능한 Oren-Nayer 코드나

텍스처 룩업을 사용하지 않고 근사치를 사용해 최적화 한 Oren-Nayer 코드가 있습니다 __)

 (포프 아저씨에게 떠넘기기 & 적절한 블로그 홍보)
 

디퍼드 렌더링 엔진에서 Oren-Nayer 사용하기

텍스처 룩업을 사용하지 않고 최적화 한 Oren-Nayer
 
----------------------------------------------------------------------------------------------------------------------------------


간단히(?) 알아보았는데요 @_@  틀린게 많을 것 같아 걱정도 많이 됩니다 ㅠ.ㅠ

수식 같은게 기억 나지 않으셔도..


 
              거친 표면으로부터의 diffuse 를 표현하기 위한 reflectance model이다!!! 
 
 
 
라고 정도만 기억하셔도 이 글을 올린 보람은 충분히 있다고 생각합니다 

나머지는 필요할때마다 참조 하셔도 되고 그 외에 많은 자료들이 있을 것입니다
다 같이 열심히 하셔서 고렙들이 되는 그날 까지 화이팅입니다^^
 

반응형