EVE에서는 쉐이더 폭발에 대비해 우버 쉐이더 Uber Shader를 사용하고 있습니다. 우버 쉐이더에 대해서는 위의 링크를 참고 해보시면 됩니다.
쉐이더 폭발이란?
효과가 추가 됨에 따라 쉐이더 코드가 중첩되고 기하급수적으로 코드가 늘어나느 상황. 예로 스키닝이 적용되는 메시와 그렇지 않은 메시를 그릴때, 스키닝 메시용 셰이더, 비스키닝 메시용 쉐이더 코드를 따로 만들어내야 한다. 이런식으로 추가 효과가 늘어나면 늘어날수록 쉐이더 코드가 비대해진다.
효과가 추가 됨에 따라 쉐이더 코드가 중첩되고 기하급수적으로 코드가 늘어나느 상황. 예로 스키닝이 적용되는 메시와 그렇지 않은 메시를 그릴때, 스키닝 메시용 셰이더, 비스키닝 메시용 쉐이더 코드를 따로 만들어내야 한다. 이런식으로 추가 효과가 늘어나면 늘어날수록 쉐이더 코드가 비대해진다.
현재 EVE는 지연 렌더링 Deferred Rendering을 메인으로 하고 있어서 여기에 맞는 쉐이더 코드를 작성해뒀습니다. 포워드 렌더링 Foward Rendering이나 OpenGL을 쓸때는 다른 쉐이더코드를 만들어주어야 겠죠. ( 이건 나중에 하고... )
지연 렌더링에서는 조명 계산을 나중에 하기 때문에 쉐이더 코드를 G버퍼 - 라이트(조명) - 포스트(후처리) 식으로 3가지 형태로 분류했습니다. 그리고 각 부분은 #define 을 통해 특정 효과를 키고, 끌수 있는 형태로 되어있죠.
VS_OUTPUT VS_Gbuffer( VS_INPUT In ) { // 출력 데이터 초기화 VS_OUTPUT Out = (VS_OUTPUT)0; // 정점을 월드좌표로 변환 float4 WorldPos = mul( In.Position, g_World ); #ifdef _SHADER_USE_SKINING // 본 행렬 float4 BonePos = 0.0f; for( int j=0; j<4; ++j ) { BonePos += mul( WorldPos, g_Bone[In.Indices[j]] ) * In.Weight[j]; } // 가중치가 적용된 정점을 투영 Out.Position = mul( BonePos, g_ViewProjection ); #else // 정점을 투영 Out.Position = mul( WorldPos, g_ViewProjection ); #endif // 텍스쳐 좌표 입력 Out.TexCoord = In.TexCoord; // 노멀과 정점 정보를 뷰스페이스로 변환 Out.Normal = mul( In.Normal, g_WorldView ); Out.Position2 = mul( In.Position, g_WorldView ); return Out; }
G버퍼 버텍스 쉐이더의 한 부분입니다. 중간에 _SHADER_USE_SKINING 정의를 통해 스키닝 사용 여부를 정하고 있습니다. 일단 위의 쉐이더 코드를 파싱해놓은 상태에서 앞줄에 #define _SHADER_USE_SKINING을 추가해 이펙트 Effect 파일을 생성하면 스키닝이 적용된 이펙트를 얻을수 있고, 그렇지 않으면 스키닝이 적용되지 않은 이펙트 파일을 얻을 수 있습니다.
// 쉐이더 코드 읽어 오기 bool ev_CGPUProgramManagerDX9::InitializeFromFile( const tstring& strFileName ) { wifstream file( strFileName.c_str( ) ); if( !file.good() ) { file.close(); return false; } wstringstream out; out << file.rdbuf(); m_strShaderSource = out.str(); file.close(); return true; } /* 쉐이더 사용 예 */ // 스키닝을 사용한다면? if( m_dwMask & _SHADER_USE_SKINING ) strShaderDefine += "#define _SHADER_USE_SKINING\r\n"; // 쉐이더 코드 앞 부분에 정의문을 붙여넣는다 strUberShaderCode = strShaderDefine + pShaderCode; // 이펙트 생성 if( FAILED( hr = D3DXCreateEffect( pRenderer->GetDevice(), strUberShaderCode.c_str(), (int)strUberShaderCode.size(), NULL, NULL, dwShaderFlags, NULL, &m_pEffect, &pErr ) ) )) ); return hr;
이렇게 생성된 이펙트는 마스크( DWORD ) 값을 키 값으로 해서 HASH_MAP에 보관합니다. 그리고 해당 이펙트를 필요로 하는 곳에 넘겨주고 사용되죠. 이펙트는 또한 스마트 포인터로 관리되기 때문에 해당 이펙트를 더 이상 사용하는 곳이 없으면 자동 소멸하도록 만들어 두었습니다.
안구 정화로 포스트 마무리