5

金属で 2 つの異なる頂点バッファーを描画しています。1 つはテクスチャを使用 (頂点カラー データを無視) し、もう 1 つはテクスチャを使用せず (純粋に頂点カラー データを描画):

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 には加算/減算/最小/最大のオプションしかなく、乗算はないようですか?

4

1 に答える 1

2

これを行う別の方法は、2 つの異なるフラグメント関数を使用することです。1 つはテクスチャ フラグメントをレンダリングし、もう 1 つは色付きの頂点を処理します。

MTLRenderPipelineState最初に、ロード時に2 つの異なるものを作成する必要があります。

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)

頂点関数を変更する必要はありません。フラグメント関数を 2 つに分割する必要がありますが、次のようになります。

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

You could even go ahead and have two different vertex functions that output two different vertex structures in order to save a few bytes. In fact the textured fragment function only needs the texCoord field and not the color, while the untextured function is the other way around.

EDIT: You can use this fragment function to use both the texture color and the vertex 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;

}
于 2016-12-02T10:15:31.117 に答える