请选择 进入手机版 | 继续访问电脑版

查看: 4259|回复: 0

[原创] Unity立体渲染(四)|有向距离函数

[复制链接]

1214

主题

1918

帖子

2万

贡献

管理员

Rank: 9Rank: 9Rank: 9

积分
25200
QQ
发表于 2016-9-30 09:27:57 | 显示全部楼层 |阅读模式
本教程为Unity立体渲染(Volumetric Rendering,为保持一致,后续译文均称立体渲染)系列最终篇,前面三篇分别介绍了立体渲染的基本概念、Raymarching技术以及表面着色。本篇将介绍如何在体积着色器中创建复杂的三维模型。

本系列教程全集:
  • 第一篇:立体渲染。介绍立体渲染的概念以及在Unity中如何实现立体渲染。
  • 第二篇:光线追踪。文章着重说明如何实现距离辅助的光线追踪,这是是渲染立体的事实性标准技术。
  • 第三篇:表面着色。全面指引如何逼真地进行立体着色。
  • 第四篇:有向距离函数。一篇对于数学工具更深入的讨论,让我们能制作和组合任意几何体。

本片教程介绍了如何在体积着色器中创建复杂的三维模型。有向距离函数(通常被称为场)是用来描述球形,盒子和环面的几何形状的数学工具。和传统的由三角形组成的3D模型相比,有向距离函数提供了几乎无限的分辨率,并且适合进行计划操作。下面的动画来自于一个动画教程:制作一个蜗牛,展示了如何使用更简单的形状去创建一个蜗牛。



介绍

大多数现代的3D引擎-例如Unity-都使用三角形来处理几何体。每一个物体,无论多复杂,都是由原始的三角形所组成。尽管这在计算机图形学中是实际的标准,不是所有物体都能用三角形表示。球形以及其他曲面几何形状就无法被细分为平面实体。虽然我们可以通过在表面覆盖大量的小三角形来得到一个近似的球体,但这会增加更多的图形绘制成本。

用于表现几何形状的替代方法是存在的。其中一个就是使用有向距离函数,就是我们要展现的物体的数学描述。当你用一个球体的方程来取代它的几何形状时,你就能把所有由近似描述所带来的错误从3D引擎中移除。你可以把有向距离函数视作与SVG等量的三角形。你可以缩放和变焦SDF的几何形状而不丢失它的细节。球体永远都是光滑的,无论你离他的边缘多么地近。

有向距离函数是基于这样一种思想,即每一个原始对象都必须有一个对应的函数。它以3D坐标作为参数,并返回一个值,用于表示这个点与物体表面的距离。

SDF 球体

在这个系列的第一部分体积渲染中,我们学习了一个用于计算我们是否在球体内的碰撞函数:

[C#] 纯文本查看 复制代码
bool sphereHit (float3 p)
{
    return distance(p,_Centre) < _Radius;
}

我们可以改变这个函数让他返回距离球体表面的距离

[C#] 纯文本查看 复制代码
float sdf_sphere (float3 p, float3 c, float r)
{
    return distance(p,c) - r;
}

如果sdf_spher 返回了一个正数,那么我们就没有碰撞到球体。返回负数则表示我们是在球体内,而零实际上表示的就是表面上的点。

并集和交集

上一篇Raymarching教程中, 在介绍摄像机射线到材质的进阶部分中, 我们简要介绍了有向距离函数的概念。还有另一个使用有向距离函数的原因,是因为他们是适合进行组合的。比如有两个不同球形的SDFs,我们该如何将他们合并成一个呢?

我们可以从相机射线的角度来考虑这个问题。在每一步中,射线必须找到与它最接近的障碍物。如果有两个球体,我们需要评估两者的距离以得到最小值,因为我们不希望射线超出球体的范围很多,所以我们必须作最保守的估算。

这个玩具的例子可以扩展到任何两个SDF,获取他们之间的最小值并返回另一个相当于他们并集的SDF:
[C#] 纯文本查看 复制代码
float map (float3 p)
{
    return min
        (
            sdf_sphere(p, - float3 (1.5, 0, 0), 2), // Left sphere
            sdf_sphere(p, + float3 (1.5, 0, 0), 2)  // Right sphere
        );
}

结果可以在下面的图片中看到(它还具有其他视觉增强功能)



因为同样的原因,显而易见的获取两个SDF的最大值则返回了他们的交集。
[C#] 纯文本查看 复制代码
float map (float3 p)
{
    return max
        (
            sdf_sphere(p, - float3 (1.5, 0, 0), 2), // Left sphere
            sdf_sphere(p, + float3 (1.5, 0, 0), 2)  // Right sphere
        );
}


SDF 盒子

很多几何图形可以用我们已知的方式进行构建。如果我们想把知识更进一步,我们需要引入一个新的SDF原型:半空间。就像名字所指出的那样,它只是一个原始的占据了半个3D空间的东西。
[C#] 纯文本查看 复制代码
// X Axis
d = + p.x - c.x; // Left half-space full
d = - p.x + c.x; // Right half-space full
 
// Y Axis// X Axis
d = + p.x - c.x; // Left half-space full
d = - p.x + c.x; // Right half-space full
 
// Y Axis
d = + p.y - c.y; // Left half-space full
d = - p.y + c.y; // Right half-space full
 
// Z Axis
d = + p.z - c.z; // Left half-space full
d = - p.z + c.z; // Right half-space full
d = + p.y - c.y; // Left half-space full
d = - p.y + c.y; // Right half-space full
 
// Z Axis
d = + p.z - c.z; // Left half-space full
d = - p.z + c.z; // Right half-space full

关键点是要用6个平面相交,以创建一个给定大小为s的盒子,就如下面动画所示:
[C#] 纯文本查看 复制代码
float sdf_box (float3 p, float3 c, float3 s)
{
    float x = max
    (   p.x - _Centre.x - float3(s.x / 2., 0, 0),
        _Centre.x - p.x - float3(s.x / 2., 0, 0)
    );
 
    float y = max
    (   p.y - _Centre.y - float3(s.y / 2., 0, 0),
        _Centre.y - p.y - float3(s.y / 2., 0, 0)
    );
 
    float z = max
    (   p.z - _Centre.z - float3(s.z / 2., 0, 0),
        _Centre.z - p.z - float3(s.z / 2., 0, 0)
    );
 
    float d = x;
    d = max(d,y);
    d = max(d,z);
    return d;
}



有更简洁(但不够精确)的方法来创建一个盒子,利用了中心周围的对称性。
[C#] 纯文本查看 复制代码
float vmax(float3 v)
{
    return max(max(v.x, v.y), v.z);
}
float sdf_boxcheap(float3 p, float3 c, float3 s)
{
    return vmax(abs(p-c) - s);
}

形状混合

如果你熟悉alpha混合的概念,你可能会认得下面的代码:
[C#] 纯文本查看 复制代码
float sdf_blend(float d1, float d2, float a)
{
    return a * d1 + (1 - a) * d2;
}


这么做的目的是创建d1和d2两个值之间的混合,通过a的值(从0到1)进行控制。用于混合颜色的代码也可以用于混合形状。例如,下面的代码将一个球体混合到一个立方体中:
[C#] 纯文本查看 复制代码
d = sdf_blend
(
    sfd_sphere(p, 0, r),
    sfd_box(p, 0, r),
    (_SinTime[3] + 1.) / 2.
);



光滑合并

在上一章节中我们已经看到两个SDF可以通过取最小值的方式合并在一起。SDF的合集虽然确实是有效的,但它的结果会有一点不真实。SDF可以将原物体以多种方式混合在一起。其中一个技巧就是,指数平滑(链接:最小光滑)已经被广泛使用在本教程的原始动画中。
[C#] 纯文本查看 复制代码
float sdf_smin(float a, float b, float k = 32)
{
    float res = exp(-k*a) + exp(-k*b);
    return -log(max(0.0001,res)) / k;
}

当两个形状以这个新的操作进行结合时,他们会平滑地合并,创建一个温和的步骤去移除所有锋利的边缘。在下面的动画中,你可以看到球体们是如何合并在一起的:



SDF 代数

可以预见的是,那些SDF元物体以及操作是有向距离函数代数的一部分。旋转,缩放,混合,扭曲…所有这些操作都可以用有向距离函数来表示。

在他名为《使用有向距离函数建模》的文章中,Íñigo Quílez创造了很多SDF,可以用来构建更复杂的几何体。你们可以通过点击下面的可交互ShaderToy进行查看:点击链接进入ShaderToy

一个更大的元物件和操作的集合在MERCURY团队创建的hg_sdf(链接)库中。虽然是由GLSL写的,但是函数可以很方便的移植到Unity的Cg/HLSL中。

结论

可以通过SDF表现的几何体几乎是无限的。这篇文章只是提供一个该主题的简介。如果你真的想掌握体积渲染,增加你对SDF的了解是一个很好的起点



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表