[GTL] W02 - Texture, Batch Render

brights2ella·2025년 4월 5일
0

정글게임테크랩1기

목록 보기
4/14
post-thumbnail

Picking

2차원 스크린의 클릭 위치를 어떻게 3차원 공간으로 변환시킬 것인가?

Ray

NDC의 near, far 평면에 수직으로 닿는 선을 두어 Projection 변환, View 변환의 역을 곱하면, 마우스가 클릭한 지점에서 시작하는 반직선이 월드 좌표계에 생긴다.

이때 두 위치 벡터를 잡아서 계산하는데, Projection 변환 자체가 방향 벡터를 넣는 용도가 아니기 때문에 방향 벡터를 넣으면 값이 날아간다. (역변환이면 0이 된다)

FRay Geometry::CreateRayWithMouse(float NDCMouseX, float NDCMouseY) {
	FWindowInfo WInfo = UEngine::GetEngine().GetWindowInfo();

	float NDCX = NDCMouseX;
	float NDCY = NDCMouseY;

	FVector4 RayOrigin = FVector4(NDCX, NDCY, 0.0f, 1.0f);
	FVector4 RayEnd = FVector4(NDCX, NDCY, 1.0f, 1.0f);

	// Projection 공간으로 변환
	FMatrix InvProjMat = UEngine::GetEngine().GetWorld()->GetProjectionMatrix().Inverse();

	RayOrigin = InvProjMat.TransformVector4(RayOrigin);
	RayOrigin /= RayOrigin.W;
    
	RayEnd = InvProjMat.TransformVector4(RayEnd);
	RayEnd /= RayEnd.W;

	// View 공간으로 변환
	FMatrix InvViewMat = UEngine::GetEngine().GetWorld()->GetViewMatrix().Inverse();
	RayOrigin = InvViewMat.TransformVector4(RayOrigin);
	RayEnd = InvViewMat.TransformVector4(RayEnd);

	FVector RayDir = (RayEnd - RayOrigin).GetSafeNormal();

	return FRay(RayOrigin, RayDir);
}

Ray vs AABB

bool Geometry::IsRayIntersectAABB(FBoundingBox aabb, FRay ray, float maxDistance = 100.f) {

	// reference: https://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms
    FVector rayDir = ray.Direction.GetSafeNormal();
	FVector dirfrac(1 / rayDir.X, 1 / rayDir.Y, 1 / rayDir.Z);

	float t1 = (aabb.min.X - ray.Origin.X) * dirfrac.X;
	float t2 = (aabb.max.X - ray.Origin.X) * dirfrac.X;
	float t3 = (aabb.min.Y - ray.Origin.Y) * dirfrac.Y;
	float t4 = (aabb.max.Y - ray.Origin.Y) * dirfrac.Y;
	float t5 = (aabb.min.Z - ray.Origin.Z) * dirfrac.Z;
	float t6 = (aabb.max.Z - ray.Origin.Z) * dirfrac.Z;

	float tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
	float tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6));

	// if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us
	if ( tmax < 0 ) {
		return false;
	}

	// if tmin > tmax, ray doesn't intersect AABB
	if ( tmin > tmax ) {
		return false;
	}

	return true;
}

참고 : stackexchange

Ray vs OBB

AABB에서 회전을 포함한 여려 변환이 들어간 OBB는 역변환을 취해 다시 AABB에 대한 문제로 접근할 수 있다.

bool UCubeComponent::IsRayIntersect(FRay ray, float hitDistance, FVector& hitPoint) const {
	// OBB (Transformed ray with AABB)
	FMatrix transform = GetWorldMatrix().Inverse();
	FRay transformedRay = FRay(transform.TransformPositionVector(ray.Origin), transform.TransformDirectionVector(ray.Direction).GetSafeNormal());
	FBoundingBox AABBorigin = FBoundingBox(FVector(-0.5, -0.5, -0.5), FVector(0.5, 0.5, 0.5));

	bool result = Geometry::IsRayIntersectAABB(AABBorigin, transformedRay, hitDistance);
	if (result) {
		hitPoint = GetComponentLocation();
	}
	
	return result;
}

Ray vs Triangle

Möller–Trumbore 알고리즘이라고 한다.

bool Geometry::IsRayIntersectWithTriangle(const FRay& ray, const FVector& v0, const FVector& v1, const FVector& v2, float hitDistance, FVector& hitpoint) {

    hitpoint = FVector::Zero();

    const float epsilon = 1e-6f;
    FVector edge1 = v1 - v0;
    FVector edge2 = v2 - v0;
    FVector ray_cross_e2 = ray.GetNormalizedDirection().Cross(edge2);

    float det = edge1.Dot(ray_cross_e2);

    if ( det > -epsilon && det < epsilon )
        return false;    // This ray is parallel to this triangle.

    float inv_det = 1.0f / det;
    FVector s = ray.Origin - v0;
    float u = inv_det * s.Dot(ray_cross_e2);

    if ( (u < 0 && abs(u) > epsilon) || (u > 1 && abs(u - 1) > epsilon) )
        return false;

    FVector s_cross_e1 = s.Cross(edge1);
    float v = inv_det * ray.GetNormalizedDirection().Dot(s_cross_e1);

    if ( (v < 0 && abs(v) > epsilon) || (u + v > 1 && abs(u + v - 1) > epsilon) )
        return false;

    // At this stage we can compute t to find out where the intersection point is on the line.
    float t = inv_det * edge2.Dot(s_cross_e1);

    if ( t > epsilon ) // ray intersection
    {
        hitpoint = ray.Origin + ray.GetNormalizedDirection() * t;
        return true;
    } else { // This means that there is a line intersection but not a ray intersection.
        return false;
    }
}

참고 : wikipedia

Texture

텍스처를 그리기 위해 아래 2가지 리소스가 필요하다.

  • SRV (Shader Resource View): DX11의 ID3D11ShaderResourceView로 관리되고, HLSL의 Texture2D로 바인딩된다. 텍스처 이미지를 직접 담고 있는 자원이다.
  • Sampler State: DX11의 ID3D11SamplerState로 관리되고, HLSL의 SamplerState로 바인딩된다. 텍스처 샘플링을 어떤 방식으로 할 지 관리한다.

이 외에도 Vertex Buffer에 uv좌표를 넘겨줘야 한다. 일반적으로 TEXCOORD라는 시맨틱을 사용한다.

struct VS_INPUT
{
    float4 position : POSITION; // Input position from vertex buffer
    float2 texCoord : TEXCOORD; // Input texture coordinate from vertex buffer
};

이후 Pixel Shader에서 다음과 같이 Texture에 접근할 수 있다. Texturefloat4로 각각 r, g, b, a를 의미한다.

float4 Texture = AtlasTexture.Sample(AtlasSampler, Input.texCoord);

참고 : https://unialgames.tistory.com/entry/DirectX11Sampler

Batch Rendering

그리드처럼 많은 선으로 이루어진 객체는 선 하나하나마다 draw를 하기엔 비용이 너무 크다. 때문에 하나의 Vertex Buffer에 몰아넣고 한번의 draw로 그리는 방식이 훨씬 효율적이다.

그러나 Batch도 무조건 만능은 아니다. 비록 Dynamic으로 Vertex Buffer를 생성할 수 있어도, 많은 수의 Vertex들을 수정하고 관리하긴 어렵기 때문에, 부분적으로 Culling을 적용한다던가 일부 Vertex만 업데이트한다거나 하는 일은 꽤나 어렵다.

TroubleShooting

AABB 계산과 Bounding Box

처음에는 AABB 계산을 AABB의 min, max 벡터에 변환행렬을 곱하면 될 줄 알았으나, 회전이나 음수의 스케일에 대해선 대응이 불가능했다.
결국 처음 AABB Box의 8점에 모두 행렬을 곱해 그 중 각 좌표축의 최대/최소값을 뽑아 새 min, max 벡터를 구해야 했다.

정확한 AABB를 구하려면 메쉬의 모든 정점을 순회해야 했지만 그러면 본 AABB의 의의에 맞지 않아 대략적인 정도로 계산하였다.

Gizmo는 Actor인가?

해당 프로젝트에서 Gizmo는 AActor가 아닌 UObject를 상속받도록 만들었다. 그나마 IClickable로 Gizmo와 AActor를 같이 묶어주긴 했으나 별 소용은 없었고, 피킹 등 구현부에선 모든 Actor와 Gizmo를 순회하는 식으로 사용하였다.

template 함수 template으로 래핑하지 말기

	template<typename T>
	T* SpawnActor(FString InName) {
		T* actor = UEngine::GetEngine().GetWorld()->SpawnActor<T>(
			InName,
			FVector(SpawnLocation[0], SpawnLocation[1], SpawnLocation[2]),
			FRotator(SpawnRotation[0], SpawnRotation[1], SpawnRotation[2]),
			FVector(SpawnScale[0], SpawnScale[1], SpawnScale[2]),
			nullptr
		);
		return actor;
	}

이런 식으로 자주 쓰는 템플릿 함수를 다시 한번 템플릿 함수로 래핑하면 디버깅하기 난감해진다고 한다. 지양하자.

0개의 댓글