物惯(子到父节点)转换顺序原因和不同坐标系下的变换顺序

物惯(子到父节点)变换顺序原因和不同坐标系下的变换顺序
在基于同一个参考坐标系中(也就是子节点到父节点坐标系中的变换),对于一个基本变换,D3D中要先缩放->旋转->平移。OGL中要先平移->旋转->缩放,其中的原因解释?

OGL中是左乘矩阵,因此子节点变换到父节点是先平移后旋转缩放,才是正确的变换顺序。
关于先平移后旋转不是OGL中解释的每次是基于当前嵌套坐标系变换,而是因为变换的矩阵乘法顺序和乘法规则规定的。
D3D中先旋转后平移才能如描述期望的正确变换。

如果改为先平移后旋转,出现问题的原因不是因为绕父节点基向量变换(公转,D3D中可以考虑为基于当前坐标系绕父节点公转),也不是绕子节点基向量变换(基于本地坐标,OGL中的变换顺序平移->旋转->缩放是可以这样考虑的),旋转没有问题,但是平移出现了不同。因为在子节点坐标系为当前变换基向量中,进行了基于父节点的平移向量,乘以每个当前基向量提取出了部分相对于父节点基向量的权重也就是列向量,进行平移,作为结果。进行了基于将要变换到的坐标系(旋转后坐标系,而不是平移)的分解组合结果,进行基于父节点坐标系的变换(平移量)。解释为矩阵变换顺序和乘法规则导致的结果即可,且要理解在D3D矩阵乘法规则下,子节点变换到父节点顺序是:缩放->旋转->平移, OGL中相反(乘法顺序不同)。

缩放和投影会改变轴大小,旋转会改变轴的走向,平移改变物体本地坐标原点位置;更多的是他们的乘法规则,乘法顺序会得到不一样的结果,结果是否是期望的作为判定是否正确标准
物体做不基于本地坐标系的变换,需要先平移回本地放射坐标系中进行放射变换,再平移回去原来位置,实现变换,但是一般设计中都是美术设计好基于模型本地坐标系进行缩放旋转平移变换,再平移到世界坐标系中。

下图为证明过程:

物惯(子到父节点)转换顺序原因和不同坐标系下的变换顺序

OGL中也是要缩放->旋转->平移顺序才得到在父坐标系(例如世界)中描述的结果,但是因为OGL中矩阵表示是列式矩阵,所以连续变换的顺序是左乘当前矩阵(矩阵变换向量也是左乘向量)才相当于D3D中的右连乘,所以OGL中同一个坐标系中变换是平移->旋转->缩放矩阵连序左乘才得到正确结果。

故OGL视图模型变换中,先设置视图变换,然后再设置模型变换。但平移旋转缩放不是相对于相同的父坐标系(如世界)那么乘法顺序就另当别论了。


OGL中的连续骨骼节点变换,基于全局坐标系的思考,照样描述(D3D),实现代码用连续左乘(也确保了最终节点左乘在最左边)得到叶子节点的变换矩阵,左乘向量得结果。

OGL中连续变换在节点内也可以用本地坐标系统考虑,本地坐标系统会跟着物体变换,也就是先平移到一个位置,本地坐标系统也平移了这个位置,在进行旋转,然后基于旋转后的本地坐标系进行缩放,在节点之间平移累加是不分先后的直接累加即可

OGL中行星卫星绕恒星旋转示例:

/*
 * Copyright (c) 1993-2003, Silicon Graphics, Inc.
 * All Rights Reserved
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright
 * notice and this permission notice appear in supporting documentation,
 * and that the name of Silicon Graphics, Inc. not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" AND
 * WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, LOSS OF
 * PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF THIRD
 * PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN ADVISED OF
 * THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE POSSESSION, USE
 * OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights 
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor clauses
 * in the FAR or the DOD or NASA FAR Supplement.  Unpublished - rights
 * reserved under the copyright laws of the United States.
 *
 * Contractor/manufacturer is:
 *	Silicon Graphics, Inc.
 *	1500 Crittenden Lane
 *	Mountain View, CA  94043
 *	United State of America
 *
 * OpenGL(R) is a registered trademark of Silicon Graphics, Inc.
 */

/*
 *  planet.c
 *  This program shows how to composite modeling transformations
 *  to draw translated and rotated models.
 *  Interaction:  pressing the d and y keys (day and year)
 *  alters the rotation of the planet around the sun.
 */
#include <GL/glut.h>
#include <stdlib.h>

static int year = 0, day = 0;
static int sateliteDay = 0;

void init(void) 
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_FLAT);
}

void display(void)
{
   glClear (GL_COLOR_BUFFER_BIT);
   glColor3f (1.0, 1.0, 1.0);
   // 备份下之前的视口,投影,视图变换
   glPushMatrix();
   // 绘制太阳
   glutWireSphere(1.0, 20, 16);   /* draw sun */
   // 1.D3D中解释是量是父坐标的,缩放改变轴大小(沿着父坐标受轴走向原点影响),旋转改变轴走向(绕父坐标受原点影响),
   // 平移改变原点(在父坐标上不受缩放旋转影响)。其实按照行主序的矩阵,乘法结果是基于当前坐标系作父坐标值在当前坐标系相对于父坐标值分解权重变换。
   // 其实行主序矩阵中,在父坐标系描述的值,进行期望的矩阵累积变换就是:缩放旋转平移的顺序。
   // OGL中列式矩阵刚好是行矩阵的转置,乘法顺序不变,故在父坐标值描述中,进行期望的矩阵累积变换顺序是:平移旋转缩放,
   // 刚好可以解释为如果每次都是基于当前坐标系的,且变换的量也是基于当前坐标系进行的(其实是等于父坐标中的量)。

   // 2.子坐标值变换到父坐标,一个完整的变换(包含缩放旋转平移)完成后会得到一个向量(位移)(无论是重下往上,还是重上往下), 接着嵌套坐标计算,
   // 结果位移累加即可。父坐标值变换到子坐标(坐标参考系改变了)作子坐标变换到父坐标的逆变换即可。在完整变换之间D3D和OGL是一样的。

   // 3.故第一个glRotatef是绕太阳系公转,glTranslatef是在公转坐标系中平移,第二个glRotatef是平移后旋转倾斜一个固定角度,
   // 第三个glRotatef是绕倾斜固定角度后旋转(自转)。

   // 4.矩阵的连乘可以改变本地坐标系(轴大小走向,平移除外),所以:
   // glRotatef((GLfloat)day-23.5, 0.0, 1.0, 0.0);不能代替两个glRotatef变换。

   // 绘制行星
   glRotatef((GLfloat)year, 0.0, 1.0, 0.0);
   glTranslatef (2.0, 0.0, 0.0);
   glRotatef((GLfloat)-23.5, 0.0, 0.0, 1.0);
   glRotatef((GLfloat)day, 0.0, 1.0, 0.0);
   glutWireSphere(0.2, 10, 8);    /* draw smaller planet */

   // 绘制卫星1
   glPushMatrix();
   // 绕行星公转,D3D解释是TR变换,平移到了一个位置,旋转是绕平移之前的父坐标旋转所以是公转,
   // 公转的量不是基于平移后的坐标,也不是父坐标,而是父坐标值在旋转后的权重累积。
   // OGL解释用本地坐标系思考,旋转是基于当前坐标的,量也是当前坐标的;平移是旋转后的,无论旋转到什么角度都是平移这么多。
   // 其实在一个完整变换内部OGL的思维更加简单一些。
   glRotatef((GLfloat)sateliteDay, 0.0, 1.0, 0.0);
   glTranslatef(0.5, 0.0, 0.0);
   glutWireSphere(0.1, 5, 4);    /* draw satelite1 */
   glPopMatrix();

   // 绘制卫星2
   glPushMatrix();
   // 绕行星公转
   glRotatef((GLfloat)sateliteDay + 180, 0.0, 1.0, 0.0);
   glTranslatef(0.5, 0.0, 0.0);
   glutWireSphere(0.1, 5, 4);    /* draw satelite2 */
   glPopMatrix();
   // 5. 恢复视口,投影,视图变换变换;避免下一次display取得不是想要的结果。
   glPopMatrix();

   glutSwapBuffers();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h); 
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
   case 's':
	   sateliteDay = (sateliteDay + 10) % 360;
	   glutPostRedisplay();
	   break;
   case 'S':
	   sateliteDay = (sateliteDay - 10) % 360;
	   glutPostRedisplay();
	   break;
      case 'd':
         day = (day + 10) % 360;
         glutPostRedisplay();
         break;
      case 'D':
         day = (day - 10) % 360;
         glutPostRedisplay();
         break;
      case 'y':
         year = (year + 5) % 360;
         glutPostRedisplay();
         break;
      case 'Y':
         year = (year - 5) % 360;
         glutPostRedisplay();
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (500, 500); 
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display); 
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}