java3d dda算法,A Fast Voxel Traversal Algorithm for Ray Tracing

这篇文章主要讲在3D空间中,一种简单的体素遍历算法。从一个体素到它临近的体素的计算,只需要去比较两个浮点数,比较后对其中一个添加。当然多条射线在多个物体中交互,在超过一个体素是不能用这种算法的。

在提到这种算法前,首先我们从简单的2D空间的直线生成算法开始。当我们要在屏幕上画一条直线时,由于屏幕由一个个像素(正方形)组成,所以实际上计算机显示的直线是由一些像素点近似组成的,直线生成算法解决的是如何选择最佳的一组像素来显示直线的问题。对于这个问题,最简单的方法是从直线起点开始令x或y每次增加1直到终点,每次根据直线方程计算对应的函数值再四舍五入取整,即可找到一个对应的像素,但这样做每一步都要进行浮点数乘法运算,效率极低,所以出现了DDA和Bresenham两种直线生成算法。

数值微分法(DDA算法)

e10b59960a40a18f71df19b6ccdbbb88.png

float slope = (endPoint - startPoint).y / (endPoint - startPoint).x;

float deltaX = slope > 1 ? 1 / slope :1;

float deltaY = slope > 1 ? 1 : slope;

Vector2 vec = new Vector2();

for (int i = 0; i < 10; i++)

{

vec.x += deltaX;

vec.y += deltaY;

posList.Add(new Vector2(Mathf.Floor(vec.x), Mathf.Floor(vec.y)));

}

根据上式可知△x=1时,x每递增1,y就递增k,所以只需要对x和y不断递增就可以得到下一点的函数值,这样避免了对每一个像素都使用直线方程来计算,消除了浮点数乘法运算。但是还是需要加法和取整操作。

Bresenham算法

5fd213014ce513af349f74bff945fd96.png

1615652ba454e43cb3365ea514aeca93.png

其中△x起点到终点x轴上距离,△y为y轴上距离,k=△y/△x,c是常量,与像素位置无关。

令ei=△x(d1-d2),则ei的计算仅包括整数运算,符号与d1-d2一致,称为误差量参数,当它小于0时,直线更接近右方像素,大于0时直线更接近右上方像素。

可利用递增整数运算得到后继误差量参数,计算如下:

743b71fc56f9c2dfac78dfc64ae39f4b.png

所以选择右上方像素时(yi+1-yi=1):

349e07d06e577322f21132c0393f71ce.png

初始时,将k=△y/△x代入△x(d1-d2)中可得到起始像素的第一个参数:

cc27583a97d879c1620c5d48b4d5b5b7.png

代码如下:

float dx, dy, e, x = startPoint.x, y = startPoint.y;

dx = endPoint.x - startPoint.x; dy = endPoint.y - startPoint.y;

e = 2 * dy - dx;

while (x <= endPoint.x)

{

if (e >= 0)

{

e = e + 2 * dy - 2 * dx;

y++;

vec.y = y;

}

else

e = e + 2 * dy;

x++;

vec.x = x;

posList.Add(new Vector2(vec.x, vec.y));

}

这些都是在2D空间下从一个点到另外一个点的算法优化,那么在3D空间中也是有对应的优化算法的。其中一点就是光线追踪算法中的射线遍历。

体素遍历优化算法

1eebd5f4ddd96a3af7f1e68ba73fd0d8.png

从上图可以知道,为了正确地遍历网格,遍历算法必须按顺序访问体素a、b、c、d、e、f、g和h。射线的方程为向量u加上t乘以向量v(t≥0)。新的遍历算法将射线分解为t段间隔,每个间隔一个体素。我们从射线原点开始,以间隔顺序访问每一个体素。

loop {if(tMaxX < tMaxY)

{

tMaxX= tMaxX + tDeltaX;X= X + stepX;

}

else

{

tMaxY= tMaxY + tDeltaY;Y= Y + stepY;}

NextVoxel(X,Y);

}

我们循环比较,直到找到一个具有非空对象列表的体素,否则从当前体素中退出。将算法扩展到三维只需要添加适当的z变量,并在每次迭代中找到tMaxX、tMaxY和tMaxZ的最小值。具体伪代码如下:

list= NIL;

do

{

if(tMaxX < tMaxY)

{

if(tMaxX < tMaxZ)

{

X= X + stepX;

if(X == justOutX)

return(NIL);

else/* outside grid */tMaxX= tMaxX + tDeltaX;}

{

Z= Z + stepZ;if(Z == justOutZ)return(NIL);

tMaxZ= tMaxZ + tDeltaZ;

}

} else

{

if(tMaxY < tMaxZ)

{

Y= Y + stepY;

if(Y == justOutY)

return(NIL);

tMaxY= tMaxY + tDeltaY;

} else

{

Z= Z + stepZ;

if(Z == justOutZ)

return(NIL);

tMaxZ= tMaxZ + tDeltaZ;

}

}

list= ObjectList[X][Y][Z];

}while(list == NIL);

return(list);

上面的需要进行两个浮点纹理数的比较,一个浮点纹理的加法,两个整形比较。每一次迭代一个整形加法。如果射线的起始点在体素里面,需要33个浮点计算,如果是在体素外面则需要40次计算。

4839c2a4a4973af78a80418ba1a7f619.png

首先,检查确保交点在体素内这样需要进行六次浮点数比较。但我们可以将其简化为一次比较。与当前体素中所剩的最大值相比。如果它小于或等于最大允许值,则它位于当前体素中;否则,我们继续遍历,直到与交叉点相交,或者找到一个更近的对象。执行这个比较,最简单的方法是在确定了tMaxX、tMaxY和tMaxZ的最小值之后将其包含到增量遍历代码中。当然,这里我们不想做这个比较。我们使用有两个遍历函数。在找到一个交集之后,我们再次调用递增遍历函数;这次我们需要进行额外的比较。如果交集在当前体素中,则函数返回NULL,然后停止。否则,我们继续遍历,直到找到一个非空的体素,或者找到发生交集的体素。因此,这种方式消耗最低,我们可以确定交集是否在当前体素内。

参考链接:cse.chalmers.se/edu/yea

2D算法链接:https://www.cnblogs.com/LiveForGame/p/11706904.html

声明:发布此文是出于传递更多知识以供交流学习之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与我们联系,我们将及时更正、删除,谢谢。

作者:南山上

来源:https://zhuanlan.zhihu.com/p/126583341

More:【微信公众号】u3dnotes

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值