OpenGL(sharpGL)支持任意相机可平移缩放的轨迹球原理描述及源码

本文提供一个本人编写的轨迹球类(ArcBall.cs),它可以直接应用到任何 camera 下,还可以同时实现缩放和平移。 工程源代码在文末。
OpenGL(sharpGL)支持任意相机可平移缩放的轨迹球原理描述及源码

 

1.轨迹球原理:

OpenGL(sharpGL)支持任意相机可平移缩放的轨迹球原理描述及源码

上面是我黑来的两张图,拿来说明轨迹球的原理。看左边这个,网格代表绘制 3D 模型的窗口,上面放了个半球,这个球就是轨迹球。 假设鼠标在网格上的某点 A,过 A 点作网格所在平面的垂线,与半球相交于点 P, P 就是 A 在轨迹球上的投影。鼠标从 A1 点沿直线移动到 A2 点,对应着轨迹球上的点 P1 沿球面移动到了P2。那么,从球心 O 到 P1 和 P2 分别有两个向量 OP1 和 OP2。 OP1 旋转到了 OP2,我们就认为是模型也按照这个方式作同样的旋转。这就是轨迹球的旋转思路。右边这个图没用上...

 

2.轨迹球实现

实现轨迹球,首先要求出鼠标点 A1、 A2 投影到轨迹球上的点 P1、 P2 的坐标,然后计算两个向量 A1P1 和 A2P2 之间的夹角以及旋转轴,最后让模型按照求出的夹角和旋转轴,调用glRotate 就可以了
OpenGL(sharpGL)支持任意相机可平移缩放的轨迹球原理描述及源码

如图所示,红绿蓝三色箭头的交点是摄像机 eye 的位置,红色箭头指向 center 的位置,绿色 箭头指向 up 的位置,蓝色箭头指向右侧。说明: 1.Up 是可能在蓝色 Right 箭头的垂面内的任意方向的,这里我们要把它调整为与红色视线垂直的 Up,即上图所示的 Up。 2.绿色和蓝色箭头组成的平面即为程序窗口所在位置,因为 Eye 就在这里嘛。而且 Up 指的就是屏幕正上方, Right 指就是屏幕正右方。 3.显然轨迹球的半球在图中矩形所在这一侧,球心就是 Eye。
鼠标在 Up 和 Right 所在的平面移动,当它位于 A 点时, 投影到轨迹球的点 P。现在已知的是 Eye、 Center、原始 Up、 A 点在屏幕上的坐标、 向量 Eye-P 的长度、向量 AP 的长度。现在要求 P 点的坐标,只不过是一个数学问题了。
 

参考源码(C#)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpGL.SceneGraph.Cameras;
using SharpGL.SceneGraph;
using SharpGL.SceneGraph.Lighting;
using SharpGL;
using SharpGL.SceneGraph.Core;
using SharpGL.SceneGraph.Assets;
using SharpGL.SceneGraph.Quadrics;
using SharpGL.SceneGraph.Effects;

namespace SharpGLWinformsApplication1
{
    public partial class FormMain : Form
    {
        private ArcBallEffect objectArcBallEffect;
        private ArcBallEffect axisArcBallEffect;

        public FormMain()
        {
            InitializeComponent();
            this.sceneControl1.MouseWheel += sceneControl1_MouseWheel;
        }

        void sceneControl1_MouseWheel(object sender, MouseEventArgs e)
        {
            objectArcBallEffect.ArcBall.Scale -= e.Delta * 0.001f;
        }
        const float near = 0.01f;
        const float far = 10000;
        private bool mouseDownFlag;

        private void InitElements(Scene scene)
        {
            var objectRoot = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Root" };
            scene.SceneContainer.AddChild(objectRoot);
            // This implements free rotation(with translation and rotation).
            var camera = GetCamera();
            objectArcBallEffect = new ArcBallEffect(
                camera.Position.X, camera.Position.Y, camera.Position.Z,
                camera.Target.X, camera.Target.Y, camera.Target.Z,
                camera.UpVector.X, camera.UpVector.Y, camera.UpVector.Z);
            objectRoot.AddEffect(objectArcBallEffect);
            var axisRoot = new SharpGL.SceneGraph.Primitives.Folder() { Name = "axis root" };
            scene.SceneContainer.AddChild(axisRoot);
            axisArcBallEffect = new ArcBallEffect(camera.Position.X,
                camera.Position.Y, camera.Position.Z,
                camera.Target.X, camera.Target.Y, camera.Target.Z,
                camera.UpVector.X, camera.UpVector.Y, camera.UpVector.Z);
            axisRoot.AddEffect(axisArcBallEffect);

            InitLight(objectRoot);
            InitAxis(objectRoot);
            InitAxis(axisRoot);
            InitFrameElement(6, 24, 7, objectRoot);
            InitGridElement(1.5f, 3, 0, 3, 24, objectRoot);
        }

        private void InitGridElement(float x, float y, float z, float width, float length, SceneElement parent)
        {
            var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Grid" };
            parent.AddChild(folder);

            var grid = new GridElement(x, y, z, width, length);
            folder.AddChild(grid);
        }

        private void InitFrameElement(int width, int length, int height, SceneElement parent)
        {
            var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Frame" };
            parent.AddChild(folder);

            var frame = new FrameElement(width, length, height);
            folder.AddChild(frame);
        }

        private void InitAxis(SceneElement parent)
        {
            var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Axis" };
            parent.AddChild(folder);

            // X轴
            Material red = new Material();
            red.Emission = Color.Red;
            red.Diffuse = Color.Red;

            Cylinder x1 = new Cylinder() { Name = "X1" };
            x1.BaseRadius = 0.05;
            x1.TopRadius = 0.05;
            x1.Height = 1.5;
            x1.Transformation.RotateY = 90f;
            x1.Material = red;
            folder.AddChild(x1);

            Cylinder x2 = new Cylinder() { Name = "X2" };
            x2.BaseRadius = 0.1;
            x2.TopRadius = 0;
            x2.Height = 0.2;
            x2.Transformation.TranslateX = 1.5f;
            x2.Transformation.RotateY = 90f;
            x2.Material = red;
            folder.AddChild(x2);

            // Y轴
            Material green = new Material();
            green.Emission = Color.Green;
            green.Diffuse = Color.Green;

            Cylinder y1 = new Cylinder() { Name = "Y1" };
            y1.BaseRadius = 0.05;
            y1.TopRadius = 0.05;
            y1.Height = 1.5;
            y1.Transformation.RotateX = -90f;
            y1.Material = green;
            folder.AddChild(y1);

            Cylinder y2 = new Cylinder() { Name = "Y2" };
            y2.BaseRadius = 0.1;
            y2.TopRadius = 0;
            y2.Height = 0.2;
            y2.Transformation.TranslateY = 1.5f;
            y2.Transformation.RotateX = -90f;
            y2.Material = green;
            folder.AddChild(y2);

            // Z轴
            Material blue = new Material();
            blue.Emission = Color.Blue;
            blue.Diffuse = Color.Blue;

            Cylinder z1 = new Cylinder() { Name = "Z1" };
            z1.BaseRadius = 0.05;
            z1.TopRadius = 0.05;
            z1.Height = 1.5;
            z1.Material = blue;
            folder.AddChild(z1);

            Cylinder z2 = new Cylinder() { Name = "Z2" };
            z2.BaseRadius = 0.1;
            z2.TopRadius = 0;
            z2.Height = 0.2;
            z2.Transformation.TranslateZ = 1.5f;
            z2.Material = blue;
            folder.AddChild(z2);
        }

        private void InitLight(SceneElement parent)
        {
            Light light1 = new Light()
            {
                Name = "Light 1",
                On = true,
                Position = new Vertex(-9, -9, 11),
                GLCode = OpenGL.GL_LIGHT0
            };
            Light light2 = new Light()
            {
                Name = "Light 2",
                On = true,
                Position = new Vertex(9, -9, 11),
                GLCode = OpenGL.GL_LIGHT1
            };
            Light light3 = new Light()
            {
                Name = "Light 3",
                On = true,
                Position = new Vertex(0, 15, 15),
                GLCode = OpenGL.GL_LIGHT2
            };
            var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Lights" };

            parent.AddChild(folder);
            folder.AddChild(light1);
            folder.AddChild(light2);
            folder.AddChild(light3);
        }

        private void sceneControl1_MouseDown(object sender, MouseEventArgs e)
        {
            this.mouseDownFlag = true;
            objectArcBallEffect.ArcBall.SetBounds(this.sceneControl1.Width, this.sceneControl1.Height);
            objectArcBallEffect.ArcBall.MouseDown(e.X, e.Y);
            axisArcBallEffect.ArcBall.SetBounds(this.sceneControl1.Width, this.sceneControl1.Height);
            axisArcBallEffect.ArcBall.MouseDown(e.X, e.Y);
        }

        private void sceneControl1_MouseMove(object sender, MouseEventArgs e)
        {
            objectArcBallEffect.ArcBall.MouseMove(e.X, e.Y);
            axisArcBallEffect.ArcBall.MouseMove(e.X, e.Y);
        }

        private void sceneControl1_MouseUp(object sender, MouseEventArgs e)
        {
            this.mouseDownFlag = false;
            objectArcBallEffect.ArcBall.MouseUp(e.X, e.Y);
            axisArcBallEffect.ArcBall.MouseUp(e.X, e.Y);
        }

        private void sceneControl1_KeyDown(object sender, KeyEventArgs e)
        {
            const float interval = 1;
            if (e.KeyCode == Keys.W || e.KeyCode == Keys.Up)
            {
                this.objectArcBallEffect.ArcBall.GoUp(interval);
            }
            else if (e.KeyCode == Keys.S || e.KeyCode == Keys.Down)
            {
                this.objectArcBallEffect.ArcBall.GoDown(interval);
            }
            else if (e.KeyCode == Keys.A || e.KeyCode == Keys.Left)
            {
                this.objectArcBallEffect.ArcBall.GoLeft(interval);
            }
            else if (e.KeyCode == Keys.D || e.KeyCode == Keys.Right)
            {
                this.objectArcBallEffect.ArcBall.GoRight(interval);
            }
        }

        private LookAtCamera GetCamera()
        {
            return this.sceneControl1.Scene.CurrentCamera as LookAtCamera;
        }

        private void FormMain_Resize(object sender, EventArgs e)
        {
            this.objectArcBallEffect.ArcBall.SetBounds(this.sceneControl1.Width, this.sceneControl1.Height);
            var gl = this.sceneControl1.OpenGL;
            var axis = gl.UnProject(50, 50, 0.1);
            axisArcBallEffect.ArcBall.SetTranslate(axis[0], axis[1], axis[2]);
            axisArcBallEffect.ArcBall.Scale = 0.001f;
        }

        private void sceneControl1_OpenGLInitialized(object sender, EventArgs e)
        {
            var scene = this.sceneControl1.Scene;
            scene.SceneContainer.Children.Clear();
            scene.RenderBoundingVolumes = false;
            // 设置视角
            var camera = GetCamera();
            camera.Near = near;
            camera.Far = far;
            camera.Position = new Vertex(12.5f, -1.5f, 11.5f);
            camera.Target = new Vertex(4.5f, 7, 2.5f);
            camera.UpVector = new Vertex(0.000f, 0.000f, 1.000f);

            InitElements(scene);
            axisArcBallEffect.ArcBall.SetTranslate(
                12.490292456095853f, -1.5011389593859834f, 11.489356270615454f);
            axisArcBallEffect.ArcBall.Scale = 0.001f;
        }

        private void FormMain_Load(object sender, EventArgs e)
        {

        }
    }
}