lixiaojun
2025-03-21 d03e8c5d165426f820c10e0c876e5c6af14d270b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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);
        }
 
 
 
 
 
 
 
 
    }
}