절두체 컬링(Frustum Culing)이란?

카메라의 시야(절두체)에서 들어오지 않은 객체를 제외하는 것,

즉, 그 객체들은 렌더링에서 제외된다. 하지만 제외작업을 수행하기 위한 연산작업은 필요로 한다.

 

World Space에 배치된 Mesh(객체)들을 <View 변환> 하는 과정

절두체 컬링을 하기 위해서는 World Space에 있는 절두체 영역을(a그림) 구해야된다.

최종 절두체 영역(c그림)의 8개 정점에서 역으로 계산을 해야한다. 

 

Vworld = VLocal * TM(Transfrom Matrix)  :  Vertex data를 월드변환하는 과정의 식

최종Vertex = Vworld * ViewMatrix * ProjectMatix : 월드로 배치된 Vertex를 View(카메라)행렬과 투영행렬을 곱해 카메라를 원점으로 Vertex가 배치되는 식이다.

 

최종Vertex = Vworld * ViewMatrix * ProjectMatirx 식 양변에 카메라 * 투영 역행렬를 곱해주면

최종 Vertex * (카메라 * 투영 역행렬) = Vworld가 나오게 된다. 

( ∵  카메라 * 투영 역행렬 : (ViewMatrix * ProjectMatix)^-1 )

이러면 WorldSpace에 있는 절두체의 영역(우리가 보고있는 카메라 화면)을 찾을수있게된다. 


필요한 구성요소는

절두체 컬링을 하기위해 절두체 class가 필요함 -> Frustum class 를 생성

View행렬과 Matrix행렬을 가져오기 위한 Camera -> Camera 포인터 변수 필요

절두체 면(Plane) 과 벡터의 수직상 거리를 비교할때 필요한 오차범위 -> planeEpsilon;

절두체 면을 관리할 vector -> frustumPlane[6] //밑에 그림의 숫자와 index와 일치

 

8개의 veretx로 6개의 plane을 만들어 절두체 영역을 만들것, 코드영역에서도 이렇게 표현될 예정

Init함수 , 최종Vertex를 구성하는 함수, Frustum 내부에 객체가 있는지 check하는 함수 등등으로 구성하면 될것이다.

 

<Frusutum class> :

벡터(객체의 원점)를 인자값으로 가져와 Frustum 영역의 내부,외부를 체크하여 외부에 있다고 판단시 절두체컬링을 하게하는 class다.

더보기
//Frustum.h

#pragma once
#include "Components/DXCamera.h"

class Frustum
{
private:
	DXCamera* camera; //view,Projection 행렬을 가져오기 위한 변수 

	float planeEpsilon; // 평면테스트에서 사용되는 부동 소수점 오차범위를 나타냄 
	XMVECTOR frustumPlane[6]; //Plane : 평면 -> 절두체 평면 

public:
	bool Initialize(DXCamera* camera, float planeEpsilon = FLT_EPSILON);
	void ConstructFrustum();

	//IsInFrustumBounds~ 함수 설명
    //물체의 기준으로 히트박스(Bounds뒤에 들어가는 도형,도형은 자유) 만들어서 그 안에있으면 있따고 처리 체크 

	bool IsInFrustum(XMFLOAT3 v); //Frustum 안에 물체 확인 bool 변수
	bool IsInFrustum(XMVECTOR v); //Frustum 안에 물체 확인 bool 변수
	bool IsInFrustumExceptUpDown(XMFLOAT3 v); //Frustum의 윗면과 아랫면은 무조건 있는걸로 취급하고 좌우앞뒤만 체크하는 변수

	bool IsInFrustumBoundsSphere(XMFLOAT3 v,float radius); //Frustum 안에 물체의 기준으로 구 모형 안에 있는지 물체 확인 bool 변수
	bool IsInFrustumBoundsSphere(XMVECTOR v, float radius); //Frustum 안에 물체의 기준으로 구 모형 안에 있는지 물체 확인 bool 변수
	bool IsInFrustumBoundsSphereExceptUpDown(XMFLOAT3 v, float radius); //Frustum안에 물체의 기준으로 구 모혀 위와 아래는무조건 있는걸로 취급하고 좌우앞뒤만 체크하는 변수

	bool IsInFrustumBoundsCube(XMFLOAT3 v, float radius); //Frustum 안에 육면체 모양 안에 물체 확인 bool 변수
	bool IsInFrustumBoundsCube(XMVECTOR v, float radius); //Frustum 안에 육면체 모양 안에 물체 확인 bool 변수

	//Rectangle : AABB/OBB -> 2D게임 만들때거나 3D게임의 UI 만들때 사용
	bool IsInFrustumBoundsRectangle(XMFLOAT3 v, XMFLOAT3 r); //Frustum 안에 2D사각형 안에 물체 확인 bool 변수
	bool IsInFrustumBoundsRectangle(XMVECTOR v, XMVECTOR r); //Frustum 안에 2D사각형 안에 물체 확인 bool 변수

private:
	bool IsInBoundsCube(XMVECTOR p, XMFLOAT3 v, XMFLOAT3 r);
};

//====================================================================

#include "Frustum.h"
bool Frustum::Initialize(DXCamera* camera, float planeEpsilon)
{
	if (!camera) return false; // 매개변수 camera 가 존재하지 않으면 false 반환

	//Frustum data 초기화
	this->camera = camera;
	this->planeEpsilon = planeEpsilon;
	for (int i = 0; 6 > i; i++) frustumPlane[i] = XMVectorSet(0, 0, 0, 0);
	return true;
}

void Frustum::ConstructFrustum()
{
	//최종 vertex는 (-1,-1,0) ~ (1,1,1) 사이의 값으로 바뀐다.
	//최종Vertex = Vworld * ViewMatrix * ProjectMatirx해서 나온 Frusutm 영역의 8개 vertex data임.
    XMFLOAT3 frustumVertices[8] =
	{
		{-1.0f, -1.0f, 0.0f }, // near left bottom v0
		{1.0f, -1.0f, 0.0f }, // near right bottom v1
		{1.0f, -1.0f, 1.0f }, // far right bottom v2
		{-1.0f, -1.0f, 1.0f }, // far left bottom v3
		{-1.0f, 1.0f, 0.0f }, // near left top v4
		{1.0f, 1.0f, 0.0f }, // near right top v5
		{1.0f, 1.0f, 1.0f }, // far right top v6
		{-1.0f, 1.0f, 1.0f }, // far left top v7
	};

	//view * proj의 역행렬 계산.
	XMMATRIX invViewProjectionMatrix = XMMatrixInverse(NULL, camera->GetViewMatrix() * camera->GetProjectionMatrix());
	
	XMVECTOR frustumVectors[8]; //최종vertex * (viewprojectionMatrix)T를 계산한 vertex들의 vector
	for (int i = 0; 8 > i; i++)
	{
		// XMVector3TransformCoord : 주어진 행렬을 사용하여 벡터로 변형하는 함수
		//최종vertex * (viewprojectionMatrix)T 계산,
		frustumVectors[i] = XMVector3TransformCoord(XMVectorSet(frustumVertices[i].x, frustumVertices[i].y, frustumVertices[i].z, 1.0f),
			invViewProjectionMatrix);
	}
	
    //계산된 Vworld(월드상의 vertex)들을 3개씩 사용하여 절두체의 평면을 구성 
    //인덱스 순서에 따라 절두체면(폴리곤)의 윗면이 결정되어 윗면이 법선벡터의 방향이다.(중요)
	frustumPlane[0] = XMPlaneFromPoints(frustumVectors[4], frustumVectors[7], frustumVectors[6]); // 상 평면(top)
	frustumPlane[1] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[1], frustumVectors[2]); // 하 평면(bottom)
	frustumPlane[2] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[4], frustumVectors[5]); // 근 평면(near)
	frustumPlane[3] = XMPlaneFromPoints(frustumVectors[2], frustumVectors[6], frustumVectors[7]); // 원 평면(far)
	frustumPlane[4] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[3], frustumVectors[7]); // 좌 평면(left)
	frustumPlane[5] = XMPlaneFromPoints(frustumVectors[1], frustumVectors[5], frustumVectors[6]); // 우 평면(right)
}


bool Frustum::IsInFrustum(XMFLOAT3 v)
{
	return IsInFrustum(XMVectorSet(v.x, v.y, v.z, 1.0f));
}
bool Frustum::IsInFrustum(XMVECTOR v)
{
	return IsInFrustumBoundsSphere(v, 0.0f);
}
bool Frustum::IsInFrustumExceptUpDown(XMFLOAT3 v)
{
	return IsInFrustumBoundsSphereExceptUpDown(v, 0.0f);
}
bool Frustum::IsInFrustumBoundsSphere(XMFLOAT3 v, float radius)
{
	return IsInFrustumBoundsSphere(XMVectorSet(v.x, v.y, v.z, 1.f), radius);
}

//절두체 안에 경계구가 있는지 체크하는 함수( 작동방식 밑에 서술)
bool Frustum::IsInFrustumBoundsSphere(XMVECTOR v, float radius)
{
	for (int i = 0; 6 > i; i++) // 왜 6인가? 절두체의 면이 6개임
	{
		//XMPlaneDotCoord의 함수 반환값은 XMVECTOR인데, XMVECTOR의 모든값을 내적크기로 반환한다.
		//VectorGetX,Y,Z 함수 아무거나 사용해도 괜찮다. 전부 내적의 값이기 떄문이다.
		if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], v)) > (radius + planeEpsilon))
		{
			// 벡터의 내적(물체와 절두체 면의 수직상의 거리) > 물체의 경계(히트박스)거리 가 참인경우
			return false; //frustum 영역 외부에 있음
		}
	}

	return true;//frustum 영역 내부에 있음
}

bool Frustum:: IsInFrustumBoundsSphereExceptUpDown(XMFLOAT3 v, float radius)
{
	XMVECTOR vector = XMVectorSet(v.x, v.y, v.z, 1.0f);

	for (int i = 2; 6 > i ; i++) // 0,1 인덱스의 vertex는 윗면과 ,아랫면의 vertex이므로 제외
	{
		//XMPlaneDotCoord (평면(plane) , 벡터(vertex) ) : 평면과 벡터사이의 내적값을 반환.
		if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], vector)) > (radius + planeEpsilon)) return false;
	}

	return true;
}

//절두체를 경계박스로 컬링하는 함수 
bool Frustum::IsInFrustumBoundsCube(XMFLOAT3 v, float radius)
{
	for (int i = 0; 6 > i; i++)
	{
		if (IsInBoundsCube(frustumPlane[i], v, XMFLOAT3(radius, radius, radius))) continue;
		return false;
	}
	return true;
}

bool Frustum::IsInFrustumBoundsCube(XMVECTOR v, float radius)
{
	XMFLOAT3 dest;
	XMStoreFloat3(&dest, v);
	return IsInFrustumBoundsCube(dest, radius);
}

//절두체를 2D사각형으로 컬링하는 함수(2D게임에서 사용하게됨)
bool Frustum::IsInFrustumBoundsRectangle(XMFLOAT3 v, XMFLOAT3 r)
{
	for (int i = 0; 6 > i; i++)
	{
    	//
		if (IsInBoundsCube(frustumPlane[i], v, r)) continue;
		return false;
	}
	return true;
}

bool Frustum::IsInFrustumBoundsRectangle(XMVECTOR v, XMVECTOR r)
{
	XMFLOAT3 destV, destR;
	XMStoreFloat3(&destV, v);
	XMStoreFloat3(&destR, r);
	return IsInFrustumBoundsRectangle(destV, destR);
}

//경계박스 ,및 경계사각형(2D)의 함수 작동방식 
bool Frustum::IsInBoundsCube(XMVECTOR p, XMFLOAT3 v, XMFLOAT3 r)
{
	return
		(
        	//절두체 면(p) ,벡터v,r연산
            //모든 벡터의 내적이 오차범위 보다 작으면 있면 frustum 안에 존재 
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y - r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y - r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y + r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y + r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y - r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y - r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y + r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y + r.y, v.z + r.z, 1.0f))) <= planeEpsilon)
		);
}

 

최종 Vertex의 값 범위가 (-1,-1,0) ~ ( 1,1,1) 사이의 값으로 바뀐다 라는 주석이 있는데

이 값은 D3D에서는 절대 고정이며 view 변환시 카메라 시점을 원점으로 바꾸고 투영행렬을 계산하여 이미지화 (3D->2D)화 시키기 때문에 그런것이다.

//최종 vertex는 (-1,-1,0) ~ (1,1,1) 사이의 값으로 바뀐다.
	//최종Vertex = Vworld * ViewMatrix * ProjectMatirx해서 나온 Frusutm 영역의 8개 vertex data임.
    XMFLOAT3 frustumVertices[8] =
	{
		{-1.0f, -1.0f, 0.0f }, // near left bottom v0
		{1.0f, -1.0f, 0.0f }, // near right bottom v1
		{1.0f, -1.0f, 1.0f }, // far right bottom v2
		{-1.0f, -1.0f, 1.0f }, // far left bottom v3
		{-1.0f, 1.0f, 0.0f }, // near left top v4
		{1.0f, 1.0f, 0.0f }, // near right top v5
		{1.0f, 1.0f, 1.0f }, // far right top v6
		{-1.0f, 1.0f, 1.0f }, // far left top v7
	};

 

 

 

 

절두체 각 면의 법선벡터 방향을 정하는 것은

그래픽스 파이프라인 폴리곤을 그리는 방식에 따라 정해진다.

이 블로그의 그래픽스 파이프라인 - 기본용어 정리 - Vertex를 이용하여 삼각형을 그릴때 필요한 개념 내용을 참고하면 좋다.

https://01149.tistory.com/4

 

기본용어 정리

글 작성자가 공부하고 배운것을 간략하게 정리하는 페이지 입니다. 필요한 정보가 있으시면 확인하시고 만약 잘못된 내용이 있거나 이런 내용이 추가됬으면 좋겠다라면 댓글 부탁드립니다. 감

01149.tistory.com

 

절두체 컬링의 경계 구 방식의 작동방식을 설명 할 것이다.

bool Frustum::IsInFrustumBoundsSphere(XMVECTOR v, float radius)
{
	for (int i = 0; 6 > i; i++) // 왜 6인가? 절두체의 면이 6개임
	{
		//내적은 frustumPlane에서 v까지의 수직상 거리와 같다.
		//벡터의 내적의 크기(벡터 * 법선벡터 = 해당면으로 수직의 벡터크기(길이) 
		// {벡터의 내적 < 오브젝트의 구 범위(반지름이 더 크면)} frustum안에 있다는 뜻이다.
		// XMPlaneDotCoord의 함수 반환값은 XMVECTOR인데, XMVECTOR의 모든값을 내적크기로 반환하기 떄문이다.
		// VectorGetX,Y,Z 함수 아무거나 사용해도 괜찮다. 전부 내적의 값이기 떄문이다.
		if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], v)) > (radius + planeEpsilon))
		{
			// 벡터의 내적(물체와 절두체 면의 수직상의 거리) > 물체의 경계(히트박스)거리 가 참인경우
			return false; //frustum 영역 외부에 있음
		}
	}

	return true;//frustum 영역 내부에 있음
}
절두체 컬링 - 경계구 방식의 작동 방식

위의 그림 과정을 절두체 면이 6개니 총 6번 반복하여 동작을한다.

if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], v)) > (radius + planeEpsilon))
{
    return false;
}

반복문 동작중 절두체 6면 중 한 면이라도,

벡터의 내적(수직상의 거리) 이 radius + planeEpsilon (경계 구 반지름 + 오차범위)보다 길면
frustun 외부에 존재하는 것이기 때문에 절두체 컬링(화면에서 렌더링을 제외하는)을 작업하게 된다.

<DirectX11Graphics> : DriectX 11 기반으로 Scene을 렌더링하기 위한 그래픽스 객체

더보기
// DirectX11Graphics.h
#include "Frustum.h"
class DirectX11Graphics final : public Graphics
{
Private:

	// Mesh의 이미지 관련 함수들을 담당하고 있음
    std::unique_ptr <PrimitiveModel> primitive; 
    // 컴포넌트 구조의 Mesh Obj : 컴포턴트 추가 및 Update관련(Mesh의 크기,회전,이동 함수등) 
	DXGameObject primitiveObj; 

	Frustum frustum; //절두체컬링을 위한 절두체 객체 추가
};

//========================================================

// DirectX11Graphics.cpp

void DirectX11Graphics::RenderFrame() //그래픽스 객체의 Draw 함수(프레임마다 렌더링함수)
{
	//렌더링 할때마다 , 카메라시점 기준(뷰행렬,투영행렬)으로 절두체 영역 생성
	frustum.ConstructFrustum();

    // GameObject Primitive Mesh Draw
    //절두체 컬링 - 경계구 판정을 사용하여 Mesh의 위치vector와 크기Vector를 인자값으로 가져감
    //크기vector는 경계구의 반지름으로 사용, 해당 객체가 카메라영역에서 사라지고 경계구까지 영역에서 벗어나게되면 절두체 컬링진행
   	//(이 자리에 경계 구의 반지름 값을 조정하여 넣을 수있음 )
    if (frustum.IsInFrustumBoundsSphere(primitiveObj.GetTransform()->GetPosition(), XMVectorGetX(primitiveObj.GetTransform()->GetScale())/*bounds*/))
    {
    	//절두체 6면, 영역 내에 Mesh(객체)가 존재 함으로 Draw를 한다.
    	primitiveObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
	}
}


bool DirectX11Graphics::InitializeScene() // 그래픽스 객체의 Scene(장면) Init
{
	//상자 mesh
    Primitive = std::make_unique<PrimitiveCube>(); // 상자 모양 mesh
    Primitive->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);
    Primitive->MakePrimitive("Textures\\box.jpg"); // mesh에 텍스쳐 이미지 추가
    PrimitiveObj.AddComponent<DXMeshRenderer>()->SetModel(Primitive.get());
    
    //절두체 객체 Init , 카메라의 뷰행렬,투영행렬이 필요하기 때문 -> 역행렬을 구해야함
	frustum.Initialize(cameraObj.GetComponent<DXCamera>());

}

 

D3D 공부하던 코드를 긁어온거입니다. 코드 보실때 참고만해주세요 


3D 공간에서 기존 카메라의 영향을 받지 않는 2D Obj를 만든다.

즉, D3D를 이용하여 2D 게임을 만들거나, 게임의 UI를 구성할때 많이 사용된다.

 

Sprite2D의 구성요소는 

유저에게 보여줘야할 2D Obj -> sprite

2D를 비춰줄 전용 카메라 Obj -> camera2D

2D를 이미지를 렌더링해줄 컴포넌트 ->SpriteRenederer

2D에 사용할 전용 VS셰이더와 PS셰이더 -> sprite_vertexShader, sprite_pixelShader

셰이더를 사용하면 필요한 HLSL -> sprite2D_ps.hlsl,sprite2D_vs.hlsl (따로작성)

 

<HLSL 작성>

더보기
//sprite2D.ps.hlsl

struct PixelInput
{
	float4 position : SV_POSITION;
	float4 color : COLOR;
	float2 texcoord : TEXCOORD0;
}; //빛의 영향을 받지 않기 때문에 법선벡터 data를 가질필요가 없다.

Texture2D tex : TEXCOORD: register(t0);
SamplerState sample : SAMPLER: register(s0);
float4 main(PixelInput input) : SV_TARGET
{
	return input.color * tex.Sample(sample, input.texcoord);
}

//======================================================

// sprite2D.vs.hlsl

#pragma pack_matrix(row_major)
cbuffer constants
{
	matrix worldMatrix;
	matrix viewProjectionMatrix;
};
struct VertexInput
{
	float3 position : POSITION;
	float4 color : COLOR;
	float2 texcoord : TEXCOORD0;
	float3 normal : NORMAL;
};
struct VertexOutput
{
	float4 position : SV_POSITION;
	float4 color : COLOR;
	float2 texcoord : TEXCOORD0;
}; // ps.hlsl의 input과 vs.hlsl의 output은 똑같아야한다.(중요)

VertexOutput main(VertexInput input)
{
	VertexOutput output;
	output.position = mul(float4(input.position, 1.0f), worldMatrix);
	output.position = mul(output.position, viewProjectionMatrix);
	output.color = input.color;
	output.texcoord = input.texcoord;
	return output;
}

<SpriteRenderer 생성(컴포넌트)>

더보기
//SpriteRenderer class 생성 - 컴포넌트로 사용

// DXSpriteRenderer.h
#pragma once
#include "../DXGameObject.h"
#include "../Model.h"
class DXSpriteRenderer : public RenderComponent
{
    private:
        std::unique_ptr<Mesh> mesh; //그림을 그릴 mesh
        
        ID3D11DeviceContext* deviceContext;
        ConstantBuffer<Matrices>* vsConstantBuffer; //VertexShader에서 사용할 행렬data
    public:
        DXSpriteRenderer(DXGameObject* owner); // Render컴포넌트를 자기자신으로 선언 하는 생성자
        
        //sprite이미지를 로드해서 Mesh에 초기화
        void LoadSpriteImage(ID3D11Device* device, ID3D11DeviceContext* deviceContext, ConstantBuffer<Matrices>& vsConstantBuffer, const std::string& filename);
        
    protected:
    	virtual void Opertate() override; 
};

//=========================================

// DXSpriteRenderer.cpp

#include "DXSpriteRenderer.h"
DXSpriteRenderer::DXSpriteRenderer(DXGameObject* owner) :
    RenderComponent(owner),
    deviceContext(nullptr),
    vsConstantBuffer(nullptr)
{
}


void DXSpriteRenderer::LoadSpriteImage(ID3D11Device* device, ID3D11DeviceContext* deviceContext, ConstantBuffer<Matrices>& vsConstantBuffer, const std::string& filename)
{
	//2DspriteRenderer의 deviceContext,vsConstantBuffer 초기화
	this->deviceContext, = deviceContext;
	this->vsConstantBuffer = &vsConstantBuffer;
	
    //vertex 벡터 초기화
	std::vector<ModelVertex> vertices =
	{
		ModelVertex(-0.5f, -0.5f, 0.0f,0.0f, 0.0f), // top left
		ModelVertex(0.5f, -0.5f, 0.0f,1.0f, 0.0f), // top right
		ModelVertex(-0.5f, 0.5f, 0.0f,0.0f, 1.0f), // bottom left
		ModelVertex(0.5f, 0.5f, 0.0f,1.0f, 1.0f), // bottom right
	};
    //index 벡터 초기화 
	std::vector<DWORD> indices =
	{
		0, 1, 2,
		2, 1, 3
	};
    
    //인자값으로 가져온 텍스쳐 파일이름을 Texture생성자에서 2D이미지 로드
	std::vector<Texture> textures = { Texture(device, filename) };
	//mesh에 data를 초기화해서 모델링하는 작업 
    mesh = std::make_unique<Mesh>(device, deviceContext, vertices, indices, textures, XMMatrixIdentity());
}

//constantBuffer = 정점 및 픽셀 셰이더에서 사용될 상수를 모아 놓은 버퍼이다.
//ConstantBuffer에 월드행렬과 뷰투영 행렬을 초기화 하고 메모리락(ApplyChanges) 한 뒤 mesh를 Draw한다. 
void DXSpriteRenderer::Opertate()
{	
	deviceContext->VSSetConstantBuffers(0, 1, vsConstantBuffer->GetAddressOf());
	vsConstantBuffer->data.world = mesh->GetTransformMatrix() * owner->GetTransform()->GetWorldMatrix();
	vsConstantBuffer->data.viewProjection = viewProjectionMatrix;
	vsConstantBuffer->ApplyChanges();
	mesh->Draw();
}

<DirectX 11 Graphics 의 2D sprite 추가>

: DirectX 11 Graphics : DirectX 11 기반으로 씬(Scnen)을 렌더링하기 위한 그래픽스 객체

더보기
// DirectX11Graphics.h

#include "Components/DXSpriteRenderer.h" //SpriteReneder 헤더 추가
class DirectX11Graphics final : public Graphics
{
Private:
	...
    VertexShader sprite_vertexShader; //2Dsprite VS
    PixelShader sprite_pixelShader; //2Dsprite PS
    DXGameObject camera2D; //2D 전용 카메라 Obj
    DXGameObject sprite; //2D 이미지 mesh
	...
};

//=================================

// DirectX11Graphics.cpp

void DirectX11Graphics::RenderFrame()
{
	...
    //deviceContext에 sprite관련 data를 Set(IA,VS,PS)
    deviceContext->IASetInputLayout(sprite_vertexShader.GetInputLayout());
    deviceContext->VSSetShader(sprite_vertexShader.GetShader(), NULL, 0);
    deviceContext->PSSetShader(sprite_pixelShader.GetShader(), NULL, 0);
    //Camera2D에 뷰투영행렬을 인자값으로 가져와서 draw를 실행
    sprite.Draw(camera2D.GetComponent<DXCamera>()->GetViewProjectionMatrix());
    swapChain->Present(NULL, NULL);
}

//Shader Init
bool DirectX11Graphics::InitializeShader()
{
   ...
    //vs,ps HLSL Initialize
    if (!sprite_vertexShader.Initialize(device.Get(), L"sprite2D.vs.hlsl", inputLayout,
    numElements)) return false;
    if (!sprite_pixelShader.Initialize(device.Get(), L"sprite2D.ps.hlsl")) return false;
    return true;
}

//Scene Init
bool DirectX11Graphics::InitializeScene()
{

...
		//Cam(Base컴포넌트)에 DXCamera(컴포넌트)추가
		DXCamera* cam = camera2D.AddComponent<DXCamera>();
        //Cam의 투영방법 Set : 직교 투영
		cam->SetProjectionType(ProjectionType::Orthographic);
		//카메라 좌표계 원점을 left,top에서 중앙으로 옮기는 과정 
		cam->SetProjection(screenWidth, screenHeight, -10.0f, 10.0f);

		//2D 이미지(sprite)dp Renderer 컴포넌트 추가후 이미지 로드
		sprite.AddComponent<DXSpriteRenderer>()->LoadSpriteImage(
			device.Get(), deviceContext.Get(), constantMatricesBuffer,
			"Textures\\hello_world_sprite.jpg");

		//2D이미지는 Scale 및 Translation 조정시 3D처럼 원본의 배율이 아닌 직접 값을 입력해야된다. 
		//sprite.GetTransform()->SetLocalScale({ 100.0f, 100.0f, 1 }); // Scale Set
		//sprite.GetTransform()->SetLocalRocation({ 0, 0, 30.0f }); // Rotation Set(Z축을 회전시켜야 2D이미지가 회전됨)
        //sprite.GetTransform()->SetTranslation({ 100.0f, 100.0f, 0 }); // Translation Set
        /**/


...
}

 

2D 카메라 세팅중 직교투영과 좌표계 수정이 필요하다.

bool DirectX11Graphics::InitializeScene()
{        ...
        //Cam의 투영방법 Set : 직교 투영
		cam->SetProjectionType(ProjectionType::Orthographic);
		//카메라 좌표계 원점을 left,top에서 중앙으로 옮기는 과정 
		cam->SetProjection(screenWidth, screenHeight, -10.0f, 10.0f);
        ...
}

 [2D카메라 투영 세팅]

//DXCamera 투영 행렬을 Set 하는 함수
void DXCamera::SetProjection(float width, float height, float nearZ, float farZ, float fovDegrees)
{
	this->width = width;
	this->height = height;
	this->nearZ = nearZ;
	this->farZ = farZ;
	this->fovDegrees = fovDegrees;

	switch (type)
	{ // 원근/직교(Perspective/Orthographic) 투영 행렬 생성.
	case ProjectionType::Perspective: projectionMatrix = XMMatrixPerspectiveFovLH(fovDegrees * Deg2Rad, (width / height), nearZ, farZ); break;
	case ProjectionType::Orthographic: projectionMatrix = XMMatrixOrthographicOffCenterLH(-width / 2, width / 2, height / 2, -height / 2, nearZ, farZ); break;
	}
}

DXCamera 객채 생성자에서 원근투영(Perspective)으로 set하게 해놨기 떄문에 직교투영(Orthographic)으로 set해줘야한다.

 

-원근투영

뷰투영행렬 연산을 거쳐 카메라 화면(유저가 보는 화면 = viwe port)이 모니터에 출력되는데

이때 데카르트 좌표계(정규좌표)를 사용하고 있다.

우리가 흔히 쓰는 데카르트 좌표계가 기준

 그렇기 때문에 원점(0,0)이 Width,Height 값만 있으면 원점은 정중앙으로 인식된다.

XMMatrixPerspectiveFovLH(fovDegrees * Deg2Rad, (width / height), nearZ, farZ); 

view Space의 각도(=카메라의 각도) , (카메라 화면 폭 / 카메라 화면 높이) , 가까운 Z축 plane , 멀리있는 Z축 plane

 

-직교투영

원근투영과는 다르게 직교투영은 화면좌표계(컴퓨터좌표계)를 사용한다.

원점(0,0)이 정중앙이 아닌 left,top으로 되있기 때문에 쉽게 사용하라면 원근투영처럼 원점을 정중앙으로 옮겨줘야 된다.

Y축의 증감 방향이 반대로 되어있다.

XMMatrixOrthographicOffCenterLH(-width / 2, width / 2, height / 2, -height / 2, nearZ, farZ);  

width와 height의을 절반으로 나눠 -,+값으로하면 원하는 카메라 화면을 구할수 있다.

 

 

계층구조는 이 블로그를 참고하여 작성하였습니다. 

5장 - 1 에서는 계층구조의 종류 및 계층구조 구현(행렬 계산)에 대하여 작성되어있고,

5장 - 2 에서는 계층구조를 코드를 이용하여 구현하는것을 작성되어있다. 

https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sipack7297&logNo=220427434575 

 

[D3D] 5장 : 계층구조(Hierarchy)_1

계층구조(Hierarchy) 물체들의 대부분이 관절체(articulated body)로 이루어져 있다. 이러한 관절체의 가...

blog.naver.com

https://blog.naver.com/sipack7297/220428447625

 

[D3D] 5장 : 계층구조(Hierarchy)_2

계층구조 실제 구현 처음에는 해골책에 있는 소스를 활용해서  지구와 달과의 관계를 시뮬레이션을 해...

blog.naver.com

 


계층구조(Hierarchy)

더보기

물체의 대부분이 관절체(Articulated Body)로 이루어져있다.

관절체의 큰 특징은 부모-자식 관계를 갖는 구조로 이루어져 있으며 이를 계층구조라 한다.

(상속 개념 X)

자료구조의 트리(Tree)를 이용하여 구현이 가능하다. 

부모 노드(Node) -자식 노드(Node)로 계층구조가 이루어져 있다. 

즉, 계층 구조는 부모-자식 관계를 3D로 구현할떄 사용된다. 

 

계층구조의 예시 - 사람

그림 출처 : https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sipack7297&logNo=220427434575

왼쪽 그림은 인간형태로 모델링된 메시, 오른쪽 그림은 인간모델의 계층구조를 도식화 한것

트리 구조로 보자면 허리(코어)가 최상위 부모(Root Node)가 되고 허리의 자식노드들이 가슴(상반신)과 엉덩이(하반신)이 되고 이것이 점점 아래로 내려가면서 트리 구조를 구현한다.

 

p.s 
상속이 계층구조를 띄는거지 계층구조가 상속인것은 아니다.
컴퓨터 그래픽스의 계층구조는 tree자료구조인 nood로 연결되어있는 구조(중요) 

 

계층구조의 구현 (행렬 계산)

더보기

계층 구조 구현은 행렬의 계산이므로 vertex들을 월드매트릭스로 옮기는 과정을 똑같이 진행하면 된다.

행렬 곱 계산시 순서(위치)가 매우 중요하므로 , 순서는 반드시 이 순서대로만 해야된다. (중요)

 

TransformMatrix(변환행렬) = scaleMatrix * rotationMatrix * translationMatrix

정점 변환 (월드) : Vworld = Vlocal * TM(TransformMatrix)

계층구조의 정점 (자식노드의 메쉬 구현) : Vworld =  Vlocal * TMChild * TMParent

 

Vlocal : Local Space에 배치되어있는 Vertex

(Local/Model Space : 3D 오브젝트 모델을 처음 생성할 때에 모델을 가장 편리한 방법으로 배치 / 편리한 방법 : 자기 자신을 원점으로 잡음)

 

계층구조의 정점 구현은 LocalSpace의 자신(Child)의 vertex들이 배치되어있고 TM을 곱하여 월드 매트릭스로 변환 하고 부모의 변환행렬을 곱하여 자신(Child) LocalSpace의 기준을 부모의 원점을 기준으로 바꾸어 구현한다.

즉, 계층구조의 정점 구현시 LocalSpace가 자기 자신이 아닌 부모의 원점을 기준으로 구현하게 된다. 

 

 

계층구조는 n개이상으로 구현이 되면 , 부모TM들은 내 위에 부모노드 들을 순서에 유의하며  n개를 전부 곱한다.

n개 계층 표현식 : Vworld = Vlocal * TMChild * TMParent(n) * TMParent(n-1) * ... * TMParent(1) 

 

그림 출처 : https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sipack7297&logNo=220427434575

예를 들어 오른발(foot_r) 메시가 구현(렌더링)되기 위해서는 다음과 같은 과정을 거친다. 

Vworld =

Vx3ds_foot_r(오른발의 Local Vertex)

* TMx3ds_foot_r (오른발 변환행렬)

* TMx3ds_leg_dr(오른 다리 변환행렬)  

* TMx3ds_leg_ur(오른 허벅지 변환 행렬) 

* TMx3ds_hip(엉덩이 변환행렬) 

* TMx3ds_waist(허리 변환행렬)

 

 

 

밑에 부분은 그냥 내가 공부했던 내용중에 있어서 기입했다. 

p.s 부모 노드들을 다 곱해야되는가?

 꼭 그럴필요는 없다. 다 곱하게되면 세밀한 표현 및 정확한 위치를 얻을수 있지만, 구현 및 처리비용에 중점을 두게 된다면 구현시에 LocalVertex를 mesh를 그릴때 연산해주면된다.

즉, TM을 WorldMatrix라고 지칭하고, mesh를 렌더링(draw)하는 구간에 VLocal(Local vertex)를 연산해주면 월드에 구현이 된다. 

//예시) 오른 다리의 부모TM을 다 곱했다고 가정 

//오른 다리 구현
WorldMatrix = ScaleM * RotationM * TranslationM ;//월드 매트릭스 = TM 
//오른 다리 draw
Vworld_3ds_leg_dr  = Vx3ds_leg_dr * WorldMatrix * WorldMatrix_parent; // WorldMatrix_parent : 오른 다리의 부모TM의 곱
▼
//오른 발 구현 
WorldMatrix = ScaleM * RotationM * TranslationM ;//월드 매트릭스 = TM 
//오른 발 draw
Vworld_x3ds_foot_r = Vx3ds_foot_r * WorldMatrix  * WorldMatrix_parent; // WorldMatrix_parent : 오른 발의 부모TM의 곱(오른다리가 포함되어있음)

 

<코드 영역>

더보기

※ 공부중인 코드 일부분을 긁어온거라 참고용으로만 봐주세요.


<지구 자전>

//DirectX11Graphics.h
class DirectX11Graphics 
{
private:
	...
    
    DXGameObject cameraObj; // 카메라 Obj
    
   	// PrimitiveModel은 mesh data와 vertex shader에서 사용할 행렬 data가 담겨있다.
    // 즉, Textuer를 렌더링하는 data를 담당하는 변수 
	std::unique_ptr <PrimitiveModel> Earth; 
   
    //Component 구조로 설계한 DXGameObject를 사용
    //Transform으로 크기행렬,회전행렬,이동행렬 수정 및 Set할때 사용됨 
    //RenderComponent만 추가하여 Render 할때 사용되는 변수 
	DXGameObject EarthObj; 

    
    ...
}

//DirectX11Graphics.cpp
void DirectX11Graphics::Update() // update 영역에서 지구를 자전시키는 코드를 생성
{
	...
    
    // timer class를 만들어서 델타타임을 활용 하게 만듬
	const float deltaTime = timer->GetMilisecondsElapesed();
	static float earthYaw = 0;
	
    earthYaw += deltaTime * 0.1f;
	EarthObj.GetTransform()->SetLocalRotation({ 0,earthYaw, 0 });  // 지구의 y축 회전
	
    ...
}

bool DirectX11Graphics::InitializeScene()
{
	...
    
    //지구
    Earth = std::make_unique<PrimitiveSphere>(); //mesh를 구 모양으로 초기화 
    Earth->Set_size(3); //지구의 scale 크기 Set
    Earth->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);// Earth Model Init
    Earth->MakePrimitive("Textures\\earth2048.bmp"); // mesh에 텍스쳐 이미지 추가
    EarthObj.AddComponent<DXMeshRenderer>()->SetModel(Earth.get()); //Renderer component를 추가후 Earth Mesh를 저장
    
    ...
}

void DirectX11Graphics::RenderFrame() 
{
	...
    //GameObject Primitive Mesh Draw
    //world space에 배치된 obj를 뷰투영행렬을 연산해 카메라시점으로 변환
    //인자값으로 카메라obj에 있는 뷰투영행렬을 Get
	EarthObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
    
    swapChain->Present(NULL, NULL);
}

코드 작성후 빌드를 하면

 

이런식으로 지구가 자전을 하는 모습을 볼 수 있다.

 

이제 계층구조를 활용하여 간단한 태양계를 만들어 볼건데, 

일반적으로 태양-지구-달 계층구조를 생각하면 왼쪽을 생각할 것이다.

이 그림의 의미는 계층구조를 구성시 부모-자식 관계가 꼭 Mesh 들로만 구성을 하지 않아도 된다는것이다.
(중요한 포인트)
왼쪽 처럼 구성을 한 경우 화면에 보여지는건 정상적으로 움직일 것이다.

하지만 

태양의 자전을 멈추게하면 지구는 공전을 하지 않게 된다.
과학적으로는 태양의 자전으로 인해 지구가 공전 이런 내용이 맞다 하지만, 

내가 사용하고싶은건 태양은 자전을 하지 않지만 지구는 공전을 하는것이 의도면 왼쪽처럼 구성하면 안된다.

 

만약 태양과 지구가 직접 node로 설계되어있다면

지구Vworld = 지구Vlocal * 지구TM * 태양TM 이 변환식일것이다.
이러면 태양 TM의 크기,회전,이동 행렬이 전부 지구에 직접적으로 연관을 주게 되어 태양이 자전이 멈추게 되면 
지구도 공전을 하지 않게 된다.

 

하지만 오른쪽 그림의 지구공전이라는 node를 구성하게 되면
지구Vworld = 지구Vlocal * 지구TM * 지구공전TM * 태양TM 이 변환식일것이다.

지구 공전 TM은 회전만하면 되기 때문에 회전행렬만 update가 되고 위치나 크기는 전부 태양TM을 따르게된다.

이렇게 되면 실질적으로 Render 할경우 태양은 자전(회전행렬)을 하지 않아도도 지구공전TM은 회전행렬을 update 하고 있으며 그 동시에 태양 TM의 위치와 크기를 가지고 있다. 이제 지구TM에 부모TM(지구공전TM * 태양TM )을 곱해주면 지구는 지구TM의 회전행렬은 지구공전의 회전행렬이 연산되고 태양 TM의 위치,크기값이 연산됨으로 의도한 결과값을 낼수 있다.

 

즉, 지구공전,달 공전은 회전행렬만 Update하는 객체(Obj)이며 위치나 크기는 부모들을 따라간다.

내용이 많이 길어지고 복잡해졌지만 적고싶었던 이유는 <계층구조는 무조건 Mesh로만 구성되어있는것이 아니다>는것을 말하고 싶었고, 원하는 의도를 낼려면 이런식으로도 사용 할 수 있다는 것을 적고싶었다.

 

<태양계 생성>

//DirectX11Graphics.h
class DirectX11Graphics 
{
private:
	...
    
    DXGameObject cameraObj; // 카메라 Obj
    
    // PrimitiveModel은 mesh data와 vertex shader에서 사용할 행렬 data가 담겨있다.
    // 즉, Textuer를 렌더링하는 data를 담당하는 변수 
	std::unique_ptr <PrimitiveModel> Earth; 

    //Component 구조로 설계한 DXGameObject를 사용
    //Transform으로 크기행렬,회전행렬,이동행렬 수정 및 Set할때 사용됨 
    //RenderComponent만 추가하여 Render 할때 사용되는 변수 
	DXGameObject EarthObj; 
  
    //====태양계 생성으로 인한 태양,달 추가 =====//
    std::unique_ptr <PrimitiveModel> Sun; 
    std::unique_ptr <PrimitiveModel> Moon; 
    
    DXGameObject SunObj; 
    DXGameObject MoonObj; 
    DXGameObject Sun_Earth_Obj; //지구 공전 obj
    DXGameObject Earth_Moon_Obj; //달 공전 obj
    
    ...
}

//DirectX11Graphics.cpp
void DirectX11Graphics::Update() // update 영역에서 지구를 자전시키는 코드를 생성
{
	...
    
    // timer class를 만들어서 델타타임을 활용 하게 만듬
	const float deltaTime = timer->GetMilisecondsElapesed();
	static float earthYaw = 0;
	
    earthYaw += deltaTime * 0.1f;
	EarthObj.GetTransform()->SetLocalRotation({ 0,earthYaw, 0 });  // 지구의 y축 회전(자전)
    
	//====태양계 생성으로 인한 태양,달 추가 =====//
	static float SunYaw =0 , MoonYaw =0;
	SunYaw += deltaTime * 0.05f;
	MoonYaw += deltaTime * 0.1f;
    
	SunObj.GetTransform()->SetLocalRotation({ 0, SunYaw, 0 }); // 태양의 y축 회전(자전)
	Sun_Earth_Obj.GetTransform()->SetLocalRotation({ 0, SunYaw, 0 }); // 태양-지구의 y축 회전(지구 공전)
	Earth_Moon_Obj.GetTransform()->SetLocalRotation({ 0, MoonYaw, 0 }); // 달의 y축 회전(자전)
	MoonObj.GetTransform()->SetLocalRotation({ 0, MoonYaw, 0 }); // 지구-달 의 y축 회전(달 공전)
    
    ...
}

//Scenc(장면) Init 함수 (화면에 보여지는 구성 요소들을 초기화하는 함수)
bool DirectX11Graphics::InitializeScene()
{
	...
    //====태양계 생성으로 인한 태양,달 추가 및 node연결 작업 =====//
    //태양
   	Sun = std::make_unique<PrimitiveSphere>(); //mesh를 구 모양으로 초기화 
    Sun->Set_size(10); //태양의 Scale Set
    Sun->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer); //Sun Model Init
    Sun->MakePrimitive("Textures\\sun1024.jpg"); // mesh에 텍스쳐 이미지 추가
    SunObj.AddComponent<DXMeshRenderer>()->SetModel(parentPrimitive.get());//Renderer component를 추가후 Sun Mesh를 저장

    //지구공전(Sun_Earth_Obj)의 부모를 태양(SunObj)로 Set
    Sun_Earth_Obj.GetTransform()->SetParent(SunObj.GetTransform());
   
    //지구
    Earth = std::make_unique<PrimitiveSphere>(); //mesh를 구 모양으로 초기화 
    Earth->Set_size(3); //지구의 scale Set
    Earth->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);// Earth Model Init
    Earth->MakePrimitive("Textures\\earth2048.bmp"); // mesh에 텍스쳐 이미지 추가
    EarthObj.AddComponent<DXMeshRenderer>()->SetModel(Earth.get()); //Renderer component를 추가후 Earth Mesh를 저장
    
     // 지구의 x축 좌표 이동(지구 공전 obj 기준으로 x축이동)
    add_pos = { 25.0f,0,0 };
    EarthObj.GetTransform()->AdjustPosition(add_pos); 
    
    //지구(EarthObj)의 부모를 지구공전(Sun_Earth_Obj)로 Set
    EarthObj.GetTransform()->SetParent(Sun_Earth_node.GetTransform());

     //달 공전(Earth_Moon_Obj)의 부모를 지구(EarthObj)로 Set
    Earth_Moon_Obj.GetTransform()->SetParent(EarthObj.GetTransform());
	
	//달
    Moon = std::make_unique<PrimitiveSphere>(); // 구 모양 mesh
    Moon->Set_size(1);
    Moon->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);
    Moon>MakePrimitive("Textures\\moon1024.bmp"); // mesh에 텍스쳐 이미지 추가
    MoonObj.AddComponent<DXMeshRenderer>()->SetModel(childPrimitive.get());
    
    // 달의 x축 좌표 이동(달 공전 obj 기준으로 x축이동)
    add_pos = { 5.0f,0,0 };
    MoonObj.GetTransform()->AdjustPosition(add_pos);
    
     //달(MoonObj)의 부모를 지구공전(Earth_Moon_Obj)로 Set
    MoonObj.GetTransform()->SetParent(Earth_Moon_Obj.GetTransform());
    
    ...
}

void DirectX11Graphics::RenderFrame() 
{
	...
    //GameObject Primitive Mesh Draw
    //world space에 배치된 obj를 뷰투영행렬을 연산해 카메라시점으로 변환
    //인자값으로 카메라obj에 있는 뷰투영행렬을 Get
	EarthObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
    
     //====태양계 생성으로 인한 태양,달의 카메라시점 변환 =====//
   	SunObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
    MoonObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
    
    swapChain->Present(NULL, NULL);
}
최종적으로 이런 간단한 태양계가 렌더링된다.

 

그래픽스 파이프라인의 각 과정의 설명 간단하게 적으려고 한다.
코드나 추가내용이 필요한 부분은 따로 파트별로 글을 작성하려고 한다.
이 글에는 코드의 내용은 없으면 역할 정도만 간단한 설명을 하고있다.


Graphics Pipeline(그래픽스 파이프라인)

더보기
그래픽스 파이프라인

3차원 이미지를 2차원 래스터 이미지로 표현을 하기 위한 단계적 방법

그래픽스 렌더링 파이프라인, 렌더링 파이프 라인 이라고도 불리며 
코딩을 하는순서가 아님!!, 컴퓨터 내에서 그래픽을 생성하는 진행도를 나타냄

그래픽스 파이프라인 진행도에 따른 폴리곤 생성 과정

주황색 박스 : 조작가능(Programmable)한 단계를 나타낸다.
파이프라인 옆에 있는 그림처럼 각 진행도처럼 적용된다.

그래픽스 파이프라인에서 작업되는 진행도(레스터라이즈 이후에는 색을 칠한다)

 

Raster(래스터) :

컴퓨터에서 어떠한 도형(또는 이미지)를 2차원 배열 형태의 픽셀로 구성하여 이 점들의 모습을 조합, 

일정한 간격의 픽셀들로 하나의 Scene(장면)을 표현하는 것 

 

//그래픽스 파이프라인 참고사이트 

https://learn.microsoft.com/ko-kr/windows/uwp/graphics-concepts/graphics-pipeline https://m.blog.naver.com/jyh0841/220473615493

Input Assembler(IA, 입력 조립기)

더보기

 

vertex버퍼의 정점(vertex) 데이터를 이용하여 파이프라인에서 사용할 primitive(프리미티브)로 조립
vertex버퍼의 data는 좌표,uv좌표,색상등 여러가지 data값이 있다.

 

p.s primitive(프리미티브) : 이용 가능한 가장 단순한(작은) 처리 단위 

Vertex Shader(VS, 정점 셰이더)

더보기

조립된 primitive를 이용하여 모든 vertex에 대한 연산 처리

-Shape Transform(공간변환) 작업 수행

-많은 비용이 드는 작업이기 때문에 GPU에서 처리

Shape Transform(공간변환) 과정

더보기

World Transform(월드 변환)Local / Model Space(로컬/모델 공간)에서 World Space(월드 공간)으로 변환

3D월드(필드)로 Mesh를 옮기는 과정 , 행렬(Matrix) 변환이 필수

행렬을 이용하여 Translate(이동),Rotate(회전),Scalling(크기) 변환을 수행

 

World Matrix(월드 행렬) = Scale Matrix(크기 행렬) * Rotation Matrix(회전 행렬) * Translation Matrix(이동 행렬)


 

Shape Transform 의 World Transform&nbsp; 진행도

Local/Model Space : 3D 오브젝트 모델을 처음 생성할 때에 모델을 가장 편리한 방법으로 배치
편리한 방법 = 오브젝트(도형) 자신의 기준점을 원점
ex) 구를 만들 땐 구의 중심의 좌표를 (0,0,0)으로 설정하고, 반지름을 간단하게(반지름 =1) 설정

World Space : 각각의 오브젝트(도형)들이 월드의 원점을 기준으로 하고

오브젝트의 상대적 위치를 표현하기 위한 좌표계(공간) 이다.

-월드 좌표계에는 한 개의 원점이 존재한다.
-월드 좌표계는 한 개 이상 존재할 수 있으며 , 월드 좌표계가 다르면 오브젝트끼리 서로 침범 할 수 없다.

 

월드 변환이 필요한 이유 : 각각의 오브젝트를 월드변환을 거치지 않고 World Space에 배치하면 오브젝트끼리 겹치하는 현상이 발생


따로 글 작성 예정 / 필요내용 : 게임수학 - 행렬 계산 방법 , 변환 행렬 들 작성  

(링크 달 공간)

Tesslator(테셀레이터)

더보기

폴리곤 안에 vertex를 추가하여 굴곡(곡면)을 주는 작업

-안티앨리어싱과 유사함(똑같은건 아님)

-이 과정은 생략이 가능

View Transform(뷰 변환)

더보기

월드 상의 오브젝트들을 쉽게 관찰하기 위한 작업

World Space(월드 공간)의 오브젝트들을 view / camera space (카메라 공간)으로 변환 하는 작업 

월드에 배치된 오브젝트들을 관찰점(카메라 좌표) 기준으로 변환한다.

 


따로 글 작성 예정 / 필요내용 : 카메라 오브젝트(객체) 만드는 코드 등 

(링크 달 공간)

Projection Transform(투영)

더보기

3차원(n차원) 2차원(n-1차원)으로 변환 

그림 출처 : https://mooneegee.blogspot.com/2015/02/directx9-world-transform-viewing.html

그림처럼 3D의 빨간 구와 노란 구는 near clip plane으로 투영되서 2D 이미지가 되었다.
위의 구성은 먼 거리(빨간 구)는 크게 , 가까운 거리(노란 구)는 작게 구성을 한다.
단 Near와 Far Clip의 비율은 같아야한다. 

원근감을 살릴려면 빨간구를 작게 노란 구가 커야된다고 생각하지만

그림 출처 : https://zamezzz.tistory.com/98

가까운 쪽을 늘리면서 원근감을 만들것이기 떄문에 상관이 없어진다


그림 출처 : http://www.songho.ca/opengl/gl_projectionmatrix.html

녹색 구는 Clipped 지역에 있으므로 녹색 구는 Clip Space 밖에 있어 투영되지 않는다. 

-Clip Space(클립 공간)은 Frusturm(절두체)이 정의 ( Clip Space = Frusturm )
-Clip Space는 NDC(Normalize Device Coordinate)라고도 불리며, 이 공간은 투영(Proection) 행렬에 정의

 

따로 글 작성 예정 / 필요내용 : 투영 행렬 작성

(링크 달 공간)


p.s NDC(Normalize Device Coordinate) = 정규 좌표

: 정규 좌표(NDC)는 1을 기준으로 하는 2차원 좌표, 좌표계의 원점은 화면의 점 중앙( 0,0)

NDC (Normalize Device Coordinate)

이 공간안에 있는 것들은 투영하고 밖에 있는것은 제외하게 된다 

정규 좌표를 사용하면 해상도에 따른 화면좌표 계산이 단순화 됨, 해상도를 제외한 값을 계산해 두면 해상도가 변할 경우 쉽게 실제 화면 계산이 가능해진다.

즉, 화면 해상도 차이에 빠르게 적응하기 위한 하나의 도구입니다.

Geometry Shader(GS, 기하 도형 셰이더)

더보기

처리된 vertex 데이터에 입력 받은 정보를 이용하여 파티클,털,모션블러 등등의 기하처리를 한다.

PS(Pixel Shader)에서 사용할 보간 작업을 수행

 

p.s 보간

: 새로운 점을 만들기 위해 수많은 점들을 평균화시키는 것, 즉, 두 점을 연결하는 방법을 의미한다.

여기서 말한 연결은 궤적을 생성한다는 뜻이다.

예를 들어 컴퓨터에서 선을 그릴때 ,선은 무수히 많은 점들을 그려 선을 만드는 것이다.

하지만 이 점들을 전부 메모리로 가지고 있으면 메모리 낭비가 심하기 때문이다.

그렇기 때문에 보간작업시  두 점(vertex)을 이용하여 선의 색을 보간에 맞춰 작업 하고 그린다.

포물선 같은경우는 3개의 점이 필요로 하다.

보간에 관련하여 참고한 사이트
https://m.blog.naver.com/ojh6t3k/20196349385

Rasterizer(RS,레스터라이저)

더보기

PS(pixel shader)를 작업하기 위해 준비를 갖추는 과정
-하드웨어 자체 알고리즘을 통해 동작

-Pixel Shader의 호출 방법을 결정
-Pixel Shader 단계를 위한 기본 요소를 준비

Rasterize(레스터라이즈) 과정

: 레스터라이즈 진행시에 작업되는 내용들을 정리했다.


Light(조명

더보기

조명을 비추어 빛이 물체에 닿았을 때 물체의 표면으로부터 반사되는 색상

빛이 월드 공간에 적용됐을 때 물체가 반사하려는 색상을 결정

 

빛을 적용시키려면 빛의 반사 벡터를 알아야되는데 , 이 내용은 나중에 게임수학을 다루면서 적도록 하겠다.

(링크 적용)

 

1. 빛의 요소 : 

1-1.Spcular Light(정반사광) : 한 방향으로만 반사되는 빛 , 입사각과 반사각이 똑같은 특징을 갖고있음

1-2.Diffues Light(난반사광) : 물체의 표면에 반사되어 여러 각도로 반사되는 빛 

1-3.Ambient Light(환경광) : 다른 표면에 반사되어 전반적인 장면을 밝게하는 빛 

빛의 요소 3가지를 간략히 표혀한 그림

 

2.광원

2-1. Direction Light(방향성 광원) : 위치는 가지지 않음, 지정된 방향으로 평행하게 빛을 발산 . ex)태양

2-2. Point Light(점 광원) : World Space내에 위치를 가지며 모든 방향으로 빛을 발산 ex)성냥불,전구

2-3. SpotLight(스팟 광원) : 광원은 위치를 가지며 특정한 방향으로 원뿔형태의 빛을 발산 ex)손전등


이 요소들을 구조체로 보면 아래와 같은 코드로 적용이 된다.
//Material 구조체에 포함되어있는 빛의요소 data
struct Material
{
	XMFLOAT4 ambient;
	XMFLOAT4 diffuse;
	XMFLOAT4 specular;
	float specularPower;
};

//Light 구조체 , Material을 멤버변수로 가지고 있다.
struct Light
{
	XMFLOAT4 camPosition; // 카메라의 위치(빛 위치)
	XMFLOAT4 light_color; // 빛의 색
	XMFLOAT4 light_direction; // 빛의 방향 
	XMFLOAT4 globalAmbient; //월드 환경광 -> material과 합쳐져서 유저한테 보여진다.
	Material material;
}

 Material 안에 Light(빛의 요소)들이 적용되어 색상을 반사하게 되는것이다. 

코드 중의 specularPower는 정반사광의 날카로운 정도를 지정하는 변수이다.(값이 높을수록 하이라이트를 강조)

 

Light를 적용시 Material은 반드시 필요한 존재가 된다.

 

Back-face Culling(후면 컬링)

더보기

레스터라이저에서 세팅한 전면 그리기 방식 방향을 이용하여 보이지 않는 뒷면을 렌더링 및 연산에서 제외

사진 예시가 이런것 일뿐 , 사실 후면도 전면이다, 카메라 시 점을 180도로 돌리면  물체의 뒷면도 그려야되기 때문

Cliping(클리핑)

더보기

Clip Space(클립 공간)에서 벗어난 요소를 렌더에서 제외(렌더링에서 제외 될뿐 연산은 유지)

Cliped 부분을 제외하게 된다.

Frustum Culling(절두체 컬링)

더보기

-완전한 내부 : 폴리곤이 완전히 절두체 내부에 위치하면 보존 
-완전한 외부 : 폴리곤이 완전히 절두체 외부에 위치하면 제외 
-부분적 내부 : 폴리곤이 부준적으로 절두채 내부에 위치하면 두 부분으로 나누어 내부에 위치한 부분은 보존하고 나머지는 제외 

이 절두체 컬링은 cliping과 비슷해보이지만 다른 개념이다.

Cliping은 렌더링 되는 객체들을 화면에 표시하기 전에 잘라내는 과정을 말하는것이고 
Frustum Cilling은 카메라의 시야(Frustum)에 포함되지 않는 객체를 제거하는 과정이다. 

하지만 두 공통점은 렌더링에서는 제외지만 연산은 적용이 된다는 점이다. 

Culling 과 Cliping의 차이

더보기
 
 
컬링 
클리핑 
의미 
보이지 않는 폴리곤은 렌더링 및 연산에서 제외 
보이지 않는 폴리곤은 렌더링에서 제외 
연산 
제외 
실행 
정확도 
낮다 
높다 
속도 
빠르다 
느리다 

ViewPort(뷰포트)

더보기

ClipSpace(투영공간)의 좌표를 스크린(우리가 보는 화면) 좌표로 변환 

-투영된 정보를 Screen에 출력할 수 있는 크기로 변환 

-클라이언트 Clip Space를 출력할 영역을 설정

뷰포트 예시

Pixel/Fragment Shader (PS, 픽셀 셰이더)

더보기

각 픽셀의 데이터를 생성

-입력 값(Textuer)을 결합하여 색상을 결정

-기본 요소에 대한 보간된 데이터를 받아 색상과 같은 픽셀 데이터를 생성(Resterizer 단계에서 가져온 data)

-조명 및 픽셀에 대한 후 처리 가능

-CPU는 Pixel 각각 한개의 전부 접근해서 작업하지만 GPU는 병렬작업이 가능하기때문에 PS는 GPU로 사용됨

Output Merger(OM ,병합 출력기)

더보기

다양한 유형의 출력 데이터(Pixel Shader값 ,Z/Stencil buffer 정보)를 렌더링 대상Z-buffer,Stencil_buffer의 내용과 결합하여 최종 파이프라인 결과를 생성한다. 

 

글 작성자가 공부하고 배운것을 간략하게 정리하는 페이지 입니다.

필요한 정보가 있으시면 확인하시고 만약 잘못된 내용이 있거나 이런 내용이 추가됬으면 좋겠다라면 댓글 부탁드립니다.

감사합니다.

 


좌표계

더보기

x,y,z 축이 있는 좌표계를 사용,

왼손좌표계 :
-D3D(DirectX3D)에서 사용되는 좌표계
-MS사에서 제작한 API, 윈도우 최적화
-윈도우 개발환경(빌드) 인경우 개발시에 많이 사용됨

 

오른손 좌표계:

-OpenGL(Open Graphics Library)에서 사용되는 좌표계

-멀티 OS 환경에서 사용(멀티플랫폼) -> 범용성은 좋으나, 최적화는 떨어짐

좌표계에 따라 Z축의 방향이 달라진다.

Z축의 방향이 중요한 이유 : 어떤 방향으로 그릴지의 기준이 되기 떄문!!

 

Vertex(정점)

더보기

말그대로 꼭지점, 물체의 기본을 이루는 원소와 유사함

Polygon(폴리곤)을 이루는 삼각형의 꼭지점

Vertex는 다양한 정보를 가지게 할 수있으며 , 그 정보를 토대로 그래픽스 작업을 한다.

 

ex) Vertex에 담겨있는 데이터들

-좌표계의 좌표 -> x,y,z,w

w=1 이동변환의 영향O

w=0 이동변환의 영향 X, 노멀값(노멀벡터)과 같은 방향 벡터일 경우

(w는 위치좌표 -> 3차원은 4x4 행렬을 취함)

-회전 각도 -> x,y,z,r (축을 중심으로 회전,각도 )

-UV좌표계 -> u,v ( 범위가 0,0 ~ 1,1인 좌표계, u = x , v= y)

-색깔(color) -> r,g,b,a(RGB값 알파블렌드(투명도))

... API에서 제공하는 Vertex가 있을수도 있고 개발자가 만들어 원하는 값으로 쓸 수도 있다.

 

Polygon(폴리곤)

더보기

3D 컴퓨터 그래픽에서 면의 조합으로 물체를 표현할 때의 각 요소

각 요소는 삼각형을 취하고 있음, 원자(Vertex)들이 모여져 만든 분자와 유사함

즉, 삼각형 하나가 폴리곤이라고 생각하면 편하다.

 

p.s 왜 Polygon의 각 요소는 삼각형을 취하고 있는가??

▶삼각형은 곡면(구체)를 만들기에 최적화가 되어있기 떄문이다.

 

Mesh(메시)

더보기

다면체의 형태를 구성하는 폴리곤의 집합 (실제로 사용자들이 눈으로 보는 물체)

Polygon의 집합체, Mesh

 

Vertex,Polygon,edge 및 Mesh가 전부 포함

 

Modeling(모델링)

더보기

컴퓨터가 이해할 수 있는 형태로 Mesh등의 정보를 저장한 데이터

프로그래머들이 사용하는 요소, 아트 계열 프로그래머들을 모델러 라고 부르는 이유이다.

 

Vertex를 이용하여 삼각형을 그릴때 필요한 개념

더보기
<왼손좌표계>시작좌표는 원하는 위치로 잡고 시작하면된다. 3차원 좌표계이기 떄문에 뒷면도 존재

Vertex의 인자값(좌표값)을 입력순서에 따라 폴리곤 그리는 방향이 정해진다

왼손 좌표계(D3D) : 시계방향, 시계방향으로 그려진 면이 앞면
오른손 좌표계(OpenGL) : 시계반대 방향, 시계반대 방향이 앞면

 

이 방향으로 인하여 왼손 좌표계 <> 오른손 좌표계 전환에 활용 할 수 있다.

즉,D3D에서 빌드하던것을 가져와서 설정값(vertex순서 및 좌표 등등)을 변경 후 OpenGL환경에서 사용할 수 있는것이다.

 

Polygon의 앞면,뒷면 선정이 중요한 이유 

더보기

유저들이 보는 면은 앞면일 확률이 높다. 그러므로 앞면에 이미지를 그리는것이 일반적이다.

그럼 뒷면에도 그림을 그려야 하는가? 아니다.

 

개발시에 그림을 그리는 작업의 처리비용이 매우 높기 때문이다.

그런 이유로 앞면을 정해서 앞면만 그리고 뒷면은 그리지않아 처리비용을 줄이려고 하는것이다.
(일반적으로 그런거지, 뒷면도 그리게 할수 있음)

 

일반적으로 게임에서 벽 텍스처를 뚫고 지나가면 뒷면에 아무것도 없는 경험이 보통 이런경우이다.

 

Rendering(렌더링)

더보기

월드 상에 모델링을 이용하여 만들어진 장면(Scene)을 컴퓨터가 조명의 배체와 면의 특성, 기타 다른 설정들을 바탕으로 계산하여 그림을 생성

렌더러는 2D의 이미지를 1장의 사진(그림)을 찍는는 개념이다.
이 이미지들을 이제 연속으로 찍어 보여주는것이 애니메이션이다.

레이아웃 : 물체(Object)를 작업 공간에 배치

애니메이션 : 배치된 물체의 움직임을 설정

 

Texture(텍스처)

더보기

3차원 물체의 표면 2차원 이미지를 입혀서 적은 폴리곤으로도 높은 디테일을 표현 할 수 있게 해주는 렌더링 관련 요소, 텍스처의 좌표축은 UV좌표계를 사용한다.

 

UV좌표계 : 0,0 ~ 1,1의 크기를 가지고있는 좌표계,
이유 : 텍스처 이미지를 로드할때 비율,사이즈 등 각각 다르기때문에 UV 좌표계 기준으로 변환한것이다. 

UV좌표계를 이용하여 이미지를 텍스처 맵핑 하는 간략한 과정

 

Suface(서피스)

더보기

이미지 데이터를 저장하는데 사용되는 버퍼, 화면에 출력되는 면

즉 2D이미지 버퍼 ( 텍스처파일은 원래 2D이고 3차원 물체이 입히는 형식)
서피스는 WINAIP의 backDC와 유사하다 

(backDC 개념은 더블버퍼링에서 나온 것, 더블버퍼링도 화면에 출력 할 이미지를 저장 후 사용하는 용도이다.)
서피스는 보통 폴리곤의 앞면,뒷면의 서피스를 각각 갖고있다.

 

2023.06.05

수정: 컴퓨터 그래픽스에서 말하는 Surface는 버퍼이다. 
(버퍼 : 메모리(data)를 저정하는 공간)

취소선의 내용은 내가 똑같은 Surface라고 정리를 했는데, 
폴리곤의 surface는 그대로 '표면'이란 뜻으로 사용된거다.

폴리곤 앞면(front surface),뒷면(back surface)

즉, 여기서 설명한건 버퍼의 역할이기 때문에 잘못 설명한것이 된다. 

 

 

Culing(컬링)

더보기

렌더링 작업 중 렌더링 할 필요가 없는 요소들을 선별하여 제외하는 기법
가시성 선별(Visibility Culing)기법이라고도 불린다.

 

cut에서 가져온 단어 -> 이미지의 필요없는 부분을 자른다는 뜻

ex) surface는 앞면,뒷면을 다 가지고 있지만 뒷면은 그릴 필요가 없기에 제외할때 사용됨

 

Anti-Aliasing(안티 앨리어싱)

더보기

이미지의 픽셀계단처럼 출력되는것을 앨리어싱이라 하며, 이 계단현상을 없애기 위해 생겨난 기법

비교사진

기법으로는 슈퍼샘플링,멀티샘플링 등이 있다.(여러 종류가 있음)
슈퍼 샘플링 : 처리비용이 높음, 효율이 좋음
멀티 샘플링 : 처리비용이 낮, 효율이 낮음

 

Projection(투영)

더보기

간략히 말하면 n차원을 n-1차원으로 바꾸는 것,

투영 개념은 투영행렬 및 선형대수가 필요한 개념이기에 따로 정리가 필요하다.

Frustum(절두체)를 설명하려면 필요한 개념이기에 간략히 적어놓았음

 

Frustum(절두체)

더보기

투영행렬(Projection Matrix)을 계산할 때 범위의 모양이 사각뿔의 머리를 잘라 둔 것처럼 생겨서 절두체라고 한다.
카메라의 크기와 범위 모양

절두체 = 카메라 영역 = 렌더링 영역

: 카메라 앵글(유저의 시점)밖에 있는건 찍지 않고 카메라에 담긴 영역(렌더링하는 영역)을 그려야되기 떄문에 영역(절두체)를 알아야 한다.

카메라의 시점이 사각뿔 모양처럼 생겼고 이걸 렌더링하는것이 절두체이다.

그래픽스파이프라인의 Projectino Transforn(투영변환)을 공부할때 더욱 자세하 알수 있을것이다.

 

Z-buffer(깊이 버퍼), Stencil Buffer(스텐실 버퍼)

더보기

Z-buufer : 화면에 그려진 각 픽셀의 Z(깊이) 값을 저장,
절두체에서 픽셀의 깊이 값을 기록하는데 사용(원근감)

Frusturm의 화면을 렌더링하였을때 : (왼)겹치는 영역을 표시 (오) Z-buffer를 적용하여 원근감을 준 화면

각각 다른 Z값(깊이)를 가지고 있는 물체들이 존재 할때  카메라시점으로 보면 A,B의 겹치는 영역이 존재한다.
Frusturn(절두체)에서 기록한 Z값을 이용하여 A,B의 겹치는 영역중 가까운 영역부터 그려서 원근감을 부여한다.


Stencil Buffer : 특정 영역이 렌더링 되는것을 막기위해 사용되는 버퍼 

FPS 게임중 조준 하는 경우 (왼)적용되기전 (오)Stencil버퍼를 적용 후

위에 사진처럼 렌더링 하기전 Stencil buffer의 데이터를 이용하여 렌더링에서 제외하는것이다.
예시로는 저격총의 에임, 거울에 비춘 물체 시점(거울 안에서만 비춰야되고 거울 밖은 비추면 안되는상황)
스파이/잡임 게임에서 플레이어의 시점으로만 조명이 비추고 나머지는 어둡게하는 상황을 표현할때도 사용됨

 

이 두 버퍼는 렌더링 되기전에 사용되는 버퍼이며 이 두 버퍼는 개발시에 보통 한번에 만들어서 한번에 작업처리를 한다. 

 

Shader(셰이더)

더보기

3D컴퓨터 그래픽에서 최종적으로 화면에 출력하는 픽셀의 색 정해주는 함수

색의 농담,색조,명암 효과를 주는 주체 

그래픽스파이프라인에서 VertexShader,PixerShader에 대해 배울때 자세히 알수있기때문에 간략히 적어놓았다.

 

회전(rotation)

더보기

▶Euler Angle(오일러 각)

3차원 공간의 절대적 좌표를 기준으로 물체의 회전을 측정하는 방법
X,Y,Z 을 기준으로 회전하기 때문에 직관적이고 조작이 용이하다.

180도가 넘는 회전의 표현이 가능하다.

3번에 나누어 계산하기 때문에 비용이 크며 Gimbal Lock(짐벌 락)이 발생할 수 있다.

 

간단히 말하면 X축 회전 계산, Y축을 회전, 계산 Z축을 회전 계산 계산이 많이 들수 밖에 없다. 

 

▶Quaternion(쿼터니언)

방향회전을 표현한다.

4개의 성분(x,y,z,w)로 이루어져 있으며 각 성분은 축이나 각도가 아니다.

벡터(x,y,z : 방향)와 스칼라(w:roll,회전)를 의미한다.

180도가 넘는 회전을 표현하지 못한다. (대처가능 : 210도 회전 = 179도 회전 + 31도 추가회전)

각 축을 한번에 계산하여 비용이 적고 Gimbal Lock(짐벌 락)이 발생하지 않는다.

 

Gimbal Lock(짐벌 락): 두개의 회전 축이 서로 겹쳐지는 현상, 축이 3개가 되고 나서 발생한 문제

축이 겹쳐지면서 하나의 축이 제 역할을 못하게 되는 현상 
Euler (gimbal lock) Explained 이 영상 한번 보면 이해가 쉽게 됩니다. 

 

회전은 자세히 들어가면 사원수라는 수학 개념이 들어가기 때문에
나중에 수학을 다뤄볼때 자세하 적어보겠다.


D3D에서는 회전을 표현하는 용어가 따로 있다.

X축 : pich(피치)

Y축 : yaw(요)

Z축 : roll(롤)

D3D에서 D3DQuaternionRotaionYawPitchRoll()를 이용하여 쿼터니언 회전이 가능

 

Material(재질)

더보기

조명을 비추어서 빛이 물체에 닿았을 때 물체의 표면으로부터 반사되는 색상

빛이 월드 공간에 적용 됐을 때 물체가 반사하려는 색상을 결정

빛(조명)이 적용될때 반드시 Material(재질)이 필요하다.(중요)

 

Amibient(환경광) Diffues(난반사광) Specular(정반사광) 을 다 합치면 최종적으로 우리가 보는 물체가 나온다.

위 이미지에서 나온내용은 빛(조명)에 관련된 내용이기 때문에 설명을 따로 하지 않겠다.

 

OverDraw(오버드로우)

더보기

렌더링의 단일 프레임에서 시스템이 화면에 한 픽셀을 여러 번 그리는 것 즉, 겹쳐서 그려진다는 뜻
처리비용이 높아지는 이유 중 하나 (문제점)

이미지 출처 :&nbsp;https://www.racketboy.com/retro/about-video-games-rasterization-and-z-buffer

이미지는 Z-Buffer를 설명하는 이미지이긴 하지만, 오버드로우를 표현하기에는 적절한 이미지여서 포함했다.
현재 이미지를 보면 한 픽셀에 S2 -> S1->S3 순서로 전부 한번씩 그려진후에 최종적으로 S3가 그려진다.
이 부분들은 S3만 그리게 되면 한번만 그리면 되는데 이런식으로 그리게 되면 총 3번의 작업이 되며 처리비용이 높아진다.


*오버드로우를 대처 및 수정하는 방법

1.레이아웃에서 불필요한 배경삭제

의미없는 배경들을 최대한 삭제로 오버드로우를 줄일수 있다.

 

2.뷰 계층 구조의 평면화

레이아웃들이 겹치면 안보이는데 그려야되는 경우가 생길수 있다.
그럼으로 이런 점을 고려하여 그리면 줄일 수 있다.

 

3.투명도 감소

화면에 투명 픽셀을 렌더링하는 것(알파렌더링)오버드로우의 주요 원인,

기존 픽셀에 먼저 그리고 투명 픽셀을 혼합하여 원하는 시각효과를 뽑아냄으로 오버드로우가 발생한다.

즉, 투명객체의 수를 줄이면 오버드로우를 어느정도 대처 할 수 있다.

ex)회색 텍스트를 출력하려 할때 , 검정색 텍스트 + 투명 픽셀(투명도 조절) 이게 아닌 회색으로 텍스트를 출력하면 더욱 성능개선이 된다는 뜻

 

Quad OverDraw(쿼드 오버드로우) [위에서 설명한 오버드로우랑은 다른 개념]

더보기

폴리곤을 그릴때 4개의 픽셀을 하나의 단위로 그

리는데, 이때 낭비되는 영역쿼드 오버드로우다.

회색 삼각형을 그릴때 녹색픽셀만 그리는게 아닌 빨간색 영역까지 그려지게 된다.&nbsp; (빨간색 영역 = 쿼드 오버드로우)

4개의 픽셀을 기준으로 그리는 이유는 텍스쳐 샘플링 떄문이다.
이 텍스처 샘플링이 부하가 큰 작업이다. 그래서 픽셀 수에 딱 맞는 해상도의 텍스처를 미리 여러개 준비해놓고 픽셀 수에 맞는 준비된 레벨의 텍스쳐만을 사용해서 렌더링을 하게된다.
이 작업을 텍스쳐의 mip level(밉 레벨) 이라고 한다. 

즉, mip level은 동일한 그림을 다양한 해상도의 사이즈로 텍스쳐를 준비하는 작업이다.

밉 레벨 계산은 4픽셀 씩 그리면서 ddx,ddy 미분을 해서 정확한 텍스쳐의 밉레벨 계산을 한다.


폴리곤의 크기(화면상의 크기)가 작을수록 쿼드 오버드로우가 심하게 발생된다.

가까이 있는 물체들은 그렇게 심하지 않지만 멀어지면 멀어질수록 쿼드오버드로우가 발생하고있다.

이미지의 빨간색이 쿼드오버드로우가 발생하고 있다는 뜻이며 
점점 멀어지게 되면 폴리곤은 한픽셀보다도 작아질수 있다.
픽셀보다 작은데 그 하나의 폴리곤을 나타내기 위해 4개의 픽셀을 사용하는것이다.

쿼드오버드로우를 줄이기 위하여 삼각형의 크기를 한 픽셀보다 더 작아지지 않게 하기 위한 기능이 바로 LOD 기법이다. (오버드로우 때문이 아님) 

쿼드오버드로우는 https://bbs.ruliweb.com/hobby/board/600001/read/1678 이 게시글을 참고하여 작성하였습니다.

 

+ Recent posts