神奇的深度图:复杂的效率,不复杂的规律

0x00 前言

正文是《有趣的深度图》的第二篇著作,上一篇小说《有趣的深度图:可见性问题的解法》中已经和我们介绍了深度图在化解可见性问题中的应用。其实,利用深度音信我们得以兑现无数幽默而又显得“高大上”的效用。
而是那么些功用固然看上去高大上,但是一旦了解了规律就会发觉实现这种效能实在是可怜简单的。
那么本文会包括以下多少个有意思的效果在Unity中的实现:

  • 有点科幻的扫描网
  • 通过墙壁绘制背后的“人影”
  • 护盾/能量场效果
  • 边缘检测

行云高山巅,万象纵奇观,本来无一物,心若往之
必观其形~~~韩一韩一

0x01 获取深度信息

为了采纳深度音信来贯彻多少功力,我们首先需要得参与景的深度信息。在移动游戏开发中常用的前向渲染路径(Forward
Rendering)下,大家需要手动设置相机,让它提供场景的纵深消息。

camera.depthTextureMode = DepthTextureMode.Depth;

要是在延迟渲染路径(Deferred
Lighting)下,由于延迟渲染需要场景的深度音信和法线音讯来做光照总结,所以并不需要我们手动设置相机。

如此这般我们就能够在shader中走访_CameraDepthTexture来收获保存的景象的深浅新闻,之后再利用UNITY_SAMPLE_DEPTH这个宏来处理_CameraDepthTexture的值,这样大家就赢得了某个像素的纵深值。

float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, uv));

而是正如上一篇散文中所说,此时的深度值并非是线性的,因而我们经常需要运用另一个内建的艺术Linear01Depth将结果转化为线性的。这样,我们就能将气象的深度消息渲染为一张灰度图。

float linear01Depth = Linear01Depth(depth);

图片 1

先天自己是坐在车里看外面的苍天,这一个时候正是太阳整个儿落尽山后背在此以前的结尾一道亮光,云朵争先恐后地排队前来,都想要披上一件七彩的霞衣,然后就成了进一步生动的云朵,它们像顽皮的少年一样,翻滚打转
,变幻着各类意想不到的形象!

0x02 有点科幻的扫描网

不明了有没有青年伴玩过《无人深空》这款游戏,当初ps4版预售时自我就用行动襄助了这款看上去很有吸重力的沙盒游戏,当然第二天挂闲鱼就是后话了。即便这款游戏让人感觉有些失望,不过中间的片段镜头效果仍然很有趣的,而且也和这篇作品的核心相关——利用情况的深浅音讯来落实部分科幻效果——比如说,在繁星上用扫描仪举办围观的功效。

图片 2

咱俩也可以在Unity中贯彻类似的职能,关键就是拔取情状的吃水信息。

图片 3

故而只要项目应用了前向渲染路径,我们就无法不在本子中手动将相机的depthTextureMode
设置为DepthTextureMode.Depth,假诺是延迟渲染则不需要我们手动设置。

camera.depthTextureMode = DepthTextureMode.Depth;

匡助,这种全屏效果平日作为屏幕特效(image
effect)来落实,也就是说我们需要视频机先将气象渲染成一副图片,之后对这张图纸的像素做处理。设想一下一旦不这样做的话,大家不光要统计场景内所有被渲染对象和录像机的相距,还需要至少五个pass,其中一个回到被渲染物体的正规颜色,另一个则来实现和扫描颜色的附加。假使场景内被渲染的目标众多来说,这样的操作效用就变得非常放下了。
从而,在cs脚本中我们还会用到OnRenderImage以此回调以获取录像机渲染的光景图像。

void OnRenderImage(RenderTexture src, RenderTexture dst)
{
     //TODO
}

重复,随着岁月的蹉跎扫描网逐步扫描整个场馆分明是一个动态的效果。由此大家还需要把时间这么些因子也引入,时间影响了扫描网和起源的偏离。当然,大家既可以在shader文件中考虑时间的影响,也能在cs文件中考虑时间的熏陶。

假使我们要向来在shader中拿走时间的信息的话,就需要用到unity的放置变量float4
_Time : Time (t/20, t, t*2,
t*3) 
了。它的4个轻重分别代表了t/20、t、t*2、t*3。由此,在shader中大家应用_提姆e.y就足以拿到当前的岁月了,依照时间我们就能算出扫描网当前活动的大体距离了。

除此之外,我们自然也得以在cs文件中间接采纳提姆e类和Update艺术间接统计扫描网的移位距离,然后再将结果传到shader。这样,我们就完事了一个最佳简单的cs脚本:

 /*
 * Created by Chenjd
 * http://www.cnblogs.com/murongxiaopifu/
 */ 
using UnityEngine;
using System.Collections;

public class ScannerEffect : MonoBehaviour
{
    #region 字段

    public Material mat;
    public float velocity = 5;
    private bool isScanning;
    private float dis;

    #endregion


    #region unity 方法

    void Start()
    {
        Camera.main.depthTextureMode = DepthTextureMode.Depth;
    }

    void Update()
    {
        if (this.isScanning)
        {
            this.dis += Time.deltaTime * this.velocity;
        }

        //无人深空中按c开启扫描
        if (Input.GetKeyDown(KeyCode.C))
        {
            this.isScanning = true;
            this.dis = 0;
        }

    }


    void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        mat.SetFloat("_ScanDistance", dis);
        Graphics.Blit(src, dst, mat);
    }

    #endregion

}

关于shader?这就更简明了,现在大家收获了相机渲染之后的场景图,这样图上的各样像素只需要取得自己的吃水信息:

    float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
    float linear01Depth = Linear01Depth(depth);

接下来再和扫描网现在的地方做个对照——当然我们还是可以够进入扫描网的幅度这个概念——符合条件的像素颜色和扫描网的水彩举行叠加就可以了。最后为了更健全一点,我们还需要判定一下深度值是否比1小,因为吃水值在[0,1]以此区间内,而1对应的是远裁切面,因而倘诺不判断1的话,整个远方最终都会被扫描网的水彩举办叠加。

if (linear01Depth < _ScanDistance && linear01Depth > _ScanDistance - _ScanWidth && linear01Depth < 1)
{
    float diff = 1 - (_ScanDistance - linear01Depth) / (_ScanWidth);
    _ScanColor *= diff;
    return col + _ScanColor;
}

完整的花色得以到此处到此处下载:UnitySpecialEffectWithDepth

云彩如我疲惫,我却不如云彩自在!哎~~

0x03 透过墙壁绘制背后的“人影”

透过障碍物看到障碍物后的高亮目的,国内外众多游戏都会用到近似的效用。
图片 4

以此看起来很有巨大上的视觉效果,其实从创制一个unity的Unlit
shader文件到终极做到这些效应只需要大约30s。

规律很简单,即基于目的是否被屏蔽重返不同的水彩即可。目的被障碍物遮住的一对其深度值必然要超越障碍物,因而我们可以用一个pass处理当深度值大于障碍物的时也就是目的被障碍物遮住的有的的颜料——例如我们回到紫色。

    Pass
    {
        ZTest Greater

        ...

        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 col = fixed4(1, 0, 0, 1);
            return col;
        }
    }

再用另一个pass处理对象未被遮挡住的一些,也就是深浅值小于障碍物时再次来到目的的正常颜色。

    Pass
    {
        ZTest Less 

        ...

        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 col = tex2D(_MainTex, i.uv);
            return col
        }
    }

图片 5

然而墙后的大敌要是只是显示一个革命是否有点太枯燥了呢?还有为数不少戏耍,它的透视效果是下面那样的:目的身上多了有些描边。
图片 6
本条效应的实现其实也很简单。大家得以按照观测方向和目的绝大部分形的法线方向的夹角来判定目的的边缘——毕竟目标面向我们的面的法线和我们着眼方向的夹角相对较小,而边缘部分的面的法线和大家的观测方向的夹角显著更大——这里的边缘判断用到了观看方向,下文大家还会扯淡跟观察方向无关的边缘检测。
图片 7

就此,给墙后的靶子描边这件事就又变得十分简约了。我们只需要在拍卖被屏蔽部分的分外pass中回到的紫色变为与法线和观赛方向的夹角相关的一个值就好了。
为了贯彻那些目的,我们第一要收获法线和观赛方向的音讯。

o.normal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

之后再总括法线和观测方向的夹角音信:

float NdotV = 1 - dot(i.normal, i.viewDir) ;

说到底,只需要把那多少个值当作影响最后颜色输出的元素就好了。

return _EdgeColor * NdotV;

图片 8
全体的花色得以到这里到这里下载:UnitySpecialEffectWithDepth

现今大家一家五口人,正开车前往临汾,作为一年一度的一家子出游,韩一说到底仍然个男女,一路甜美地话个没完,还有大叔母亲也很喜气洋洋,聊了很多经常的市井趣事和农村里听来的各样八卦……

0x04 护盾/能量场效果

图片 9

洋洋科幻游戏也有这种能量场或者护盾的功能。例如暴雪的守望先锋中的猩猩温斯顿的遮挡发射器、光环系列的圣堂防卫者的能量护盾甚至一些手游中也有接近的功力,比如果壳网的光明大陆。
图片 10
以此功能的落实和原理其实也并不复杂。一句话来说可以分成以下那多少个部分:

  • 半透明效果
  • 会友高亮,首要指能量场和另外物体相交的地方是高亮突显
  • 外表扭曲
  • 一个和观赛方向相关的描边效果

首先我们要敞开透明混合并指定渲染队列为透明。

SubShader
{
    ZWrite Off
    Cull Off
    Blend SrcAlpha OneMinusSrcAlpha

    Tags
    {
        "RenderType" = "Transparent"
        "Queue" = "Transparent"
    }

    ...
}

从此像上一个例子这样,依照观测方向绘制能量场的边缘。

//vert
o.normal = UnityObjectToWorldNormal(v.normal);

o.viewDir = normalize(UnityWorldSpaceViewDir(mul(unity_ObjectToWorld, v.vertex)));


//frag
float rim = 1 - abs(dot(i.normal, normalize(i.viewDir)));

诸如此类,我们就收获了一个半透且带有描边效果球体,能量场已经初具雏形了。

图片 11

接下去,我们就要贯彻相交高亮的效率了。所谓的交接高亮指的是能量场和另外物体相交时,在相交处绘制出高亮效果。这时大家就要用到深度音信了。当能量场和某个物体相交时,二者的深度信息应该一致,基于这几个相比深度音信,大家可以用来打量一个像素的“相交程度”。

亟需留意的是,能量场的shader在履行时_CameraDepthTexture中只保留了气象中不透明物体的吃水音讯,由此这些时候不可以从CameraDepthTexture中收获能量场的纵深音讯,所以要在vert中统计顶点的深度,这里自己动用了COMPUTE_EYEDEPTH本条松手的宏。在以后的frag内就可以很有利的取得场景和能量场当前片元的吃水了。

//vert
o.screenPos = ComputeScreenPos(o.vertex);
COMPUTE_EYEDEPTH(o.screenPos.z);


//frag
float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
float partZ = i.screenPos.z;

两者相减就是深浅的差距diff,再用1 – diff就取得了一个“相交程度”。

float diff = sceneZ - partZ;

float intersect = (1 - diff) * _IntersectPower;

图片 12

终极大家还需要贯彻一个能量场的扭转效能。扭曲效率是娱乐里面日常有的一个功能,其实也很粗略,大家只需要一张渲染能量场往日的情景的渲染图,之后随时间调整uv的舞狮就可以效仿扭曲的功用了。

GrabPass
{
    "_GrabTempTex"
}

...

//frag
float4 offset = tex2D(_NoiseTex, i.uv - _Time.xy) * _DistortTimeFactor;
i.grabPos.xy -= offset.xy * _DistortStrength;
fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);

...

图片 13
总体的档次方可到这边到这边下载:UnitySpecialEffectWithDepth

老姨妈指着一朵异常像孔雀的云:一一快看,这孔雀的羽绒开得好大啊”“哇,对啊,真的好像啊,它的头仰得这么高,像一只考了一百分的,很自负的孔雀耶”我听了甚是喜悦,韩一这段时间百折不回阅读课外书,果然在用词和想象力上有很大的上进!

0x05 边缘检测

边缘检测的目标是标识数字图像中属性分明扭转的点。图像属性中的显著变化一般反映了性能的要紧转变。那个概括:

  • 深度上的不总是
  • 外表法线方向不连续
  • 水彩不总是
  • 亮度不连续

图片 14

亟待小心的是边缘可能与考察方向有关——也就是说边缘可能随着观望方形的不同而转变,例如上文中的描边实现;也说不定与观看方向无关——那平日反映被考察物体的性能如表面纹理和表面形状。在这一个有些,我们的关注点紧假诺接班人。

之所以,依照不同的习性变化也有很多种政策来拍卖边缘检测,例如利用深度、利用法线、利用深度+法线、利用颜色等等。边缘是灰度值不总是的结果,这种不连续常可应用求导数便于地检测到,一般常用一阶和二阶导数来检测边缘。其中一阶导数的增长率值来检测边缘的留存,幅度峰值一般对应边缘地方。
图片 15
唯独为了简化统计,在其实中常用小区域模板卷积来近似总计偏导数。对Gx和Gy各用1个模板,所以需要2个模板组合起来以结合1个梯度算子。最简易的梯度算子是罗伯特(Robert)(Bert)交叉(罗伯特(Robert)(Bert)(Robert)s
cross)算子。

图片 16

实际在unity的image
effect中就含有了描边这些功用,而里面又有5种不同的不二法门,其中的一种叫做RobertsCrossDepthNormals便是采纳了罗伯特(Robert)(Bert)算子,各位假若有趣味的话能够参照。

大叔和韩一又玩起了击手掌,黑白配,剪刀石头布等经典的手游(就是全程用手出席的游戏),车后座真是锣鼓喧天得要命!

0x06 小结

如上便是广大的三种拔取深度信息来贯彻的视觉效果。
总体的品种得以到这边到这边下载:UnitySpecialEffectWithDepth
诸君要是认为好玩的话,欢迎点个赞。

ref:

【1】Siggraph2011_SpecialEffectsWithDepth_WithNotes。“Special Effects
with Depth” talk at SIGGRAPH – Unity
Blog

【2】Unity Shaders – Depth and Normal Textures (Part
2)

【3】题图来自《杀手5:赦免》

-华丽的分割线-
最后打个广告,欢迎襄助自己的书《Unity
3D脚本编程》

图片 17
迎接大家关心自己的斯柯达号慕容的游玩编程:chenjd01
图片 18

二伯是个自认为相比掌握的老人,所以忍不住会给韩一和大妈讲解一下沿途看到的修建和景色,韩一也会很般配地问一些奇怪怪好无语的题目,精粹的是曾祖父的答非所问和阿婆的均等离谱的评论,他们多少个享受着最朴实的天伦之乐,我和红鼻子一边晃动嗤鼻,一边笑而不语,很默契地我们牵住了手……

韩一又和大伯玩起了刮鼻子的玩耍,齐驱并骤,因为岳丈也和韩一一样,总是耍赖,所以也分不出个胜负来,只能求助二姑,让奶奶做裁判。不过三姑那些裁定也是无须公正和主张可言,反而变本加厉了大叔和韩一的顶牛,把韩一急得窜了起来,一口咬上了大伯的鼻头,五伯此刻假装大哭状,刹那间把韩一逗得咯咯咯,止不住地笑。真噪耳,却不沉闷!韩一笑得口水都叭嗒嗒滴下来,然后嗖一声又吸了回到,好恶心,但自己只是假装嫌弃地白了一眼,并没有平常的怒发冲冠!

小叔尽管被咬了鼻子,不过笑得可暧昧了,用我们这边的话说,就是骨头轻死了,都溶化了呀!四姨又补了一句:往日人家总是说大家家韩一是个塌鼻子,还说塌鼻头住高楼,果然大家韩一现行确实住上了大厦啦!”

韩一听了努力害羞地覆盖了团结的塌鼻子,一只手用力捶打着三姨,二叔很夸张地挠着韩一的瘙痒,一边哈哈大笑,把韩一气得手脚并用,向大伯丈母娘发起了总进攻!

自家和红鼻子捡到了一块儿的欢乐,也重拾了这几年来,每回的全家旅程中的点点滴滴!转眼就到了酒楼,说实话我不喜欢出门,往往还没到目标地,我就想着回家啊,不过再不习惯,我也是很乐意的,因为生活是协调在过的,苦着,煎熬着的同时,也该给协调撒点葱花和糖粉吧……

说到底,老人们在日益地老去,和韩一,和红鼻子一起陪陪他们的光阴在渐渐回落,每日看着五叔和大姨还算健朗的躯干和为了我们仨奔前忙后的样板,我能做的也只是每餐晚饭前给他倆斟满酒,而自我也吞下六到八颗的干白杨梅,最终吃完三姨烧出来的拥有菜肴,仅此而已……


❤️前几日住在衡水定海,明晚还得起个早,目标地:岱山岛!

❤️微信号jll800903

❤️微信名:桑塔纳

发表评论

电子邮件地址不会被公开。 必填项已用*标注