文章目录
- 背景
- openGL中的坐标转换
- 简单的概念介绍
- 屏幕坐标转世界坐标
- openGL的实现
- filament通过射线拾取计算
- filament官方给出的转换方式
- filament实现坐标转换的QA
- 1、View::pick()
- 2、为什么filament的计算方式没有除以w分量?
- 3、为什么别的资料上都是inverse(viewMatrix * projectionMatrix) ?
- 4、怎么判断这个世界坐标是不是在模型上,也就是怎么判断是不是点击了模型?
- 世界坐标转屏幕坐标
- 矩阵运算
背景
模型渲染到屏幕上之后,鼠标点击屏幕,我们怎么知道是否点击了模型,点击了模型的哪个位置呢? 这些需求都需要坐标转换,常规来说就是从世界坐标系转换到屏幕坐标系,或者从屏幕坐标系转换到世界坐标系。
博主在渲染这块也是个新手,大家一起学习。
本文使用的是google的filament渲染引擎。
openGL中的坐标转换
提起坐标转换,就不得不提经典的openGL了,可以参考以下文章:
ModelMatrix、ModelViewMatrix、ProjectionMatrix、NormalMatrix模型矩阵、模型视图矩阵、投影矩阵、正规矩阵详解_妙为的博客-CSDN博客
经典渲染流程:
坐标转换流程:
简单的概念介绍
假定读者已经了解了基本的渲染知识,我们这里不做过多解释,毕竟不是重点。以下概念来源: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