여기까지의 작업내용: https://github.com/hwi-middle/HimchanSoftwareRenderer/tree/bd9fed296433cc0cfb0299d3d511b2eebba8b39b
(또또) 오랜만입니다
와, 정말 바쁜 해였다. 졸업작품이 끝났으니 렌더러를 다시 손대보자 싶어서 자리에 앉았다. 특히, 올해 안으로 이 렌더러를 3D로 만들고 싶었기 때문에 들뜬 마음으로 작업을 시작했다.
삼각형 클리핑 수정
지난 게시물에서 나는 삼각형 클리핑을 구현했다. 삼각형이 뷰포트 밖으로 나가면 그리지 않는 처리이다.
삼각형이 뷰포트 밖에 있다는 것을 어떻게 알 수 있을까? 나는 각각의 edge들이 모두 Clip되면 화면 밖에 있는 것으로 생각했다.
하지만 이런 상황이 생긴다면? 3개의 edge가 모두 clip되었지만 화면 밖으로 나간 것은 아니다.
그런데 그것이 실제로 일어났습니다 ㅋㅋ
사각형을 렌더링 할 때 사각형을 오른쪽 아래로 내리면 그림으로 예시를 든 상황과 같아지면 삼각형을 그려야하는 상황에도 그리지 않아버린다.
if (Y1 >= Height || Y3 < 0)
{
return;
}
if ((X1 < 0 && X2 < 0 && X3 < 0) ||
(X1 >= Width && X2 >= Width && X3 >= Width))
{
return;
}
구현을 수정해서 이렇게 수정했다. 삼각형을 클리핑해야 하는 경우는 아래와 같다.
case 1: 가장 위쪽에 있는 Y1이 뷰포트 아래에 있거나, 가장 아래쪽에 있는 Y3가 뷰포트 위에 있는 경우
case 2: 모든 X 좌표가 뷰포트 왼쪽에 있거나 모든 X좌표가 뷰포트 오른쪽에 있는 경우
이렇게 간단한걸 왜 ClipLine으로 구현했을까 생각해보면, ClipLine이라는게 하나의 Line 전체가 Clip되는거라고 착각하고 구현했던 것 같다. 바보 같은 실수...
Transform, Camera 클래스 구현
이제 3차원으로 렌더러를 진화(?) 시키려면 필연적으로 카메라가 필요하다. 그런데 카메라를 만드려면 Transform도 필요하다. 왜냐하면 물체를 그리려면 카메라에서 뷰 행렬을 만들어야하고, 뷰 행렬을 만드려면 카메라의 트랜스폼이 필요하기 때문이다. 이제 단순히 평면에 점찍고 선 긋는 것으로는 안된다.
아주 단순한 형태의 Transform과 Camera 클래스를 만들었다.
class Transform
{
public:
Transform() : Yaw(0), Pitch(0), Roll(0) {};
FORCEINLINE Matrix4x4 GetModelingMatrix() const;
void Rotate(float InYaw, float InPitch, float InRoll);
private:
Vector3 Position;
Vector3 Scale;
Vector3 Right = Vector3::UnitX;
Vector3 Up = Vector3::UnitY;
Vector3 Forward = Vector3::UnitZ;
float Yaw;
float Pitch;
float Roll;
};
FORCEINLINE Matrix4x4 Transform::GetModelingMatrix() const
{
return Matrix4x4(
Vector4(Right * Scale.X, 0.f),
Vector4(Up * Scale.Y, 0.f),
Vector4(Forward * Scale.Z, 0.f),
Vector4(Position, 1.f)
);
}
가장 먼저 Transform 클래스. 간단한 Getter와 Setter는 생략하면 이런 느낌. 모델링 행렬을 뱉어주는 함수가 가장 중요하다!
class Camera
{
public:
Camera() = default;
FORCEINLINE Matrix4x4 GetViewMatrix() const;
private:
Transform TransformComponent;
};
FORCEINLINE Matrix4x4 Camera::GetViewMatrix() const
{
const Vector3 viewX = TransformComponent.GetLocalX();
const Vector3 viewY = TransformComponent.GetLocalY();
const Vector3 viewZ = TransformComponent.GetLocalZ();
const Vector3 position = TransformComponent.GetPosition();
return Matrix4x4(
Vector4(Vector3(viewX.X, viewY.X, viewZ.X), 0.f),
Vector4(Vector3(viewX.Y, viewY.Y, viewZ.Y), 0.f),
Vector4(Vector3(viewX.Z, viewY.Z, viewZ.Z), 0.f),
Vector4(Vector3::Dot(-viewX, position), Vector3::Dot(-viewY, position), Vector3::Dot(-viewZ,position), 1.f)
);
}
그리고 Camera 클래스. 뷰 행렬을 구하는 함수가 있고, Transform을 멤버로 갖는다.
일단 요렇게 약식으로(?) 구현하고 넘어가겠다. 추후에 필요에 따라 더 개발하나가는 걸로...
큐브 그리기
다음으로 정육면체(큐브)를 렌더링해보도록 하자.
struct Vertex
{
public:
Vector4 Position;
::Color Color;
Vector2 UV;
// ...
};
일단 Vectex 클래스의 Position을 Vector4로 선언한다. 이제부터 동차 좌표계를 사용할 것이기 때문이다.
이제 다음 일은 간단하다. 버텍스 버퍼와 인덱스 버퍼를 구성하고 모델링 행렬에 뷰 행렬을 곱해서 그리면 된다!
두둥. 이렇게 큐브를 렌더링했다. 약간 아쉬운 것은 뒷면까지 그려지고 있다는 것. 백페이스 컬링도 구현해보자.
백페이스 컬링
뒷면을 안그리려면 어떻게 해야하는가. 뒷면이라는 것을 판별하기만 하면 된다. 뒷면이라는 것을 판별하려면 렌더러의 규칙을 세워야한다.
나는 이 렌더러를 3D로 설계할 때, 좌표계는 유니티와 일치시켰다. Y-up 왼손 좌표계이고, Z축의 양의 방향이 앞을 향한다. 그리고, CW(시계방향)으로 그려진 면을 정면으로 본다.
자, 그러면 이런 배경을 가지고 수식을 구할 수 있다. 삼각형을 그릴 때 edge간의 외적을 통해 법선벡터 N을 구할 수 있다. V0에서 V1으로 가는 edge와 V0에서 V2로 가는 edge를 외적하면 될 것이다.
그리고 카메라의 뷰 벡터를 V라고 하자. 그러면 N dot V가 양수이면 뒷면일 것이다. 이때 뷰 벡터는 매우 쉽게 구할 수 있는데, 백페이스 컬링을 뷰 공간에서 진행하면 뷰벡터는 그냥 단위 Z벡터 쓰면 된다.
그리하여 백페이스 컬링까지 마친 큐브. 아름다운 자태! 결국 해냈다~!!
해냈다, 3D 렌더러!
할 수 있을 줄 몰랐는데, 생각보다 3D로 만드는 건 금방 했다. 뭐가 그리 두려웠던거지? 라는 생각이 들었을 정도로.
이제 원근투영도 하고 이래저래 더 손대보도록 해야겠다.
'개발일지 > 소프트 렌더러' 카테고리의 다른 글
소프트웨어 렌더러 만들기 - 16 (삼각형 클리핑 + 버그 수정) (0) | 2024.08.22 |
---|---|
소프트웨어 렌더러 만들기 - 15 (키보드 입력) (0) | 2024.07.27 |
소프트웨어 렌더러 만들기 - 14 (텍스처링) (1) | 2024.07.25 |
소프트웨어 렌더러 만들기 - 13 (삼각형 채우기, 보간) (1) | 2024.07.22 |
소프트웨어 렌더러 만들기 - 12 (삼각형 그리기 + 버그 수정) (0) | 2024.07.19 |