如何在一个 Metal API 场景中使用不同的片段着色器?
How to use different fragment shaders in one Metal API scene?
我最近一直在用 Apple 的 Metal API 做一些实验,现在我来到主题问题 - 如何在一个 Metal API 场景中使用不同的片段着色器?可能吗?
背景:整个几何基元由一个简单的顶点-片段链渲染,内部有颜色 defined/calculated(假设我们有一个立方体,它的所有面都用描述的方法渲染)。接下来,需要使用纹理额外渲染图元的一部分(仅向其中一个面添加一些图片)。
我们需要使用不同的片段着色器来实现吗?我想可以在第一步使用一些默认纹理,这将提供一些解决方案。
你会推荐什么?
//=============编辑部分更进一步==========//
我尝试将 MTLRenderPipelineState 的两个不同对象与两对不同的渲染函数一起使用,正如 Warren 所建议的那样。使用以下代码我没有得到想要的结果。 状态中的每一个在单独完成时都按照预期的方式呈现,但一起完成只会让我们呈现第一个一个。
创作:
id <MTLFunction> fragmentProgram = [_defaultLibrary newFunctionWithName:@"color_fragment"];
// Load the vertex program into the library
id <MTLFunction> vertexProgram = [_defaultLibrary newFunctionWithName:@"lighting_vertex"];
// Create a vertex descriptor from the MTKMesh
MTLVertexDescriptor *vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(_boxMesh.vertexDescriptor);
vertexDescriptor.layouts[0].stepRate = 1;
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
// Create a reusable pipeline state
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"MyPipeline";
pipelineStateDescriptor.sampleCount = _view.sampleCount;
pipelineStateDescriptor.vertexFunction = vertexProgram;
pipelineStateDescriptor.fragmentFunction = fragmentProgram;
pipelineStateDescriptor.vertexDescriptor = vertexDescriptor;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = _view.colorPixelFormat;
pipelineStateDescriptor.depthAttachmentPixelFormat = _view.depthStencilPixelFormat;
pipelineStateDescriptor.stencilAttachmentPixelFormat = _view.depthStencilPixelFormat;
NSError *error = NULL;
_pipelineStateColor = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
if (!_pipelineStateColor) {
NSLog(@"Failed to created pipeline state, error %@", error);
}
pipelineStateDescriptor.fragmentFunction = [_defaultLibrary newFunctionWithName:@"lighting_fragment"];
_pipelineStateTexture = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
if (!_pipelineStateTexture) {
NSLog(@"Failed to created pipeline state, error %@", error);
}
渲染:
- (void)renderInto:(id <MTLRenderCommandEncoder>)renderEncoder
WithPipelineState:(id<MTLRenderPipelineState>)pipelineState
{
[renderEncoder setRenderPipelineState:pipelineState];
[renderEncoder setVertexBuffer:_boxMesh.vertexBuffers[0].buffer offset:_boxMesh.vertexBuffers[0].offset atIndex:0 ];
[renderEncoder setVertexBuffer:_dynamicConstantBuffer offset:(sizeof(uniforms_t) * _constantDataBufferIndex) atIndex:1 ];
[renderEncoder setVertexBuffer:_textureBuffer offset:0 atIndex:2];
[renderEncoder setFragmentTexture:_textureData atIndex:0];
MTKSubmesh* submesh = _boxMesh.submeshes[0];
[renderEncoder drawIndexedPrimitives:submesh.primitiveType
indexCount:submesh.indexCount
indexType:submesh.indexType
indexBuffer:submesh.indexBuffer.buffer
indexBufferOffset:submesh.indexBuffer.offset];
}
- (void)_render
{
dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);
[self _update];
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
__block dispatch_semaphore_t block_sema = _inflight_semaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
dispatch_semaphore_signal(block_sema);
}];
MTLRenderPassDescriptor* renderPassDescriptor = _view.currentRenderPassDescriptor;
if(renderPassDescriptor != nil)
{
id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";
[renderEncoder setDepthStencilState:_depthState];
[self renderInto:renderEncoder WithPipelineState:_pipelineStateColor];
[self renderInto:renderEncoder WithPipelineState:_pipelineStateTexture];
[renderEncoder endEncoding];
[commandBuffer presentDrawable:_view.currentDrawable];
}
_constantDataBufferIndex = (_constantDataBufferIndex + 1) % kMaxInflightBuffers;
[commandBuffer commit];
}
最后是 片段着色器:
fragment float4 color_fragment(ColorInOut in [[stage_in]])
{
return float4(0.8f, 0.f, 0.1f, 0.5f);
}
fragment float4 texture_fragment(ColorInOut in [[stage_in]],
texture2d<float, access::sample> texture [[texture(0)]])
{
constexpr sampler s(coord::normalized,
address::clamp_to_zero,
filter::linear);
return texture.sample(s, in.texture_coordinate);
}
您可以通过创建多个渲染管道状态在单个 frame/pass 中使用多个片段着色器。只需为每个 vertex/fragment 函数对创建一个管道状态,并在您的渲染命令编码器上调用 setRenderPipelineState:
以在发出绘制调用之前设置适当的管道状态。您将需要编写单独的片段着色器函数来执行颜色直通和纹理采样。
我最近一直在用 Apple 的 Metal API 做一些实验,现在我来到主题问题 - 如何在一个 Metal API 场景中使用不同的片段着色器?可能吗?
背景:整个几何基元由一个简单的顶点-片段链渲染,内部有颜色 defined/calculated(假设我们有一个立方体,它的所有面都用描述的方法渲染)。接下来,需要使用纹理额外渲染图元的一部分(仅向其中一个面添加一些图片)。
我们需要使用不同的片段着色器来实现吗?我想可以在第一步使用一些默认纹理,这将提供一些解决方案。
你会推荐什么?
//=============编辑部分更进一步==========//
我尝试将 MTLRenderPipelineState 的两个不同对象与两对不同的渲染函数一起使用,正如 Warren 所建议的那样。使用以下代码我没有得到想要的结果。 状态中的每一个在单独完成时都按照预期的方式呈现,但一起完成只会让我们呈现第一个一个。
创作:
id <MTLFunction> fragmentProgram = [_defaultLibrary newFunctionWithName:@"color_fragment"];
// Load the vertex program into the library
id <MTLFunction> vertexProgram = [_defaultLibrary newFunctionWithName:@"lighting_vertex"];
// Create a vertex descriptor from the MTKMesh
MTLVertexDescriptor *vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(_boxMesh.vertexDescriptor);
vertexDescriptor.layouts[0].stepRate = 1;
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
// Create a reusable pipeline state
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"MyPipeline";
pipelineStateDescriptor.sampleCount = _view.sampleCount;
pipelineStateDescriptor.vertexFunction = vertexProgram;
pipelineStateDescriptor.fragmentFunction = fragmentProgram;
pipelineStateDescriptor.vertexDescriptor = vertexDescriptor;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = _view.colorPixelFormat;
pipelineStateDescriptor.depthAttachmentPixelFormat = _view.depthStencilPixelFormat;
pipelineStateDescriptor.stencilAttachmentPixelFormat = _view.depthStencilPixelFormat;
NSError *error = NULL;
_pipelineStateColor = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
if (!_pipelineStateColor) {
NSLog(@"Failed to created pipeline state, error %@", error);
}
pipelineStateDescriptor.fragmentFunction = [_defaultLibrary newFunctionWithName:@"lighting_fragment"];
_pipelineStateTexture = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
if (!_pipelineStateTexture) {
NSLog(@"Failed to created pipeline state, error %@", error);
}
渲染:
- (void)renderInto:(id <MTLRenderCommandEncoder>)renderEncoder
WithPipelineState:(id<MTLRenderPipelineState>)pipelineState
{
[renderEncoder setRenderPipelineState:pipelineState];
[renderEncoder setVertexBuffer:_boxMesh.vertexBuffers[0].buffer offset:_boxMesh.vertexBuffers[0].offset atIndex:0 ];
[renderEncoder setVertexBuffer:_dynamicConstantBuffer offset:(sizeof(uniforms_t) * _constantDataBufferIndex) atIndex:1 ];
[renderEncoder setVertexBuffer:_textureBuffer offset:0 atIndex:2];
[renderEncoder setFragmentTexture:_textureData atIndex:0];
MTKSubmesh* submesh = _boxMesh.submeshes[0];
[renderEncoder drawIndexedPrimitives:submesh.primitiveType
indexCount:submesh.indexCount
indexType:submesh.indexType
indexBuffer:submesh.indexBuffer.buffer
indexBufferOffset:submesh.indexBuffer.offset];
}
- (void)_render
{
dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);
[self _update];
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
__block dispatch_semaphore_t block_sema = _inflight_semaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
dispatch_semaphore_signal(block_sema);
}];
MTLRenderPassDescriptor* renderPassDescriptor = _view.currentRenderPassDescriptor;
if(renderPassDescriptor != nil)
{
id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";
[renderEncoder setDepthStencilState:_depthState];
[self renderInto:renderEncoder WithPipelineState:_pipelineStateColor];
[self renderInto:renderEncoder WithPipelineState:_pipelineStateTexture];
[renderEncoder endEncoding];
[commandBuffer presentDrawable:_view.currentDrawable];
}
_constantDataBufferIndex = (_constantDataBufferIndex + 1) % kMaxInflightBuffers;
[commandBuffer commit];
}
最后是 片段着色器:
fragment float4 color_fragment(ColorInOut in [[stage_in]])
{
return float4(0.8f, 0.f, 0.1f, 0.5f);
}
fragment float4 texture_fragment(ColorInOut in [[stage_in]],
texture2d<float, access::sample> texture [[texture(0)]])
{
constexpr sampler s(coord::normalized,
address::clamp_to_zero,
filter::linear);
return texture.sample(s, in.texture_coordinate);
}
您可以通过创建多个渲染管道状态在单个 frame/pass 中使用多个片段着色器。只需为每个 vertex/fragment 函数对创建一个管道状态,并在您的渲染命令编码器上调用 setRenderPipelineState:
以在发出绘制调用之前设置适当的管道状态。您将需要编写单独的片段着色器函数来执行颜色直通和纹理采样。