Fragment Post-Processing

Date:     Updated:

카테고리:

태그:

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
1 1

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

1

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 정의

1

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;
  }
}

1

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

1



맨 위로 이동하기

Metal 카테고리 내 다른 글 보러가기

댓글 남기기