使用旋转组件在 3D space 中投射光线
Casting Rays in 3D space with rotation components
我目前正在研究基于 CPU 的简单光线追踪器,以将几个三角形渲染为一个项目。除了生成实际光线外,我对它的每个方面都满意。我不希望将世界坐标投影到屏幕 space,但实际上是根据相机在 3D 坐标中的位置创建光线。
现在,我有一个相当不错的算法,它允许我为屏幕上的每个像素生成一条射线,以围绕 Y 轴进行任何旋转,并且它也尝试合并 X 轴,从而提供一些向上和向下看的能力,但是当用户向上或向下看时,图像会变形。
这是我到目前为止的计算结果:
Ray ray = new Ray(Camera.position,
new Vector3(
Math.sin(Camera.rotation.y+(x*2/Main.renderSize.width)/2) * Math.cos(Camera.rotation.x+(y*2/Main.renderSize.height)/2),
Math.sin(Camera.rotation.x+(-y*2/Main.renderSize.height)/2),
Math.cos(Camera.rotation.y+(x*2/Main.renderSize.width)/2) * Math.cos(Camera.rotation.x-(y*2/Main.renderSize.height)/2)
));
当相机不面向任何向上或向下的方向时,这为我提供了良好的观看投影。
相机向前时的投影图像:
.
相机稍微向上看时的投影图像
.
如果有人能帮助我解决算法问题或指出新算法或好的来源,我将不胜感激。速度是必需的,因为它是实时的。谢谢。
除了光线方向错误之外,可能还会发生更多事情。因此,这里要更清楚地说明我的评论的更详细含义:
所以假设所有的东西都在相机 space 坐标系中。所以屏幕在平面 z=0
上,中间像素是 (0,0,0)
。焦点位于 focus=(0,0,-f)
,其中 f
是投影的焦距。现在你需要为每个像素投射光线(或更多),所以它的位置和方向是:
pos = (x,y,0);
dir = pos-focus = (x,y,f);
// most likely you should also normalize it so
dir = dir / |dir|;
当您的 x,y 坐标不是世界单位而是像素时,您应该相应地重新缩放它们。所以让屏幕分辨率 xs,ys
并且我们希望在 x
轴 FOVx = 60.0deg
中有视野,那么你需要稍微改变一下:
// pixel size
sz = f*tan(0.5*FOVx)/(0.5*xs);
// x,y screen position [pixels] -> xx,yy [world units]
xx=(x-(0.5*xs))*sz;
yy=(y-(0.5*ys))*sz;
// ray
pos = (xx,yy,0);
dir = pos-focus = (xx,yy,f);
dir = dir / |dir|;
现在进来。让 M
成为代表您屏幕的变换矩阵。原点设置为屏幕中间,x,y
轴向量对应于屏幕轴。您还需要 M0
矩阵,它是 M
的副本,但原点设置为 (0,0,0)
因为你想在世界全局坐标系中制作所有东西 GCS 然后从屏幕 space 转换到世界做:
world_position = M *screen_position;
world_direction = M0*screen_direction;
但是如果在上面 link 中使用不同的矩阵元素和操作数顺序约定,这可能会略有不同。
所以现在世界 GCS 中的光线将是:
// pixel size
sz = f*tan(0.5*FOVx)/(0.5*xs);
// x,y screen position [pixels] -> xx,yy [world units]
xx=(x-(0.5*xs))*sz;
yy=(y-(0.5*ys))*sz;
// ray
pos = (xx,yy,0);
dir = pos-focus = (xx,yy,f);
// convert to world GCS
pos = M *pos;
dir = M0*dir;
// normalize
dir = dir / |dir|;
通常你可以从 M
作为单位矩阵开始,然后乘以击键的旋转和平移来处理视图移动和旋转。在每次渲染之前,您可以计算 M0=M
,然后将保持原点的 3 个元素设置为零。此外,如果您的引擎使用 4D 向量(使用 x,y,z,w
),那么您也可以使用 M
而不是 M0
,您只需将转换向量的 w
设置为 w=0
用于方向和 w=1
用于位置。
我目前正在研究基于 CPU 的简单光线追踪器,以将几个三角形渲染为一个项目。除了生成实际光线外,我对它的每个方面都满意。我不希望将世界坐标投影到屏幕 space,但实际上是根据相机在 3D 坐标中的位置创建光线。
现在,我有一个相当不错的算法,它允许我为屏幕上的每个像素生成一条射线,以围绕 Y 轴进行任何旋转,并且它也尝试合并 X 轴,从而提供一些向上和向下看的能力,但是当用户向上或向下看时,图像会变形。
这是我到目前为止的计算结果:
Ray ray = new Ray(Camera.position,
new Vector3(
Math.sin(Camera.rotation.y+(x*2/Main.renderSize.width)/2) * Math.cos(Camera.rotation.x+(y*2/Main.renderSize.height)/2),
Math.sin(Camera.rotation.x+(-y*2/Main.renderSize.height)/2),
Math.cos(Camera.rotation.y+(x*2/Main.renderSize.width)/2) * Math.cos(Camera.rotation.x-(y*2/Main.renderSize.height)/2)
));
当相机不面向任何向上或向下的方向时,这为我提供了良好的观看投影。
相机向前时的投影图像:
相机稍微向上看时的投影图像
如果有人能帮助我解决算法问题或指出新算法或好的来源,我将不胜感激。速度是必需的,因为它是实时的。谢谢。
除了光线方向错误之外,可能还会发生更多事情。因此,这里要更清楚地说明我的评论的更详细含义:
所以假设所有的东西都在相机 space 坐标系中。所以屏幕在平面 z=0
上,中间像素是 (0,0,0)
。焦点位于 focus=(0,0,-f)
,其中 f
是投影的焦距。现在你需要为每个像素投射光线(或更多),所以它的位置和方向是:
pos = (x,y,0);
dir = pos-focus = (x,y,f);
// most likely you should also normalize it so
dir = dir / |dir|;
当您的 x,y 坐标不是世界单位而是像素时,您应该相应地重新缩放它们。所以让屏幕分辨率 xs,ys
并且我们希望在 x
轴 FOVx = 60.0deg
中有视野,那么你需要稍微改变一下:
// pixel size
sz = f*tan(0.5*FOVx)/(0.5*xs);
// x,y screen position [pixels] -> xx,yy [world units]
xx=(x-(0.5*xs))*sz;
yy=(y-(0.5*ys))*sz;
// ray
pos = (xx,yy,0);
dir = pos-focus = (xx,yy,f);
dir = dir / |dir|;
现在进来M
成为代表您屏幕的变换矩阵。原点设置为屏幕中间,x,y
轴向量对应于屏幕轴。您还需要 M0
矩阵,它是 M
的副本,但原点设置为 (0,0,0)
因为你想在世界全局坐标系中制作所有东西 GCS 然后从屏幕 space 转换到世界做:
world_position = M *screen_position;
world_direction = M0*screen_direction;
但是如果在上面 link 中使用不同的矩阵元素和操作数顺序约定,这可能会略有不同。
所以现在世界 GCS 中的光线将是:
// pixel size
sz = f*tan(0.5*FOVx)/(0.5*xs);
// x,y screen position [pixels] -> xx,yy [world units]
xx=(x-(0.5*xs))*sz;
yy=(y-(0.5*ys))*sz;
// ray
pos = (xx,yy,0);
dir = pos-focus = (xx,yy,f);
// convert to world GCS
pos = M *pos;
dir = M0*dir;
// normalize
dir = dir / |dir|;
通常你可以从 M
作为单位矩阵开始,然后乘以击键的旋转和平移来处理视图移动和旋转。在每次渲染之前,您可以计算 M0=M
,然后将保持原点的 3 个元素设置为零。此外,如果您的引擎使用 4D 向量(使用 x,y,z,w
),那么您也可以使用 M
而不是 M0
,您只需将转换向量的 w
设置为 w=0
用于方向和 w=1
用于位置。