使用ue4材质检验一个世界坐标点是否在视野(视锥体)内

[复制链接]
查看9284 | 回复19 | 2017-8-23 01:21:52 | 显示全部楼层 |阅读模式
对于类似这样的问题:“判断一个世界空间中的点是否在视野范围内,是否能被看见?”、“一个点(位置)是否能被某NPC的视线看到?”,这里我给出一个非常简单的做法, 不用在世界空间与视锥体进行任何数学计算(不用任何点、面、射线。。。),接下来的例子中大家会看到该方法是多么难以置信的简单。 QQ图片20170823012359.png

为了验证,我们需要现在场景中放一个cube,来模拟一个点,尽量缩小一点,并在属性中记下它的位置(x,y,z)坐标。

然后创建一个材质,起名叫printPos,用来可视化坐标信息。

进入材质编辑,主要用到TransformToClipSpace节点(转换一个世界坐标到裁剪空间),DebugFloat2Value2(打印一个Float2向量的值),
QQ图片20170823014112.png
如图连接后,在左侧的材质预览窗口应该可以看到一个红绿显示的两行数字,分别代表一个二维向量的x,y的值。

这个值就是我们给定的世界坐标变换到裁剪空间后的坐标,我们回到主场景中,为了看到坐标值,我们要再拖一个cube到场景中,把printPos材质赋给这个cube。

这时候,你可以移动视角,先让那个较小的cube(以下称为模拟点)出现在视野中,注意观察一下坐标值,当模拟点位于视野正中心时,坐标值为(0,0);
当模拟点在屏幕x方向超出视野时,红色数字的绝对值>1;
QQ图片20170823010424.png
当模拟点在屏幕y方向超出视野时,绿色数字的绝对值>1。
QQ图片20170823010751.png

这就是我们判断的理论依据了,当世界坐标被变换到裁剪空间时(此时的坐标为归一化透视坐标),x,y在区间(-1,1)内,当x或y的绝对值超过1时该坐标不在视锥体内,即不可被看见。稍作扩展,如果我们将这个点变换到另一个View(例如一个怪物或NPC)的裁剪空间,就可以判断该点是否能被怪物(NPC)看到。这里我就不展开讨论如何获取指定NPC的View了,在蓝图或C++代码中应该可以很方便获取到。

当然,本文仅提供一个方法,即我们不用在世界空间中拿这个点来判断是否在视锥体内,这个数学计算通常很复杂,需要视锥体的六个面方程,简化的计算通常也导致结果不精确,而使用本文的方法即将坐标转换到裁剪空间再进行比较,结果非常精确(这也是管线进行视锥体剔除的方法),实现步骤也很简单(一次空间转换,一次比较),大家有兴趣可以实践一下。

注:DebugFloat2Values节点还有对应的几个DebugFloat3Values,DebugFloat4Values方法是很有用的调试功能,可以用来可视化一些不容易看到的数值。

补充:评论中有人提到如果是正背对着目标的时候,有一段也是在-1~1这个范围的,这的确是一个没有考虑到的问题。
原因是由于我们使用TransformToClipSpace函数返回的"Clip Space XY"没有给我们提供z坐标,也就是说我们丢失了z坐标信息,这个z坐标是指View空间的forward方向的值,导致我们没有信息判断目标是否在视线后方,那怎么办呢?其实z值是可以得到的:将TransformToClipSpace节点使用新建Custom节点替代,
QQ图片20170825014441.png
Custom Code中写入mul(float4(In.xyz,1),View.WorldToClip)就可以返回完整的齐次坐标(x,y,z,w),之前的Clip Space X,Y在这就是x/w,y/w,而z值就是w分量(注意,z值已被存储到w分量,而不是z分量),通过判断这个w的符号就可以知道目标在前方还是后方。

Eragon | 2017-8-23 10:11:16 | 显示全部楼层
        /**
         * Returns true if this actor has been rendered "recently", with a tolerance in seconds to define what "recent" means.
         * e.g.: If a tolerance of 0.1 is used, this function will return true only if the actor was rendered in the last 0.1 seconds of game time.
         *
         * @param Tolerance  How many seconds ago the actor last render time can be and still count as having been "recently" rendered.
         * @return Whether this actor was recently rendered.
         */
        UFUNCTION(Category="Rendering", BlueprintCallable, meta=(Keywords="scene visible"))
        bool WasRecentlyRendered(float Tolerance = 0.2) const;
回复 支持 1 反对 0

使用道具 举报

59427046111 | 2017-8-23 09:14:26 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 1 反对 0

使用道具 举报

尛尛 | 2017-8-23 08:33:49 | 显示全部楼层
学习了,感谢
回复 支持 反对

使用道具 举报

仁义礼智信 | 2017-8-23 08:35:25 | 显示全部楼层
这脑洞可以尝试写小说了,我也去试一下,感谢分享思路
回复 支持 反对

使用道具 举报

cowolfox | 2017-8-23 09:02:27 | 显示全部楼层
感谢分享,好厉害
回复 支持 反对

使用道具 举报

hanzhao111 | 2017-8-23 09:15:33 | 显示全部楼层
很厉害,66666
回复 支持 反对

使用道具 举报

gambol | 2017-8-23 09:49:22 | 显示全部楼层
这个有BUG哈,如果是正背对着目标的时候,有一段也是在-1~1这个范围的
回复 支持 反对

使用道具 举报

234981730 | 2017-8-23 09:53:39 | 显示全部楼层
你是专门做材质的吗
回复 支持 反对

使用道具 举报

mxlhy | 2017-8-23 09:55:06 | 显示全部楼层
学习了,谢谢楼主分享
回复 支持 反对

使用道具 举报

AirsoftGoGo | 2017-8-23 10:03:12 | 显示全部楼层
59427046111 发表于 2017-8-23 09:14
但是没有办法把这个数传出来啊。。。。。。。。。。。

在代码中可以做同样的事情,材质只是提供可视化验证
回复 支持 反对

使用道具 举报

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

本版积分规则

11

主题

21

回帖

226

积分

初阶编码师

积分
226