: Tessellation을 이용한 지형 컴포넌트
#pragma once
#include "CRenderComponent.h"
class CLandScape :
public CRenderComponent
{
private:
UINT m_FaceX;
UINT m_FaceZ;
Ptr<CTexture> m_HeightMapTex;
public:
void SetHeightMap(Ptr<CTexture> _HeightMap) { m_HeightMapTex = _HeightMap; }
private:
void Init();
void CreateMesh();
public:
virtual void finaltick() override;
virtual void render() override;
virtual void UpdateData() override;
public:
CLONE(CLandScape);
CLandScape();
~CLandScape();
};
#include "pch.h"
#include "CLandScape.h"
#include "CAssetMgr.h"
void CLandScape::Init()
{
CreateMesh();
}
void CLandScape::CreateMesh()
{
// 지형 전용 메쉬 생성 및 적용
vector<Vtx> vecVtx;
Vtx v;
// 정점 배치
for (UINT i = 0; i < m_FaceZ + 1; ++i)
{
for (UINT j = 0; j < m_FaceX + 1; ++j)
{
v.vPos = Vec3(j, 0.f, i);
v.vTangent = Vec3(1.f, 0.f, 0.f);
v.vNormal = Vec3(0.f, 1.f, 0.f);
v.vBinormal = Vec3(0.f, 0.f, -1.f);
v.vColor = Vec4(1.f, 1.f, 1.f, 1.f);
v.vUV = Vec2(j, m_FaceZ - i);
vecVtx.push_back(v);
}
}
// 인덱스 지정
vector<UINT> vecIdx;
for (UINT i = 0; i < m_FaceZ; ++i)
{
for (UINT j = 0; j < m_FaceX; ++j)
{
// 0
// | \
// 2--1
vecIdx.push_back( (m_FaceX + 1) * (i+1) + j);
vecIdx.push_back( (m_FaceX + 1) * i + (j + 1));
vecIdx.push_back( (m_FaceX + 1) * i + j);
// 1--2
// \ |
// 0
vecIdx.push_back((m_FaceX + 1) * i + (j + 1));
vecIdx.push_back((m_FaceX + 1) * (i + 1) + j);
vecIdx.push_back((m_FaceX + 1) * (i + 1) + j + 1);
}
}
Ptr<CMesh> pMesh = new CMesh;
pMesh->Create(vecVtx.data(), vecVtx.size(), vecIdx.data(), vecIdx.size());
SetMesh(pMesh);
// 지형 전용 재질 적용
SetMaterial(CAssetMgr::GetInst()->FindAsset<CMaterial>(L"LandScapeMtrl"));
}
Init에서는 면의 개수를 기준으로 정점을 직접 만들어 매쉬를 생성한다.
#include "pch.h"
#include "CLandScape.h"
#include "CTransform.h"
CLandScape::CLandScape()
: CRenderComponent(COMPONENT_TYPE::LANDSCAPE)
, m_FaceX(64)
, m_FaceZ(64)
{
Init();
}
CLandScape::~CLandScape()
{
}
void CLandScape::finaltick()
{
}
void CLandScape::render()
{
UpdateData();
GetMesh()->render();
}
void CLandScape::UpdateData()
{
Transform()->UpdateData();
GetMaterial()->SetScalarParam(SCALAR_PARAM::INT_0, m_FaceX);
GetMaterial()->SetScalarParam(SCALAR_PARAM::INT_1, m_FaceZ);
GetMaterial()->SetTexParam(TEX_PARAM::TEX_0, m_HeightMapTex);
GetMaterial()->UpdateData();
}
#ifndef _LANDSCAPE
#define _LANDSCAPE
#include "value.fx"
// =======================================
// LandScape Shader
// MRT : Deferred
// RS_TYPE : CULL_BACK
// DS_TYPE : LESS
// BS_TYPE : DEFAULT
// Parameter
// g_int_0 : Face X
// g_int_1 : Face Z
// g_tex_0 : HeightMap Texture
// =======================================
struct VS_IN
{
float3 vPos : POSITION;
float2 vUV : TEXCOORD;
float3 vTangent : TANGENT;
float3 vBinormal : BINORMAL;
float3 vNormal : NORMAL;
};
struct VS_OUT
{
float3 vPos : POSITION;
float2 vUV : TEXCOORD;
float3 vTangent : TANGENT;
float3 vBinormal : BINORMAL;
float3 vNormal : NORMAL;
};
VS_OUT VS_LandScape(VS_IN _in)
{
VS_OUT output = (VS_OUT) 0.f;
output.vPos = _in.vPos;
output.vUV = _in.vUV;
output.vTangent = _in.vTangent;
output.vBinormal = _in.vBinormal;
output.vNormal = _in.vNormal;
return output;
}
// Hull Shader
struct PatchLevel
{
float arrEdge[3] : SV_TessFactor;
float Inside : SV_InsideTessFactor;
};
PatchLevel PatchConstFunc(InputPatch<VS_OUT, 3> _in, uint patchID : SV_PrimitiveID)
{
PatchLevel output = (PatchLevel) 0.f;
output.arrEdge[0] = g_vec4_0.x;
output.arrEdge[1] = g_vec4_0.y;
output.arrEdge[2] = g_vec4_0.z;
output.Inside = g_vec4_0.w;
for (int i = 0; i < 3; ++i)
{
if (output.arrEdge[i] == 0.f)
output.arrEdge[i] = 1.f;
}
if (output.Inside == 0.f)
{
output.Inside = 1.f;
}
return output;
}
struct HS_OUT
{
float3 vPos : POSITION;
float2 vUV : TEXCOORD;
float3 vTangent : TANGENT;
float3 vBinormal : BINORMAL;
float3 vNormal : NORMAL;
};
[patchconstantfunc("PatchConstFunc")]
[outputtopology("triangle_cw")]
[domain("tri")]
[maxtessfactor(64)]
[partitioning("integer")] //[partitioning("fractional_odd")]
[outputcontrolpoints(3)]
HS_OUT HS_LandScape(InputPatch<VS_OUT, 3> _in, uint _idx : SV_OutputControlPointID)
{
HS_OUT output = (HS_OUT) 0.f;
output.vPos = _in[_idx].vPos;
output.vUV = _in[_idx].vUV;
output.vTangent = _in[_idx].vTangent;
output.vBinormal = _in[_idx].vBinormal;
output.vNormal = _in[_idx].vNormal;
return output;
}
struct DS_OUT
{
float4 vPosition : SV_Position;
float2 vUV : TEXCOORD;
float3 vViewPos : POSITION;
float3 vViewNormal : NORMAL;
};
[domain("tri")]
DS_OUT DS_LandScape(PatchLevel _pathlevel // 각 제어점 별 분할 레벨
, const OutputPatch<HS_OUT, 3> _Origin // 패치 원본 정점
, float3 _Weight : SV_DomainLocation) // 각 원본 정점에 대한 가중치)
{
DS_OUT output = (DS_OUT) 0.f;
float3 vLocalPos = (float3) 0.f;
float2 vUV = (float2) 0.f;
float3 vTangent = (float3) 0.f;
float3 vBinormal = (float3) 0.f;
float3 vNormal = (float3) 0.f;
for (int i = 0; i < 3; ++i)
{
vLocalPos += _Origin[i].vPos * _Weight[i];
vUV += _Origin[i].vUV * _Weight[i];
vTangent += _Origin[i].vTangent * _Weight[i];
vBinormal += _Origin[i].vBinormal * _Weight[i];
vNormal += _Origin[i].vNormal * _Weight[i];
}
// 높이맵 텍스쳐가 있을 때
if (g_btex_0)
{
float2 FullUV = vUV / float2(g_int_0, g_int_1);
vLocalPos.y = g_tex_0.SampleLevel(g_sam_0, FullUV, 0).x;
// 주변 정점(위, 아래, 좌, 우) 로 접근할때의 로컬스페이스상에서의 간격
float LocalStep = 1.f / _pathlevel.Inside;
// 주변 정점(위, 아래, 좌, 우) 의 높이를 높이맵에서 가져올때 중심UV 에서 주변UV 로 접근할때의 UV 변화량
float2 vUVStep = LocalStep / float2(g_int_0, g_int_1);
// 위
float3 vUp = float3( vLocalPos.x
, g_tex_0.SampleLevel(g_sam_0, float2(FullUV.x, FullUV.y - vUVStep.y), 0).x
, vLocalPos.z + LocalStep);
// 아래
float3 vDown = float3(vLocalPos.x
, g_tex_0.SampleLevel(g_sam_0, float2(FullUV.x, FullUV.y + vUVStep.y), 0).x
, vLocalPos.z - LocalStep);
// 좌
float3 vLeft = float3(vLocalPos.x - LocalStep
, g_tex_0.SampleLevel(g_sam_0, float2(FullUV.x - vUVStep.x, FullUV.y), 0).x
, vLocalPos.z);
// 우
float3 vRight = float3(vLocalPos.x + LocalStep
, g_tex_0.SampleLevel(g_sam_0, float2(FullUV.x + vUVStep.x, FullUV.y), 0).x
, vLocalPos.z);
vTangent = mul(float4(vRight, 1.f), g_matWorld).xyz - mul(float4(vLeft, 1.f), g_matWorld).xyz;
vBinormal = mul(float4(vDown, 1.f), g_matWorld).xyz - mul(float4(vUp, 1.f), g_matWorld).xyz;
vNormal = normalize(cross(vTangent, vBinormal));
output.vViewNormal = normalize(mul(float4(vNormal, 0.f), g_matView).xyz);
}
else
{
output.vViewNormal = normalize(mul(float4(vNormal, 0.f), g_matWV).xyz);
}
output.vPosition = mul(float4(vLocalPos, 1.f), g_matWVP);
output.vUV = vUV;
output.vViewPos = mul(float4(vLocalPos, 1.f), g_matWV).xyz;
return output;
}
struct PS_OUT
{
float4 vColor : SV_Target0;
float4 vPosition : SV_Target1;
float4 vNormal : SV_Target2;
float4 vEmissive : SV_Target3;
};
PS_OUT PS_LandScape(DS_OUT _in) : SV_Target
{
PS_OUT output = (PS_OUT) 0.f;
output.vColor = float4(0.4f, 0.4f, 0.4f, 1.f);
output.vPosition = float4(_in.vViewPos, 1.f);
output.vNormal = float4(_in.vViewNormal, 1.f);
output.vEmissive = float4(0.f, 0.f, 0.f, 0.f);
return output;
}
#endif
: 카메라부터의 거리를 기준으로 테셀레이션의 단계를 낮추거나 높여 성능을 향상시키는 기술

: 미리 여러단계의 매쉬를 만들어 놓고 카메라와의 거리에 따라서 만들어뒀던 매쉬중 알맞은 매쉬를 사용하는 방법
: 카메라와의 거리를 계산하여 동적으로 메쉬의 정밀도를 바꿔주는 방법

인접한 패치 간의 분할 단계의 차이가 있을 경우 서로의 메쉬가 맞물리지 않으면서 지형이 일부 들리는 현상이 생길수 있다.
Tessellation 단계에서 LOD의 기준을 int로 하지 않고 float으로 하여 거리에 비례하여 메쉬를 분할한다면 인접하는 지형간의 차이가 없기 때문에 들림현상이 해결된다.
: LOD에 따라 해상도가 다른 텍스쳐를 세팅하는 방법


Mipmap을 사용하지 않는다면, 뷰포트에서는 아주 작은 영역에 해당하는 곳도 같은 수준의 텍스쳐를 사용하여 샘플링하게 된다. 이 경우 UV 값이 튀는 현상이 발생해 멀리있는 지형은 노이즈가 생기게 된다.
Mipmap을 사용하게 되면 더 많은 텍스쳐를 저장하니 더 많은 메모리를 써야한다고 생각할 수 있다. 하지만 mipmap으로 만들어지는 텍스쳐는 원본 텍스쳐보다 훨씬 작기 때문에 모든 계층을 합친다고 해도 전체 메모리 사용량은 원본 텍스쳐의 크기의 약 1.33배에 불과하다.
반면에 항상 커다란 텍스쳐를 사용하는것이 아니라, 상황에 맞는 텍스쳐를 사용하기 때문에 GPU의 텍스쳐 캐시를 더 효율적으로 활용할 수 있고, 텍스쳐 로딩시에도 더 빠르다.
결과적으로 Mipmap을 사용하면 텍스처의 전체 메모리 사용량이 약간 증가하지만, 텍스쳐 샘플링 및 핑터링 작업에서 메모리 대역폭과 캐시 효율성을 크게 개선할 수 있고, 이는 렌더링 선응을 향상시키고, 결과적으로 더 나은 프레임 속도와 시각적 품질을 제공할 수 있게 해준다.