我是 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专家有什么建议吗?
我不是着色器专家,但这个问题看起来很有趣,我想自己尝试一下。我在网上做了一些搜索,找到了Mikael Hvidtfeldt Christensen的Plotting 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
是不言自明的,我们进行了多次采样。步骤似乎是每个样本之间移动多少。它与模糊着色器非常相似:
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);
}
}
这甚至更好,但现在我注意到我们正在根据厚度(样本计数)改变函数的原点。这是因为我们的
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);
}
}
我想说这已经足够好了,至少在小厚度下是这样。可能有一种更好的方法来做到这一点,可能是通过您之前提到的导数方法,但这适用于没有(易于计算的)导数的函数。 显然您可以根据需要自行扩展;像向着色器公开颜色、更改函数等之类的事情。我不确定向用户公开我们正在绘制的函数的好方法,但我想如果需要,您可以使用不同的函数制作着色器变体.