Wolfenstein风格3D渲染背后的理论 [英] Theory behind Wolfenstein-style 3D rendering

查看:129
本文介绍了Wolfenstein风格3D渲染背后的理论的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在研究一个有关3D渲染的项目,我正在尝试制作一个简单的程序,该程序可以使用pygame显示一个简单的3D房间(静态阴影,没有玩家移动,只有旋转)



到目前为止,我已经研究了理论:




  • 从X的坐标列表开始每个节点的Z和Z

  • 节点按顺序形成闭环,因此一对节点将形成墙的任一侧

  • 墙的高度是在渲染时确定的,相对于距相机的距离

  • 墙是使用画家的算法绘制的,因此在更远的物体上绘制更近的对象

  • 用于遮蔽假对比,基于两个节点之间的渐变来使墙壁变亮/变暗



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



使用Google搜索这个主题到目前为止仅能满足以下等式:



screenX = (worldX / worldZ)



screenY =(worldY / worldZ)



这似乎对我来说是有缺陷的,因为如果Z坐标为0,您将得到除以零的误差。



如果有人可以帮助解释这一点,我将非常感激。

解决方案

好吧

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

全部只是 z 的透视除法,而不是用于DOOM或Wolfenstein技术。



Doom 中,只有一个视角(您可以向左/向右转,但不能仅向上翻/向下翻屏,这是不一样的)。因此,我们需要知道玩家的位置和方向 px,py,pz,pangle z 仅在您还想实现z轴移动/外观时才需要...



如果在一条直线(红色)中查看,在播放器屏幕中,与3D越过该线的所有对象都投影到单个x坐标...





因此,如果我们在某个方向(红色)看,则任何对象/点交叉/触摸该红线都将位于屏幕(在 x 轴上)。

从角度来看,我们需要定义多大的视图我们得到的角度...



img src = https://i.stack.imgur.com/QWDxx.png alt = FOVx>



这限制了我们的观点绿线的任何点都将投射在视图边缘(在 x 轴上)。由此我们可以计算任意点(x,y,z的屏幕 x 坐标 sx 直接:

  //相对于玩家方向的点角
sx = point_ang -缠结;
如果(sx <-M_PI)sx + = 2.0 * M_PI;
如果(sx> + M_PI)sx- = 2.0 * M_PI;
//缩放为像素
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 与玩家的距离和墙的大小。我们可以利用三角形相似性。



< img src = https://i.stack.imgur.com/7CrJO.png alt = FOVy>



so:

  sy = screen_size_y / 2(+/-)wall_height *焦距/距离

其中焦距是高度为100%的墙壁沿 y 轴完全覆盖整个屏幕的距离。如您所见,我们除以可能为零的距离。必须避免这种状态,因此,如果直接站在单元格边界上,则需要确保在下一个单元格中对光线进行评估。另外,我们需要选择焦距,以便将正方形墙投影为正方形。



这里是我的《毁灭战士》引擎的一段代码(全部放在一起):

  doublediv(double x,double y)
{
如果((y> =-1e-30)&& amp;(y <= + 1e-30))返回0.0;
返回x / y;
}
bool Doom3D :: cell2screen(int& sx,int& sy,double x,double y,double z)
{
double a,l;
// x,y相对于玩家
x- = plrx;
y- = plry;
//将z从[单元格]转换为单位
z * = _ Doom3D_cell_size;
//角度-> sx
a = atan2(y,x)-plra;
如果(a< -pi)a + = pi2;
如果(a> + pi)a- = pi2;
sx = double(sxs2)*(1.0+(2.0 * a / view_ang));
//垂直距离-> sy
l = sqrt((x * x)+(y * y))* cos(a);
sy = sys2 + divide((double(plrz + _Doom3D_cell_size)-z-z)* wall,l);
//在玩家面前?
的回报(fabs(a)<= 0.5 * pi);
}

其中:

  _Doom3D_cell_size = 100; // [units] cell cube size 
view_ang = 60.0 * deg; // FOVx
focus = 0.25; // [cells] view focus length(uncorrected)
wall = double(sxs)*(1.25+(0.288 * a)+(2.04 * a * a))* focus / double(_Doom3D_cell_size); // [px]投影墙尺寸比size =高度*墙/距离
sxs,sys =屏幕分辨率
sxs2,sys2 =屏幕一半分辨率
pi = M_PI,pi2 = 2.0 * M_PI

不要忘记使用垂直距离(乘以 cos(a)),否则会发生严重的鱼眼效应。有关更多信息,请参见:




I'm currently working on a project about 3D rendering, and I'm trying to make simplistic program that can display a simple 3D room (static shading, no player movement, only rotation) with pygame

So far I've worked through the theory:

  • Start with a list of coordinates for the X and Z of each "Node"
  • Nodes are kept in an order which forms a closed loop, so that a pair of nodes will form either side of a wall
  • The height of the wall is determined when it is rendered, being relative to distance from the camera
  • Walls are rendered using painter's algorithm, so closer objects are drawn on top of further ones
  • For shading "fake contrast", which brightens/darkens walls based on the gradient between it's two nodes

While it seems simple enough, the process behind translating the 3D coordinates into 2D points on the screen is proving the difficult for me to understand.

Googling this topic has so far only yeilded these equations:

screenX = (worldX/worldZ)

screenY = (worldY/worldZ)

Which seem flawed to me, as you would get a divide by zero error if any Z coordinate is 0.

So if anyone could help explain this, I'd be really greatful.

解决方案

Well the

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

is not the whole stuff that is just the perspective division by z and it is not meant for DOOM or Wolfenstein techniques.

Well in Doom there is only single angle of viewing (you can turn left/right but cannot look up/down only duck or jump which is not the same). So we need to know our player position and direction px,py,pz,pangle. The z is needed only if you want to implement also z axis movement/looking...

If you are looking in a straight line (Red) all the object that cross that line in the 3D are projected to single x coordinate in the player screen...

So if we are looking at some direction (red) any object/point crossing/touching this red line will be place at the center of screen (in x axis). What is left from it will be rendered on the left and similarly whats on right will be rendered on the right too...

With perspective we need to define how large viewing angle we got...

This limits our view so any point touches the green line will be projected on the edge of view (in x axis). From this we can compute screen x coordinate sx of any point (x,y,z) directly:

// 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

where screen_size_x is resolution of our view area and point ang is angle of point x,y,z relative to origin px,py,pz. You can compute it like this:

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

but if you truly do a DOOM ray-casting then you already got this angle.

Now we need to compute the screen y coordinate sy which is dependent on the distance from player and wall size. We can exploit triangle similarity.

so:

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

Where focal length is the distance at which wall with 100% height will cover exactly the whole screen in y axis. As you can see we dividing by distance which might be zero. Such state must be avoided so you need to make sure your rays will be evaluated at the next cell if standing directly on cell boundary. Also we need to select the focal length so square wall will be projected as square.

Here a piece of code from mine Doom engine (putted all together):

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);
    }

where:

_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

Do not forget to use perpendicular distances (multiplied by cos(a) as I did) otherwise serious fish-eye effect will occur. For more info see:

这篇关于Wolfenstein风格3D渲染背后的理论的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆