DirectX 11 3d

230615 LandScape - Splating(색칠)

슬뷔 2023. 6. 26. 00:04

타일이 32 by 32로 만들어서 한 칸에 patch 한 장씩 들어간다. (패턴이 반복됨)

왜냐하면 우리가 사용하는 샘플러가 램모드? 이기 때뭉니다.

램모드?는 UV가 1을 초과하면 넘어가더라도 다시 정수부분은 버리고 소수부분으로 샘플링을 시도한다.

 

카메라가 원점에 있다고 가정하고 거리에 따른 테슬레이션을 적용했다.

카메라 근처에 있는 애들은 16분할 그다음은 8분할 .. 그다음은 4... 이런식으로

 

분할레벨을 카메라로부터의 거리로 설정한다.

거리가 가깝냐 머냐에 따라서 엣지를 몇 분할할건지 정한다.

인사이드는 정점 3개를 더한다음에 3으로 나오면 중간값이 나오니까 그 위치가 어디냐에 따라 거리에 따른 분할을 잡는다.

LandScape.fx

float3 vUpDown = (_input[1].vViewPos + _input[2].vViewPos) / 2.f;
float3 vLeftRight = (_input[0].vViewPos + _input[2].vViewPos) / 2.f;
float3 vSlide = (_input[0].vViewPos + _input[1].vViewPos) / 2.f;
float3 vMid = (_input[0].vViewPos + _input[1].vViewPos + _input[2].vViewPos) / 3.f;

output.Edges[0] = pow(2, (int)GetTessFactor(vUpDown, 1, 4, 500.f, 2000.f));
output.Edges[1] = pow(2, (int)GetTessFactor(vLeftRight, 1, 4, 500.f, 2000.f));
output.Edges[2] = pow(2, (int)GetTessFactor(vSlide, 1, 4, 500.f, 2000.f));
output.Inside   = pow(2, (int)GetTessFactor(vMid, 1, 4, 500.f, 2000.f));

TessFactor에 좌표값을 넣어서 몇 분할할건지 정한다.

pow(2, (int)GetTessFactor(vUpDown, 1, 4, 500.f, 2000.f));

pow의 첫번째인자 2 의 의미 => 2의 승수로 전환 (1단계면 2^1 니까 2분할, 2단계면 2^2 니까 4분할)

GetTessFactor 인자의 의미 => 500.f ~ 2000.f 를 분할 단계 1레벨 ~ 4레벨까지 나누겠다는 의미(몇 단계로 나눌 것인지)

 

카메라에 가까울수록 테슬레이션을 높여서 디테일을 높인다.

 

func.fx

float GetTessFactor(float3 _vPos, int _iMinLevel, int _iMaxLevel, float _MinDistance, float _MaxDistance)
{
    float fDistance = abs(length(_vPos));

    if (_MaxDistance < fDistance)
    {
        return 0.f;
    }     
    else if (fDistance < _MinDistance)
    {
        return _iMaxLevel;
    }
    else
    {
        float fLevel = _iMaxLevel - (_iMaxLevel - _iMinLevel) * ((fDistance - _MinDistance) / (_MaxDistance - _MinDistance));

        return fLevel;
    }
}

뷰스페이스는 원점에 카메라가 있다. 

즉, 거리 값(fDistance)은 카메라로부터의 거리가 된다. 

 

그런데 뷰스페이스를 기준으로 했으면 카메라가 보는 기준으로 실시간으로 값이 계속 바뀌어야한다.

그런데 지금은 월드에 있는 원점을 기준으로 분할을 나누었다.

why?

원래라면 뷰스페이스로 계산을 해야하는데, LandScape 정점쉐이더에서 worldpos를 줬다.

카메라가 이동하면서 실시간으로 바뀌니까 보기가 힘들어서 일부러 월드를 넘긴 것이다.

가정은 뷰스페이스라고 계산을 했지만, 실제로는 월드포즈를 넘겼기 때문에 

장판들이 현재 월드기준으로 원점으로부터 거리 기준으로 분할 레벨이 나뉜 것이다.

 

(수업 중에 월드포즈 -> 뷰포즈로 바꿔서 이제 진짜 카메라가 움직일 때마다 실시간으로 바뀜)

 


Tess Factor를 수정해야하는 이유는 Camera의 위치에 상관없이 지형의 Vertex가 많이 쪼개지면, 퍼포먼스의 하락도 있고, 정작 정점은 많이 쪼개져 있어 디테일 하게 표현이 되지만, 원근법에 의해 멀리 있는 지역은 적은 Pixel에 표현되어, 정보를 다 담아 낼 수 없기 때문이다.
따라서, 카메라에서 가까이에 있는 지형은 정점을 많이 쪼개고, 카메라에서 먼 지형은 정점을 적게 차등적으로 쪼갤 것이다.
여기서 주의해야하는 점은 인접한 Trianlge에서 같은 변을 공유하는 변이 서로 다르게 정점을 쪼개버리면, 높이 값을 참조할때 들림 현상이 발생할 수 있다. 따라서, 같은 변에 대해서는 같은 수치의 쪼갬을 적용할 것이다.
이는 거리를 기준으로, 정점을 쪼갤 것인지 말 것인지 결정하기에 해결할 수 있는 문제이다.

 

Weight Shader (가중치 쉐이더)

Splating(색칠) 모드일때는 가중치 쉐이더를 사용한다 (높이맵말고)

Weight Map은 현재 1024 * 1024 사이즈의 구조화 버퍼로 이루어저 있다.

가중치 버퍼에 있는 값을 읽어 텍스쳐를 그린다.

가중치 버퍼에는 픽셀 한 칸에 arrWeight[4] float 4개짜리 배열이 하나 들어가있다.

(배열로 4개의 텍스쳐가 넘어온다)

float한칸 한칸이 의미하는 것은 어느정도의 가중치를 가질 것인지를 의미한다.

4칸의 가중치의 합은 1을 넘길 수 없다.

어떤 텍스쳐에 얼만큼 더 힘을 줄 것인지 정한다. (가중치는 색상을 뽑을 Texture의 가중치를 의미한다.)

즉, Tex1, Tex2, Tex3, Tex4가 float[0], float[1], float[2], float[3] 과 연동이 되어 있으며, 각각의 가중치만큼 색상을 더해서 출력해주는 방식이다.

 

WeightMap은 사용자가 직접 마우스 클릭 및 드래그를 이용하여 색칠할 수 있다.

LandScape.cpp -> finaltick()

else if (LANDSCAPE_MOD::SPLAT == m_eMod)
{
	// 교점 위치정보를 가중치를 수정함	
	m_pCSWeightMap->SetInputBuffer(m_pCrossBuffer); // 레이 캐스트 위치
	m_pCSWeightMap->SetBrushArrTex(m_pBrushTex);
	m_pCSWeightMap->SetBrushIndex(0);
	m_pCSWeightMap->SetBrushScale(m_vBrushScale); // 브러쉬 크기
	m_pCSWeightMap->SetWeightMap(m_pWeightMapBuffer, m_iWeightWidth, m_iWeightHeight); // 가중치맵, 가로 세로 개수			
	m_pCSWeightMap->SetWeightIdx(m_iWeightIdx);
	m_pCSWeightMap->Execute();
}

위의 Compute Shader가 동작함에 따라, Weight Map 구조화 버퍼에 값들이 채워지게 되며, 이는 LandScape가 rendering될시 PixelShader에서 색상 참고의 지표가 된다.

LandScape.fx

for (int i = 0; i < 4; ++i)
{
     vColor += TileTexArr.SampleGrad(g_sam_0, float3(_in.vUV, i), derivX, derivY) * vWeight[i];
     //vColor += TileTexArr.SampleLevel(g_sam_0, float3(_in.vUV, i), 7) * vWeight[i];

     if (fMaxWeight < vWeight[i])
     {
          fMaxWeight = vWeight[i];
          iMaxWeightIdx = i;
     }
}
output.vColor = float4(vColor.rgb, 1.f);

// 타일 노말
if (-1 != iMaxWeightIdx)
{
     float3 vTangentSpaceNormal = TileTexArr.SampleGrad(g_sam_0, float3(_in.vUV, iMaxWeightIdx + TileCount), derivX, derivY).xyz;
     //float3 vTangentSpaceNormal = TileTexArr.SampleLevel(g_sam_0, float3(_in.vUV, iMaxWeightIdx + TileCount), 7).xyz;
     vTangentSpaceNormal = vTangentSpaceNormal * 2.f - 1.f;

     float3x3 matTBN = { _in.vViewTangent, _in.vViewBinormal, _in.vViewNormal };
     vViewNormal = normalize(mul(vTangentSpaceNormal, matTBN));
}

원래 float2를 사용하는데 float3(_in.vUV, i) 인 이유는 인덱스를 고르는 것인데, 몇 번째 텍스쳐에서 샘플링할 것인지  알아야되기 때문.

 

노말은 제일 높은걸 사용하고 있다.

엄밀히 말하면 배열 텍스쳐를 6장을 사용하고 있는데, 그 이유는 3장은 텍스쳐이고 나머지 3장은 그 텍스쳐에 대응하는 노말텍스쳐이다.

 

그래서 가중치버퍼에서 arr 를 4칸 만들었지만, 3개를 이용해서 3개의 텍스쳐에서 색상을 만들어낸다.

노말맵은 어떤것을 사용하냐면 가중치가 3개의 텍스쳐 중 가장 높은 것을 사용한다.

색은 가중치에 따라 섞이지만, 노말은 가장 큰 것을 사용한다.

 

가중치버퍼는 구조화버퍼로 만들었다.

가중치를 텍스쳐형태로 만들었다면 지형을 색칠할 때 타일의 종류를 4개밖에 만들지 못한다.

왜냐면 텍스쳐 포맷이 rgba가 끝이기 때문이다.

 

만약 지형을 색칠할 때 배열 텍스쳐에 색상텍스쳐만 6개를 넣어서 이를 조합해서 색상을 색칠 할 수 있고, (노말도 당연히 쌍으로 6개 더 생김.. 총 12개) 더 많이 쓸 수도 있는데 픽셀 하나에 가중치가 6개가 들어갈 수가 없다.. 그런 픽셀 포멧이 없기 때문이다.

 

구조화버퍼로 만들면 한 칸의 단위를 내 마음대로 커스텀할 수 있기 때문에 가중치버퍼를 구조화버퍼로 만들었다.

 

'DirectX 11 3d' 카테고리의 다른 글

230621 Directional Light Shadow  (0) 2023.07.13
230619 Mapmapping  (0) 2023.06.26
230609 LandScape Heightmap  (0) 2023.06.25
230607 LandScape 포워드 -> 디퍼드 전환  (0) 2023.06.14
230602 지형 만들기, Height Map (다시)  (0) 2023.06.04