filament渲染引擎中的坐标转换

背景

模型渲染到屏幕上之后,鼠标点击屏幕,我们怎么知道是否点击了模型,点击了模型的哪个位置呢? 这些需求都需要坐标转换,常规来说就是从世界坐标系转换到屏幕坐标系,或者从屏幕坐标系转换到世界坐标系。

博主在渲染这块也是个新手,大家一起学习。
本文使用的是google的filament渲染引擎。

openGL中的坐标转换

提起坐标转换,就不得不提经典的openGL了,可以参考以下文章:
ModelMatrix、ModelViewMatrix、ProjectionMatrix、NormalMatrix模型矩阵、模型视图矩阵、投影矩阵、正规矩阵详解_妙为的博客-CSDN博客
经典渲染流程:
image.png
坐标转换流程:
image.png

简单的概念介绍

假定读者已经了解了基本的渲染知识,我们这里不做过多解释,毕竟不是重点。以下概念来源:OpenGL矩阵变换的数学推导-腾讯云开发者社区-腾讯云

  • 首先OpenGL有个世界坐标系,我们渲染的物体就是在世界坐标系中,我们的模型需要放到世界坐标系中,那么当我们还没放的时候,模型就和世界坐标系没有联系,它就还处于自己的坐标系中,我们叫做模型坐标系、局部空间、局部坐标系,也就是图中的LOCAL SPACE。
  • 当我们把模型放到世界坐标系中,模型就在世界坐标系里有了坐标,也就是原来在LOCAL SPACE中的那些坐标值,变成了世界坐标系中的坐标值,帮助我们完成这个变换的就是模型矩阵,对应图中的MODEL MATRIX,于是这样我们就把模型放到了世界坐标系WORLD SPACE中
  • 放到世界坐标系后,是不是就确定了我们渲染出来看到的样子?还没有,大家可以想像一下,我把一个东西放在世界坐标系的某个地方,我可以从近处看观察它,也可以从远处观察它,还可以从上下左右观察它,甚至还可以倒着观察它,因些还需要确定我们观察它的状态。OpenGL里帮我们虚拟出了一个Camera(特别注意,这里的Camera不是指我们硬件的Camera),从API的层面上看,我们只需要设置Camera的位置、朝向的点坐标、以及Camera的上方向向量就能将观察状态定下来,而这些设置最终会转换成OpenGL中的视图矩阵,对应图中的VIEW MATRIX -经过View Matrix的变换后,我们观察它的结果就确定了,图中是从距离它一定的距离、上往下观察它,这时候的点坐标就来到了视图坐标系下,对应图中的VIEW SPACE -这时候,我们能看到什么东西,基本已经确定了,不过还有一步投影变换,这是什么东西?大家想像一下,我们看到同一个东西,是不是通常都是近大远小?那么如何实现近大远小?就要靠投影变换,OpenGL提供正交投影和透视投影,正交投影没有近大远小的效果,不管在什么距离上看,都一样大,透视投影则有近大远小的效果,也是符合我们实际生活的一种效果,透视投影应用得比较多

还可以参考以下文章加深理解:
ModelMatrix、ModelViewMatrix、ProjectionMatrix、NormalMatrix模型矩阵、模型视图矩阵、投影矩阵、正规矩阵详解_妙为的博客-CSDN博客
[OpenGL]OpenGL坐标系及坐标转换-腾讯云开发者社区-腾讯云

屏幕坐标转世界坐标

openGL的实现

上面的图让我们知道了渲染的矩阵变换,从屏幕坐标到世界坐标其实就是反步骤来计算。例如顺序矩阵变化是viewMatrix * projectionMatrix,那么求逆就是inverse(viewMatrix * projectionMatrix) 了。
可以参考stack overflow的回答:https://stackoverflow.com/questions/46749675/opengl-mouse-coordinates-to-space-coordinates/46752492#46752492

通过逆矩阵的方式

mat4 inversePrjMat = inverse( prjMat );
vec4 viewPosH      = inversePrjMat * vec4(ndc_x, ndc_y, 2.0*depth - 1.0, 1.0)
vec3 viewPos       = viewPos.xyz / viewPos.w;

filament通过射线拾取计算

https://github.com/google/filament/discussions/5998?sort=new
这种是不使用官方的pick()函数,自己手写计算的射线拾取。这个issue主要存在的问题就是没有设置裁剪空间的w,并且也没有除以w。
射线拾取参考:屏幕坐标转世界坐标与射线生成

filament官方给出的转换方式

 /**
 * screen space coordinates in GL convention, this can be used to compute the view or
 * world space position of the picking hit. For e.g.:
 *   clip_space_position  = (fragCoords.xy / viewport.wh, fragCoords.z) * 2.0 - 1.0
 *   view_space_position  = inverse(projection) * clip_space_position
 *   world_space_position = model * view_space_position
 *
 * The viewport, projection and model matrices can be obtained from Camera. Because
 * pick() has some latency, it might be more accurate to obtain these values at the
 * time the View::pick() call is made.
 */

具体代码

// 1、获取clip_space_position
const Viewport& vp = view->getViewport();
float clip_space_x = (result.fragCoords.x / vp.width  - 0.5f) * 2.0f;
float clip_space_y = (result.fragCoords.y / vp.height - 0.5f) * 2.0f;
float clip_space_z = result.fragCoords.z * 2.0f - 1.0f;
float4 clip_space_position {clip_space_x, clip_space_y, clip_space_z, 1.0f};

// 2、获取视图矩阵
mat4 projection = camera.getProjectionMatrix();
mat4 model = camera.getModelMatrix();
mat4 view_space_position = model * inverse(projection);
// 3、获取世界坐标系
float4 world_space_position = clip_space_position * view_space_position;

filament实现坐标转换的QA

1、View::pick()

View::pick()会有延迟,需要在pick()函数中获取模型矩阵,投影矩阵进行计算。在pick()函数外获取投影矩阵是不准确的,我踩的坑就是在pick()函数外获取的投影矩阵,在使用的时候发现矩阵变化了,导致inverse出来了inf和-nan等值。
inf 表示一个数超过了浮点类型所能表示的最大范围,通常为正无穷或负无穷。
nan 表示一个数不是一个合法的数字,通常出现在无法进行有效运算时。

2、为什么filament的计算方式没有除以w分量?

剪辑空间是齐次坐标系,转换成笛卡尔坐标系需要除以w,而我们设置的w是1.0,当缩放坐标的W为1时,坐标不会增大或缩小,保持原有的大小。所以,当W=1,不会影响到X,Y,Z分量的值。因此不影响最终结果。
什么是齐次坐标系?为什么要用齐次坐标系?

3、为什么别的资料上都是inverse(viewMatrix * projectionMatrix) ?

在filament中,viewMatrix = inverse(getModelMatrix()) ,所以:
inverse(viewMatrix * projectionMatrix) = inverse(viewMatrix) * inverse(projectionMatrix)
= getModelMatrix() * inverse(projectionMatrix)

参考:https://stackoverflow.com/questions/66160973/finding-world-position-of-element-in-screen-space
https://stackoverflow.com/questions/68870053/how-to-get-world-coordinates-from-the-screen-coordinates

4、怎么判断这个世界坐标是不是在模型上,也就是怎么判断是不是点击了模型?

filament中是通过pick()函数来实现的,获取到点击位置对应的实体,如果是模型的话,就可以获取到模型对应实体的name。底层是调用了driver.readPixels()方法来从渲染目标缓冲区中读取相应的像素信息,然后进行点击判断。

世界坐标转屏幕坐标

这部分的转换就是按照上面的渲染流程计算即可。

// 1、获取模型空间位置并将其转换为剪辑空间
vec4 clipSpacePos = projectionMatrix * (viewMatrix * vec4(point3D, 1.0));
// 2、从剪辑空间转换到标准化设备坐标空间(NDC 空间)
vec3 ndcSpacePos = clipSpacePos.xyz / clipSpacePos.w;
// 3、获取窗口位置
vec2 windowSpacePos = ((ndcSpacePos.xy + 1.0) / 2.0) * viewSize + viewOffset;

参考:https://stackoverflow.com/questions/8491247/c-opengl-convert-world-coords-to-screen2d-coords

矩阵运算

坐标转换离不开矩阵的运算,建议是再回头看看线性代数。。没时间系统的看的话,也可以先找一些网上的文章看看。以下几篇讲解的比较通俗,可以看看。
线性代数的秘密:矩阵相乘的本质是什么?
讲一点点数学:什么是矩阵?
线性代数的秘密:逆矩阵的意义是什么?(上)

模型矩阵,投影矩阵,视图矩阵的推导
OpenGL矩阵变换的数学推导-腾讯云开发者社区-腾讯云
屏幕坐标转世界坐标与射线生成

end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铁柱同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值