water reflection 수면 반사 방법! DirectX

2011/12/15 10:12

복사 http://blog.naver.com/doridori3510/30126259858


수면 반사에 대해 shaderx2 라는 책을보고 했지만 !

쉐이더 코드는 어셈으로 되어있었고ㅜㅜ 하다못해 소스는 컴파일이 안되고ㅜㅜ

삽질하는 경우가 많아서. 정리해본다.

처리 프로세스를 간략하게 정리하자면..

1. 현재 화면을 렌더링 한다.

2. 현재 카메라의 view 매트릭스를 미러링 한다.

3. 미러링된 view로 텍스쳐에 현재 화면을 그린다. ( 단, 수면을 경계로 cliping 한 영역을 그린다 )

4. 그려진 텍스쳐를 쉐이더 코드를 통해서 렌더링한다.

TIP. 카메라를 미러링 하기

그림판으로 대충 그린 그림으로 보고 이해를 하자면.

원본 카메라의 view를 플랜을 기준으로 미러링을 시킨다.

책에서는 자세하게 설명이 되어있는데.

어쨋든 현재 view를 미러링 시키기 위한 matrix를 만들어낼수 있다.

// 반사 매트릭스

D3DXMATRIX mtxReflect;
D3DXMatrixIdentity( &mtxReflect );
mtxReflect._22 = -1.0f;
mtxReflect._42 = 2.0f * WATER_HEIGHT;

현재 카메라의 view를 반사 매트릭스와 곱하면, 미러링된 카메라의 view를 얻을수 있다.

이 view를 셋팅을 하고 장면을 렌더링하면 반사된 장면을 얻을수 있다.

참고사항 :

1. 물을 기준으로 클리핑을 해서 미러 view로 렌더링 해야한다.

클리핑 평면을 셋팅하는데 쉐이더 코드로 메쉬를 그린다면! 평면을 trasform 해주어야 한다.

// 이런식으로 평면을 한번더 Transform 해준후에 디바이스에 셋팅하자.

D3DXMATRIX mtxView = *(pCamera->GetViewMatrix());
D3DXMATRIX mtxProj = *(pCamera->GetProjMatrix());

D3DXMATRIXA16 WorldToProjection = mtxView * mtxProj;

D3DXMatrixInverse(&WorldToProjection, NULL, &WorldToProjection);
D3DXMatrixTranspose(&WorldToProjection, &WorldToProjection);

D3DXPlaneTransform( &m_clip_plane, &m_local_plane, &WorldToProjection );
D3DXPlaneNormalize( &m_clip_plane, &m_clip_plane );

2. 평면을 기준으로 클리핑 할때, 약간 더 실제 평면 아래쪽으로 클리핑해야지 수면과의 경계에서 약간씩 틈이 보이는것을 방지할 수있다. 일명 약간의 꼼수!

< 왼쪽 : 실제 현재 view > < 오른쪽 : 미러 view로 직은 모습 >

TIP. 반사된 장면을 평면에 투영하기

반사된 장면을 찍었는데, 그냥 평면에다가 SetTexture로 그리면 아마 뭔가 좌표값이 안맞아서 이상하게 그려질 것이다. 이 문제로 한 3일간 삽질한것 같은데..

결론은. 평면을 렌더링 할때 텍스쳐의 uv 좌표계를 계산해주어야 한다.

즉, ( 투영 공식 = 미러뷰 * 프로젝션 ) 에서 정점위치를 계산한후에 이를 픽셀 세이더에 넘기고

픽셀에서는 투영된 정점 위치를 기준으로 다시 uv 좌표계를 계산한다.

# 정점 쉐이더

// UV 좌표 계산
o.ReflectPos = mul( float4( Pos, 1.0f ) , g_mWorld );
o.ReflectPos = mul( o.ReflectPos , g_Transfo ); // g_Transfo는 위에 있는 투영공식임.

# 픽셀 쉐이더

// UV 좌표 계산

float2 ProjectedTexCoords;
ProjectedTexCoords.x = input.ReflectPos.x / input.ReflectPos.w / 2.0f + 0.5f;
ProjectedTexCoords.y = -input.ReflectPos.y / input.ReflectPos.w / 2.0f + 0.5f;

후에 계산된 좌표값으로 샘플링 하면 된다.

참고사항 :

// 펙셀에서 저렇게 하는 이유 ( GPG 에있는 june8036님의 답변 )

x/w, y/w가 x/뷰변환된z y/뷰변환된z 라고 생각하면 될꺼 같네요.
D3DMatrixPerspectiveFovLH 이런 프로젝션 함수를 직접 구현해보시면 더 느낌이 오실거 같은데요.
결국 마지막 w값은 뷰변환되어져 나온 z값과 같이 되지요.
그래서 이 뷰변환된 z값을 x, y에 나누어 주면, 멀리 있는 물체는 화면의 정 중간에서 더 가깝게
만들어지고, 가까운 물체는 화면의 정 중간에서 더 멀게 나오는걸로 알고 있습니다.






http://blog.naver.com/doridori3510/30127128770


+ 수면 아래 장면 만들기

수면 위의 반사장면 만드는것과 동일하게 물을 기준으로 아래 장면을 텍스쳐로 만든후.

물 평면에 투영한다 !

좀더 좋은 효과를 만들기위해서 물과 지형과의 거리에 비례해서 알파값을 계산한다 !

TIP. 수면 아래 지형을 렌더링할때 픽셀 쉐이더 코드

// 수면과 지형의 거리 계산
float d = fWaterHeight - ((input.WorldPos.y) * test_Y );

// 색상공식
float4 r0;
r0.a = mul( test_A, d );
r0.a = saturate( r0.a );
r0.a = sqrt( r0.a );
r0.a = saturate( r0.a );

r0.r = saturate( exp2( -d * test_R ) );
r0.g = saturate( exp2( -d * test_G ) );
r0.b = saturate( exp2( -d * test_B ) );

+ 프레넬 공식에 비례해서 수면 위 장면, 수면 아래 장면을 섞어서 그릴지 계산.

TIP. 물 픽셀 쉐이더 코드에서 프레넬 계산

// 프레넬로 혼합
float fresnelTerm = (float)0;
fresnelTerm = 0.02+0.97f*pow((1-dot(eyeVector, normalVector)),5);
fresnelTerm = fresnelTerm * fresnelForce * DwColor.a; // 프레넬도 알파 적용
fresnelTerm = saturate( fresnelTerm );

// 프레넬로 반사,굴절 색상 결정
float4 mixColor = ( UpColor * fresnelTerm ) + ( DwColor * (1-fresnelTerm) );

+ 범프 텍스쳐를 기준으로, 수면 위, 아래 장면의 UV를 흔든다.

TIP. 물 픽셀 쉐이더 코드에서 범프 UV계산

// 범프 계산
float4 bumpColor1 = tex2D( bump_sampler, input.BumpPos1 );
float4 bumpColor2 = tex2D( bump_sampler, input.BumpPos2 );
float4 bumpColor = (bumpColor1 * fBumpRate ) + ( bumpColor2 * (1-fBumpRate) );

// 수면 아래
float2 DwBump = fDwBumpForce * (bumpColor.rg - 0.5f);
float4 DwPos = input.DwPos;
DwPos.xy += DwBump;
float4 DwColor = tex2Dproj( Dw_sampler, DwPos );

// 수면위
float2 UpBump = fUpBumpForce * (bumpColor.rg - 0.5f);
float4 UpPos = input.UpPos;
UpPos.xy += UpBump;
float4 UpColor = tex2Dproj( Up_sampler, UpPos );

+ 범프 텍스쳐를 노멀벡터로 생각해서 각각의 조명을 계산한다.

범프 텍스쳐를 기준으로 노멀 벡터를 구하는데, 노멀벡터의 Y값을 세우기 위해서 Z값을 Y값으로 생각해서 채워 넣는다.

이 방법이 통하는것은.

물이 평면이고 접선공간으로 변환해서 하는 결과값 ( bump.rgb * 조명방향[접선공간] ) = (bump.rbg)

이 성립하기 때문이다.

정확하게 말하자면, bump에서 읽어온 노멀값과 접선공간으로 변환된 조명과 연산해야한다.

// 조명의 방향을 접선공간으로 변형.
o.LightDir.x = dot(worldLightDirection, T);
o.LightDir.y = dot(worldLightDirection, B);
o.LightDir.z = dot(worldLightDirection, N);

// ViewVec 역시 접선공간으로 변형.

float3 viewVec = normalize(vWorldCameraPos - worldPos);
o.ViewVec.x = dot(viewVec, T);
o.ViewVec.y = dot(viewVec, B);
o.ViewVec.z = dot(viewVec, N);

ㅇㅇㅁㅅㅄ
TIP. 물 픽셀 쉐이더에서 조명 계산

// 범프에 따른 노멀벡터 구하기
float4 nomalBump = bumpColor * 2.0f - 1.0f; // 텍스쳐 좌표값을 노멀맵으로 변환
float4 bumpNormalVector = float4( nomalBump.r, nomalBump.b, nomalBump.g, nomalBump.a );
float4 normalVector = normalize( bumpNormalVector );

// 조명 계산
float4 Color = CalcAmbient() + CalcDiffuse( normalVector );

// 스펙큘라
float4 half = normalize( -LightDirection + eyeVector );
float4 speccolor = CalcSpecular( normalVector * fNomalPower, half, fSpecPower );

+ 최종적인 물의 칼라값 결정

원래 물 고유의 색상과 혼합한다.

TIP. 물 픽셀 쉐이더의 최종 output 색상

// 물 색상
float4 waterColor = float4(water_R, water_G, water_B, water_A );

float4 outputColor = ( ( (waterColor * rate) + ( mixColor * (1 - rate ) ) ) * Color ) + speccolor;
outputColor.a = DwColor.a; // 물깊이에 따른 알파값 적용

< 최종적인 스샷 >

세부적인 수치같은거 조절해야하는데... 귀찮다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

암튼 색상이랑 수치 비율이랑 조절하면 더 예뻐질것임. 성형수술같네;ㅁ;

비행기는 계속해서 움직이고 수면에 실시간으로 반사함 !

< 수치 조절 좀 했더니 >엉? 사진올려놓고 보니 비슷비슷한가?ㅋㅋ 실제로 보면 아닌데 ㅜ.ㅜ

[출처] water shader ! 물효과!|작성자 달혜

반응형

+ Recent posts