-
[DirectX11] Normal의 이해와 활용2024년 03월 06일
- 유니얼
-
작성자
-
2024.03.06.:03
728x90DirectX 11로 게임 엔진 아키텍처 만들기
3D 그래픽에서, Normal 벡터는 단순히 한 점이 아니라, 그 점이 속한 표면이나 면이 어느 방향을 향하고 있는지를 나타내는 방향성을 가진 벡터입니다. 이러한 Normal 벡터는 3D 모델의 렌더링, 특히 조명과 음영, 그리고 물리적 상호작용을 계산하는 데 있어 필수적인 요소입니다. DirectX11을 사용하는 게임 엔진 아키텍처를 설계하면서, Normal 벡터의 이해와 적절한 활용은 사실적이고 다이나믹한 3D 환경을 구현하는 데 있어 핵심적인 역할을 합니다.
참고강의 링크:
Normal 벡터란?
Normal 벡터는 표면이나 경계의 방향을 나타내는 단위 벡터입니다. 3D 그래픽스에서, 각 정점(Vertex)에 대한 Normal 벡터는 그 정점이 속한 면의 방향을 가리킵니다. 이는 빛의 반사, 음영 처리 등을 계산할 때 필수적인 정보로 사용됩니다.
Normal의 중요성
- 조명 및 음영 처리: 라이팅 계산에서 Normal 벡터는 빛의 방향과 면의 방향 사이의 관계를 결정하는 데 사용됩니다. 이를 통해 물체의 밝은 부분과 그림자가 지는 부분을 정확히 계산할 수 있습니다.
- 시각적 디테일의 향상: Normal 맵핑 기술을 사용하여, 고해상도 디테일을 저해상도 모델에 적용함으로써, 성능을 크게 저하시키지 않고 시각적 디테일을 향상시킬 수 있습니다.
- 물리적 상호작용: 충돌 검출, 반사, 굴절 등 물리적 상호작용을 시뮬레이션할 때 Normal 벡터는 충돌하는 표면의 방향 정보를 제공합니다.
DirectX11에서의 Normal 활용
DirectX11과 같은 고급 그래픽 API를 사용하면, 셰이더 프로그래밍을 통해 Normal 데이터를 효율적으로 활용할 수 있습니다. 예를 들어, Pixel Shader에서 Normal 맵을 사용하여 표면의 디테일을 향상시키거나, Vertex Shader에서 Normal 벡터를 조정하여 특정 조명 효과를 구현할 수 있습니다.
실습: Normal 맵핑
Normal 맵핑은 표면의 작은 요철을 표현하기 위해 사용되는 기법으로, 모델에 더 많은 디테일을 추가하려 할 때 유용합니다. 다음은 DirectX11을 사용하여 Normal 맵을 적용하는 간단한 예제입니다.
Normal.fx
// 변환 행렬과 텍스처, 조명 방향을 정의합니다. matrix World; matrix View; matrix Projection; Texture2D Texture0; float3 LightDir; // 버텍스 쉐이더로 넘어오는 입력 데이터 구조체입니다. // 각 버텍스의 위치, 텍스처 좌표, 법선 벡터를 포함합니다. struct VertexInput { float4 position : POSITION; float2 uv : TEXCOORD; float3 normal : NORMAL; }; // 버텍스 쉐이더의 출력 데이터 구조체입니다. // 처리된 버텍스 위치, 텍스처 좌표, 법선 벡터를 포함합니다. struct VertexOutput { float4 position : SV_POSITION; float2 uv : TEXCOORD; float3 normal : NORMAL; }; // 버텍스 쉐이더 함수입니다. VertexOutput VS(VertexInput input) { VertexOutput output; // 입력된 버텍스 위치를 월드, 뷰, 프로젝션 행렬을 사용하여 변환합니다. output.position = mul(input.position, World); output.position = mul(output.position, View); output.position = mul(output.position, Projection); // 입력된 텍스처 좌표와 법선 벡터를 출력 데이터에 복사합니다. // 법선 벡터는 월드 행렬로 변환하여 방향을 조정합니다. output.uv = input.uv; output.normal = mul(input.normal, (float3x3)World); return output; } // 샘플러 상태를 정의합니다. SamplerState Sampler0; // 픽셀 쉐이더 함수입니다. float4 PS(VertexOutput input) : SV_TARGET { // 정규화된 법선 벡터와 조명 방향을 계산합니다. float3 normal = normalize(input.normal); float3 light = -LightDir; // 텍스처 샘플링 결과와 조명 방향과의 내적을 곱하여 최종 색상을 계산합니다. // 이는 간단한 확산 조명 효과를 생성합니다. return Texture0.Sample(Sampler0, input.uv) * dot(light, normal); } // 래스터라이저 상태를 정의합니다. 여기서는 와이어프레임 모드를 설정합니다. RasterizerState FillModeWireFrame { FillMode = Wireframe; }; // 렌더링 기법을 정의합니다. 이 기법은 두 개의 패스를 사용합니다. technique11 T0 { pass P0 { // 첫 번째 패스에서는 버텍스 쉐이더와 픽셀 쉐이더를 설정합니다. SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } pass P1 { // 두 번째 패스에서는 래스터라이저 상태를 와이어프레임 모드로 설정하고, // 동일한 버텍스 쉐이더와 픽셀 쉐이더를 재사용합니다. SetRasterizerState(FillModeWireFrame); SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } };
프로젝트 호출
NormalDemo.h
#pragma once #include "IExecute.h" #include "Geometry.h" class NormalDemo : public IExecute { public: void Init() override; void Update() override; void Render() override; shared_ptr<Shader> _shader; // Object shared_ptr<Geometry<VertexTextureNormalData>> _geometry; shared_ptr<VertexBuffer> _vertexBuffer; shared_ptr<IndexBuffer> _indexBuffer; Matrix _world = Matrix::Identity; // Camera shared_ptr<GameObject> _camera; shared_ptr<Texture> _texture; Vec3 _lightDir = Vec3(0.f, -1.f, 0.f); };
NormalDemo.cpp
#include "pch.h" // 프리컴파일 헤더 파일 #include "08. NormalDemo.h" // 이 클래스의 헤더 파일 #include "GeometryHelper.h" // 지오메트리 생성 도우미 함수가 있는 헤더 파일 #include "Camera.h" // 카메라 클래스 헤더 파일 #include "GameObject.h" // 게임 오브젝트 관리를 위한 클래스 헤더 파일 #include "CameraScript.h" // 카메라 스크립트(카메라 제어) 헤더 파일 void NormalDemo::Init() { // 쉐이더 로드 _shader = make_shared<Shader>(L"07. Normal.fx"); // 지오메트리 생성: 여기서는 구(Sphere)를 생성함 _geometry = make_shared<Geometry<VertexTextureNormalData>>(); GeometryHelper::CreateSphere(_geometry); // 구를 생성하는 도우미 함수 호출 // 버텍스 버퍼 생성 및 지오메트리의 버텍스 데이터로 초기화 _vertexBuffer = make_shared<VertexBuffer>(); _vertexBuffer->Create(_geometry->GetVertices()); // 인덱스 버퍼 생성 및 지오메트리의 인덱스 데이터로 초기화 _indexBuffer = make_shared<IndexBuffer>(); _indexBuffer->Create(_geometry->GetIndices()); // 카메라 설정 _camera = make_shared<GameObject>(); // 카메라를 위한 게임 오브젝트 생성 _camera->GetOrAddTransform(); // 변환 컴포넌트 추가 혹은 가져오기 _camera->AddComponent(make_shared<Camera>()); // 카메라 컴포넌트 추가 _camera->AddComponent(make_shared<CameraScript>()); // 카메라 스크립트(제어) 추가 // 텍스처 로드 _texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg"); } void NormalDemo::Update() { _camera->Update(); // 카메라 업데이트(예: 위치, 회전 등의 변화 반영) } void NormalDemo::Render() { // 쉐이더에 행렬 및 텍스처 리소스 바인딩 _shader->GetMatrix("World")->SetMatrix((float*)&_world); _shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView); _shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection); _shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get()); _shader->GetVector("LightDir")->SetFloatVector((float*)&_lightDir); // 조명 방향 설정 // 버텍스 및 인덱스 버퍼를 입력 어셈블러 스테이지에 바인딩 uint32 stride = _vertexBuffer->GetStride(); uint32 offset = _vertexBuffer->GetOffset(); DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset); DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0); // 쉐이더를 사용하여 지오메트리 렌더링 _shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0); }
결론
Normal 벡터는 3D 그래픽스에서 모델의 형태와 조명을 정확하게 표현하는 데 필수적인 요소입니다. Normal 정보를 활용하여 보다 사실적이고 디테일한 3D 씬을 구현할 수 있습니다. Normal 맵핑과 같은 기술을 통해 성능 저하 없이 시각적 품질을 크게 향상시킬 수 있으므로, 이러한 기법을 적극 활용하는 것이 좋습니다.
반응형다음글이전글이전 글이 없습니다.댓글