Oren-Nayar Reflectance Lighting Model
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 방식과 대충 비슷한 양상을 띠게 됩니다
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에는 다음과 같은 것들이 있는데요
|
저 부분에 대해서는 다른 훌륭하신 분들이 해주실거라고... 믿습니다...
구현 |
구현에 있어 특이한 사항은 원래 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를 통하여 대체 하는 방식을 사용하는 부분을 말합니다.
아래는 HLSL에서 참조되는 룩업텍스처를 만드는 방법을 나타내고 있습니다.
{
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 코드나
텍스처 룩업을 사용하지 않고 근사치를 사용해 최적화 한 Oren-Nayer 코드가 있습니다 __)
(포프 아저씨에게 떠넘기기 & 적절한 블로그 홍보)
거친 표면으로부터의 diffuse 를 표현하기 위한 reflectance model이다!!!
|
나머지는 필요할때마다 참조 하셔도 되고 그 외에 많은 자료들이 있을 것입니다
다 같이 열심히 하셔서 고렙들이 되는 그날 까지 화이팅입니다^^
참조 링크 :
http://www.t-pot.com/program/80_OrenNayer/index.html
http://content.gpwiki.org/index.php/D3DBook:(Lighting)_Oren-Nayar
http://en.wikipedia.org/wiki/Oren%E2%80%93Nayar_reflectance_model
http://wiki.blender.org/index.php/Doc:2.4/Manual/Materials/Properties/Diffuse_Shaders
http://glog.springnote.com/pages/7398385
http://www.cs.columbia.edu/CAVE/projects/oren/