【UE】SDF - 使用有向距离场(Signed Distance Fields)整各种活-1-2D距离场基元


由于时间和精力的关系,这系列文章都没有一次写完,内容都会保持动态更新,会进行补充。


介绍

以下内容列举了常见的 SDF(Signed Distance Function,有向距离场)基元。
包含其材质蓝图的实现方法,以及在 Custom 节点中使用 HLSL 编写的实现方式。
此外,还附上了 SDF仙人 Inigo Quilez 制作的在线演示链接,便于参考与预览。

在材质开发中,从性能和优化的角度出发,建议尽可能使用蓝图节点实现,而非直接使用 Custom 节点。这一点与“游戏逻辑代码用蓝图还是用 C++ 编写”的讨论完全不同。通常来说,材质蓝图通过节点实现可以更好地利用引擎的编译优化能力,从而获得更高的运行效率。

具体说明,参考官方文档文末说明:文档连接
在这里插入图片描述
因此,除非必要,建议优先选择蓝图节点实现功能。此外,尽量复用已计算的结果或公式,以进一步减少多余的计算开销,从而提升性能。处于这种考虑,在蓝图部分我或许会列举一些相同结果的计算过程。
以下方的Circle 圆基元为例,如果在之前的计算中,你已经使用过了Distance 0.5,则没必要再使用Length来计算相同结果。

距离场基元

后续会陆续补充更多的距离场基元造型,目前已经列出了一些基本形状,但还没来的急做完。建议收藏并关注,后续会尽快补全内容。
另外,这里是 SDF仙人IQ的网站,代码以 GLSL 为主,可以参考学习,看看后续这些基元都是什么样子。
正常我会按顺序制作。但如果在那里看到你正需要的基元,可以评论区告诉我,我会插个队先把你需要的做出来。

DebugSDF 距离场预览函数

首先制作一个DebugSDF函数,方便我们预览2D SDF结果

  • 蓝色为正外部
  • 绿色为负内部
  • 红色为表面

在这里插入图片描述
在这里插入图片描述

这是最新版的DebugSDF。
可以看到,本文中的部分基元演示仍使用旧版本DebugSDF。由于这些Debug演示与 SDF 基元算法本身无关(就是懒),下面的部分蓝图截图未作更新。

2D基元


Circle 圆

蓝图
Text

Custom

// 计算UV到原点的距离,并减去半径,返回距离场值
return length(UV) - Radius;

在线演示


Rounded Box 圆角矩形

蓝图
在这里插入图片描述

Custom

Radius.xy = (UV.x > 0.0) ? Radius.xy : Radius.zw;
Radius.x  = (UV.y > 0.0) ? Radius.x  : Radius.y;
float2 Q = abs(UV) - BoxSize + Radius.x;
return min(max(Q.x, Q.y), 0.0) + length(max(Q, 0.0)) - Radius.x;

在线演示


Box 矩形

蓝图
Text

Custom

float2 Distance = abs(UV) - BoxSize;
return length(max(Distance, 0.0)) + min(max(Distance.x, Distance.y), 0.0);

在线演示


Oriented Box 方向矩形

蓝图
Text

Custom

float LengthAB = length(PointB - PointA);
float2 Direction = (PointB - PointA) / LengthAB;
float2 Offset = UV - (PointA + PointB) * 0.5;
Offset = mul(float2x2(Direction.x, -Direction.y, Direction.y, Direction.x), Offset);
Offset = abs(Offset) - float2(LengthAB, Thickness) * 0.5;
return length(max(Offset, 0.0)) + min(max(Offset.x, Offset.y), 0.0);

在线演示


Segment 线段

蓝图
在这里插入图片描述

Custom

float2 PA = UV - PointA;
float2 BA = PointB - PointA;
float H = clamp(dot(PA, BA) / dot(BA, BA), 0.0, 1.0);
return length(PA - BA * H);

在线演示


Rhombus 菱形

蓝图
在这里插入图片描述

Custom

UV = abs(UV);
float2 Temp = Size * (Size - 2.0 * UV);
float H = clamp((Temp.x - Temp.y) / dot(Size, Size),-1.0, 1.0);
float Distance = length(UV - 0.5 * Size * float2(1.0 - H, 1.0 + H));//不分正负的距离,如果只是要描边就可以直接用它
return Distance * sign(UV.x * Size.y + UV.y * Size.x - Size.x * Size.y);

在线演示


Isosceles Trapezoid 等腰梯形

蓝图
在这里插入图片描述

Custom

float2 TrapezoidCenter = float2(BottomRadius, Height);
float2 TrapezoidEdge = float2(BottomRadius - TopRadius, 2.0 * Height);
UV.x = abs(UV.x);
float2 InternalDistance = float2(UV.x - min(UV.x, (UV.y < 0.0) ? TopRadius : BottomRadius), abs(UV.y) - Height);
float2 EdgeDistance = UV - TrapezoidCenter + TrapezoidEdge * clamp(dot(TrapezoidCenter - UV, TrapezoidEdge) / dot(TrapezoidEdge, TrapezoidEdge), 0.0, 1.0);
float Sign = (EdgeDistance.x < 0.0 && InternalDistance.y < 0.0) ? -1.0 : 1.0;
return Sign * sqrt(min(dot(InternalDistance, InternalDistance), dot(EdgeDistance, EdgeDistance)));

在线演示


Parallelogram 平行四边形

蓝图
Text

Custom

float2 TiltHeight = float2(Skew, Height);
UV = (UV.y < 0.0) ? -UV : UV;
float2 HorizontalDistance = UV - TiltHeight; 
HorizontalDistance.x -= clamp(HorizontalDistance.x, -Width, Width);
float2 DistanceResult = float2(dot(HorizontalDistance, HorizontalDistance), -HorizontalDistance.y);
float SignedArea = UV.x * TiltHeight.y - UV.y * TiltHeight.x;
UV = (SignedArea < 0.0) ? -UV : UV;
float2 ClosestPoint = UV - float2(Width, 0); 
ClosestPoint -= TiltHeight * clamp(dot(ClosestPoint, TiltHeight) / dot(TiltHeight, TiltHeight), -1.0, 1.0);
DistanceResult = min(DistanceResult, float2(dot(ClosestPoint, ClosestPoint), Width * Height - abs(SignedArea)));
return sqrt(DistanceResult.x) * sign(-DistanceResult.y);

在线演示


Equilateral Triangle 等边三角形

版本1

蓝图
在这里插入图片描述

Custom

float SlopeFactor = sqrt(3.0);
UV.x = abs(UV.x);
UV -= float2(0.5, 0.5 * SlopeFactor) * max(UV.x + SlopeFactor * UV.y, 0.0);
UV.x = UV.x - clamp(UV.x, -Radius, Radius);
UV.y = -UV.y - Radius * (1.0 / SlopeFactor);
return length(UV) * sign(UV.y);
版本2

出于学习目的几何反射算法

蓝图
Text

Custom

float SlopeFactor = sqrt(3.0);
UV.x = abs(UV.x) - Radius; 
UV.y = UV.y + Radius / SlopeFactor; 
if (UV.x + SlopeFactor * UV.y > 0.0)
{
    UV = float2(UV.x - SlopeFactor * UV.y, -SlopeFactor * UV.x - UV.y) / 2.0;
}
UV.x -= clamp(UV.x, -2.0 * Radius, 0.0);
return -length(UV) * sign(UV.y);

在线演示


Isosceles Triangle 等腰三角形

蓝图
Text

Custom

UV.x = abs(UV.x);
float2 ClosestPointA = UV - TriangleSize * clamp(dot(UV, TriangleSize) / dot(TriangleSize, TriangleSize), 0.0, 1.0);
float2 ClosestPointB = UV - TriangleSize * float2(clamp(UV.x / TriangleSize.x, 0.0, 1.0), 1.0);
float SignFactor = -sign(TriangleSize.y);
float2 DistanceResult = min(
    float2(dot(ClosestPointA, ClosestPointA), SignFactor * (UV.x * TriangleSize.y - UV.y * TriangleSize.x)),
    float2(dot(ClosestPointB, ClosestPointB), SignFactor * (UV.y - TriangleSize.y))
);
 return -sqrt(DistanceResult.x) * sign(DistanceResult.y);

在线演示


Triangle 三角形

蓝图
在这里插入图片描述

因为算法是"正义的算三遍",因此为了保护心情封装了一个邪恶函数DistanceToEdge
在这里插入图片描述

Custom

float2 Edge0 = P1 - P0;
float2 Edge1 = P2 - P1;
float2 Edge2 = P0 - P2;
float2 V0 = UV - P0;
float2 V1 = UV - P1;
float2 V2 = UV - P2;
float2 PQ0 = V0 - Edge0 * clamp(dot(V0, Edge0) / dot(Edge0, Edge0), 0.0, 1.0);
float2 PQ1 = V1 - Edge1 * clamp(dot(V1, Edge1) / dot(Edge1, Edge1), 0.0, 1.0);
float2 PQ2 = V2 - Edge2 * clamp(dot(V2, Edge2) / dot(Edge2, Edge2), 0.0, 1.0);
float S = sign(Edge0.x * Edge2.y - Edge0.y * Edge2.x);
float2 D = min(
    min(
        float2(dot(PQ0, PQ0), S * (V0.x * Edge0.y - V0.y * Edge0.x)),
        float2(dot(PQ1, PQ1), S * (V1.x * Edge1.y - V1.y * Edge1.x))
    ),
    float2(dot(PQ2, PQ2), S * (V2.x * Edge2.y - V2.y * Edge2.x))
);
return -sqrt(D.x) * sign(D.y);

在线演示


Uneven Capsule 不规则胶囊

蓝图
Text

Custom

UV.x = abs(UV.x);
float Slope = (Radius1 - Radius2) / Height; // 半径变化的斜率
float Normal = sqrt(1.0 - Slope * Slope);  // 法线的系数(垂直于斜率方向)
float Projection = dot(UV, float2(-Slope, Normal)); // 点积结果用于判断位置
if (Projection < 0.0) 
{
    return length(UV) - Radius1;
}
if (Projection > Normal * Height) 
{

    return length(UV - float2(0.0, Height)) - Radius2;
}
return dot(UV, float2(Normal, Slope)) - Radius1;

Regular Pentagon 正五边形

蓝图
在这里插入图片描述

Custom

/*
float Pi = 3.14159265358979323846;
float R = Pi/5;
float3 K = float3(cos(R), sin(R), tan(R));
*/
float3 K = float3(0.809016994, 0.587785252, 0.726542528);
UV.x = abs(UV.x);
UV -= 2.0 * min(dot(float2(-K.x, K.y), UV), 0.0) * float2(-K.x, K.y);
UV -= 2.0 * min(dot(float2( K.x, K.y), UV), 0.0) * float2( K.x, K.y);
UV -= float2(clamp(UV.x, -Radius * K.z, Radius * K.z), Radius);
return length(UV) * sign(UV.y);

Regular Hexagon 正六边形

蓝图
Text

Custom

// wip

Regular Octogon 正八边形

蓝图
Text

Custom

// wip

Hexagram 六芒星

蓝图
Text

Custom

// wip

Star 5 五角星

蓝图
Text

Custom

// wip

Regular Star 正星形

蓝图
Text

Custom

// wip

Pie 扇形

蓝图
Text

Custom

// wip

Cut Disk 剪切圆盘

蓝图
Text

Custom

// wip

Arc 弧线

蓝图
Text

Custom

// wip

Ring 圆环

蓝图
Text

Custom

// wip

Horseshoe 马蹄形

蓝图
Text

Custom

// wip

Vesica 鱼形瓣

蓝图
在这里插入图片描述

Custom

UV = abs(UV); // 取绝对值
float Base = sqrt(Radius * Radius - Distance * Distance); // 计算辅助值 Base
return ((UV.y - Base) * Distance > UV.x * Base) 
    ? length(UV - float2(0.0, Base)) 
    : length(UV - float2(-Distance, 0.0)) - Radius; 
/* 更快,但外部距离尖锐
return min(
length(UV - float2(0.0, Base)), 
length(UV - float2(-Distance, 0.0)) - Radius);
*/

Oriented Vesica 方向鱼形瓣

蓝图
Text

Custom

// wip

Moon 月亮

蓝图
Text

Custom

// wip

Circle Cross 圆十字形

蓝图
Text

Custom

// wip

Simple Egg 简单鸡蛋形

蓝图
Text

Custom

// wip

Heart 心形

蓝图
Text

Custom

// wip

Cross 十字形

蓝图
Text

Custom

// wip

Rounded X 圆角 X 形

蓝图
Text

Custom

// wip

Polygon 多边形

蓝图
在这里插入图片描述

函数MF_SDF_Polygon,如上图所示,要多少顶点接多少,别忘了末尾的点要连到第一个
在这里插入图片描述

Custom

// 演示五个点,如果需要更多,在这里添加
float2 Vertices[5] = { Point0, Point1, Point2, Point3, Point4 };

float Distance = dot(UV - Vertices[0], UV - Vertices[0]);
float Sign = 1.0;
// 循环数量也要改
for (int i = 0, j = 4; i < 5; j = i, i++)
{
    float2 Edge = Vertices[j] - Vertices[i];
    float2 W = UV - Vertices[i];
    float2 B = W - Edge * clamp(dot(W, Edge) / dot(Edge, Edge), 0.0, 1.0);
    Distance = min(Distance, dot(B, B));
    bool3 Conditions = bool3(UV.y >= Vertices[i].y, UV.y < Vertices[j].y, Edge.x * W.y > Edge.y * W.x);
    if (all(Conditions) || all(!Conditions))
    {
        Sign *= -1.0;
    }
}
return Sign * sqrt(Distance);

Ellipse 椭圆

蓝图
Text

Custom

// wip

Parabola 抛物线

蓝图
Text

Custom

// wip

Parabola Segment 抛物线线段

蓝图
Text

Custom

// wip

Quadratic Bezier 二次贝塞尔曲线

蓝图
Text

Custom

// wip

Bobbly Cross 模糊十字形

蓝图
Text

Custom

// wip

Tunnel 隧道形

蓝图
Text

Custom

// wip

Stairs 楼梯形

蓝图
Text

Custom

// wip

Quadratic Circle 二次曲线圆形

蓝图
Text

Custom

// wip

Hyperbola 双曲线

蓝图
Text

Custom

// wip

Cool S 酷 S 形

蓝图
Text

Custom

// wip

Circle Wave 圆波形

蓝图
Text

Custom

// wip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值