Material
Material 이란 물체들이 가지고 있는 잡동사니 특성이라고 생각하면 된다. 쉐이더를 통해서 물체를 그려줄 때 넘겨주는 인자중 하나인 셈이다. 어떻게 물체를 표현할 것이고 이 물체는 어떤 재질이고 빛은 어느 색을 흡수할 것인지 등등 을 넘겨주는 것이다. Texture와 같은 것도 Material에 포함되어 있는 것이다.
#pragma once
#include "ResourceBase.h"
class Material : public ResourceBase
{
using Super = ResourceBase;
public:
Material();
virtual ~Material();
shared_ptr<Shader> GetShader() { return _shader; }
MaterialDesc& GetMaterialDesc() { return _desc; }
shared_ptr<Texture> GetDiffuseMap() { return _diffuseMap; }
shared_ptr<Texture> GetNormalMap() { return _normalMap; }
shared_ptr<Texture> GetSpecularMap() { return _specularMap; }
void SetShader(shared_ptr<Shader> shader);
void SetDiffuseMap(shared_ptr<Texture> diffuseMap) { _diffuseMap = diffuseMap; }
void SetNormalMap(shared_ptr<Texture> normalMap) { _normalMap = normalMap; }
void SetSpecularMap(shared_ptr<Texture> specularMap) { _specularMap = specularMap; }
void Update();
shared_ptr<Material> Clone();
private:
friend class MeshRenderer;
MaterialDesc _desc;
shared_ptr<Shader> _shader;
shared_ptr<Texture> _diffuseMap;
shared_ptr<Texture> _normalMap;
shared_ptr<Texture> _specularMap;
ComPtr<ID3DX11EffectShaderResourceVariable> _diffuseEffectBuffer;
ComPtr<ID3DX11EffectShaderResourceVariable> _normalEffectBuffer;
ComPtr<ID3DX11EffectShaderResourceVariable> _specularEffectBuffer;
};
#include "pch.h"
#include "Material.h"
Material::Material() : Super(ResourceType::Material)
{
}
Material::~Material()
{
}
void Material::SetShader(shared_ptr<Shader> shader)
{
_shader = shader;
_diffuseEffectBuffer = shader->GetSRV("DiffuseMap");
_normalEffectBuffer = shader->GetSRV("NormalMap");
_specularEffectBuffer = shader->GetSRV("SpecularMap");
}
void Material::Update()
{
if (_shader == nullptr)
return;
RENDER->PushMaterialData(_desc);
if (_diffuseMap)
_diffuseEffectBuffer->SetResource(_diffuseMap->GetComPtr().Get());
if (_normalMap)
_normalEffectBuffer->SetResource(_normalMap->GetComPtr().Get());
if (_specularMap)
_specularEffectBuffer->SetResource(_specularMap->GetComPtr().Get());
}
std::shared_ptr<Material> Material::Clone()
{
shared_ptr<Material> material = make_shared<Material>();
material->_desc = _desc;
material->_shader = _shader;
material->_diffuseMap = _diffuseMap;
material->_normalMap = _normalMap;
material->_specularMap = _specularMap;
material->_diffuseEffectBuffer = _diffuseEffectBuffer;
material->_normalEffectBuffer = _normalEffectBuffer;
material->_specularEffectBuffer = _specularEffectBuffer;
return material;
}
중요한 것은 Material의 옵션은 모든 물체가 공유한다는 점이다.
Normal Mapping
이것이 가장 중요하다고 할 수 있다. 실제 면접에서도 무조건적으로 나올 정도로 개념이 매우 중요하다. 실제 코드는 간단하지만 개념을 확실히 해야만한다.
두가지를 확실히 해야하는데
첫번째로, Normal Mapping이 무엇이고 왜 필요한지
두번째로, 이걸 코드로 어떻게 구현해야할지
를 확실히 알아야한다.
캐릭터 등을 모델링한다는 것은 결국 수많은 정점들로 이루어진 특정한 배열에 우리가 텍스처를 이쁘게 입히는 것과 같다.
가령 이런 지갑의 한 면을 만든다고 해보자.
그러면 우리는 사각형을 만들고 그 사각형에 위와 비슷한 텍스처를 입혀서 만들 것이다. 그런데 생각해보면 사각형의 정점은 4개이고 각 정점마다 normal 벡터는 전부 동일하다. 우리가 아무리 좋은 텍스처를 사용한다고 한들 음영까지는 표현할 수 없을 것이다.
그러면 우리는 해결방안으로 삼각형(정점)을 무진장 늘리는 방법이 있을 것이다. 물론 이렇게 해도 상관은 없지만 정점이 늘어나게된다는 것은 결국 렌더링 파이프라인을 지나갈 때 계산해야될 양이 늘어난다는 것이고 그게 과부화를 일으킬 것이다.
삼각형의 갯수를 늘리지 않고 음영을 표시할 수 있는 방법으로 고안된게 Normal Mapping 이다.
음영이라는 것은 결국 빛과 관련이 있고 그 빛의 연산을 하기 위해선 각 정점마다의 normal 벡터가 필요하다. 이는 조명 포스팅에서도 다뤘다.
사각형의 경우 normal 벡터가 전부 동일하기에 음영을 표기하기 쉽지 않아, 특정 픽셀에서의 음영을 위한 normal 벡터의 모음집을 Normal Mapping 이라고 한다.
이제 또 의문이 드는 것은 모델이 움직이거나 애니메이션이 동작하고 있을 때는 normal 벡터가 계속 변하게 될 것인데 이 normal Map은 어느것을 기준으로 작성되어 있는가? 이다.
바로 탄젠트 스페이스 이다.
탄젠트 스페이스는 각 정점에서 수직이 되는 벡터를 기준으로 하나의 평면을 만들고 그 평면의 xyz 를 만든 차원을 의미한다. 각 축은 T B N 으로 이루어져있다.
Normal Map에는 한 픽셀마다 특정 좌표가 기록하게 되는데 그 좌표는 이 탄젠트 좌표계를 기준으로 한 좌표이다.
이것을 사용하기 위해서는 탄젠트 좌표계로 나타난 Normal 벡터를 월드 좌표계로 변환해야만 한다.
#include "00. Global.fx"
#include "00. Light.fx"
MeshOutput VS(VertexTextureNormalTangent input)
{
MeshOutput output;
output.position = mul(input.position, W);
output.worldPosition = input.position.xyz;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)W);
output.tangent = mul(input.tangent, (float3x3)W);
return output;
}
float4 PS(MeshOutput input) : SV_TARGET
{
ComputeNormalMapping(input.normal, input.tangent, input.uv);
float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
return color;
}
technique11 T0
{
PASS_VP(P0, VS, PS)
};
void ComputeNormalMapping(inout float3 normal, float3 tangent, float2 uv)
{
// [0,255] 범위에서 [0,1]로 변환
float4 map = NormalMap.Sample(LinearSampler, uv);
if (any(map.rgb) == false)
return;
float3 N = normalize(normal); // z
float3 T = normalize(tangent); // x
float3 B = normalize(cross(N, T)); // y
float3x3 TBN = float3x3(T, B, N); // TS -> WS
// [0,1] 범위에서 [-1,1] 범위로 변환
float3 tangentSpaceNormal = (map.rgb * 2.0f - 1.0f);
float3 worldNormal = mul(tangentSpaceNormal, TBN);
normal = worldNormal;
}
적용 전과 후 이다.
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 모델 - 모델 띄우기, 계층 구조, ImGuI (1) | 2024.09.30 |
---|---|
[DirectX] 모델 - Assimp, Material 로딩, Mesh 로딩 (0) | 2024.09.27 |
[DirectX] Lighting & Material - Global Shader, Depth Stencil View, 조명 (0) | 2024.09.24 |
[DirectX] DirectX 11 3D 입문 - Height Map, Normal, Mesh (0) | 2024.09.13 |
[DirectX] DirectX 11 3D 입문 - 카메라, 텍스처, Geometry (0) | 2024.09.12 |