You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python光线追踪:3D空间多边形与射线的首次相交计算

实现方案

我们可以通过向量化的Möller-Trumbore射线-三角形相交算法实现需求,仅依赖numpy即可完成所有计算,不需要额外引入其他库。思路如下:

  • 所有输入的4顶点多边形(四边形)先拆分为2个三角形,排除用于闭合的第5个冗余顶点
  • 对每条射线(固定x、z坐标,沿y轴方向发射),批量计算和所有三角形的交点
  • 过滤有效交点后取y坐标最小的即为最先命中的交点值

完整代码实现

import numpy as np
import matplotlib.pyplot as plt

def ray_triangle_intersect(ray_orig, ray_dir, v0, v1, v2):
    """
    向量化Möller-Trumbore算法,计算射线与三角形的交点
    参数:
        ray_orig: 射线原点,形状(N, 3) 或 (3,)
        ray_dir: 射线方向,形状(N, 3) 或 (3,)
        v0, v1, v2: 三角形三个顶点,形状(M, 3) 或 (3,)
    返回:
        t: 射线方向上的交点距离,无交点则为inf
    """
    eps = 1e-8
    # 扩展维度适配批量计算
    ray_orig = np.atleast_2d(ray_orig)
    ray_dir = np.atleast_2d(ray_dir)
    v0 = np.atleast_2d(v0)
    v1 = np.atleast_2d(v1)
    v2 = np.atleast_2d(v2)
    
    edge1 = v1 - v0
    edge2 = v2 - v0
    h = np.cross(ray_dir[:, None, :], edge2[None, :, :])
    a = np.einsum('ijk,ijk->ij', edge1[None, :, :], h)
    
    # 射线与三角形平行的情况
    mask = np.abs(a) < eps
    a[mask] = eps
    
    f = 1.0 / a
    s = ray_orig[:, None, :] - v0[None, :, :]
    u = f * np.einsum('ijk,ijk->ij', s, h)
    
    # u不在[0,1]区间,无交点
    mask_u = (u < 0.0) | (u > 1.0)
    q = np.cross(s, edge1[None, :, :])
    v = f * np.einsum('ijk,ijk->ij', ray_dir[:, None, :], q)
    
    # v不在[0,1]区间,或者u+v>1,无交点
    mask_v = (v < 0.0) | (u + v > 1.0)
    t = f * np.einsum('ijk,ijk->ij', edge2[None, :, :], q)
    
    # 交点在射线反方向的情况
    mask_t = t < eps
    
    # 所有无效情况设置为inf
    t[mask | mask_u | mask_v | mask_t] = np.inf
    return t

def compute_ray_hits(quads, x_grid, z_grid, ray_dir=np.array([0, 1, 0])):
    """
    计算x-z网格上所有沿y方向发射的射线的最近交点y值
    参数:
        quads: 输入四边形列表,每个四边形形状为(4,3)(已经去掉第5个闭合顶点)
        x_grid: x坐标网格,1维数组
        z_grid: z坐标网格,1维数组
        ray_dir: 射线方向,默认沿y轴正方向
    返回:
        hit_y: 最近交点y值网格,形状为(len(z_grid), len(x_grid)),无交点则为inf
    """
    # 把所有四边形拆成三角形
    tris = []
    for quad in quads:
        # 四边形v0,v1,v2,v3拆为v0,v1,v2和v0,v2,v3两个三角形
        tris.append(quad[[0,1,2]])
        tris.append(quad[[0,2,3]])
    tris = np.array(tris)
    v0, v1, v2 = tris[:,0], tris[:,1], tris[:,2]
    
    # 生成所有射线原点
    X, Z = np.meshgrid(x_grid, z_grid)
    ray_orig = np.stack([X.ravel(), np.full_like(X.ravel(), -100), Z.ravel()], axis=1)
    
    # 批量计算所有射线和所有三角形的交点t值
    t_vals = ray_triangle_intersect(ray_orig, ray_dir, v0, v1, v2)
    
    # 取每个射线的最小t值,计算对应y坐标
    min_t = np.min(t_vals, axis=1)
    hit_y = ray_orig[:,1] + min_t * ray_dir[1]
    hit_y = hit_y.reshape(X.shape)
    return hit_y

# ------------ 测试示例 ------------
# 示例数据预处理,去掉第5个闭合顶点
square1 = np.array([
    [0, 0, 0],
    [1, 0, 0],
    [1, 0.5, 1],
    [0, 0.5, 1],
    [0, 0, 0]])[:4]
square2 = square1 + 0.5 * np.ones_like(square1)

# 测试单条射线:x=0.75, z=0.75
test_ray_orig = np.array([0.75, -100, 0.75])
test_ray_dir = np.array([0,1,0])
# 把两个四边形拆成三角形
all_tris = np.concatenate([
    square1[[0,1,2]][None,:], square1[[0,2,3]][None,:],
    square2[[0,1,2]][None,:], square2[[0,2,3]][None,:]
])
v0, v1, v2 = all_tris[:,0], all_tris[:,1], all_tris[:,2]
t = ray_triangle_intersect(test_ray_orig, test_ray_dir, v0, v1, v2)
min_t = np.min(t)
hit_y = test_ray_orig[1] + min_t * test_ray_dir[1]
print(f"x=0.75、z=0.75处射线最先命中的y坐标为:{hit_y:.4f}")

# 批量生成x-z网格的命中结果示例
x_grid = np.linspace(-1, 2, 100)
z_grid = np.linspace(-1, 2, 100)
hit_y_grid = compute_ray_hits([square1, square2], x_grid, z_grid)

# 可视化结果
plt.figure(figsize=(5,4))
plt.pcolormesh(x_grid, z_grid, hit_y_grid, shading='auto')
plt.colorbar(label='命中y坐标')
plt.xlabel('x')
plt.ylabel('z')
plt.title('射线最近命中y坐标分布')
plt.gca().set_aspect('equal')
plt.show()

输出说明

示例测试中x=0.75、z=0.75处的射线会先命中square1,输出的y坐标约为0.375,符合几何计算结果。批量计算后得到的网格可以直接用于生成深度图或渲染图像。

内容的提问来源于stack exchange,提问作者John Smith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 23:15:03