由于时间和精力的关系,这系列文章都没有一次写完,内容都会保持动态更新,会进行补充。
介绍
以下内容列举了常见的 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 圆
蓝图
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 矩形
蓝图
Custom
float2 Distance = abs(UV) - BoxSize;
return length(max(Distance, 0.0)) + min(max(Distance.x, Distance.y), 0.0);
Oriented Box 方向矩形
蓝图
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 平行四边形
蓝图
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
出于学习目的几何反射算法
蓝图
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 等腰三角形
蓝图
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 不规则胶囊
蓝图
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