金属材质中同时具有纹理和无纹理,采用混合模式叠加。

5

我正在使用Metal绘制两个不同的顶点缓冲区,一个带有纹理(忽略顶点颜色数据),另一个没有纹理(仅绘制顶点颜色数据):

let commandBuffer = self.commandQueue.makeCommandBuffer()
let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: rpd)

//render first buffer with texture
commandEncoder.setRenderPipelineState(self.rps)
commandEncoder.setVertexBuffer(self.vertexBuffer1, offset: 0, at: 0)
commandEncoder.setVertexBuffer(self.uniformBuffer, offset: 0, at: 1)
commandEncoder.setFragmentTexture(self.texture, at: 0)
commandEncoder.setFragmentSamplerState(self.samplerState, at: 0)
commandEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: count1, instanceCount: 1)


//render second buffer without texture
commandEncoder.setRenderPipelineState(self.rps)
commandEncoder.setVertexBuffer(self.vertexBuffer2, offset: 0, at: 0)
commandEncoder.setVertexBuffer(self.uniformBuffer, offset: 0, at: 1)
commandEncoder.setFragmentTexture(nil, at: 0)
commandEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: count2, instanceCount: 1)

commandEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()

这个着色器的代码如下:
#include <metal_stdlib>
using namespace metal;

struct Vertex {
    float4 position [[position]];
    float4 color;
    float4 texCoord;
};

struct Uniforms {
    float4x4 modelMatrix;
};

vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],
                          constant Uniforms &uniforms [[buffer(1)]],
                          uint vid [[vertex_id]])
{
    float4x4 matrix = uniforms.modelMatrix;
    Vertex in = vertices[vid];
    Vertex out;
    out.position = matrix * float4(in.position);
    out.color = in.color;
    out.texCoord = in.texCoord;
    return out;
}

fragment float4 fragment_func(Vertex vert [[stage_in]],
                               texture2d<float>  tex2D     [[ texture(0) ]],
                               sampler           sampler2D [[ sampler(0) ]]) {

    if (vert.color[0] == 0 && vert.color[1] == 0 && vert.color[2] == 0) {
        //texture color
        return tex2D.sample(sampler2D, float2(vert.texCoord[0],vert.texCoord[1]));
    }
    else {
        //color color
        return vert.color;
    }

}

有没有更好的方法来做这件事?我想使用纹理的任何顶点都会被设置为黑色,着色器会检查颜色是否为黑色,如果是,则使用纹理,否则使用颜色。

另外,如果彩色多边形和纹理多边形在屏幕上重叠,有没有一种方法可以使用乘法函数混合它们?似乎MTLBlendOperation只有加/减/最小/最大的选项,没有乘法?


我对Metal不熟悉,一直在苦恼如何在顶点数组上使用多个纹理,这个问题会帮助我解决它,我想。 - Hope
1个回答

2
另一种实现该功能的方法是创建两个不同的片段函数,一个用于渲染纹理片段,另一个处理有颜色的顶点。
首先,在加载时需要创建两个不同的MTLRenderPipelineState:
let desc = MTLRenderPipelineDescriptor()    

/* ...load all other settings in the descriptor... */

// Load the common vertex function.
desc.vertexFunction = library.makeFunction(name: "vertex_func")

// First create the one associated to the textured fragment function.
desc.fragmentFunction = library.makeFunction(name: "fragment_func_textured")
let texturedRPS = try! device.makeRenderPipelineState(descriptor: desc)

// Then modify the descriptor to create the state associated with the untextured fragment function.
desc.fragmentFunction = library.makeFunction(name: "fragment_func_untextured")
let untexturedRPS = try! device.makeRenderPipelineState(descriptor: desc)

在渲染时,对于纹理对象的绘制命令编码之前需设置纹理状态;而对于非纹理对象的绘制命令编码之前需切换到非纹理状态。操作步骤如下:

//render first buffer with texture
commandEncoder.setRenderPipelineState(texturedRPS) // Set the textured state
commandEncoder.setVertexBuffer(self.vertexBuffer1, offset: 0, at: 0)
commandEncoder.setVertexBuffer(self.uniformBuffer, offset: 0, at: 1)
commandEncoder.setFragmentTexture(self.texture, at: 0)
commandEncoder.setFragmentSamplerState(self.samplerState, at: 0)
commandEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: count1, instanceCount: 1)

//render second buffer without texture
commandEncoder.setRenderPipelineState(untexturedRPS) // Set the untextured state
commandEncoder.setVertexBuffer(self.vertexBuffer2, offset: 0, at: 0)
commandEncoder.setVertexBuffer(self.uniformBuffer, offset: 0, at: 1)
// No need to set the fragment texture as we don't need it in the fragment function.
// commandEncoder.setFragmentTexture(nil, at: 0)
commandEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: count2, instanceCount: 1)

顶点函数无需更改。但是需要将片段函数分成两部分:

fragment float4 fragment_func_textured(Vertex vert [[stage_in]],
                                       texture2d<float>  tex2D     [[ texture(0) ]],
                                       sampler           sampler2D [[ sampler(0) ]]) {
    //texture color
    return tex2D.sample(sampler2D, float2(vert.texCoord[0],vert.texCoord[1]));
}

fragment float4 fragment_func_untextured(Vertex vert [[stage_in]]) {
    //color color
    return vert.color;
}

你甚至可以使用两个不同的顶点函数,以输出两个不同的顶点结构,从而节省一些字节。实际上,纹理片段函数只需要texCoord字段而不需要color,而未纹理化的函数则相反。 编辑:您可以使用此片段函数同时使用纹理颜色和顶点颜色:
fragment float4 fragment_func_blended(Vertex vert [[stage_in]],
                                      texture2d<float>  tex2D     [[ texture(0) ]],
                                      sampler           sampler2D [[ sampler(0) ]]) {
    // texture color
    float4 texture_sample = tex2D.sample(sampler2D, float2(vert.texCoord[0],vert.texCoord[1]));
    // vertex color
    float4 vertex_sample = vert.color;
    // Blend the two together
    float4 blended = texture_sample * vertex_sample;

    // Or use another blending operation.
    // float4 blended = mix(texture_sample, vertex_sample, mix_factor);
    // Where mix_factor is in the range 0.0 to 1.0.
    return blended;

}

太整洁了,谢谢 :) 有没有办法实现乘法效果?如果我能在着色器内部做到这一点,那就很容易了 - 只需将纹理颜色和颜色颜色相乘即可。 - usrgnxc
当然可以!那么你只需要像这样使用另一个片段函数: - ThreeDeeZero
我只需编辑之前的答案以显示新的片段函数。 - ThreeDeeZero
啊,但我的意思是将一个多边形的纹理与场景中位于其后面的另一个多边形的颜色混合...也许这对Metal来说有点困难。我想我现在有了另一种获得所需效果的方法,通过使用2个单独的Metal视图,并使用CoreGraphics绘制它们的结果并在那里混合它们。 - usrgnxc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接