using OpenTK.Graphics.OpenGL;
|
using OpenTK.Mathematics;
|
|
namespace Yw.WinFrmUI.Hydro
|
{
|
/// <summary>
|
/// 正交转换辅助类
|
/// </summary>
|
internal class OrthoTransformHelper
|
{
|
|
|
/// <summary>
|
/// 屏幕坐标 To NDC坐标
|
/// </summary>
|
/// <param name="screenX">屏幕X坐标(左上角为原点)</param>
|
/// <param name="screenY">屏幕Y坐标(左上角为原点)</param>
|
/// <param name="depth">深度值(范围[0, 1],可选)</param>
|
/// <param name="viewportWidth">视口宽度</param>
|
/// <param name="viewportHeight">视口高度</param>
|
/// <returns></returns>
|
public static Vector3 ScreenToNDC(float screenX, float screenY, float depth, int viewportWidth, int viewportHeight)
|
{
|
// 归一化到 [0, 1]
|
float normalizedX = screenX / viewportWidth;
|
float normalizedY = screenY / viewportHeight;
|
|
// 映射到 [-1, 1]
|
float ndcX = 2.0f * normalizedX - 1.0f;
|
float ndcY = 1.0f - 2.0f * normalizedY; // 翻转Y轴
|
float ndcZ = 2.0f * depth - 1.0f; // 深度值转换
|
|
return new Vector3(ndcX, ndcY, ndcZ);
|
}
|
|
/// <summary>
|
/// NDC坐标 To 屏幕坐标
|
/// </summary>
|
/// <param name="ndc">NDC坐标(X,Y ∈ [-1, 1], Z ∈ [-1, 1])</param>
|
/// <param name="viewportWidth">视口宽度</param>
|
/// <param name="viewportHeight">视口高度</param>
|
/// <returns></returns>
|
public static Vector2 NDCToScreen(Vector3 ndc, int viewportWidth, int viewportHeight)
|
{
|
// X轴:[-1, 1] → [0, viewportWidth]
|
float screenX = (ndc.X + 1.0f) * 0.5f * viewportWidth;
|
|
// Y轴:[-1, 1] → [0, viewportHeight](翻转Y轴)
|
float screenY = (1.0f - (ndc.Y + 1.0f) * 0.5f) * viewportHeight;
|
|
// Z轴(可选):[-1, 1] → [0, 1]
|
float screenZ = (ndc.Z + 1.0f) * 0.5f;
|
|
return new Vector2(screenX, screenY);
|
}
|
|
|
/// <summary>
|
/// 读取鼠标位置对应的深度值
|
/// </summary>
|
/// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
|
/// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
|
/// <param name="viewportHeight">视口高度</param>
|
/// <returns>深度值(范围[0, 1])</returns>
|
public static float GetScreenPointDepth(int mouseX, int mouseY, int viewportHeight)
|
{
|
// 调整Y坐标:屏幕坐标系(左上原点) → OpenGL视口坐标系(左下原点)
|
int glY = viewportHeight - mouseY;
|
|
// 读取深度值
|
float[] depth = new float[1];
|
GL.ReadPixels(
|
mouseX, // 鼠标X坐标
|
glY, // 调整后的Y坐标
|
1, 1, // 读取1x1像素
|
PixelFormat.DepthComponent, // 像素格式为深度分量
|
PixelType.Float, // 数据类型为浮点数
|
depth // 存储结果的数组
|
);
|
|
return depth[0];
|
}
|
|
|
/// <summary>
|
/// 屏幕坐标 To 世界坐标
|
/// </summary>
|
/// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
|
/// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
|
/// <param name="depth">深度值(范围[0, 1],可选)</param>
|
/// <param name="mview">视图矩阵</param>
|
/// <param name="proj">投影矩阵</param>
|
/// <param name="viewportWidth">视口宽度</param>
|
/// <param name="viewportHeight">视口高度</param>
|
/// <returns></returns>
|
public static Vector3 ScreenToWorld(float mouseX, float mouseY, float depth, Matrix4 mview, Matrix4 proj, int viewportWidth, int viewportHeight)
|
{
|
var ndc = ScreenToNDC(mouseX, mouseY, depth, viewportWidth, viewportHeight);
|
var matrix = mview * proj;
|
var invert = matrix.Inverted();
|
// 将NDC坐标转换为世界坐标
|
var wp = Vector4.TransformRow(new Vector4(ndc, 1f), invert);
|
wp /= wp.W; // 透视除法
|
return new Vector3(wp.X, wp.Y, wp.Z);
|
}
|
|
/// <summary>
|
/// 世界坐标 To 屏幕坐标
|
/// </summary>
|
/// <param name="wp">世界坐标</param>
|
/// <param name="mview">视图矩阵</param>
|
/// <param name="proj">投影矩阵</param>
|
/// <param name="wiewportWidth">视口宽度</param>
|
/// <param name="viewportHeight">视口高度</param>
|
/// <returns></returns>
|
public static Vector2 WorldToScreen(Vector3 wp, Matrix4 mview, Matrix4 proj, int wiewportWidth, int viewportHeight)
|
{
|
// 转换为齐次坐标
|
var wph = new Vector4(wp, 1.0f);
|
//应用矩阵
|
var mvp = mview * proj;
|
//裁剪坐标
|
var clip = Vector4.TransformRow(wph, mvp);
|
//NDC
|
clip /= clip.W;
|
return NDCToScreen(clip.Xyz, wiewportWidth, viewportHeight);
|
}
|
|
/// <summary>
|
/// 创建射线
|
/// </summary>
|
/// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
|
/// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
|
/// <param name="depth">深度值(范围[0, 1],可选)</param>
|
/// <param name="forward">观察方向</param>
|
/// <param name="model">模型矩阵</param>
|
/// <param name="view">视图矩阵</param>
|
/// <param name="proj">投影矩阵</param>
|
/// <param name="viewportWidth">视口宽度</param>
|
/// <param name="viewportHeight">视口高度</param>
|
public static Ray3 CreateRay
|
(
|
float mouseX,
|
float mouseY,
|
float depth,
|
Vector3 forward,
|
Matrix4 model,
|
Matrix4 view,
|
Matrix4 proj,
|
int viewportWidth,
|
int viewportHeight
|
)
|
{
|
var mview = view * model;
|
var origins = ScreenToWorld(mouseX, mouseY, depth, mview, proj, viewportWidth, viewportHeight);
|
return new Ray3(origins, forward);
|
}
|
|
/// <summary>
|
/// 创建射线
|
/// </summary>
|
/// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
|
/// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
|
/// <param name="model">模型矩阵</param>
|
/// <param name="view">视图矩阵</param>
|
/// <param name="proj">投影矩阵</param>
|
/// <param name="viewportWidth">视口宽度</param>
|
/// <param name="viewportHeight">视口高度</param>
|
public static Ray3 CreateRay2
|
(
|
float mouseX,
|
float mouseY,
|
Matrix4 model,
|
Matrix4 view,
|
Matrix4 proj,
|
int viewportWidth,
|
int viewportHeight
|
)
|
{
|
var mview = view * model;
|
var near = ScreenToWorld(mouseX, mouseY, 0f, mview, proj, viewportWidth, viewportHeight);
|
var far = ScreenToWorld(mouseX, mouseY, 1f, mview, proj, viewportWidth, viewportHeight);
|
var direction = (far - near).Normalized();
|
return new Ray3(near, direction);
|
}
|
|
|
|
|
|
|
|
|
}
|
}
|