将GLSL函数转换为Metal

Convert GLSL functions to Metal

本文关键字:Metal 转换 函数 GLSL      更新时间:2023-10-16

我偶然发现了这个网站,并开始研究SDF一段时间了。然而,我仍然不太明白这段代码背后的意思:

float pMod1(inout float p, float size) {
    float halfsize = size*0.5;
    float c = floor((p + halfsize)/size);
    p = mod(p + halfsize, size) - halfsize;
    return c;
}

我把它转换成我的Metal代码:

#define _inout(T) T
...
float pMod1(_inout (float) p, float size) {
    float halfsize = size*0.5;
    float c = floor((p + halfsize)/size);
    p = mod(p + halfsize, size) - halfsize;
    return c;
}

但没有得到预期的结果。但是,如果我改为

#define _inout(T) T
...
float pMod1(_inout (float) p, float size) {
    float halfsize = size*0.5;
    float c = floor((p + halfsize)/size);
    p = mod(p + halfsize, size) - halfsize;
    return p; // <-- change from c to p
}

我得到了我想要的。

  1. 我怀疑我转换inout的方式不完全正确。我从一些Shadertoy代码中借用了它,但我不相信它真的是这样工作的。

  2. c如何有用?在代码中从侧面注释:

许多运算符将空间划分为单元格。一个标识符如果可能的话,返回单元格索引。这个返回值是打算可选地使用,例如作为一个随机的种子改变单元格内部距离函数参数

我不明白它到底是什么意思。有人可以建议一些例子如何使用单元格索引?

Update1:

我将代码改为:

float pMod1(thread float &p, float size) {
    float halfsize = size*0.5;
    float c = floor((p + halfsize)/size);
    p = mod(p + halfsize, size) - halfsize;
    return c;
}

,现在我得到另一个错误信息:

致命错误:在展开可选值

时意外发现nil

from this行:

command_encoder.setComputePipelineState(cps)

下面是来自MetaView.swift的全部代码:

import MetalKit
public class MetalView: MTKView, NSWindowDelegate {
    var queue: MTLCommandQueue! = nil
    var cps: MTLComputePipelineState! = nil
    var timer: Float = 0
    var timerBuffer: MTLBuffer!
    var mousexBuffer: MTLBuffer!
    var mouseyBuffer: MTLBuffer!
    var pos: NSPoint!
    var floatx: Float!
    var floaty: Float!
    required public init(coder: NSCoder) {
        super.init(coder: coder)
        self.framebufferOnly = false
        device = MTLCreateSystemDefaultDevice()
        registerShaders()
    }

    override public func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)
        if let drawable = currentDrawable {
            let command_buffer = queue.commandBuffer()
            let command_encoder = command_buffer.computeCommandEncoder()
            command_encoder.setComputePipelineState(cps) ///////<-- This line throw an error.
            command_encoder.setTexture(drawable.texture, atIndex: 0)
            command_encoder.setBuffer(timerBuffer, offset: 0, atIndex: 1)
            command_encoder.setBuffer(mousexBuffer, offset: 0, atIndex: 2)
            command_encoder.setBuffer(mouseyBuffer, offset: 0, atIndex: 3)
            update()
            let threadGroupCount = MTLSizeMake(8, 8, 1)
            let threadGroups = MTLSizeMake(drawable.texture.width / threadGroupCount.width, drawable.texture.height / threadGroupCount.height, 1)
            command_encoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupCount)
            command_encoder.endEncoding()
            command_buffer.presentDrawable(drawable)
            command_buffer.commit()
        }
    }
    func registerShaders() {
        queue = device!.newCommandQueue()
        do {
            let library = device!.newDefaultLibrary()!
            let kernel = library.newFunctionWithName("compute")!
            timerBuffer = device!.newBufferWithLength(sizeof(Float), options: [])
            mousexBuffer = device!.newBufferWithLength(sizeof(Float), options: [])
            mouseyBuffer = device!.newBufferWithLength(sizeof(Float), options: [])
            cps = try device!.newComputePipelineStateWithFunction(kernel)
        } catch let e {
            Swift.print("(e)")
        }
    }
    func update() {
        timer += 0.01
        var bufferPointer = timerBuffer.contents()
        memcpy(bufferPointer, &timer, sizeof(Float))
        bufferPointer = mousexBuffer.contents()
        memcpy(bufferPointer, &floatx, sizeof(NSPoint))
        bufferPointer = mouseyBuffer.contents()
        memcpy(bufferPointer, &floaty, sizeof(NSPoint))
    }
    override public func mouseDragged(event: NSEvent) {
        pos = convertPointToLayer(convertPoint(event.locationInWindow, fromView: nil))
        let scale = layer!.contentsScale
        pos.x *= scale
        pos.y *= scale
        floatx = Float(pos.x)
        floaty = Float(pos.y)
        debugPrint("Hello",pos.x,pos.y)
    }
}

更新2:我怀疑Update1中的错误是因为我分割Metal文件的方式。因此,我通过将所有函数复制到1 Metal文件中来简化它,现在我产生了新的错误:

float pMod1(thread float &p, float size) {
    float halfsize = size*0.5;
    float c = floor((p + halfsize)/size);
    p = mod(p + halfsize, size) - halfsize;
    return c;
}
static float map( float3 p )
{
    float size = 10.0;
    p.x = pMod1(p.x,size);/////<--- this produce the error.
    float box = fBox(p, float3(1));
    float sphere = length(p - float3(1)) - 1;
    float d = min(box,sphere);
    float guard = -fBoxCheap(p, float3(size*0.5));
    guard = abs(guard) + size*0.1;
    return min(d,guard);
}
错误:

pMod1的调用是二义的

Metal中的等效函数为

float pMod1(thread float &p, float size) {
    float halfsize = size*0.5;
    float c = floor((p + halfsize)/size);
    p = mod(p + halfsize, size) - halfsize;
    return c;
}

为了修改形参,需要通过引用传递它,就像在c++中一样。在Metal中,您还需要显式地将其限定为位于thread地址空间(与constant, threadgroup等相反)

hg_sdf中pMod系列函数的目的是"折叠"空间,允许您创建定期重复的对象的副本。c值类似于对象索引,表示对象位于折叠空间的哪个"分区"。你可以忽略它,除非你正在调整对象外观(通过应用不同的材料或添加表面细节等)

这些在Johann Körndorfer的演讲中都有详细的描述。

为了调用这个函数,你需要创建一个临时变量来存储你想要修改的swizzleout组件,因为你不能在Metal中通过引用传递swizzledvector组件。

float px = p.x;
float c = pMod1(px, size);
p.x = px;

由于px是通过引用传递的,因此p.x现在包含了在pMod1中写入px的值