OpenGL 坐标系统。开创者没用整数,就用 -1 到 1 的浮点数,装下了整个数学,也装下了整个 UNIVERSE,可以无穷大,也可以无穷小。
主要有 5 个不同的坐标系统:
在上图中,OpenGL 定义了后三个坐标系(裁剪坐标、NDC 坐标、屏幕坐标),前三个坐标(物体坐标、世界坐标、摄像机坐标)是为了用户方便而自定义的坐标。
\[\left[\begin{array}{l} x^{\prime} \\ y^{\prime} \\ z^{\prime} \\ 1 \end{array}\right] = \left[\begin{array}{llll} 1 & 0 & 0 & \Delta x \\ 0 & 1 & 0 & \Delta y \\ 0 & 0 & 1 & \Delta z \\ 0 & 0 & 0 & 1 \end{array}\right] \left[\begin{array}{l} x \\ y \\ z \\ 1 \end{array}\right] = \left[\begin{array}{c} x+\Delta x \\ y+\Delta y \\ z+\Delta z \\ 1 \end{array}\right]\]OpenGL 在每次顶点着色器运行之后,希望可见的顶点都可以转化为标准化设备坐标 (Normalized Device Coordinate, NDC),也就是说,每个顶点的 x,y,z 坐标都应该在(-1.0,1.0)之间,超出这个坐标范围的顶点都将不可见。通常我们会自定一个坐标的范围,之后再在顶点着色器中将这些坐标转换为标准化设备坐标。然后将这些标准化的坐标传入光栅器,变换为屏幕上的二维坐标或者像素。
物体坐标到世界坐标,主要是位移;世界坐标到视觉坐标,主要包含位移和旋转。最终要换算到 NDC 立方体内,显卡完成后继工作。 glm::mat4 内存结构是列保存的( 刚好是上面数学矩阵的转置 ):
// 列优先记法
| 0 4 8 12 |
| 1 5 9 13 |
| 2 6 10 14 |
| 3 7 11 15 |
原始坐标:
float vertices[] = {
-0.5f,-0.5f,0.0f, // left,down
0.5f,-0.5f,0.0f, // right,down
0.5f, 0.5f,0.0f, // right,top
-0.5f, 0.5f,0.0f, // left,top
};
glm::mat4 model=glm::mat4(1.0f);
// 默认应该是正交投影,饶 x 轴逆时针旋转 55°
model=glm::rotate(model,glm::radians(55.0f),glm::vec3(1.0f,0.0f,0.0f));
glm::mat4 view = glm::mat4(1.0f);
// 往上平移 0.5f,往后平移 1.0f。
// 只能看到半截,因为默认是一个 [-1,1] 的立方体正交,另外半截超出边界了。
view=glm::translate(view,glm::vec3(0.0f,0.5f,-1.0f));
glm::mat4 projection = glm::mat4(1.0f);
// 最终结果,是一个摄像机在原点的透视。
// 摄像机位置应该在 原点。
projection=glm::perspective(glm::radians(90.0f),800.0f/600.0f,0.1f,10.0f);
右手坐标系。Z 轴正方向为前进方向。
$i$,$j$,$k$ 为虚数。
$Q = w + xi + yj + zk$
其中 $w$ 是实数,而 $x$,$y$,$z$ 为复数。
另外一种常见的表达方式是:
$Q = [w, v]$
其中 $v=(x,y,z)$ 称为矢量部(虽然称为矢量,但是这个不是三维空间中的矢量,而是四维空间的,想象吧 L),$w$ 称为标量部。 使用一个单位四元数来描述方向,请记住必须是单位四元数才可以描述方向。
基本思路如下:
#define Quaternion float4
inline Quaternion SetAxisAngle(float3 axis, float radian)
{
float sinValue = 0;
float cosValue = 0;
sincos(radian * 0.5, sinValue, cosValue);
Quaternion q = Quaternion(sinValue * axis.xyz, cosValue);
return q;
}
inline float3 MultiplyQP(Quaternion rotation, float3 p)
{
float3 xyz = rotation.xyz * 2;
float3 xx_yy_zz = rotation.xyz * xyz.xyz;
float3 xy_xz_yz = rotation.xxy * xyz.yzz;
float3 wx_wy_wz = rotation.www * xyz.xyz;
float3 res;
res.x = (1 - (xx_yy_zz.y + xx_yy_zz.z)) * p.x + (xy_xz_yz.x - wx_wy_wz.z) * p.y + (xy_xz_yz.y + wx_wy_wz.y) * p.z;
res.y = (xy_xz_yz.x + wx_wy_wz.z) * p.x + (1 - (xx_yy_zz.x + xx_yy_zz.z)) * p.y + (xy_xz_yz.z - wx_wy_wz.x) * p.z;
res.z = (xy_xz_yz.y - wx_wy_wz.y) * p.x + (xy_xz_yz.z + wx_wy_wz.x) * p.y + (1 - (xx_yy_zz.x + xx_yy_zz.y)) * p.z;
return res;
}