如何使用Unity无光照着色器绘制厚度均匀的曲线

问题描述 投票:0回答:1

我是 Unity 着色器的新手,我正在使用 Unity 无光照着色器来绘制 x 的函数。例如,y = sin(x)。使用以下代码非常简单:

            float4 frag(v2f i) : SV_Target
            {
                float4 color = float4(0, 0, 0, 1);
                float y = 0.45 * sin(20 * i.uv.x) + 0.5;
                float thickness = 0.02;
                if (i.uv.y > (y - thickness) && i.uv.y < (y + thickness))
                {
                    color = float4(1, 0, 0, 1);
                }
                
                return color;
            }

更难的是让曲线具有均匀的厚度。斜率越大,曲线厚度会越薄。

我主要尝试使厚度变量成为曲线斜率的函数,但结果和性能都很差。然而,我注意到,使用 Unity Shaders,总有一个简单的技巧可以解决此类问题。 Shader专家有什么建议吗?

unity-game-engine unity3d-shaders
1个回答
0
投票

我不是着色器专家,但这个问题看起来很有趣,我想自己尝试一下。我在网上做了一些搜索,找到了Mikael Hvidtfeldt ChristensenPlotting High-Frequency Functions Use a GPU,它提出了对每个像素多次采样函数并将结果基于平均值的解决方案:

for (float i = 0.0; i < samples; i++) {
    for (float  j = 0.0;j < samples; j++) {
        float f = function(pos.x+ i*step.x)-(pos.y+ j*step.y);
        count += (f>0.) ? 1 : -1;
    }
}
// base color on abs(count)/(samples*samples)   

那里有两个未知变量,

int(?) samples
float2(?) step
samples
是不言自明的,我们进行了多次采样。步骤似乎是每个样本之间移动多少。它与模糊着色器非常相似: Showing varying samples, from 2 to 50

float4 frag (v2f i) : SV_Target
{
    float2 uv = i.uv;
    float2 step = float2(0.001,0.001);

    float count = 0.;
    for (float i = 0.0; i < _Samples; i++) {
        for (float  j = 0.0; j < _Samples; j++) {
            float f = function(uv.x + i * _StepX) - (uv.y + j * _StepY);
            count += (f > 0.0) ? 1.0 : -1.0;
        }
    }

    float result = 1.0 - (abs(count) / (_Samples * _Samples));
    return fixed4(result, 0, 0, 1);
}

好吧,这看起来不错。不过,厚度是基于样本数量的,而且这条线是模糊的。我们可以使用剪裁值来修复模糊的线条,只要颜色高于某个值,该剪裁值就会将颜色设置为我们的线条颜色。对于厚度问题,让样品以目标厚度为基础,并做

pow(result, 1.0 / _Thickness);
以免掉落得那么快:

float4 frag (v2f i) : SV_Target
{
    float2 uv = i.uv;
    float2 step = float2(0.001,0.001);

    float samples = 5 * _Thickness;
    float count = 0.;
    for (float i = 0.0; i < samples; i++) {
        for (float  j = 0.0; j < samples; j++) {
            float f = function(uv.x + i * _StepX) - (uv.y + j * _StepY);
            count += (f > 0.0) ? 1.0 : -1.0;
        }
    }

    float result = 1.0 - (abs(count) / (samples * samples));
    result = pow(result, 1.0 / _Thickness);

    if (result > _ThicknessClip) {
        return fixed4(1, 0, 0, 1);
    } else {
        return fixed4(0, 0, 0, 1);
    }
}

Showing thickness increasing, but shifting towards the bottom left


这甚至更好,但现在我注意到我们正在根据厚度(样本计数)改变函数的原点。这是因为我们的

step
使用恒定值,更大的样本数意味着我们的偏移量越来越多。

我通过从

-samples/2
循环到
samples/2
而不是
0
samples
来修复此问题:

float4 frag (v2f i) : SV_Target
{
    float2 uv = i.uv;
    float2 step = float2(0.001,0.001);

    float samples = 5 * _Thickness;
    float count = 0.;
    for (float i = -samples/2; i < samples/2; i++) {
        for (float j = -samples/2; j < samples/2; j++) {
            float f = function(uv.x + i * _StepX) - (uv.y + j * _StepY);
            count += (f > 0.0) ? 1.0 : -1.0;
        }
    }

    float result = 1.0 - (abs(count) / (samples * samples));
    result = pow(result, 1.0 / _Thickness);

    if (result > _ThicknessClip) {
        return fixed4(1, 0, 0, 1);
    } else {
        return fixed4(0,0,0,1);
    }
}

Showing thickness increasing without shifting


我想说这已经足够好了,至少在小厚度下是这样。可能有一种更好的方法来做到这一点,可能是通过您之前提到的导数方法,但这适用于没有(易于计算的)导数的函数。 显然您可以根据需要自行扩展;像向着色器公开颜色、更改函数等之类的事情。我不确定向用户公开我们正在绘制的函数的好方法,但我想如果需要,您可以使用不同的函数制作着色器变体.

© www.soinside.com 2019 - 2024. All rights reserved.