英文:
Looking up and down (Implementing Camera Pitch), and including multiple levels in ray casting engine
问题
我用SDL2制作了一个简单的射线投射引擎,包括玩家移动。在进行一些研究时,我从维基百科上注意到:“虽然世界看起来是3D的,但玩家不能上下或只能在有限的角度内倾斜变形”。
我对射线投射的理解基本上是,从玩家处发射一条射线,根据射线到墙的距离,将该墙呈现为特定大小,从而在二维表面上实现伪3D效果。然而,从理论上讲,是否不可能在左右以外的方向上上下观察,而不仅仅是左右?
此外,如何在射线投射中包含多个高度/层次?目前,我似乎只能 stuck 于1个层次。
你能否请说明一下在射线投射引擎中上下观察以及包含多个层次的问题?
英文:
I have made a simple Ray casting Engine using SDL2 complete with player movement, and doing some research noticed from Wikipedia: "While the world appears 3D, the player cannot look up or down or only in limited angles with shearing distortion".
My understanding of Ray casting is essentially that a ray is shot from a player, and according to the distance from a ray to a wall, that wall is rendered to a certain size, allowing a pseudo-3d effect on a 2d surface. However, theoretically, would it not be possible to do the same thing while looking up and down, not just left and right?
Also, how does one go about including multiple heights/levels in Ray casting, as for now I only seem to be stuck with 1 level.
Could you please shine some light on looking up and down in a Ray casting Engine, and including multiple levels?
答案1
得分: 1
2D射线投射每个视图x位置发射一束射线。如果你想自由地在多个楼层上下查看,那将需要以每像素方式发射射线,将其从2D射线投射转换为3D射线追踪,这将大大减慢速度。
然而,如果你只想在单个楼层内上下查看,那是可行的,但有一点要注意(直上直下在使用三角学时具有奇点,可能需要额外的条件判断来减慢速度)。
增加更多级别/楼层很简单,只需添加电梯或传送门...这样你仍然可以使用简单的射线投射,只看到当前级别/楼层以外的任何东西。
参考这个链接:
通过它,你可以添加可用的楼梯,这样你可以通过楼梯在不同级别之间移动,只需确保它被正确遮蔽,以便在级别之间移动时不会看到视野中的空洞。
你还需要在地图中添加地板/天花板瓷砖,类似于我在这里展示的:
如果不了解你的项目的更多细节,很难提供具体的建议。在某些情况下,你可能想要使用体素空间射线投射而不是级别...
英文:
2D raycast shoots single ray per x position of view. If you want to look up/down freely through multiple floors that would require to shoot ray on per pixel manner converting from 2D raycast to 3D raytrace which would be hugely slower.
However of you want just look up/down inside single floor that is doable up to a point (straight up/down has singularity in goniometrics used and would require additional ifs slowing stuff down due brunching)
having more levels/floors is simple you just add lifts or portals ... that way you can still have simple raycast where you never see anything else but current level/floor.
take a look at this:
with it you can add usable stairs so you could move through levels also by stairs you just have to make sure its properly shielded so you will not have holes in view while traversing between levels.
you also need floor/ceiling tiles in your map something like I have in here:
without knowing more about your project its hard to be specific. In some cases you might want to use Voxel space ray casting instead of levels ...
答案2
得分: 1
以下是代码中的注释的翻译部分:
//
//* 我使用了OpenCV进行绘制(cv::Mat和绘制函数)以及2D矢量操作(cv::Vec2d)
//
// 3D数据:作为高度场。
constexpr int HF_SX = 11;
constexpr int HF_SY = 8;
const int HF[HF_SY][HF_SX] = {
2,2,2,3,5,2,3,0,1,3,3,
2,2,2,3,5,2,1,0,1,4,3,
2,2,1,3,5,2,1,0,1,3,3,
3,0,0,1,2,0,0,0,1,1,2,
2,1,1,1,0,0,0,2,0,1,1,
3,0,0,0,0,1,0,1,0,0,1,
2,0,1,0,1,1,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,
};
// 相机位置和方向
const double CamH = 1.75;
const cv::Vec2d CamPos(5.65, 7.6);
const cv::Vec2d CamFront(0, -1);
const cv::Vec2d CamRight(-CamFront[1], CamFront[0]);
// 渲染图像大小
constexpr int ScrnW = 240 * 2;
constexpr int ScrnH = 180 * 2;
// 相机内参数(针孔相机模型)
constexpr double PC_x0 = ScrnW * 0.5;
constexpr double PC_y0 = ScrnH * 0.5;
constexpr double PC_f = 0.75 * ScrnW * 0.5;
// 函数EnumCheckPoint的返回数据类型
struct CheckPoint
{
CheckPoint(const cv::Vec2d &P, bool IsHorizontal) : Pos(P), IsHorizontalEdge(IsHorizontal) {}
cv::Vec2d Pos;
bool IsHorizontalEdge;
};
// 枚举沿着线段(从-到)的整数x或y坐标的点。
// 结果存储在向量rDst中。
void EnumCheckPoint(const cv::Vec2d &From, const cv::Vec2d &To, std::vector<CheckPoint> &rDst)
{
// 省略部分代码...
}
// 函数Cvt_CPs_to_Walls的返回数据类型
struct Wall
{
Wall(const cv::Vec2d &P, const cv::Vec2d &N, int th, int bh)
: LayHitPos(P), UnitNormal(N), TopHeight(th), BottomHeight(bh) {}
cv::Vec2d LayHitPos;
cv::Vec2d UnitNormal;
int TopHeight;
int BottomHeight;
};
// 创建高度变化的墙壁数据
void Cvt_CPs_to_Walls(const std::vector<CheckPoint> &CPs, std::vector<Wall> &rDstWalls)
{
// 省略部分代码...
}
// 绘制高度为GndH的地平面的范围(x,ybegin) -(x,yend)
void DrawGndPlane(cv::Mat &Scrn, int x, int ybegin, int yend, double GndH)
{
// 省略部分代码...
}
// 主函数
int main()
{
// 省略部分代码...
}
希望这些翻译对你有所帮助。如果需要进一步的翻译或解释,请随时提出。
英文:
> multiple heights
I considered about very simple situation "If all wall are simply vertical but have several height".
In this situation, I think, we can simply draw all the walls intersecting the ray (with Painter Algorithm like way).
Following code is I tried. (not beautiful code!).
And result image is this:
//
//* I used OpenCV to drawing(cv::Mat and drawing func) and 2D-vector operation(cv::Vec2d)
//
//3D Data : as Height Field.
constexpr int HF_SX = 11;
constexpr int HF_SY = 8;
const int HF[HF_SY][HF_SX] = {
2,2,2,3,5,2,3,0,1,3,3,
2,2,2,3,5,2,1,0,1,4,3,
2,2,1,3,5,2,1,0,1,3,3,
3,0,0,1,2,0,0,0,1,1,2,
2,1,1,1,0,0,0,2,0,1,1,
3,0,0,0,0,1,0,1,0,0,1,
2,0,1,0,1,1,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,
};
//Camera Location and Direction
const double CamH = 1.75;
const cv::Vec2d CamPos( 5.65, 7.6 );
const cv::Vec2d CamFront( 0, -1 );
const cv::Vec2d CamRight( -CamFront[1], CamFront[0] );
//Rendering Image Size
constexpr int ScrnW =240*2;
constexpr int ScrnH = 180*2;
//Camera Intrinsic Parameters (Pinhole Camera Model)
constexpr double PC_x0 = ScrnW * 0.5;
constexpr double PC_y0 = ScrnH * 0.5;
constexpr double PC_f = 0.75 * ScrnW * 0.5;
//Return_data_type for the function EnumCheckPoint below
struct CheckPoint
{
CheckPoint( const cv::Vec2d &P, bool IsHorizontal ) : Pos(P), IsHorizontalEdge( IsHorizontal ) {}
cv::Vec2d Pos;
bool IsHorizontalEdge;
};
//Enumerate points along a line segment(From - To) where x or y are integers.
//The result is stored in the vector rDst.
void EnumCheckPoint( const cv::Vec2d &From, const cv::Vec2d &To, std::vector<CheckPoint> &rDst )
{
rDst.clear();
const cv::Vec2d d = To - From;
const int MinX = (int)ceil( (std::min)(From[0],To[0]) );
const int MaxX = (int)floor( (std::max)(From[0],To[0]) );
if( MinX<=MaxX )
{
const double dy_per_x = d[1]/d[0];
for( int x=MinX; x<=MaxX; ++x )
{ rDst.emplace_back( cv::Vec2d{ (double)x, From[1]+(x-From[0])*dy_per_x }, false ); }
}
const int MinY = (int)ceil( (std::min)(From[1],To[1]) );
const int MaxY = (int)floor( (std::max)(From[1],To[1]) );
if( MinY <= MaxY )
{
const double dx_per_y = d[0]/d[1];
for( int y=MinY; y<=MaxY; ++y )
{ rDst.emplace_back( cv::Vec2d{ From[0]+(y-From[1])*dx_per_y, (double)y }, true ); }
}
auto Dist = [&From]( const CheckPoint &P )->double
{
auto D = P.Pos - From;
return fabs(D[0]) + fabs(D[1]);
};
std::sort(
rDst.begin(), rDst.end(),
[&Dist]( const CheckPoint &lhs, const CheckPoint &rhs )->bool{ return ( Dist(lhs) < Dist(rhs) ); }
);
}
//Return_data_type for the function Cvt_CPs_to_Walls below
struct Wall
{
Wall( const cv::Vec2d &P, const cv::Vec2d &N, int th, int bh )
: LayHitPos(P), UnitNormal(N), TopHeight(th), BottomHeight(bh) {}
cv::Vec2d LayHitPos;
cv::Vec2d UnitNormal;
int TopHeight;
int BottomHeight;
};
//Create Wall(where the height changes) Data
void Cvt_CPs_to_Walls( const std::vector<CheckPoint> &CPs, std::vector<Wall> &rDstWalls )
{
rDstWalls.clear();
rDstWalls.reserve( CPs.size() );
for( const auto &CP : CPs )
{
int x = (int)CP.Pos[0];
int y = (int)CP.Pos[1];
if( CP.IsHorizontalEdge )
{
if( x<0 || x>=HF_SX )continue;
if( y<=0 || y>=HF_SY )continue;
int Hu = HF[y-1][x];
int Hd = HF[y][x];
if( Hu == Hd )continue;
if( Hu > Hd ){ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ 0,1 }, Hu, Hd ); }
else{ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ 0,-1 }, Hd, Hu ); }
}
else
{
if( y<0 || y>=HF_SY )continue;
if( x<=0 || x>=HF_SX )continue;
int Hl = HF[y][x-1];
int Hr = HF[y][x];
if( Hl == Hr )continue;
if( Hl > Hr ){ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ 1,0 }, Hl, Hr ); }
else{ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ -1,0 }, Hr, Hl ); }
}
}
}
//Draw the range (x,ybegin) - (x,yend) as Ground Plane which height is GndH
void DrawGndPlane( cv::Mat &Scrn, int x, int ybegin, int yend, double GndH )
{
for( int y=ybegin; y<yend; ++y )
{
double tan = (y - PC_y0) / PC_f;
double depth_of_gnd = (CamH - GndH) / tan;
Scrn.at< cv::Vec3b >( y,x ) = cv::Vec3b( 0, (unsigned char)std::min(255.0, 255/depth_of_gnd), 0);
}
}
//main
int main()
{
//Rendering Image Buffer
cv::Mat Scrn( ScrnH, ScrnW, CV_8UC3 );
Scrn = cv::Scalar( 192, 32, 32 ); //Fill with Bkgnd Color (as Blue Sky)
//------------------------------------
//Ray Casting
std::vector< CheckPoint > CPs; //Working buffer
std::vector<Wall> Walls; //Working buffer
//for all column(x)
for( int x=0; x<Scrn.cols; ++x )
{
//Calculate ray (unit directional vector)
double theta = atan( (x - PC_x0)/PC_f );
cv::Vec2d ray = cos(theta)*CamFront + sin(theta)*CamRight;
//Get the terrain information needed to draw this column(x)
EnumCheckPoint( CamPos, CamPos + ray*15, CPs );
Cvt_CPs_to_Walls( CPs, Walls );
//Rendering
int RenderedTop = Scrn.rows; //(indicate the range y>=RenderedTop is already rendered)
int GndPlaneH = HF[ (int)CamPos[1] ][ (int)CamPos[0] ];
for( const auto &W : Walls )
{
//Calculate this Wall's render y range (y0 - y1)
auto depth = abs( W.LayHitPos[1] - CamPos[1] );
double ry0 = PC_f * (W.TopHeight - CamH) / depth;
double ry1 = PC_f * (CamH - W.BottomHeight) / depth;
int y0 = cvRound( PC_y0 - ry0 );
int y1 = cvRound( PC_y0 + ry1 );
if( y0 >= RenderedTop )continue;
if( W.UnitNormal.dot( ray ) <= 0 )
{//When the wall faces the camera
GndPlaneH = W.BottomHeight;
DrawGndPlane( Scrn, x, y1, RenderedTop, GndPlaneH );
//Draw Wall
y1 = std::min( y1, RenderedTop-1 );
cv::line( Scrn, cv::Point(x,y0), cv::Point(x,y1), cv::Scalar(0,0, std::min(255.0, 1.5*255/depth)) );
}
else
{//Else( when the wall faces away )
GndPlaneH = W.TopHeight;
DrawGndPlane( Scrn, x, std::max(0,y0), RenderedTop, GndPlaneH );
}
RenderedTop = y0;
}
//Draws the ground plane up to the y-coordinate of the horizon if needed.
DrawGndPlane( Scrn, x, cvRound(PC_y0), RenderedTop, GndPlaneH );
}
//Show Rednering Result
cv::imshow( "Result", Scrn );
cv::waitKey();
return 0;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论