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

查看: 4911|回复: 0

[原创] Unity立体渲染(二)|Raymarching

[复制链接]

1214

主题

1918

帖子

2万

贡献

管理员

Rank: 9Rank: 9Rank: 9

积分
25200
QQ
发表于 2016-10-13 08:19:48 | 显示全部楼层 |阅读模式
在立体渲染(Volumetric Rendering,为保持一致,本系列译文均称立体渲染)系列第一篇文章——Unity教程|立体渲染中,我们简单介绍了立体渲染的基本概念。对立体渲染不太熟悉的开发者,请先查阅第一篇文章

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

尽管传统着色器只能渲染材质的外壳,但还是有办法让光线穿透到材质内部的几何体,创造画面的深度。Raymarch就是最常用的技术,第一篇文章使用Raymarch技术在立方体内绘制了一个红色球体。本文将深入为大家介绍效率更高的Raymarch实现方案。

引言

不严谨地说,当光线从相机发射到物体表面时,Unity 5光照引擎的标准行为是停止渲染。目前并没有内建机制能让这些射线穿透物体表面进入内部。为了补偿这个缺陷,我们引入了光线追踪(Raymarch)技术。片段着色器包含要渲染的点的位置(世界坐标系下)以及从相机到物体的视线方向,我们手动延长这些射线,让它们射向仅存在于着色器代码中的自定义几何体。能实现该需求的着色器原型如下:
[C#] 纯文本查看 复制代码
struct v2f {        float4 pos : SV_POSITION;        // Clip space 
        float3 wPos : TEXCOORD1;        // World position  
};

// Vertex function
v2f vert (appdata_full v)
{
        v2f o;
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
        o.wPos = mul(_Object2World, v.vertex).xyz; 
        return o;
}

// Fragment function
fixed4 frag (v2f i) : SV_Target
{
        float3 worldPosition = i.wPos;
        float3 viewDirection = normalize(i.wPos - _WorldSpaceCameraPos);
        return raymarch (worldPosition, viewDirection);
}
struct v2f {
        float4 pos : SV_POSITION;        // Clip space 
        float3 wPos : TEXCOORD1;        // World position  
};

// Vertex function
v2f vert (appdata_full v)
{
        v2f o;
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
        o.wPos = mul(_Object2World, v.vertex).xyz; 
        return o;
}

// Fragment function
fixed4 frag (v2f i) : SV_Target
{
        float3 worldPosition = i.wPos;
        float3 viewDirection = normalize(i.wPos - _WorldSpaceCameraPos);
        return raymarch (worldPosition, viewDirection);
}

下文将提供raymarch函数的多种不同实现。

固定步长(Constant Step)的光线追踪

在立体渲染系列文章第一篇中实现的光线追踪就使用了固定步长。每一束射线都会沿视线方向延伸STEP_SIZE的长度,直到击中目标为止。上篇的示例将目标绘制为红色,而其他部分为白色。



固定步长的光线追踪可以通过下列代码实现:
[C#] 纯文本查看 复制代码
fixed4 raymarch (float3 position, float3 direction)
{
        for (int i = 0; i < STEPS; i++)
        {
                if ( sphereHit(position) )
                        return fixed4(1,0,0,1); // Red
 
                position += direction * STEP_SIZE;
        }

        return fixed4(0,0,0,1); // White 
}

上篇已经看到其渲染结果是看起来像平面的几何体:


下一篇要介绍的表面着色器将全权负责立体渲染的三维效果。但此前需要更好的方式来实现光线追踪。

距离辅助的光线追踪

固定步长的光线追踪非常低效,原因是射线每次都会延长同样的长度,不会考虑到几何体填满立体空间的情况。对于着色器来说,无效循环的增加就意味着性能的下降。如果要实现实时的立体渲染,就必须找到更好更高效的解决方案。

我们需要一种方法来估算射线在遇到几何体之前到底要走多远。这就需要估算射线到达几何体的距离。上一篇文章中用到了sphereHit函数,它可以判断一个点是否存在于某个球体内:
[C#] 纯文本查看 复制代码
bool sphereHit (float3 p)
{
    return distance(p,_Centre) < _Radius;
}

对其稍作修改,修改返回值为距离:
[C#] 纯文本查看 复制代码
float sphereDistance (float3 p)
{
    return distance(p,_Centre) - _Radius;
}


现在该函数属于符号距离函数(signed distance function)家族。正如函数名称所示,它测量的返回值是可正可负的。当返回值为正数时,点在球体外。而返回值为负数时,点就在球体内。为零就表示点在球的表面。

sphereDistance能做的就是提供一个保守的预估距离,告诉我们射线在到达球体之前还要前进多远。不过如果没有进行适当的着色,立体渲染的结果还是比较单调的。本例中只渲染了一个球体,看起来似乎不重要。但如果使用更复杂的几何形状,这个技术就非常有价值了。下图(取自Distance Estimated 3D Fractals,经过距离估算的3D分形)呈现了光线追踪的工作原理。每条射线都会走过到达最近物体的距离。通过这样的方式,就可以大幅减少射线命中渲染体所需的步数了。



下面是距离辅助的光线追踪实现代码:
[C#] 纯文本查看 复制代码
fixed4 raymarch (float3 position, float3 direction)
{
        for (int i = 0; i < STEPS; i++)
        {
                float distance = sphereDistance(position);
                if (distance < MIN_DISTANCE)
                        return i / (float) STEPS;

                position += distance * direction;
        }
        return 0;
}

为了更好地理解它的工作原理,将表面着色替换为渐变色,来表示光线追踪命中几何体前究竟需要多少个步骤:



很明显,立刻就能找出面向相机的平面几何体。其边缘相比之前复杂了许多。这种技术同样可以估算出到附近任意几何体之间的距离。

结论

本文介绍了实现实时光线追踪着色器实际可用的标准技术。射线会先保守估计到附近几何体的最近距离,再根据该距离进入立体空间。

下一篇文章将着重介绍使用距离函数创建基本几何体的方法,以及如何组合这些几何体以便得到您想要的任意形状。



原文:http://www.alanzucconi.com/2016/07/01/raymarching/
原文作者:Alan Zucconi
感谢Unity官方翻译组成员“E.A.S”对本文翻译所做的贡献。
转载请注明来源:Unity官方中文社区(forum.china.unity3d.com)。请勿私自更改任何版权说明信息。

本帖子中包含更多资源

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

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

本版积分规则

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