如何使用对金属着色器的fwidth()调用来移植此Shadertoy着色器?

时间:2017-05-04 01:00:43

标签: webgl shader metal

我一直在将Shadertoy着色器移植到Metal,以便学习如何编写Metal着色器。我不认为我做得正确,因为我将每个着色器都写为计算着色器,而不是顶点/片段着色器。这已经适用于我移植的很多着色器,差不多20个。但是有些端口非常慢,而其他端口包括不可用的功能。

以下是绊倒我的其中一个着色器:

https://www.shadertoy.com/view/4t2SRh

在金属计算着色器中不允许使用render()和mainImage()调用fwidth()。但是,Metal Shader Language具有fwidth(),但只能在片段着色器中调用。

这是我尝试移植到计算着色器:

#include <metal_stdlib>

using namespace metal;

float float_mod(float f1, float f2) {
    return f1-f2 * floor(f1/f2);
}


float sdfCircle(float2 center, float radius, float2 coord )
{
    float2 offset = coord - center;

    return sqrt((offset.x * offset.x) + (offset.y * offset.y)) - radius;
}

float sdfEllipse(float2 center, float a, float b, float2 coord)
{
    float a2 = a * a;
    float b2 = b * b;
    return (b2 * (coord.x - center.x) * (coord.x - center.x) +
            a2 * (coord.y - center.y) * (coord.y - center.y) - a2 * b2)/(a2 * b2);
}

float sdfLine(float2 p0, float2 p1, float width, float2 coord)
{
    float2 dir0 = p1 - p0;
    float2 dir1 = coord - p0;
    float h = clamp(dot(dir0, dir1)/dot(dir0, dir0), 0.0, 1.0);
    return (length(dir1 - dir0 * h) - width * 0.5);
}

float sdfUnion( const float a, const float b )
{
    return min(a, b);
}

float sdfDifference( const float a, const float b)
{
    return max(a, -b);
}

float sdfIntersection( const float a, const float b )
{
    return max(a, b);
}

float anti(float d) {
    return fwidth(d) * 1.0;
}

float4 render(float d, float3 color, float stroke)
{
    //stroke = fwidth(d) * 2.0;
    float anti = fwidth(d) * 1.0;
                float4 strokeLayer = float4(float3(0.05), 1.0-smoothstep(-anti, anti, d - stroke));
    float4 colorLayer = float4(color, 1.0-smoothstep(-anti, anti, d));

    if (stroke < 0.000001) {
        return colorLayer;
    }
    return float4(mix(strokeLayer.rgb, colorLayer.rgb, colorLayer.a), strokeLayer.a);
}


kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    texture2d<float, access::sample> input [[texture(1)]],
                    constant float &timer [[buffer(0)]],
                    uint2 gid [[thread_position_in_grid]])
{
    float4 fragColor;
    int width = output.get_width();
    int height = output.get_height();
    float2 resolution = float2(width,height);
    float2 uv = float2(gid) / resolution;

    float size = min(resolution.x, resolution.y);
    float pixSize = 1.0 / size;
    float stroke = pixSize * 1.5;
    float2 center = float2(0.5, 0.5 * resolution.y/resolution.x);

    float a = sdfEllipse(float2(0.5, center.y*2.0-0.34), 0.25, 0.25, uv);
    float b = sdfEllipse(float2(0.5, center.y*2.0+0.03), 0.8, 0.35, uv);
    b = sdfIntersection(a, b);

    float4 layer1 = render(b, float3(0.32, 0.56, 0.53), fwidth(b) * 2.0);


// Draw strips
    float4 layer2 = layer1;
    float t, r0, r1, r2, e, f;
    float2 sinuv = float2(uv.x, (sin(uv.x*40.0)*0.02 + 1.0)*uv.y);
    for (float i = 0.0; i < 10.0; i++) {
        t = float_mod(timer + 0.3 * i, 3.0) * 0.2;
        r0 = (t - 0.15) / 0.2 * 0.9 + 0.1;
        r1 = (t - 0.15) / 0.2 * 0.1 + 0.9;
        r2 = (t - 0.15) / 0.2 * 0.15 + 0.85;
        e = sdfEllipse(float2(0.5, center.y*2.0+0.37-t*r2), 0.7*r0, 0.35*r1, sinuv);
        f = sdfEllipse(float2(0.5, center.y*2.0+0.41-t), 0.7*r0, 0.35*r1, sinuv);
        f = sdfDifference(e, f);
        f = sdfIntersection(f, b);
        float4 layer = render(f, float3(1.0, 0.81, 0.27), 0.0);
        layer2 = mix(layer2, layer, layer.a);
    }


// Draw the handle
    float bottom = 0.08;
    float handleWidth = 0.01;
    float handleRadius = 0.04;
    float d = sdfCircle(float2(0.5-handleRadius+0.5*handleWidth, bottom), handleRadius, uv);
    float c = sdfCircle(float2(0.5-handleRadius+0.5*handleWidth, bottom), handleRadius-handleWidth, uv);
    d = sdfDifference(d, c);
    c = uv.y - bottom;
    d = sdfIntersection(d, c);
    c = sdfLine(float2(0.5, center.y*2.0-0.05), float2(0.5, bottom), handleWidth, uv);
    d = sdfUnion(d, c);
    c = sdfCircle(float2(0.5, center.y*2.0-0.05), 0.01, uv);
    d = sdfUnion(c, d);
    c = sdfCircle(float2(0.5-handleRadius*2.0+handleWidth, bottom), handleWidth*0.5, uv);
    d = sdfUnion(c, d);
    float4 layer0 = render(d, float3(0.404, 0.298, 0.278), stroke);

    float2 p = (2.0*float2(gid).xy-resolution.xy)/min(resolution.y,resolution.x);
    float3 bcol = float3(1.0,0.8,0.7-0.07*p.y)*(1.0-0.25*length(p));
    fragColor = float4(bcol, 1.0);
    fragColor.rgb = mix(fragColor.rgb, layer0.rgb, layer0.a);
    fragColor.rgb = mix(fragColor.rgb, layer1.rgb, layer1.a);
    fragColor.rgb = mix(fragColor.rgb, layer2.rgb, layer2.a);

    fragColor.rgb = pow(fragColor.rgb, float3(1.0/2.2));
    output.write(fragColor,gid);

}

这不能编译,因为fwidth()不可用。但是,如果我摆脱了fwidth(),它将编译......但当然不会画出正确的东西。

我想知道是否有更好的方法将其移植到片段/顶点着色器,以便我可以使用MSL的fwidth()?或者将它作为计算着色器写得很好,我应该找到使用fwidth()的另一种方法?

0 个答案:

没有答案