Picking
카메라에 화면을 클릭해 어떤 물체에 대한 반응을 일으키는 것을 Picking이라고 할 수 있는데
화면에 물체가 보여지기 위해선 로컬 좌표계 - 월드 좌표계 - 카메라 좌표계 - 투영 좌표계 - 스크린 좌표계로 변환해야한다.
반대로 카메라의 화면을 클릭해서 클릭된 위치의 물체를 알기 위해선 위의 순서를 반대로 진행하면 된다.
여기서 생각해봐야하는 점이 있다. Picking을 진행하는 건 CPU인가 GPU인가? 당연히 CPU이다.
Picking은 렌더링 파이프라인이랑 전혀 상관이 없다.
그렇다면 어느 좌표계에서 Picking을 진행하면 좋을까?
가령 내가 화면을 선택했을 때, 선택된 좌표는 스크린 좌표계일 거고 이를 역순으로 좌표계를 변환 할 것이다. 그리고 내가 선택하고자 하는 물체는 로컬좌표계에 있을텐데 이를 순서대로 좌표계 변환을 할 것이다. 그런데 문제는 어느 좌표계까지 가서 중간에서 서로 만나야 할까?
정답은 로컬좌표계이다. 즉, 오브젝트는 변환하지 않는게 이득이다. 그 이유는 오브젝트는 수많은 삼각형으로 이루어져있고 Picking을 할 때 선택한 좌표(레이저)는 하나의 점이기 때문이다.
물론 하나의 박스 콜라이더 같은 경우는 월드에서 판별하는게 더 유리할 수 있다.
void PickingDemo::Pick(int32 sx, int32 sy)
{
XMMATRIX P = _camera.Proj();
Matrix m = P;
// Compute picking ray in view space.
float vx = (+2.0f * sx / _clientWidth - 1.0f) / m(0, 0); // P(0, 0);
float vy = (-2.0f * sy / _clientHeight + 1.0f) / m(1, 1); // P(1, 1);
// Ray definition in view space.
XMVECTOR rayOrigin = ::XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
XMVECTOR rayDir = ::XMVectorSet(vx, vy, 1.0f, 0.0f);
// Tranform ray to local space of Mesh.
XMMATRIX V = _camera.View();
XMVECTOR D1 = ::XMMatrixDeterminant(V);
XMMATRIX invView = ::XMMatrixInverse(&D1, V);
XMMATRIX W = ::XMLoadFloat4x4(&_meshWorld);
XMVECTOR D2 = ::XMMatrixDeterminant(W);
XMMATRIX invWorld = ::XMMatrixInverse(&D2, W);
XMMATRIX toLocal = ::XMMatrixMultiply(invView, invWorld);
rayOrigin = ::XMVector3TransformCoord(rayOrigin, toLocal);
rayDir = ::XMVector3TransformNormal(rayDir, toLocal);
// Make the ray direction unit length for the intersection tests.
rayDir = ::XMVector3Normalize(rayDir);
// If we hit the bounding box of the Mesh, then we might have picked a Mesh triangle,
// so do the ray/triangle tests.
//
// If we did not hit the bounding box, then it is impossible that we hit
// the Mesh, so do not waste effort doing ray/triangle tests.
// Assume we have not picked anything yet, so init to -1.
_pickedTriangle = -1;
float tmin = 0.0f;
if (_meshBox.Intersects(rayOrigin, rayDir, tmin))
{
// Find the nearest ray/triangle intersection.
tmin = MathHelper::Infinity;
for (UINT i = 0; i < _meshIndices.size() / 3; ++i)
{
// Indices for this triangle.
UINT i0 = _meshIndices[i * 3 + 0];
UINT i1 = _meshIndices[i * 3 + 1];
UINT i2 = _meshIndices[i * 3 + 2];
// Vertices for this triangle.
XMVECTOR v0 = ::XMLoadFloat3(&_meshVertices[i0].pos);
XMVECTOR v1 = ::XMLoadFloat3(&_meshVertices[i1].pos);
XMVECTOR v2 = ::XMLoadFloat3(&_meshVertices[i2].pos);
// We have to iterate over all the triangles in order to find the nearest intersection.
float t = 0.0f;
if (TriangleTests::Intersects(rayOrigin, rayDir, v0, v1, v2, t))
{
if (t < tmin)
{
// This is the new nearest picked triangle.
tmin = t;
_pickedTriangle = i;
}
}
}
}
}
모든 삼각형을 조회해서 가장 거리가 작은 삼각형을 반환하게 된다.
Normal Mapping
어떤 물체를 아주 정밀하게 표현하기 위해선 어떤 방식이 있을까?
아주 단순하게 물체를 구성하는 정점의 개수를 늘리는 방식이 있다.
이러한 방법은 아주 단순하지만 문제는 부하가 엄청나다는 것이다. 그러면 다른 어떤 방식이 있을까?
노말 매핑은 이러한 문제를 해결하기 위해 도입된 것이다. 어떤 물체에 대해 각 픽셀에 해당되는 영역의 Normal 값을 텍스처의 형태로 저장한 것이다. 즉 각 픽셀마다 파임의 정도(빛에 대한 영향을 주기 위한 Normal값)을 저장하는 것이다.
삼각형을 늘리지 않고도 깊이 값을 표현할 수 있는 방법이다.
오른쪽은 Normal Mapping이 적용된 것이고, 왼쪽은 아니다.
//---------------------------------------------------------------------------------------
// Transforms a normal map sample to world space.
//---------------------------------------------------------------------------------------
float3 NormalSampleToWorldSpace(float3 normalMapSample, float3 unitNormalW, float3 tangentW)
{
// Uncompress each component from [0,1] to [-1,1].
float3 normalT = 2.0f * normalMapSample - 1.0f;
// Build orthonormal basis.
float3 N = unitNormalW;
float3 T = normalize(tangentW - dot(tangentW, N) * N);
float3 B = cross(N, T);
float3x3 TBN = float3x3(T, B, N);
// Transform from tangent space to world space.
float3 bumpedNormalW = mul(normalT, TBN);
return bumpedNormalW;
}
//
// Normal mapping
//
float3 normalMapSample = gNormalMap.Sample(samLinear, pin.Tex).rgb;
float3 bumpedNormalW = NormalSampleToWorldSpace(normalMapSample, pin.NormalW, pin.TangentW);
Displacement Mapping
Disaplacement Mapping은 Normal Mapping 이랑 매우 비슷한데 표면의 요철 즉, 굴곡과 균열을 나타내는 높이값을 넘겨주어 쉐이더에서 처리하는 것이다.
정점을 늘리지 않고 퀄리티를 높이는 작업인 것이다.
이렇게 움푹 파여들어 간 것처럼 표현해 입체감을 주는 것이다.
정점을 늘리지 않게하기 위해 Geometry Shader 단계 나 Tesselation 단계에서 처리해주는 것이 핵심이다.
// The domain shader is called for every vertex created by the tessellator.
// It is like the vertex shader after tessellation.
[domain("tri")]
DomainOut DS(PatchTess patchTess,
float3 bary : SV_DomainLocation,
const OutputPatch<HullOut, 3> tri)
{
DomainOut dout;
// Interpolate patch attributes to generated vertices.
dout.PosW = bary.x * tri[0].PosW + bary.y * tri[1].PosW + bary.z * tri[2].PosW;
dout.NormalW = bary.x * tri[0].NormalW + bary.y * tri[1].NormalW + bary.z * tri[2].NormalW;
dout.TangentW = bary.x * tri[0].TangentW + bary.y * tri[1].TangentW + bary.z * tri[2].TangentW;
dout.Tex = bary.x * tri[0].Tex + bary.y * tri[1].Tex + bary.z * tri[2].Tex;
// Interpolating normal can unnormalize it, so normalize it.
dout.NormalW = normalize(dout.NormalW);
//
// Displacement mapping.
//
// Choose the mipmap level based on distance to the eye; specifically, choose
// the next miplevel every MipInterval units, and clamp the miplevel in [0,6].
const float MipInterval = 20.0f;
float mipLevel = clamp((distance(dout.PosW, gEyePosW) - MipInterval) / MipInterval, 0.0f, 6.0f);
// Sample height map (stored in alpha channel).
float h = gNormalMap.SampleLevel(samLinear, dout.Tex, mipLevel).a;
// Offset vertex along normal.
dout.PosW += (gHeightScale * (h - 1.0)) * dout.NormalW;
// Project to homogeneous clip space.
dout.PosH = mul(float4(dout.PosW, 1.0f), gViewProj);
return dout;
}
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 물방울책 - 주요 내용 (0) | 2024.11.04 |
---|---|
[DirectX] 물방울 책 - Terrain, Particle, Shadow, Ambient Occlusion (0) | 2024.10.31 |
[DirectX] 물방울 책 - Tessellation (3) | 2024.10.28 |
[DirectX] 물방울 책 - Shader (1) | 2024.10.25 |
[DirectX] 물방울 책 - 조명, 텍스처 (0) | 2024.10.24 |