在 elm-webgl 中使用光线投射进行对象拾取
Object picking with Ray casting in elm-webgl
演示几乎(?)工作示例:https://ellie-app.com/4h9F8FNcRPya1/1
演示:点击绘制射线,左右旋转相机可以看到射线。 (由于原点来自相机,所以从创建的位置看不到)
上下文
我正在研究一个 elm & elm-webgl 项目,我想知道单击时鼠标是否在对象上。我要做的是尝试实现一个简单的光线投射。我需要的是两件事:
1)相机坐标(这个很简单)
2) 被点击的3D coordinate/direction space
问题
据我了解,从 2D 视图 space 到 3D 世界 space 的步骤是:
a) 使坐标相对视口在-1到1范围内
b) 反转投影矩阵和透视矩阵
c) 乘以投影和透视矩阵
d) 从标准化鼠标坐标创建 Vector4
e) 将组合矩阵与 Vector4
相乘
f) 归一化结果
目前已尝试
我做了一个函数来将 Mouse.Position 转换为坐标以画一条线到:
getClickPosition : Model -> Mouse.Position -> Vec3
getClickPosition model pos =
let
x =
toFloat pos.x
y =
toFloat pos.y
normalizedPosition =
( (x * 2) / 1000 - 1, (1 - y / 1000 * 2) )
homogeneousClipCoordinates =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
-1
1
inversedProjectionMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse (camera model))
inversedPerspectiveMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse perspective)
inversedMatrix2 =
Mat4.mul inversedProjectionMatrix inversedPerspectiveMatrix
to =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
1
1
toInversed =
mulVector inversedMatrix2 to
toNorm =
Vec4.normalize toInversed
toVec3 =
vec3 (Vec4.getX toNorm) (Vec4.getY toNorm) (Vec4.getZ toNorm)
in
toVec3
结果
这个功能的结果是光线太过中心到我点击的地方。我添加了一个屏幕截图,我在其中单击了立方体顶面的所有四个。如果我单击视口的中心,光线将被正确定位。
感觉很接近,但还不够,我不知道我做错了什么!
在尝试其他方法后,我找到了解决方案:
getClickPosition : Model -> Mouse.Position -> Vec3
getClickPosition model pos =
let
x =
toFloat pos.x
y =
toFloat pos.y
normalizedPosition =
( (x * 2) / 1000 - 1, (1 - y / 1000 * 2) )
homogeneousClipCoordinates =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
-1
1
inversedViewMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse (camera model))
inversedProjectionMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse perspective)
vec4CameraCoordinates = mulVector inversedProjectionMatrix homogeneousClipCoordinates
direction = Vec4.vec4 (Vec4.getX vec4CameraCoordinates) (Vec4.getY vec4CameraCoordinates) -1 0
vec4WorldCoordinates = mulVector inversedViewMatrix direction
vec3WorldCoordinates = vec3 (Vec4.getX vec4WorldCoordinates) (Vec4.getY vec4WorldCoordinates) (Vec4.getZ vec4WorldCoordinates)
normalizedVec3WorldCoordinates = Vec3.normalize vec3WorldCoordinates
origin = model.cameraPos
scaledDirection = Vec3.scale 20 normalizedVec3WorldCoordinates
destination = Vec3.add origin scaledDirection
in
destination
我尽可能详细地保留了它,如果有人发现我使用了不正确的术语,请发表评论,我会更新答案。
我确信有很多可能的优化(在反转或组合一些步骤之前乘以矩阵。)
在此处更新了 ellie 应用程序:https://ellie-app.com/4hZ9s8S92PSa1/0
演示几乎(?)工作示例:https://ellie-app.com/4h9F8FNcRPya1/1
演示:点击绘制射线,左右旋转相机可以看到射线。 (由于原点来自相机,所以从创建的位置看不到)
上下文
我正在研究一个 elm & elm-webgl 项目,我想知道单击时鼠标是否在对象上。我要做的是尝试实现一个简单的光线投射。我需要的是两件事:
1)相机坐标(这个很简单)
2) 被点击的3D coordinate/direction space
问题
据我了解,从 2D 视图 space 到 3D 世界 space 的步骤是:
a) 使坐标相对视口在-1到1范围内
b) 反转投影矩阵和透视矩阵
c) 乘以投影和透视矩阵
d) 从标准化鼠标坐标创建 Vector4
e) 将组合矩阵与 Vector4
相乘
f) 归一化结果
目前已尝试
我做了一个函数来将 Mouse.Position 转换为坐标以画一条线到:
getClickPosition : Model -> Mouse.Position -> Vec3
getClickPosition model pos =
let
x =
toFloat pos.x
y =
toFloat pos.y
normalizedPosition =
( (x * 2) / 1000 - 1, (1 - y / 1000 * 2) )
homogeneousClipCoordinates =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
-1
1
inversedProjectionMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse (camera model))
inversedPerspectiveMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse perspective)
inversedMatrix2 =
Mat4.mul inversedProjectionMatrix inversedPerspectiveMatrix
to =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
1
1
toInversed =
mulVector inversedMatrix2 to
toNorm =
Vec4.normalize toInversed
toVec3 =
vec3 (Vec4.getX toNorm) (Vec4.getY toNorm) (Vec4.getZ toNorm)
in
toVec3
结果
这个功能的结果是光线太过中心到我点击的地方。我添加了一个屏幕截图,我在其中单击了立方体顶面的所有四个。如果我单击视口的中心,光线将被正确定位。
感觉很接近,但还不够,我不知道我做错了什么!
在尝试其他方法后,我找到了解决方案:
getClickPosition : Model -> Mouse.Position -> Vec3
getClickPosition model pos =
let
x =
toFloat pos.x
y =
toFloat pos.y
normalizedPosition =
( (x * 2) / 1000 - 1, (1 - y / 1000 * 2) )
homogeneousClipCoordinates =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
-1
1
inversedViewMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse (camera model))
inversedProjectionMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse perspective)
vec4CameraCoordinates = mulVector inversedProjectionMatrix homogeneousClipCoordinates
direction = Vec4.vec4 (Vec4.getX vec4CameraCoordinates) (Vec4.getY vec4CameraCoordinates) -1 0
vec4WorldCoordinates = mulVector inversedViewMatrix direction
vec3WorldCoordinates = vec3 (Vec4.getX vec4WorldCoordinates) (Vec4.getY vec4WorldCoordinates) (Vec4.getZ vec4WorldCoordinates)
normalizedVec3WorldCoordinates = Vec3.normalize vec3WorldCoordinates
origin = model.cameraPos
scaledDirection = Vec3.scale 20 normalizedVec3WorldCoordinates
destination = Vec3.add origin scaledDirection
in
destination
我尽可能详细地保留了它,如果有人发现我使用了不正确的术语,请发表评论,我会更新答案。
我确信有很多可能的优化(在反转或组合一些步骤之前乘以矩阵。)
在此处更新了 ellie 应用程序:https://ellie-app.com/4hZ9s8S92PSa1/0