SharpGL(C# 中的 OpenGL)太阳系 - 相机位置计算错误

SharpGL (OpenGL in C#) Solar System - Camera position is Calculated Wrong

我目前正在学习 OpenGL,我正在尝试编写类似于教程中的简单太阳系应用程序,但由于某种原因,相机行为真的很奇怪,我不确定是什么原因造成的。我想让我的相机看太阳,但我得到的只是一些非常奇怪的行星角度或什么都没有,它甚至可能不是纯粹的相机问题。有人能告诉我我到底做错了什么吗?我会很感激一些代码。如果有人愿意提供帮助并且更喜欢检查应用程序而不是阅读此处的代码,请在下面添加 link。

这里的相机将尽可能接近教程(FPS),但我也得到了 dragging/scrolling 系统而不是这个。

public class Camera
{
    private static float eyeX, eyeY, eyeZ;
    private static float centerX, centerY, centerZ;
    private const float movingSpeed = 0.3f;
    private const float rotationSpeed = 0.25f;
    private static double i, j, k;

    public static float Height { get; set; }
    public static float Slope { get; set; }

    public void InitCamera()
    {
        eyeX = 0f;
        eyeY = 15f;
        eyeZ = 25f;
        centerX = 0;
        centerY = 2;
        centerZ = 0;
        Look();
    }

    public void Look()
    {
        Gl.MatrixMode(OpenGL.GL_MODELVIEW);
        Gl.LoadIdentity();
        Gl.LookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, 0, 1, 0);
    }

    public void UpdateDirVector()
    {
        i = -Math.Sin(((double)Slope).ToRadians()); 
        j = Math.Sin(((double)Height).ToRadians());  
        k = Math.Cos(((double)Slope).ToRadians());     

        centerX = eyeX - (float)i;
        centerY = eyeY - (float)j;
        centerZ = eyeZ - (float)k;
    }

    public static void CenterMouse()
    {
        if (GlCenter == null)
            return;
        var pos = (Point) GlCenter;
        WinApi.SetCursorPos((int)Math.Round(pos.X), (int)Math.Round(pos.Y));
    }

    public void Update(int pressedButton)
    {
        if (GlCenter == null)
            return;
        var pos = (Point)GlCenter;
        var halfHeight = GlHeight / 2;
        var halfWidth = GlWidth / 2;

        var position = new Pointer();
        WinApi.GetCursorPos(ref position);

        var diffX = (float)pos.X - position.x;
        var diffY = (float)pos.Y - position.y;

        if (position.y < halfHeight)
            Height -= rotationSpeed * diffY;
        else if (position.y > halfHeight)
            Height += rotationSpeed * -diffY;
        if (position.x < halfWidth)
            Slope += rotationSpeed * -diffX;
        else if (position.x > halfWidth)
            Slope -= rotationSpeed * diffX;
        UpdateDirVector();
        CenterMouse();

        if (pressedButton == 1) // LPM
        {
            eyeX -= (float)i * movingSpeed;
            eyeY -= (float)j * movingSpeed;
            eyeZ -= (float)k * movingSpeed;
        }
        else if (pressedButton == -1) // PPM
        {
            eyeX += (float)i * movingSpeed;
            eyeY += (float)j * movingSpeed;
            eyeZ += (float)k * movingSpeed;
        }

        Look();
    }
}

Planet.cs:

public class Planet
{
    private readonly PlanetTypes _planetType;
    private readonly Position _position;
    private float _orbitAngle;
    private readonly float _sizeRadius;
    private readonly float _velocity;
    private readonly string _texturePath;

    private uint _list;
    private float _rotationAngle;

    public Planet(float radius, PlanetTypes planetType, Position position, string texturePath, bool hasMoon)
    {
        _sizeRadius = radius;
        _planetType = planetType;
        _position = position;
        _orbitAngle = Rng.Next(360);
        _velocity = (float)Rng.NextDouble() * 0.3f;
        _texturePath = texturePath;
    }

    public void Create()
    {
        var quadric = Gl.NewQuadric(); 
        Gl.QuadricNormals(quadric, OpenGL.GLU_SMOOTH);
        Gl.QuadricTexture(quadric, (int) OpenGL.GL_TRUE);

        _list = Gl.GenLists(1);
        Gl.NewList(_list, OpenGL.GL_COMPILE);
        Gl.PushMatrix();
        Gl.Rotate(270, 1, 0, 0);
        Gl.Sphere(quadric, _sizeRadius, 32, 32); 
        Gl.PopMatrix();
        Gl.EndList();
    }

    public void DrawOrbit()
    {
        Gl.Begin(OpenGL.GL_LINE_STRIP);
        for (var i = 0; i <= 360; i++)
            Gl.Vertex(_position.X * (float)Math.Sin(i * Math.PI / 180), 0, _position.X * (float)Math.Cos(i * Math.PI / 180));
        Gl.End(); 
    }

    public void Draw()
    {
        DrawOrbit(); 

        LoadTexture($"{_texturesPath}{_texturePath}");

        Gl.PushMatrix();
        _orbitAngle += _velocity;
        _rotationAngle += 0.6f;
        Gl.Rotate(_orbitAngle, 0, 1, 0);
        Gl.Translate(-_position.X, -_position.Y, -_position.Z);

        Gl.Rotate(_rotationAngle, 0, 1, 0);

        Gl.CallList(_list);

        Gl.PopMatrix();
    } 
}

Sun.cs

public class Sun
{
    private uint _list;
    private float _rotation;
    private readonly string _texturePath;

    public Sun(string texturePath)
    {
        _texturePath = texturePath;
    }

    public void Create()
    {
        var quadratic = Gl.NewQuadric();
        Gl.QuadricNormals(quadratic, OpenGL.GLU_SMOOTH);
        Gl.QuadricTexture(quadratic, (int)OpenGL.GL_TRUE);

        _list = Gl.GenLists(1);
        Gl.NewList(_list, OpenGL.GL_COMPILE);
        Gl.PushMatrix();
        Gl.Rotate(90, 1, 0, 0);
        Gl.Sphere(quadratic, 3, 32, 32);
        Gl.PopMatrix();
        Gl.EndList();
    }

    public void Draw()
    {
        LoadTexture($"{_texturesPath}{_texturePath}");
        Gl.PushMatrix();
        _rotation += 0.05f;
        Gl.Rotate(_rotation, 0, 1, 0);
        Gl.CallList(_list);
        Gl.PopMatrix();
    } 
}

Stars.cs

public class Stars
{
    private readonly List<Position> starPositions = new List<Position>();

    public void CreateStars(int amount)
    {
        var count = 0;
        while (count <= amount)
        {
            var p = default(Position);
            p.X = Rng.Next(110) * (float)Math.Pow(-1, Rng.Next());
            p.Z = Rng.Next(110) * (float)Math.Pow(-1, Rng.Next());
            p.Y = Rng.Next(110) * (float)Math.Pow(-1, Rng.Next());

            if (!(Math.Pow(Math.Pow(p.X, 2) + Math.Pow(p.Y, 2) + Math.Pow(p.Z, 2), 1 / 3f) > 15))
                continue;
            starPositions.Add(p);
            count++;
        }
    }

    public void Draw()
    {
        Gl.Begin(OpenGL.GL_POINTS);
        Gl.Color(1, 1, 1);
        Gl.PointSize(3); 
        foreach (var starPos in starPositions)
            Gl.Vertex(starPos.X, starPos.Y, starPos.Z);   
        Gl.End(); 
    }
}

SolarSystem.cs(基本上是以上所有内容的集合,枚举不是必需的,我留下它们是因为它们将来可能会有用)

public class SolarSystem
{
    public static Random Rng { get; } = new Random();
    private readonly Stars _stars;
    private readonly Sun _sun;
    private readonly List<Planet> _planets;

    public SolarSystem()
    {
        Camera = new Camera();
        _stars = new Stars();
        _sun = new Sun("sun.bmp");
        _planets = new List<Planet>();
    }

    public void CreateScene()
    {
        _planets.Add(new Planet(0.5f, PlanetTypes.Mercury, new Position(5, 0, 0), "mercury.bmp", false)); // tylko tutaj pliki, wszedzie indziej przekształcone na .bmp
        _planets.Add(new Planet(0.7f, PlanetTypes.Venus, new Position(11, 0, 0), "venus.bmp", false));
        _planets.Add(new Planet(1, PlanetTypes.Earth, new Position(15, 0, 0), "earth.bmp", true));
        _planets.Add(new Planet(1, PlanetTypes.Mars, new Position(22, 0, 0), "mars.bmp", false));
        _planets.Add(new Planet(1.5f, PlanetTypes.Jupiter, new Position(28, 0, 0), "jupiter.bmp", false));
        _planets.Add(new Planet(1.2f, PlanetTypes.Saturn, new Position(35, 0, 0), "saturn.bmp", false));
        _planets.Add(new Planet(1.2f, PlanetTypes.Uranus, new Position(41, 0, 0), "uranus.bmp", false));
        _planets.Add(new Planet(1.2f, PlanetTypes.Neptune, new Position(51, 0, 0), "neptune.bmp", false));
        _planets.Add(new Planet(1.2f, PlanetTypes.Pluto, new Position(60, 0, 0), "pluto.bmp", false));
        _stars.CreateStars(500);
        _sun.Create();
        foreach (var planet in _planets)
            planet.Create();  
    }

    public Camera Camera { get; }

    public void DrawScene()
    {  
        _stars.Draw();
        _sun.Draw();
        foreach (var planet in _planets)
            planet.Draw();
    }

    public enum PlanetTypes
    {
        Mercury,
        Venus,
        Earth,
        Mars,
        Jupiter,
        Saturn,
        Neptune,
        Uranus,
        Pluto
    }
}

API(包括指针位置平移,因为只有这样我才能将光标居中,它将通过拖动相机系统被移除)):

public static class WinApi
{
    [DllImport("GDI32.dll")]
    public static extern void SwapBuffers(uint hdc);
    [DllImport("user32.dll")]
    public static extern void SetCursorPos(int x, int y);
    [DllImport("user32.dll")]
    public static extern void GetCursorPos(ref Pointer point);
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr hWnd);

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int X;
        public int Y;
    }

    [DllImport("User32", EntryPoint = "ClientToScreen", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool ClientToScreen(
        IntPtr hWnd,
        ref POINT pt);

    [EnvironmentPermission(SecurityAction.LinkDemand, Unrestricted = true)]
    public static Point? TransformToScreen(
        Point point,
        Visual relativeTo)
    {
        var hwndSource = PresentationSource.FromVisual(relativeTo) as HwndSource;
        if (hwndSource == null)
            return null;
        var root = hwndSource.RootVisual;

        // Transform the point from the root to client coordinates.
        var transformToRoot = relativeTo.TransformToAncestor(root);
        var pointRoot = transformToRoot.Transform(point);
        var m = Matrix.Identity;
        var transform = VisualTreeHelper.GetTransform(root);
        if (transform != null)
        {
            m = Matrix.Multiply(m, transform.Value);
        }

        var offset = VisualTreeHelper.GetOffset(root);
        m.Translate(offset.X, offset.Y);
        var pointClient = m.Transform(pointRoot);
        pointClient = hwndSource.CompositionTarget.TransformToDevice.Transform(pointClient);

        var pointClientPixels = new POINT();
        pointClientPixels.X = (0 < pointClient.X)
            ? (int)(pointClient.X + 0.5)
            : (int)(pointClient.X - 0.5);
        pointClientPixels.Y = (0 < pointClient.Y)
            ? (int)(pointClient.Y + 0.5)
            : (int)(pointClient.Y - 0.5);

        var pointScreenPixels = pointClientPixels;

        if (ClientToScreen(
            hwndSource.Handle,
            ref pointScreenPixels))
        {
            return new Point(
                pointScreenPixels.X,
                pointScreenPixels.Y);
        }
        return new Point();
    }
}

Extensions.cs

public static class Extensions
{
    public static double ToDegrees(this double radians)
    {
        return radians * (180.0 / Math.PI);
    }

    public static double ToRadians(this double degrees)
    {
        return Math.PI * degrees / 180.0;
    }
}

MainWindow.cs

public partial class MainWindow
{
    private DispatcherTimer _dispatcherTimer;
    private uint _hdc;
    public static string _texturesPath = @"Data\Textures\";
    private SolarSystem _solarSystem;
    private int _movement;

    public static OpenGL Gl { get; private set; }
    public static Point? GlCenter { get; private set; }
    public static int GlHeight { get; private set; }
    public static int GlWidth { get; private set; }

    public MainWindow()
    {
        InitializeComponent();
        Loaded += MainWindow_Loaded;
        ContentRendered += MainWindow_ContentRendered;
    }

    private void MainWindow_ContentRendered(object sender, EventArgs e)
    {
        Gl = openGLControl.OpenGL;

        var source = (HwndSource)PresentationSource.FromVisual(openGLControl);
        var hWnd = source?.Handle;
        if (hWnd != null) _hdc = (uint)hWnd;

        _solarSystem = new SolarSystem();
        _solarSystem.Camera.InitCamera();

        float[] materialAmbient = { 0.5f, 0.5f, 0.5f, 1.0f };
        float[] materialDiffuse = { 1f, 1f, 1f, 1.0f };
        float[] materialShininess = { 10.0f };
        float[] lightPosition = { 0f, 0f, 0f, 1.0f };
        float[] lightAmbient = { 0.85f, 0.85f, 0.85f, 0.0f };

        Gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, lightAmbient);
        Gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, lightPosition);
        Gl.Material(OpenGL.GL_FRONT_AND_BACK, OpenGL.GL_SHININESS, materialShininess);
        Gl.Material(OpenGL.GL_FRONT_AND_BACK, OpenGL.GL_DIFFUSE, materialDiffuse);
        Gl.Material(OpenGL.GL_FRONT_AND_BACK, OpenGL.GL_AMBIENT, materialAmbient);

        Gl.Enable(OpenGL.GL_LIGHTING);
        Gl.Enable(OpenGL.GL_LIGHT0);
        Gl.Enable(OpenGL.GL_DEPTH_TEST);

        _solarSystem.CreateScene();

        Gl.ClearColor(0, 0, 0, 1);


        GlCenter = WinApi.TransformToScreen(new Point(openGLControl.Width / 2, openGLControl.Height / 2), openGLControl);

        GlHeight = (int)openGLControl.Height;
        GlWidth = (int)openGLControl.Width;

        Camera.CenterMouse();

        _dispatcherTimer = new DispatcherTimer();
        _dispatcherTimer.Tick += DispatcherTimer_Tick;
        _dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 1);
        _dispatcherTimer.Start();
    }

    private void DispatcherTimer_Tick(object sender, EventArgs e)
    {
        Gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
        _solarSystem.Camera.Update(_movement);
        _solarSystem.DrawScene();

        WinApi.SwapBuffers(_hdc);
        Gl.Flush();
    }

    private void OpenGLControl_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left)
            _movement = 1;
        else
            _movement = -1;
    }

    private void OpenGLControl_MouseUp(object sender, MouseButtonEventArgs e)
    {
        _movement = 0;
    }

    private void Window_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.C)
        {
            GlCenter = WinApi.TransformToScreen(new Point(openGLControl.Width / 2, openGLControl.Height / 2), openGLControl); // new Point((int)(Left), (int)(Top));
        }
    }

    public static uint LoadTexture(string filename)
    {
        if (string.IsNullOrEmpty(filename))
            throw new ArgumentException(filename);

        Gl.Enable(OpenGL.GL_TEXTURE_2D);
        var texture = new uint[1];
        var id = texture[0];
        Gl.GenTextures(1, texture);
        Gl.BindTexture(OpenGL.GL_TEXTURE_2D, id);

        var bmp = new Bitmap(filename);
        var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

        Gl.TexImage2D(OpenGL.GL_TEXTURE_2D, 0, 3, bmpData.Width, bmpData.Height, 0, OpenGL.GL_BGR, OpenGL.GL_UNSIGNED_BYTE, bmpData.Scan0);

        Gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, OpenGL.GL_LINEAR);
        Gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, OpenGL.GL_LINEAR);

        bmp.UnlockBits(bmpData);

        return id;
    }
}

Link 整个应用程序:

https://www.dropbox.com/sh/uhfyeayxn8l7q9y/AAA8tFda5-ZLAjTUzJcwKUm6a?dl=0

更新 1:

我已将 Look() 函数更改为:

public void Look()
{
    Gl.MatrixMode(OpenGL.GL_PROJECTION);
    Gl.LoadIdentity();
    Gl.Viewport(0, 0, GlWidth, GlHeight);
    Gl.Perspective(45.0f, GlWidth / (double) GlHeight, 1, 200.0);
    Gl.LookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, 0, 1, 0);
    Gl.MatrixMode(OpenGL.GL_MODELVIEW);
}

现在可以了。

现在我可以看到我的应用程序出于某种原因正在加载不正确的纹理。我想那是因为我写的 LoadTextures() 方法正在使用 Gl.GenTextures(1, texture)。 (sharpgl 中没有 Gen(Single)Texture 方法,或者我完全弄错了)。

更新 2:

所以基本上我的大部分纹理都不起作用,因为它们不是 2 的幂,但从我读到的内容来看,它们不必再是了。所以我现在的问题是:如何强制 sharpGL 显示 NPOT 纹理?

更新 3:

结果我可以像这样加载它们,但是,它们是颠倒的:)。

_texture = new Texture();
...
Gl.Enable(OpenGL.GL_TEXTURE_2D);
_texture.Create(Gl, $"{_texturesPath}{_texturePath}");
_texture.Bind(Gl);

更新 4:

我可以翻转纹理以正确显示它,但问题是为什么会这样?

Gl.Enable(OpenGL.GL_TEXTURE_2D);
var bmp = new Bitmap($"{_texturesPath}{_texturePath}");
bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
_texture.Create(Gl, bmp);
_texture.Bind(Gl);

更新 5:

虽然来自 Update 4 的问题仍然存在,但我得到了最后一个问题:我如何重新计算 Camera 以便它不限于只能向上/向下查看 -90/90 度?

您似乎没有正确设置视锥体,并且在未初始化的状态下使用它。您有它的代码,但它在 MainWindow.xaml.cs:123.

中被注释掉了

您必须设置平截头体。至少在绘图前进行一次。可以是透视也可以是正交。

gl.MatrixMode(OpenGL.GL_PROJECTION);
gl.LoadIdentity();
gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);