我已经实现了什么
我已经实现了一个应用程序,它能够在屏幕上呈现多边形列表,并具有特定的静态相机设置(位置、视线和向上矢量),所有这些都是在没有OpenGL的纯Java AWT中实现的。
我首先应用模型视图矩阵,然后应用投影到2D矩阵,然后再应用视口矩阵。
我还实现了一些关于世界的基本变换矩阵,例如平移、X/Y/Z轴围绕lookAt点的旋转和围绕lookAt点的缩放。
我现在想要实现的目标
我希望能够在这个世界上"移动"。具体来说,我想用键盘箭头向前、向后、向左和向右导航,并能够用鼠标查看不同的点。就像在真实的游戏中一样。
我想这是通过每次更改相机参数并再次渲染世界来完成的。
就这么简单吗?
在世界上移动只是将x,y值添加到相机位置并观察一个点?
此外,移动鼠标是否只是将x、y添加到某个点上?
在任何情况下,我都必须触摸上矢量吗?
有信息性的答案,加上其他相关链接,也会很有帮助。
您可以通过矩阵的结构化管理来实现这种行为。尤其是视图矩阵。在大多数情况下,向x/y位置添加某些内容是不正确的,因为相机可能会旋转。
让我们用V
来表示当前视图矩阵。如果你想移动相机,你必须计算一个新的视图变换:
V := Translate(-x, -y, -z) * V
,其中Translate(a, b, c)
是平移矩阵。CCD_ 3表示在左/右方向上的移动。-y
表示上行/下行。CCD_ 5表示向前/向后。
如果你想旋转相机,这可以类似地完成:
V := Rotate(-angle, axis) * V
,其中Rotate(-angle, axis)
是旋转矩阵。绕x轴旋转可向上/向下查看,绕y轴旋转可向左/向右查看。通常不需要绕z轴旋转,因为它会给相机带来滚动。
大多数参数被否定的原因是视图变换是逆变换。也就是说,他们不定位相机对象,但他们重新定位世界,就好像相机在原点一样。
首先来看一下理解4x4齐次变换矩阵
-
提取相机轴向量
如果你看链接答案中的第一张图片,你会发现向量存储在哪里。注意,如果你得到了转置布局,那么向量也会被转置。
-
移动
移动很容易,只需
add/sub
方向向量到相机矩阵的原点。例如,我的相机将Z-
作为前进方向。因此,我从矩阵中取Z-axis
向量(在我的情况下,它已经是单位),将其与速度和时间(运动代码运行的计时器间隔)相乘。下面是我的移动代码通常看起来的示例://--------------------------------------------------------------------------- void __fastcall Twin_main::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { // Keyboard event called on any key press keys.set(Key,Shift); // set key in my keymap as pressed _redraw=true; // key press means something in scene might change so redraw } //--------------------------------------------------------------------------- void __fastcall Twin_main::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift) { // Keyboard event called on any key release keys.rst(Key,Shift); // set key in my keymap as not pressed } //--------------------------------------------------------------------------- void __fastcall Twin_main::tim_updateTimer(TObject *Sender) { // Timer runing every tim_update->Interval [ms] double dt=double(tim_update->Interval)/1000.0; // set speeds and transitions between view modes double alfa=2.0*deg; // angular speed double v=100000.0/3.6; // movement speed [km/hod] -> [m/s] // precision control if (keys.Shift.Contains(ssAlt )) { v*= 0.1; alfa*= 0.1; } if (keys.Shift.Contains(ssCtrl)) { v*=10.0; alfa*=10.0; } // rotations (local to camera space) if (keys.get(104)) { _redraw=true; eye.rep.lroty(+alfa); } // num8 if (keys.get(105)) { _redraw=true; eye.rep.lroty(-alfa); } // num9 if (keys.get(100)) { _redraw=true; eye.rep.lrotx(+alfa); } // num4 if (keys.get( 97)) { _redraw=true; eye.rep.lrotx(-alfa); } // num1 if (keys.get(111)) { _redraw=true; eye.rep.lrotz(+alfa); } // num/ if (keys.get(106)) { _redraw=true; eye.rep.lrotz(-alfa); } // num* if (keys.get( 37)) { _redraw=true; eye.rep.lroty(+alfa); } // left if (keys.get( 39)) { _redraw=true; eye.rep.lroty(-alfa); } // right // movements if (keys.get( 96)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,-v*dt)); } // num0 if (keys.get(110)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,+v*dt)); } // num. if (keys.get( 98)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,-v*dt,0.0)); } // num2 if (keys.get(101)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,+v*dt,0.0)); } // num5 if (keys.get(102)) { _redraw=true; eye.rep.lpos_set(vector_ld(-v*dt,0.0,0.0)); } // num6 if (keys.get( 99)) { _redraw=true; eye.rep.lpos_set(vector_ld(+v*dt,0.0,0.0)); } // num3 if (keys.get( 38)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,-v*dt)); } // up if (keys.get( 40)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,+v*dt)); } // down keys.rfskey(); // just do some stuff for advanced keyboard things not used here if (_redraw) { draw(); } } //---------------------------------------------------------------------------
keys
是我的类,它为每一个16位键代码都持有单位bool,允许我一次处理多个按键。eye.rep
是我的类,它同时持有直接变换矩阵和逆变换矩阵。其成员lpos_set
采用3D矢量将其从局部坐标转换为全局坐标,并将其设置为矩阵原点。它与我提到的上述过程具有相同的效果。deg
只是弧度为1度的常数,lrot?
是局部旋转,见下一个项目符号。 -
旋转
我不使用欧拉角,因为我讨厌它们。他们遇到了故障,需要以不同的方式处理电线杆,而且经常会引发问题。你有没有玩过游戏,相机的角度旋转被卡住了,不允许你旋转到你想要的地方?甚至反过来?然后是由于相机使用的欧拉角度。
我使用局部旋转
lrot?
(参见链接的答案它们是如何工作的)。它们没有边界或故障。你唯一需要记住的就是准确性。若你们多次旋转矩阵,你们就失去了精度。所以每隔几次旋转就保证了它的正交性或正交性。为此,您只需要使用叉积使轴再次垂直,并将其长度设置为1
或您使用的任何值。我在矩阵类中有一个计数器,每次更改都会递增,当它达到一个三重态时,正交性就会恢复。
正如你所看到的,我使用数字键盘进行移动,箭头用于向左/向右和向前/向后。现在大多数人都使用WSAD
,但这对我来说非常不舒服。代码取自我的一个VCL应用程序,所以你需要更改事件以匹配你的。
您还可以添加鼠标操纵器来转动。。。如果您需要操纵杆,请参阅
- 如何在C中获取JoyStick Z旋转消息++
但我怀疑它是否像适用于Windows那样适用于JAVA。