Wolfenstein式3D渲染背后的理论

时间:2016-12-16 21:02:04

标签: python 3d

我目前正在开发一个关于3D渲染的项目,我正在尝试制作一个简单的程序,可以显示一个简单的3D房间(静态着色,没有玩家移动,只有旋转)和pygame

到目前为止,我已经完成了这个理论:

  • 从每个“节点”
  • 的X和Z的坐标列表开始
  • 节点按照形成闭环的顺序保存,这样一对节点就会形成一面墙
  • 墙的高度在渲染时确定,相对于距相机的距离
  • 使用画家的算法渲染墙,因此在更远的对象上绘制更近的对象
  • 用于阴影“假对比”,根据它的两个节点之间的渐变使墙壁变亮/变暗

虽然看起来很简单,但将3D坐标转换为屏幕上的2D点的过程证明我很难理解。

谷歌搜索这个主题到目前为止只有这些方程式:

screenX = (worldX/worldZ)

screenY = (worldY/worldZ)

这对我来说似乎有缺陷,因为如果任何Z坐标为0,你会得到除零误差。

所以如果有人能帮忙解释一下,我会非常感激。

1 个答案:

答案 0 :(得分:0)

那么

screenX = (worldX/worldZ)
screenY = (worldY/worldZ)

不仅仅是z透视除法的全部内容,也不适用于DOOM或Wolfenstein技术。

好在末日中只有单一的观察角度(你可以向左/向右转,但不能只向上/向下看鸭子或跳跃,这是不一样的)。所以我们需要知道我们的球员位置和方向px,py,pz,pangle。只有当您想要实现z轴移动/寻找时才需要z

如果您正在寻找一条直线(红色),那么在3D中穿过该线的所有物体将被投射到播放器屏幕中的单个x坐标...

line of sight

因此,如果我们正在查看某个方向(红色),任何物体/点交叉/触摸此红线将位于屏幕的中心(x轴)。剩下的东西将在左侧呈现,同样右侧的内容也将在右侧呈现......

透视我们需要定义我们获得的视角大小......

FOVx

这限制了我们的视野,因此任何点都会触及绿线将投射到视图的边缘(x轴)。我们可以直接计算任意点x的屏幕sx坐标(x,y,z)

// angle of point relative to player direction
sx = point_ang - pangle;
if (sx<-M_PI) sx+=2.0*M_PI;
if (sx>+M_PI) sx-=2.0*M_PI;
// scale to pixels
sx = screen_size_x/2 + sx*screen_size_x/FOVx

其中screen_size_x是我们视区的分辨率,而点ang是相对于原点x,y,z的点px,py,pz的角度。您可以这样计算:

point_ang = atan2(y-py,x-px)

但是如果你真的做了 DOOM 光线投射,那么你已经有了这个角度。

现在我们需要计算屏幕y坐标sy,这取决于玩家和墙壁大小的距离。我们可以利用三角相似性。

FOVy

这样:

sy = screen_size_y/2 (+/-) wall_height*focal_length/distance

焦距是指100%高度的墙壁将覆盖y轴的整个屏幕的距离。正如你所看到的,我们除以距离可能为零。必须避免这种状态,因此如果直接站在细胞边界上,您需要确保在下一个细胞中评估您的光线。此外,我们需要选择焦距,以便将方形墙投影为方形。

这里是来自我的Doom引擎的一段代码(全部放在一起):

double divide(double x,double y)
    {
    if ((y>=-1e-30)&&(y<=+1e-30)) return 0.0;
    return x/y;
    }
bool Doom3D::cell2screen(int &sx,int &sy,double x,double y,double z)
    {
    double a,l;
    // x,y relative to player
    x-=plrx;
    y-=plry;
    // convert z from [cell] to units
    z*=_Doom3D_cell_size;
    // angle -> sx
    a=atan2(y,x)-plra;
    if (a<-pi) a+=pi2;
    if (a>+pi) a-=pi2;
    sx=double(sxs2)*(1.0+(2.0*a/view_ang));
    // perpendicular distance -> sy
    l=sqrt((x*x)+(y*y))*cos(a);
    sy=sys2+divide((double(plrz+_Doom3D_cell_size)-z-z)*wall,l);
    // in front of player?
    return (fabs(a)<=0.5*pi);
    }

其中:

_Doom3D_cell_size=100; // [units] cell cube size
view_ang=60.0*deg;     // FOVx
focus=0.25;            // [cells] view focal length (uncorrected)
wall=double(sxs)*(1.25+(0.288*a)+(2.04*a*a))*focus/double(_Doom3D_cell_size); // [px] projected wall size ratio size = height*wall/distance
sxs,sys = screen resolution
sxs2,sys2 = screen half resolution
pi=M_PI, pi2=2.0*M_PI

不要忘记使用垂直距离(乘以cos(a)),否则会出现严重的鱼眼效应。有关详细信息,请参阅: