I'm rendering a geometry that has some translucent areas (alpha < 1) in a metalkit MTKView
. If isBlendingEnabled
is left as false in the descriptor for the render pipeline state, then everything displays as it should (albeit with all solid colours).
I'm aware that rendering translucent objects depends on the draw order. For the time being, I just want to test what alpha blending looks like with the translucent areas blended with what is already in the render buffer, even if it's just blending through to the background (at this point still just the clear colour).
However, when I try to enable blending, the makeRenderPipelineState
fails with the following error:
Compiler failed to build request Error Domain=CompilerError Code=1 "Fragment shader does not write to render target color(0), index(1) that is required for blending"
Here's the code that tries to build the pipeline state in the MTKView's delegate. Where it inherits properties from the MTKView, I've put the value of those properties in the comments
do {
let descriptor = MTLRenderPipelineDescriptor()
descriptor.vertexFunction = vertex
descriptor.fragmentFunction = fragment
descriptor.sampleCount = view.sampleCount // 4
descriptor.depthAttachmentPixelFormat = view.depthStencilPixelFormat //.depth32Float
let renderAttachment = descriptor.colorAttachments[0]
renderAttachment?.pixelFormat = view.colorPixelFormat //.bgra8Unorm
// following 7 lines cause makeRenderPipelineState to fail
renderAttachment?.isBlendingEnabled = true
renderAttachment?.alphaBlendOperation = .add
renderAttachment?.rgbBlendOperation = .add
renderAttachment?.sourceRGBBlendFactor = .sourceAlpha
renderAttachment?.sourceAlphaBlendFactor = .sourceAlpha
renderAttachment?.destinationRGBBlendFactor = .oneMinusSourceAlpha
renderAttachment?.destinationAlphaBlendFactor = .oneMinusSource1Alpha
computePipelineState = try device.makeComputePipelineState(function: kernel)
renderPipelineState = try device.makeRenderPipelineState(descriptor: descriptor)
} catch {
print(error)
}
Given that the error complains about color(0)
, I added the color[0]
binding to the output of the fragment shader:
constant float3 directionalLight = float3(-50, -30, 80);
struct FragOut {
float4 solidColor [[ color(0) ]];
};
fragment FragOut passThroughFragment(Vertex fragIn [[ stage_in ]]) {
FragOut fragOut;
fragOut.solidColor = fragIn.color;
fragOut.solidColor.rgb *= max(0.4, dot(fragIn.normal, normalize(directionalLight)));
return fragOut;
};
And finally, the draw code:
if let renderPassDescriptor = view.currentRenderPassDescriptor,
let drawable = view.currentDrawable {
let commandBuffer = queue.makeCommandBuffer()
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.8, green: 0.8, blue: 1, alpha: 1)
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
renderEncoder.setDepthStencilState(depthStencilState)
renderEncoder.setRenderPipelineState(renderPipelineState)
//renderEncoder.setTriangleFillMode(.lines)
renderEncoder.setVertexBuffer(sceneBuffer, offset: 0, at: 0)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, at: 1)
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: Int(vertexCount) )
renderEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
I don't explicitly set any textures in the fragment shader. Does this mean that the currentDrawable is implicitly the color attachment at index 0? Why does the error message want to see color(0)
at index 1? Does blending require two color attachments? (it can't just blend additively onto what has already been rendered?)
Thanks.
You seem to be inadvertently invoking dual-source blending. Rather than setting the destinationAlphaBlendFactor
to oneMinusSource1Alpha
, try oneMinusSourceAlpha
(note the missing 1
).
Also, your intuition that Metal writes to the first color attachment by default is correct (the current drawable is configured by MTKView
to be the texture of the first color attachment). Rather than returning a struct with a member that is attributed with [[color(0)]]
, you can just return a float4
(or half4
) from your fragment function, and that color will be written to the primary color attachment. However, the way you've written it should work once your blend factors are configured correctly.