我目前正在学习 OpenGL,我正在尝试编写类似于教程中的简单太阳能系统应用程序,但由于某种原因,相机行为真的很奇怪,我不确定是什么原因造成的。我想让我的相机看着太阳,但我得到的只是行星的一些非常奇怪的角度,或者什么都没有,这甚至可能不是纯粹的相机问题。有人能告诉我我到底做错了什么吗?我会很感激一些代码。如果有人愿意提供帮助并且更愿意检查应用程序而不是在这里阅读代码,请在下面添加链接。
这里的相机将尽可能接近教程(FPS),但我也得到了拖动/滚动系统而不是这个。
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();
}
}
行星.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();
}
}
星星.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();
}
}
扩展.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;
}
}
主窗口.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;
}
}
链接到整个应用程序:
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:
虽然更新 4 的问题仍然存在,但我得到了最后一个问题:如何重新计算相机,使其不仅限于向上/向下查看 -90/90 度?