金属片段着色器 - 如何根据可以通过绘制功能调整的制服为每个像素分配颜色?

Metal fragment shader - How do I assign each pixel a color based on the uniforms that I can adjust via the draw function?

我有一个 MTKView,它正在渲染一个立方体,其大小使其其中一个面填满整个屏幕。

我将一个包含随机浮点数的结构变量(称为 theuniforms)传递给片段着色器,我想根据浮点数的值为屏幕上的每个像素着色不同的颜色。

现在,它根据浮点值将整个四边形着色为红色或蓝色,这在绘图函数中是随机的。

我认为我的设置是错误的,因为在任何帧我都只能访问一个随机浮点数,并且在该帧期间所有像素都将被着色(我不会有一组随机浮点数来确定像素颜色)。但是,我想我可以简单地向结构变量添加更多变量来解决这个问题。

主要问题是通过片段函数访问单个像素...如果它的性能足以确定屏幕上每个像素的颜色。

import UIKit
import MetalKit

class ViewController: UIViewController, MTKViewDelegate {
     
     var metaldevice: MTLDevice!
     var metalview: MTKView!
     var metallibrary: MTLLibrary!
     var metalqueue: MTLCommandQueue!
     var metalpipelinestate: MTLRenderPipelineState!
     var metalmesh: MTKMesh!
     
     var theuniforms = Uniforms(color: 1)
     
     struct Uniforms {
          var color = Float(1)
     }
     
     override var prefersStatusBarHidden: Bool { return true }
     override var prefersHomeIndicatorAutoHidden: Bool { return true }
     override func viewDidLoad() {
          super.viewDidLoad()

          self.view.backgroundColor = .black
          
          self.metaldevice = MTLCreateSystemDefaultDevice()
          self.metalview = MTKView()
          self.metalview.frame = UIScreen.main.bounds
          self.metalview.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1)
          self.metalview.delegate = self
          self.metalview.device = self.metaldevice
          self.view.addSubview(self.metalview)
          
          self.metallibrary = self.metaldevice.makeDefaultLibrary()
          self.metalqueue = self.metaldevice.makeCommandQueue()
          self.metalmesh = self.returncube()
          
          let vfunc = self.metallibrary.makeFunction(name: "vertex_main")
          let ffunc = self.metallibrary.makeFunction(name: "fragment_main")
          let descriptor = MTLRenderPipelineDescriptor()
          descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
          descriptor.vertexFunction = vfunc
          descriptor.fragmentFunction = ffunc
          descriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(self.metalmesh.vertexDescriptor)
          
          self.metalpipelinestate = try! self.metaldevice.makeRenderPipelineState(descriptor: descriptor)
          

     }


     func returncube() -> MTKMesh {
          let allocator = MTKMeshBufferAllocator(device: self.metaldevice)
          let model = MDLMesh(boxWithExtent: [2, 2, 2], segments: [1, 1, 1], inwardNormals: false, geometryType: .triangles, allocator: allocator)
          let mesh = try! MTKMesh(mesh: model, device: self.metaldevice)
          return mesh
     }
     
     func draw(in view: MTKView) {
          let descriptor = self.metalview.currentRenderPassDescriptor
          let buffer = self.metalqueue.makeCommandBuffer()
          let encoder = buffer?.makeRenderCommandEncoder(descriptor: descriptor!)
          encoder?.setRenderPipelineState(self.metalpipelinestate)
          
          self.theuniforms.color = Float.random(in: 1.0...2.0)
          
          encoder?.setVertexBytes(&theuniforms, length: MemoryLayout<Uniforms>.stride, index: 1)
          encoder?.setVertexBuffer(self.metalmesh.vertexBuffers[0].buffer, offset: 0, index: 0)
          for mesh in self.metalmesh.submeshes {
               encoder?.drawIndexedPrimitives(type: .triangle, indexCount: mesh.indexCount, indexType: mesh.indexType, indexBuffer: mesh.indexBuffer.buffer, indexBufferOffset: mesh.indexBuffer.offset)
          }
          
          encoder?.endEncoding()
          let drawable = self.metalview.currentDrawable
          buffer?.present(drawable!)
          buffer?.commit()
          
     }
     
     func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
          
     }
}


这是着色器代码

#include <metal_stdlib>
using namespace metal;
#include <simd/simd.h>

struct VertexIn {
     float4 position [[attribute(0)]];
};

struct Uniforms {
     float color;
};

struct VertexOut {
     float4 position [[position]];
     Uniforms uniforms;
};

vertex VertexOut vertex_main(const VertexIn in [[stage_in]],
                          constant Uniforms &uniforms [[buffer(1)]]
                          )
{
     VertexOut out;
     out.position = in.position;
     out.uniforms = uniforms;
     return out;
}

fragment float4 fragment_main( VertexOut in [[stage_in]])
{
     
     float4 color;
     
     if (in.uniforms.color > 1.5){ color = float4(1, 0, 0, 1); }
     else { color = float4(0, 0, 1, 1); }
     return color;
}

首先,您不应该使用制服来更改您重载 CPU 的每个像素的颜色,但这是一项 GPU 任务,因此您应该在着色器中使用一些函数。如果你想让制服相应地改变片段的颜色,你必须将它传递给片段函数 setFragmentBytes(&theuniforms, length: MemoryLayout<Uniforms>.stride, index: 1) 并在片段函数中调用它。如果我没看错,你想要立方体上的白噪声之类的东西,所以这里是着色器:

#include <metal_stdlib>
using namespace metal;
#include <simd/simd.h>

struct VertexIn {
     float4 position [[attribute(0)]];
};

struct Uniforms {
     float color;
};

struct VertexOut {
     float4 position [[position]];
};

vertex VertexOut vertex_main(const VertexIn in [[stage_in]])
{
     VertexOut out;
     out.position = in.position;
   
     return out;
}



float rand(float2 co)
{
    return fract(sin(dot(co.xy ,float2(12.9898,78.236))) * 43758.5453);
}


fragment float4 fragment_main( VertexOut in [[stage_in]],constant Uniforms &uniforms [[buffer(1)]])
{
              
                                                                                      
     float2 pos = in.position.xy;
     
     float4 color = float4(rand(pos));
     
     //if (in.uniforms.color > 1.5){ color = float4(1, 0, 0, 1); }
     //else { color = float4(0, 0, 1, 1); }
     return color;
}