2023. 8. 26. 16:01ㆍPublic/GraphicsAPI
목표
- 혼합의 작동 방식과 Direct3D에서 혼합을 사용하는 법을 이해한다.
- Direct3D가 지원하는 여러 혼합 모드를 배운다.
- 기본도형의 투명도를 알파 성분을 이용해서 제어하는 방법을 파악한다.
- HLSL의 clip 함수를 이용해서 한 픽셀이 후면 버퍼에 아예 그려지지 않게 만드는 방법을 배운다.
혼합 공식
현재 래스터화 중인 ij번째 픽셀, 즉 원본 픽셀(source pixel) 에 대해 픽셀 셰이더가 출력한 생삭이 Csrc라고 하자. 그리고 현재 후면 버퍼에 있는 ij번째 픽셀, 즉 대상 픽셀(destination pixel)의 색상이 Cdst라고 하자.
만약 혼합을 사용하지 않는다면 Csrc가 Cdst를 덮어 씌운다.(원본이 깊이,스텐실 판정을 통과했다고 할 때)
그런데 혼합을 적용하면 Csrc와 Cdst를 일정한 공식에 따라 혼합해서(섞어서) 새로운 색상 C가 Cdst를 덮어쓰게 된다.
원본 픽셀 색상과 대상 픽셀 색상을 섞을 때 Dx3D가 사용하는 혼합 공식은 다음과 같다.
C = Csrc☒Fsrc⊕Cdst☒Fdst
위 공식에서 Fsrc와 Fdst는 각각 원본과 대상의 혼합 계수(Blend Factor)로 후에 설명할 값 중 하나이다.
이 혼합 계수들을 적절히 설정하면 원본 픽셀과 대상 픽셀을 다양한 방식으로 혼합할 수 있다.
☒연산자는 색상벡터들의 성분별 곱셈을뜻하고, ⊕ 연산자는 여러이항 연산자중 하나이다.
위의 혼합 공식은 색상의 RGB 성분들에만 적용되고 알파 성분은 개별적인 공식으로 처리된다.
A = Asrc Fsrc ⊕ Adst Fdst
공식 자체는 앞의 공식과 같지만, 색상 혼합의 것과는 다른 혼합 계수들과 이항 연산자를 사용할 수 있다. RGB와 알파의 처리를 이처럼 분리한 이유는 그냥 그 둘을 독립적으로, 따라서 다른 방식으로 혼합할 수 있게 하기 위한 것이다. 이 덕분에 혼합을 아주 다양한 방식으로 적용할 수 있다.
알파 성분의 혼합은 RGB 성분들의 혼합에 비해 훨씬 덜 쓰인다. 이는 일반적으로 후면 버퍼의 알파값들이 별로 중요하지 않기 때문이다. 후면 버퍼의 알파 성분은 대상 알파 값을 필요로 하는 일부 알고리즘에서만 중요하다.
혼합 연산
다음은 혼합 공식의 이항 ⊕연산자로 사용할 수 있는 연산자들이다.
typedef enum D3D12_BLEND_OP
{
D3D12_BLEND_OP_ADD = 1, //C = Csrc ☒ Fsrc + Cadd ☒ Fdst
D3D12_BLEND_OP_SUBTRACT = 2, //C = Cdst ☒ Fdst - Csrc ☒ Fsrc
D3D12_BLEND_OP_REV_SUBTRACT = 3,//C = Csrc ☒ Fsrc - Cdst ☒ Fdst
D3D12_BLEND_OP_WIN = 4, //C = min(Csrc,Cdst)
D3D12_BLEND_OP_MAX = 5, //C = max(Csrc,Cdst)
} D3D12_BLEND_OP;
min,max 연산에는 혼합 계수들이 아무런 영향을 미치지 않는다.
알파 혼합 공식에도 동일한 연산자들이 쓰인다. 다시 이야기하지만, RGB 성분 혼합의 연산자와 알파 성분 혼합의 연산자를 따로 선택할 수 있다. 예를 들어 RGB 성분들은 더하되 알파 성분들은 하나에서 하나를 뺄 수도 있는 것이다.
C = Csrc ☒ Fsrc + Cdst ☒ Fdst
A = Adst Fdst - Asrc Fsrc
최근 Direct3D에는 원본 색상과 대상 색상을 방금 본 전통적인 혼합 공식 대신 논리 연산자를 이용해서 섞는 기능이 추가되었다.
사용할 수 있는 논리 연산자들은 다음에서 확인
https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/ne-d3d12-d3d12_logic_op
- 전통적인 혼합과 논리 연산자 혼합을 동시에 하용할 수는 없음을 주의. 즉, 둘 중 하나만 사용할 수 있다.
- 논리 연산자 혼합은 그것을 지원하는 렌더 대상에서만 사용할 수 있다. 특히, 픽셀 형식이 반드시 부호 없는 정수 형식이어야 한다.
혼합 계수
혼합 연산자들과 원본 혼합 계수, 대상 혼합 계수를 여러 가지로 조합함으로써 수십 가지의 서로 다른 혼합 효과를 얻을 수 있다.
다음은 Fsrc와 Fdst 모두에 적용되는 기본적인 혼합 계수들이다. 추가적인 고급 혼합 계수들에 관해서는 SDK 문서화의 D3D12_BLEND 열거형 항목을 참고하기 바란다.
Csrc = (Rs,Gs,Bs) , Asrc = As (픽셀 셰이더가 출력한 RGBA값들)
Cdst = (Rd,Gd,Bd) , Adst = Ad(렌더 대상에 이미 저장되어 있는 값들)
F = Fsrc 또는 Fdst
F = Fsrc 또는 Fdst (같은 기울임꼴)
https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/ne-d3d12-d3d12_blend
RGB 혼합 공식에는 이상의 혼합 계수들을 모두 사용할 수 있다. 알파 혼합 공식의 경우 _COLOR로 끝나는 계수들은 사용할 수 없다.
혼합 상태
Direct3D의 다른 상태들과 마찬가지로, 혼합 설정들을 담은 혼합 상태는 PSO(파이프라인 상태 객체)의 일부이다.
혼합 상태를 기본 상태와는 다르게 설정하려면 D3D12_BLEND_DESC 구조체를 채워야 한다.
D3D12_BLEND_DESC 구조체의 정의는 다음과 같다.
typedef struct D3D12_BLEND_DESC
{
BOOL AlphaToCoverageEnable; //기본값 False
BOOL IndependentBlendEnable;// 기본값 False
D3D12_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D12_BLEND_DESC;
- AlphaToCoverageEnable
TRUE로 설정하면 알파-포괄도(alpha-to-coverage) 변환이 활성화 FALSE로 설정하면 비활성화
알파 포괄도 변환은 군엽(식물의)이나 창살 텍스처의 렌더링에 유용한 다중표본화(multisampling) 기법
알파 포괄도 변환을 사용하려면 반드시 다중표본화가 활성화되어 있어야 한다.(즉, 다중표본화에 맞는 후면 버퍼와 깊이 버퍼를 생성해 두어야 한다). - IndependentBlendEnable
Direct3D에서 한 장면을 동시에 렌더링할 수 있는 렌더 대상은 최대 8개이다. 이 플래그를
TRUE로 설정하면 렌더 대상마다 혼합을 다른 방식으로(혼합 활성화 여부, 혼합 계수, 연산자 등을 다르게 두어서) 수행할 수 있다.
FALSE로 설정하면 모든 렌더 대상의 혼합 연산이 D3D12_BLEND_DESC::RenderTarget 배열의 첫 원소에 있는 설정에 따라 동일하게 수행
다중 렌더 대상은 고급 알고리즘에 쓰이는 것이므로,일단 지금은 장면을 한번에 렌더 대상에만 렌더링한다고 가정 - RenderTarget
D3D12_RENDER_TARGET_BLEND_DESC 원소 여덟 개짜리 배열로, 이 배열의 i번째 원소는 장면을 동시에 여러 렌더 대상에 렌더링할 때 i번째 렌더 대상에 적용할 혼합 설정을 담은 구조체이다. IndependentBlendEnable을 FALSE로 설정했다면 RenderTarget[0]이 모든 렌더 대상의 혼합에 쓰인다.
D3D12_RENDER_TARGET_BLEND_DESC 구조체의 정의는 다음과 같다.
typedef struct D3D12_RENDER_TARGET_BLEND_DESC {
BOOL BlendEnable; //기본값 : FALSE
BOOL LogicOpEnable; //기본값 : FALSE
D3D12_BLEND SrcBlend; //기본값 : D3D12_BLEND_ONE
D3D12_BLEND DestBlend; //기본값 : D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOp; //기본값 : D3D12_BLEND_OP_ADD
D3D12_BLEND SrcBlendAlpha; //기본값 : D3D12_BLEND_ONE
D3D12_BLEND DestBlendAlpha; //기본값 : D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOpAlpha; //기본값 : D3D12_BLEND_OP_ADD
D3D12_LOGIC_OP LogicOp; //기본값 : D3D12_BLEND_OP_NOOP
UINT8 RenderTargetWriteMask; //기본값 : D3D12_COLOR_WRITE_ENABLE_ALL
} D3D12_RENDER_TARGET_BLEND_DESC;
- BlendEnable
혼합을 활성화 하려면 TRUE , 아니면 FALSE
BlendEnable과 LogicOpEnable 둘 다 TRUE로 설정할 수 없음을 주의하자. 보통의 혼합과 논리 연산자 혼합 중 하나만 사용 가능 - LogicOpEnable
활성화 하려면 TRUE, 아니면 FALSE
BlendEnable과 LogicOpEnable 둘 다 TRUE로 설정할 수 없음을 주의하자. 보통의 혼합과 논리 연산자 혼합 중 하나만 사용 가능 - SrcBlend
RGB 혼합의 원본 혼합 계수 Fsrc에 해당
D3D12_BLEND 열거형의 한 멤버를 지정해야 한다. - DstBlend
RGB 혼합의 대상 혼합 계수 Fdst에 해당
D3D12_BLEND 열거형의 한 멤버를 지정해야 한다. - BlendOp
RGB 혼합의 연산자에 해당
D3D12_BLEND_OP 열거형의 한 멤버를 지정해야 한다. - SrcBlendAlpha
알파 혼합의 원본 혼합 계수 Fsrc에 해당
D3D12_BLEND 열거형의 한 멤버를 지정해야 한다. - DestBlendAlpha
알파 혼합의 대상 혼합 계수 Fdst에 해당
D3D12_BLEND_열거형의 한 멤버를 지정해야 한다. - BlendOpAlpha
알파 혼합의 혼합 연선자에 해당
D3D12_BLEND_OP 열거형의 한 멤버를 지정해야 한다. - LogicOp
논리 연산자 혼합에서 원본 색상과 대상 색상을 섞는 데 사용할 논리 연산자에 해당
D3D12_LOGIC_OP 열거형의 한 멤버를 지정해야 한다. - RenderTargetWriteMask
렌더 대상 쓰기 마스크로, 다음 플래그 중 하나 또는 여러개의 조합을 지정할 수 있다.
typedef enum D3D12_COLOR_WRITE_ENABLE {
D3D12_COLOR_WRITE_ENABLE_RED = 1,
D3D12_COLOR_WRITE_ENABLE_GREEN = 2,
D3D12_COLOR_WRITE_ENABLE_BLUE = 4,
D3D12_COLOR_WRITE_ENABLE_ALPHA = 8,
D3D12_COLOR_WRITE_ENABLE_ALL
} ;
이 플래그들은 혼합의 결과가 기록될 후면 버퍼 색상 채널들을 결정한다.
예를 들어 결과가 RGB 채널들에는 기록되지 않고 알파 채널에만 기록되게 하고 싶으면 D3D12_COLOR_WRITE_ENABLE_ALPHA를 지정하면 된다. 이러한 유연성은 고급 기법들에 유용하게 쓰인다. 혼합을 비활성화한 경우에는 픽셀 셰이더가 돌려준 색상에 그 어떤 쓰기 마스크도 적용되지 않는다.
혼합은 공짜가 아니다. 혼합 연산을 위해서는 픽셀마다 추가적인 작업이 필요하므로, 필요한 경우에만 혼합을 활성화하고, 다 적용했으면 비활성화 해야한다.
다음은 혼합 상태를 생성, 설정하는 방법을 보여주는 코드이다.
//혼합이 비활성화된 PS0로 시작
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc;
D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendEsc.BlendEnable = true;
transparencyBlendEsc.LogicOpEnable = false;
transparencyBlendEsc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
transparencyBlendEsc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
transparencyBlendEsc.BlendOp = D3D12_BLEND_OP_ADD;
transparencyBlendEsc.SrcBlendAlpha = D3D12_BLEND_ONE;
transparencyBlendEsc.DestBlendAlpha = D3D12_BLEND_ZERO;
transparencyBlendEsc.BlendOpalpha = D3D12_BLEND_OP_ADD;
transparencyBlendEsc.LogicOp = D3D12_LOGIC_OP_NOOP;
transparencyBlendEsc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&transparentPsoDesc, IID_PPV_ARGS(&mPS0s["transparent"]))));
다른 PSO들과 마찬가지로, 혼합 상태들은 응용 프로그램 초기화 시점에서 모두 만들어 두고 응용 프로그램 실행 도중에 필요에 따라 ID3D12GraphicsCommandList::SetPipelineState 메서드로 특정 혼합 상태를 적용하면 된다.
예제
이 예들은 RGB성분 혼합만 다루지만 알파 성분 혼합도 마찬가지 방식으로 적용 가능하다.
1. 색상 기록 금지
- 원본 픽셀이 대상 픽셀을 덮어쓰거나 대상 픽셀과 섞이게 하지 않고 항상 대상 픽셀이 그대로 유지되게 하고 싶다.
- 이런 방식은 이를테면 렌더링 결과를 깊이,스텐실 버퍼에만 기록하고 후면 버퍼에는 기록하지 않고자 할때 유용하다.
사용방법
원본픽셀의 혼합 계수 = D3D12_BLEND_ZERO
대상 혼합 계수 = D3D12_BLEND_ONE
혼합 연산자 = D3D12_BLEND_OP_ADD
혼합 공식
C = Csrc ☒ Fsrc ⊕ Cdst ☒ Fdst
C = Csrc ☒ (0,0,0) + Cdst ☒ (1,1,1)
C = Cdst
이는 다소 작위적인 예이다. D3D12_RENDER_TARGET_BLEND_DESC 구조체의 RenderTargetWriteMask 멤버를 0으로 설정해서 어떤 색상 채널에도 값이 기록되지 않게 해도 같은 효과를 얻을 수 있다.
2. 가산 혼합과 감산 혼합(Addtive,Subtractive)
사용방법
원본 혼합 계수: D3D12_BLEND_ONE
대상 혼합 계수 : D3D12_BLEND_ONE
혼합 연산자 : D3D12_BLEND_OP_ADD
혼합 공식
C = Csrc ☒ Fsrc ⊕ Cdst ☒ Fdst
C = Csrc ☒ (1,1,1) + Cdst ☒ (1,1,1)
C = Csrc + Cdst
대상 픽셀에서 원본 픽셀을 빼려면(감산혼합) 혼합 계수들은 위와 같게 설정하되 혼합 연산자를 D3D12_BLEND_OP_SUBTRACT로 설정하면 된다.
3. 승산 혼합
원본 픽셀을 대상 픽셀에 성분별로 곱한다.
사용방법
원본 혼합 계수: D3D12_BLEND_ZERO
대상 혼합 계수 : D3D12_BLEND_SRC_COLOR
혼합 연산자 : D3D12_BLEND_OP_ADD
혼합 공식
C = Csrc ☒ Fsrc ⊕ Cdst ☒ Fdst
C = Csrc ☒ (0,0,0) + Cdst ☒ Csrc
C = Cdst ☒ Csrc
4.투명도
원본 알파 성분 As가 원본 픽셀의 불투명도(opacity)를 제어하는 비율이라고 생각해 보자. 예를 들어 알파가 0이면 0% 불투명한 , 즉 완전히 투명한 것이고 0.4이면 40% 불투명한 것, 1.0이면 100% 불투명한 것이다. 불투명도가 A이고 투명도(transparency)가 T라고 할 때, 둘의 관계는 그냥 T = 1 -A이다. 예를 들어
어떤 픽셀이 0.4만큼 투명하다는 것은 1 - 0.4 = 0.6만큼 투명하다는 것과 같은 뜻이다. 원본 픽셀과 대상 픽셀을 원본 픽셀의 불투명도에 비례해서 혼합한다고 하자.
사용방법
원본 혼합 계수: D3D12_BLEND_SRC_ALPHA
대상 혼합 계수 : D3D12_BLEND_INV_SRC_ALPHA
혼합 연산자 : D3D12_BLEND_OP_ADD
혼합 공식
C = Csrc ☒ Fsrc ⊕ Cdst ☒ Fdst
C = Csrc ☒ (As,As,As) + Cdst ☒ (1-As,1-As,1-As)
C = As Csrc + (1 - As)Cdst
예를들어 As = 0.25이면 원본 픽셀이 25%만큼만 불투명하다는 뜻 이 조합으로 원본 픽셀을 대상 픽셀과 섞으면 최종 색상은
원본 픽셀 색상의 25%와 대상 픽셀(원본 픽셀 "뒤에"있는) 색상의 75%를 결합한 것이 된다.
원본 픽셀이 75% 투명하므로 대상 픽셀의 75%가 보존된 것이라고 생각하면 된다. 이 예에서 혼합 공식은 다음과 같다.
C = As Csrc + (1 - As)Cdst
C = 0.25 Csrc + 0.75 Cdst
이 혼합 방법을 이용하면 물처럼 반투명한 물체를 그릴 수 있다. 이 혼합 방법에서는 물체들을 그리는 순서가 중요함을 주목하자.
혼합과 깊이 버퍼
위의 혼합에서는 깊이 판정과 관련해서 문제점이 하나 발생한다.
물체들의 집합 S를 가산 혼합으로 렌더링한다고 하자. 이러한 혼합의 의도는 S의 물체들이 서로를 가리는 것이 아니라 해당 색상들이 모두 누적되어서 더 밝은 모습이 되게 한다는 것이다. 이러한 의도를 만족하려면 S의 물체들에 대해 깊이 판정을 수행하지 말아야한다.
만일 깊이 판정을 수행한다면, 물체들을 뒤에서 앞의 순서로 정렬해서 그리지 않는 한 어떤 물체가 다른 물체를 가릴 것이며, 그러면 가려진 물체의 픽셀들이 깊이 판정에 실패해서 해당 픽셀 색상들이 누적되지 않는다. S의 물체들 사이의 깊이 판정을 비활성화하는 한 가지 방법은 S의 물체들을 렌더링 하는 동안에는 깊이 버퍼 기록(쓰기)를 비활성화(Zwrite Off)하는 것이다.
깊이 버퍼 기록을 비활성화한 상태에서 S의 물체들을 가산 혼합으로 그리면, 한 물체의 깊이 값이 깊이 버퍼에 기록되지 않으므로 그 이후의 물체들이 깊이 판정 때문에 그 물체에 가려지는 일도 생기지 않는다. S의 물체들을 그릴 때 깊이 기록만 비활성화한다는 점을 주의하자. 깊이 읽기와 깊이 판정 자체는 여전히 활성화한다. 그래야 여전히 비혼합 기하구조(혼합 물체들을 그리기 전에 렌더링된)가 그 뒤에 있는 혼합 물체들을 가리게 된다.
픽셀 잘라내기(clip)
종종, 원본 픽셀을 완전히 '기각(reject)'해서 더 이상의 처리가 일어나지 않게 해야 할 때가 있다. 그러한 픽셀 잘라내기 (clipping 또는 절단)를 수행하는 한 가지 방법은 내장 HLSL 함수 clip( x )를 이용하는 것이다. 픽셀 셰이더에서만 호출할 수 있는 이 함수는 만일. x < 0 이면 현재 픽셀을 폐기해서 더 이상의 처리가 일어나지 않게 한다.
다음은 이 함수의 용법을 보여주는 픽셀 셰이더이다. 픽셀 셰이더는 텍스처에서 알파 성분을 가져온다. 만일 알파 값이 0에 가까우면 현재 픽셀이 완전히 투명하다고 간주해서, 이상의 처리가 일어나지 않도록 잘라낸다.
float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin,texC) * gDiffuseAlbedo;
#ifdef ALPHA_TEST
//텍스처 알파가 0.1보다 작으면 픽셀을 폐기한다. 셰이더 안에서
//이 판정을 최대한 일찍 수행하는 것이 바람직하다. 그러면 폐기 시
// 셰이더의 나머지 코드의 실행을 생략할 수 있으므로 효율적이다.
clip(diffuseAlbedo.a - 0.1f);
#endif
//흔히 하는 방식대로 분산 재질에서 알파를 가져온다.
litColor.a. = diffuseAlbedo.a;
return litColor;
}
ALPHA_TEST가 정의되어 있는 경우에만 픽셀 잘라내기를 적용한다는 점을 주의
clip을 호출하지 않는 것이 바람직한 렌더 항목들도 있으므로, 이처럼 특화된 셰이더를 이용해서 픽셀 잘라내기를 활성화 / 비활성화해야 한다. 또한, 이러한 알파 판정에는 비용이 따르므로, 필요한 경우에만 사용해야 한다.
혼합을 이용해도 같은 효과를 얻을 수 있지만, 지금 방법이 더 효율적이다.
또한, 그리기 순서가 결과에 영향을 미치지 않는다. 더 나아가서, 픽셀 셰이더에서 픽셀을 일찌감치 폐기하면 픽셀 셰이더의 나머지 명령들을 생략할 수 있다.
안개
게임에서 특정한 기상(weather)조건을 흉내 낼 때 유용
장점
적절히 이용시 먼 거리의 렌더링 결함이나 파핑(popping)현상을 숨길 수 있다. 먼 거리에 안개를 깔아두면 그런 파핑이 가려진다.
날씨가 맑다고 해도 먼 거리에 약간의 안개를 포함시키는 것이 바람직하다. 맑은 날이라도 지평선 부근의 산처럼 멀리 있는 물체는 흐릿해 보이기 때문.(깊이에 비례해서 선명함이 줄어든다.) 안개 효과는 그러한 대기 원근(atmospheric perspective) 현상을 흉내 내기에 적합.
구현방법
안개 색상과 안개가 시작하는 거리(카메라 기준), 그리고 안개의 범위(안개 시작 지점에서 물체가 안개에 완전히 묻혀서 보이지 않게 되는 지점까지)를 설정해 둔다. 렌더링 도중에는 삼각형의 한 점의 색상을 다음과 같이 원래 색상과 안개 색상의 가중 평균으로 구한다.
foggedColor = litColor + s(fogColor - litColor)
=(1 - s) · litColor + s · fogColor
매개변수 s는 카메라 위치와 표면 점 사이의 거리의 함수로, 값 (치역)은 0 이상 1 이하이다. 표면 점과 카메라(시점) 사이의 거리가 멀수록 이 값이 1에 가까워져서 표면 점이 안개에 더 많이 묻힌다. 이 매개변수의 정의는 다음과 같다.
s = saturate((dist(p,E) - fogstart) / fogRange);
여기서 dist(p,E)는 표면 점 p와 카메라 위치 E 사이의 거리이고 saturate는 주어진 인수를 [0,1] 구간으로 한정하는 함수이다.
'Public > GraphicsAPI' 카테고리의 다른 글
Direct3D 첫번째 요약 (0) | 2022.09.25 |
---|---|
Direct3D 응용 프로그램의 디버깅 (0) | 2022.09.25 |
Direct3D 초기화 예제 (0) | 2022.09.25 |
DX11 Tutorial02 코드 설명 (0) | 2022.08.22 |
광원 (0) | 2022.08.15 |