unity片元着色器中获取屏幕坐标_Unity实现地图扫描效果

d5641c8713fe59f3b598a405c8f824ab.png

简介

游戏中许多地方会需要用到扫描效果,比如无人深空中的扫描,手雷扔出去前的预判范围,死亡搁浅等等,笔者在扫描的基础上作了一定个人发挥,使扫描支持自定义贴图,最终效果如下:

7c3e60d77f4650ac338cd86a4d13877f.png
unity 地图扫描效果 scan shaderhttps://www.zhihu.com/video/1248720342061064192

同时再附上相关游戏的图示:

529689d94d9b9d3051794520f5ee8b1e.png
无人深空

3a499eca508fc3230b8cda151a29b716.png
死亡搁浅

c93ca736114878b5f18792ae2c664880.png
全境封锁

基础概念

诚然,我们可以非常直接的使用单独物体并附上相关shader,在vertex阶段计算顶点位置,在frag阶段计算距离中心点位置的距离并显示相对应的颜色即可。但往往扫描这件事情是对整个地图进行的,中间扫描到的物体不能完全保证就是使用扫描材质,而且如果是大量物体,还会导致大量的多余计算。

5fc1aff2feaee153872168f8925a6dcc.png

换一种方式,我们使用屏幕后处理,只处理要显示的像素,那就可以省下大量的性能

6de42b162b4dd78a2c529d56b2f320d0.png

那么怎样才能获取到对应的位置信息呢,这就可以利用到摄像机的深度贴图

关于深度图部分,建议优先阅读文章神奇的深度图:复杂的效果,不复杂的原理,同时这里参考了Shaders Case Study - No Man's Sky: Topographic Scanner视频中的实现,以上图片也是部分截取,视频对深度图以及效果的实现都有详细介绍,推荐观看。

了解完毕深度相关的知识后,我们可以利用以下相关代码来测试深度值的获取:

//cs部分

放下对比图:

ee270536a0858d242ffa25d44463e6cd.png
原图

c474638a9220f1ab0d920f48ed648a7f.png
深度图

我们可以非常明显的看到,越往远处颜色越亮,也就代表深度值越接近1,像素点的距离也就越远,因此只要在frag阶段对比扫描深度和像素深度就可以非常快速的实现以相机为中心的扫描,核心代码如下

fixed4 

效果如下:

c54ad9217a85cba76e8337e237f7ae03.gif

实际上做到这里,如果是做基于摄像机的扫描,我们的工作已经基本完成了,但是要实现类似死亡搁浅或全境封锁中的效果,我们还需要介绍怎么获取屏幕像素坐标以及像素法线。

获取屏幕像素的世界坐标

由于我们使用的是屏幕后处理,不能直接获取到屏幕坐标,需要使用一些trick。这里参考了Unity Shader笔记(三) 在片段着色器中获取世界坐标,文章讲的非常清晰,这里不多赘述,简单总结归纳如下:

  1. 使用矩阵方式,我们可以得到像素的屏幕坐标以及深度坐标,使用相机的投影逆矩阵就可以计算出像素位置,这个方法适用性较好,但在需要在frag阶段做矩阵计算,效率较低。
  2. 由于屏幕后处理的特殊性,最终渲染阶段就是一个quad平面,而且是非常规整的矩形,其vert阶段实际只计算4次,可以在这一阶段把相机原平面相对于相机的坐标位置保存,到了frag阶段就会自动做插值,得到自相机到远平面的像素点的向量,用这个向量加上相机自身的世界坐标就能够得到像素的坐标,但需要注意相机是正交还是透视。

为了实现效率,我们使用透视相机加上第二种方案实现。

有了屏幕像素的世界坐标,我们就可以非常方便的计算除了相机之外其他点的对应距离,此时就能实现全境封锁中的手雷效果:

946aae7d903367af711f39ec81f2a652.gif

使用屏幕法线信息

此时的你觉得效果还是不够炫酷,想要给扫描做出点花样,加点花纹之类的,这时候就需要我们的法线图登场。

法线图,顾名思义就是记录了当前屏幕各个像素法线信息的图像,我们这时候需要将相机的depthTextureMode设置为DepthNormals模式,shader部分如下:

sampler2D _CameraDepthNormalsTexture;

fixed4 frag (v2f i) : SV_Target
{
    float tempDepth;
    half3 normal;  
    //从法线深度图中获取对应的值
    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), tempDepth, normal);  
    //让法线由屏幕坐标转化为世界坐标
    normal = mul( (float3x3)_CamToWorld, normal);  
    //对法线方向取绝对值
    return fixed4(abs( normal),1);
}

我们可以得到如下效果:

73ec277b0fc9e80ff1771984c71831d0.png

从图中我们可以看到主要有三种颜色,红绿蓝,分别对应了rgb,对应到向量上就是xyz轴的值,举个例子,地面非常绿即g通道值非常大,符合(0,1,0)向量,由此可见,获取的向量是正确的。

此时我们拥有,像素点的世界坐标,像素点的法线,但还差非常关键的一点,uv映射关系,由于是后处理,我们无法得到整体mesh信息,所以我们需要构造一套计算uv的方式。

这里采用了法线投影的计算方式:

  1. 我们将整个世界划分为无数个等边的正方形,让每一个像素点归一化,每一个像素点相对于所在的正方形只要相对位置相同,获取到的颜色也相同,类似于体材质的概念
  2. 颜色的计算方式为默认所在正方体周围都有贴图,然后分别在三个坐标平面里取值
  3. 最后,将获取的各个平面颜色值该像素点的法线在该平面的法线上的投影值相乘后相加,最终得到颜色

相关代码:

fixed4 

最终效果:

4eef486ae7312b5f904aa49e04d89b41.gif

这个方案本身存在一定缺陷,无法达到完美实现,但在相关信息缺失的情况下,只能尽可能的去追求理想效果,如果有更好的方案,欢迎私信。

小结

这次做这个效果最主要的动机还是想要在shader圈试试水,再加上看到youtube上的实现,不禁就想做一次尝试,也希望在以后能够为读者们带来更多优秀的内容。

项目源码地址:https://github.com/cyclons/DepthShader

参考文章

神奇的深度图:复杂的效果,不复杂的原理

Shaders Case Study - No Man's Sky: Topographic Scanner

Unity Shader笔记(三) 在片段着色器中获取世界坐标

Unity Shader 深度值重建世界坐标

Unity3D 屏幕空间雪场景Shader渲染

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值