Fragment Post-Processing
카테고리: Metal
Main Reference
- Metal by Tutorials - 4th edition
These operations are somtimes referred to as per-sample processing
- alpha testing
- depth testing
- stencil testing
- scissor testing
- blending
- anti-aliasing
- etc…
Alpha Testing
| Tree Model | Texture |
|---|---|
![]() |
![]() |
Leaf의 경우 quad에 Texture를 sampling하는 형태인데, 오른쪽 texture를 보면 leaf의 주변은 transparent 처리가 되어 있다. 이런 경우 device나 api 정책에 따라 black or white로 렌더링 되게 된다. 따라서 프로그래머가 transparent 부분을 직접 처리해주어야 하는데 먼저 transparent, translucent, and opaque objects의 차이가 뭔지부터 알아보자.
Transparent: object allows light to entirely pass through it. (투명)Translucent: object distorts light as it passes through it. (반투명)Opaque: object does not allow any light to pass throuh it. (불투명)
// in fragment function..
float4 color = baseColorTexture.sample(
textureSampler,
in.uv * params.tiling);
if (params.alphaTesting && color.a < 0.1) {
discard_fragment();
return 0;
}
material.baseColor = color.rgb;
- Texture의 alpha 값이 threshold 보다 낮으면
discard_fragment

Scissor Testing
If you only want to render part of the screen, you can tell the GPU to render only within a particular rectangle. Keep in mind that any objects rendered before you set the scissor rectangle are not affected.
if params.scissorTesting {
let marginWidth = Int(params.width) / 4
let marginHeight = Int(params.height) / 4
let width = Int(params.width) / 2
let height = Int(params.height) / 2
let rect = MTLScissorRect(
x: marginWidth, y: marginHeight, width: width, height: height)
renderEncoder.setScissorRect(rect)
}
Alpha Blending
Alpha blending is different from alpha testing in that only works with total transparency. For translucent or partially transparent objects, discard fragments in not the best solution because you want the fragment color to contribute to a certain extent of the existing framebuffer color. You don’t just want to replace it. \(\overrightarrow{C*{b}} = \alpha \ \ast \ \overrightarrow{C*{s}} + (1- \alpha) \ \ast \ \overrightarrow{C\_{d}}\)
C_s: Source color. The current color you just added to the scene.C_d: Destination color. The color that already exists in the framebuffer.C_b: Final blended color.
There are two ways to work with bending
Programmable way- In tile-based deferred rendering… A fragment function can directly read color attachment textures(memoryless texture) in a single pass with programmable blending.
Fixed-function way
Opacity Map
When you define transparency in models
- create opacity map : uv 좌표마다 opacity 지정 가능
- define opacity in the submesh’s material : object 전체에 동일한 opacity 정의

static func createForwardTransparentPSO(colorPixelFormat: MTLPixelFormat) -> MTLRenderPipelineState {
// ...
let attachment = pipelineDescriptor.colorAttachments[0]
attachment?.isBlendingEnabled = true
attachment?.rgbBlendOperation = .add
attachment?.sourceRGBBlendFactor = .sourceAlpha
attachment?.destinationRGBBlendFactor = .oneMinusSourceAlpha
}
// in fragment function..
if (params.alphaBlending) {
if (!is_null_texture(opacityTexture)) {
material.opacity =
opacityTexture.sample(textureSampler, in.uv).r;
}
}

Transparent Mesh Rendering Order
The blending order is important. You need to render opaque objects first, and then render transparency objects. However, it may not always be convenient to work out exactly which models require blending.
// Submesh struct..
var transparency: Bool {
return textures.opacity != nil || material.opacity < 1.0
}
// Mesh class..
// If any of the model’s submeshes have transparency,
// you’ll process the model during the transparency render phase.
hasTransparency = meshes.contains {
mesh in mesh.submeshes.contains { $0.transparency }
}
// Mesh class's draw function..
if submesh.transparency != params.transparency { continue }
// Render Pass's draw function..
params.transparency = false
for model in scene.models {
model.render(
encoder: renderEncoder,
uniforms: uniforms,
params: params)
}
// transparent mesh
let models = scene.models.filter {
$0.hasTransparency
}
params.transparency = true
if params.alphaBlending {
renderEncoder.setRenderPipelineState(transparentPSO)
}
for model in models {
model.render(
encoder: renderEncoder,
uniforms: uniforms,
params: params)
}
- Params 구조체에 tranparency 프로퍼티 추가
transparency = true인 model들만 먼저 렌더링- renderPipelineState 교체 후
transparency = false인 model들 렌더링- 이러면 불투명 객체 구분할 필요도 없고, renderPSO도 별도로 바인딩하기 때문에 불필요한 blending 연산도 최적화 가능
- 다만, 불투명 오브젝트가 여러 개인 경우 거리에 따라 순서대로 렌더링 해야 함
Antialiasing
https://inhopp.github.io/opengl/opengl18/
// createForwardPSO_MSAA
pipelineDescriptor.rasterSampleCount = 4
let pipelineState = params.antialiasing ?
pipelineStateMSAA : pipelineState
let transparentPSO = params.antialiasing ?
transparentPSOMSAA : transparentPSO
view.sampleCount = options.antialiasing ? 4 : 1



댓글 남기기