获取多边形网格的使用过的 UV 平铺 背景信息目标到目前为止我所拥有的相关

问题描述

背景信息

UDIM Tiles with UVs

多边形网格使用 UV 集将图像纹理映射到其表面。使用 UDIM 纹理,您可以使用整数图块在 UV 图块所在的整数范围内应用不同的纹理,例如tile [0,0] 可以具有与 UV tile [0,1] 不同的纹理。

鉴于 UV 壳/点,我想快速检测 UV 集当前使用的整数图块。 在实践/生产中,UV 很少跨越 UV 平铺边界,因为它们是按 UDIM 平铺布局的。不过,我也想准确检测这些不太常见的情况。

目标

给定一个网格的 UV,我想检测哪些 UV 块被占用。上面的示例图像在 UV 平铺的边界框内有壳,但从技术上讲,UV 可以跨越 UV 平铺的边界并延伸到下一个(甚至更远)。

我想查询/检测正在使用的 UV 瓷砖:

  • UV 点可以全部位于图块之外(例如,图块 [0,0] 周围的多边形的 4 个点)并且图块包含在多边形中。平铺应包含在 UV 中。
  • UV 点可以围绕磁贴(例如,超过 4 个点的 NGon UV 围绕磁贴弯曲(因此边界框撞击磁贴,但 UV 实际上不包括磁贴)。
  • 其他情况是 UV 可以与图块重叠/相交或完全包含在图块内。然后应包括图块。

我正在尝试在 Autodesk Maya 中使用 Python 执行此操作,但我对任何快速的数学方法感到满意,而且我可以轻松弄清楚如何使用 Python 进行编码。

本质上,它是在查询 2D 多边形与哪些整数图块/单元格重叠。我不需要知道交叉点 - 只需要知道哪些 UV 平铺包含 UV。

我宁愿避免包含仅在边界上被击中的图块。例如,如果多边形仅与 [0,0] 重叠,则 [0,1] 上的 UV 点/边不需要包含平铺 [0,1]。

到目前为止我所拥有的

目前我只在 Maya 中完成了一些原型,这些原型基于 gist here 中的边界进行比较。评论中还有其他一些方法代码

from maya import cmds

    
def get_bounding_Box_tiles(bb):
    u_minmax,v_minmax = bb
    
    # If the max is exactly on the integer boundary we allow it to be 
    # part of the tile of the previos integer so we can subtract one.
    # But since we'll need to add one to iterate "up to" the maximum for 
    # the `range` function we will instead add one to the opposite cases.
    # Inputs are tuples so don't assign elements but override tuple
    if u_minmax[1] != int(u_minmax[1]):
        u_minmax = (u_minmax[0],u_minmax[1] + 1)
    if v_minmax[1] != int(v_minmax[1]):
        v_minmax = (v_minmax[0],v_minmax[1] + 1)
        
    tiles = []
    for v in range(*map(int,v_minmax)):
        for u in range(*map(int,u_minmax)):
            tiles.append((u,v))
    return tiles


def get_uv_udim_tiles(mesh,uv_set=None):
    """Return the UV tiles used by the UVs of the input mesh.
    
    Warning:
         This does not capture the case where a single UV shell
         might be layout in such a way that all its UV points are
         around another tile since it uses the UV shell bounding
         Box to compute the used tiles. In the image below imagine 
         the lines being the UVs of a single shell and the `x` being 
         an emtpy UV tile. It will not detect the empty tile.
         
         / - \
         | x |
    
    Args:
        mesh (str): Mesh node name.
        uv_set (str): The UV set to sample. When not
            provided the current UV map is used.
            
    Returns:
        list: sorted list of uv tiles
    
    """
        
    kwargs = {}
    if uv_set is not None:
        kwargs["uvSetName"] = uv_set

    bb = cmds.polyEvaluate(mesh,boundingBox2d=True,**kwargs)
    tiles = get_bounding_Box_tiles(bb)
    if len(tiles) == 1:
        # If there's only a single tile for the bounding Box
        # it'll be impossible for there to be empty tiles
        # in-between so we just return the given tiles
        return tiles
    
    # Get the bounding Box per UV shell
    uv_shells = cmds.polyEvaluate(mesh,uvShell=True,**kwargs)
    if uv_shells == 1:
        # If there's only a single UV shell it must span
        # all the UV tiles
        return tiles
    
    tiles = set()
    for i in range(uv_shells):
        shell_uvs = cmds.polyEvaluate(mesh,uvsInShell=i,**kwargs)
        shell_bb = cmds.polyEvaluate(shell_uvs,boundingBoxComponent2d=True,**kwargs)
        shell_tiles = get_bounding_Box_tiles(shell_bb)
        tiles.update(shell_tiles)
        
    return sorted(tiles,key=uv2udim)
        
        
def uv2udim(tile):
    """UV tile to UDIM number.
    
    Note that an input integer of 2 means it's
    the UV tile range using 2.0-3.0.
    
    Examples:
        >>> uv2udim((0,0)
        # 1001
        >>> uv2udim((0,1)
        # 1011
        >>> uv2udim((2,0)
        # 1003
        >>> uv2udim(8,899)
        # 9999
    
    Returns:
        int: UDIM tile number
        
    """
    u,v = tile    
    return 1001 + u + 10 * v 


# Example usage
for mesh in cmds.ls(selection=True):
    tiles = get_uv_udim_tiles(mesh)
    
    print mesh
    print tiles
    for tile in tiles:
        print uv2udim(tile)

相关

解决方法

这是一个适用于 Python 的 Maya 的可能解决方案。

建议的方法:

  • 确定网格 UV 壳的全局边界框(单个 bbox 包围所有壳),并确定覆盖的 UV 瓦片集。
  • 创建一个面数与 UV 平铺相同的平面(每个 UV 平铺 1 个面,遵循相同的 U/V 比率)。 例如:如果边界框覆盖了 (0,0) 到 (5,3) 的图块,则创建一个 6x4 平面。
  • 缩放/定位平面的 UV 以适应 UV 网格(1 个面正好覆盖 1 个 UV 平铺)。
  • 确定平面 UV 壳和其他网格 UV 壳之间的重叠。

这可能不是最好的方法,但它避免了必须确定 UV 壳边界和计算 UV 网格(由 Maya 处理)之间的交点。

它处理特定情况:

  • 被视为重叠的图块:
    • UV 点完全包含在瓷砖内
    • UV 点全部位于图块之外,但将其包围
    • UV 仅与图块相交
  • 图块不被视为重叠:
    • 瓷砖周围的 UV 点,但没有覆盖或相交
    • UV 位于图块边界上

一些限制:

  • 代码将在 Maya 中执行(在 Maya 2020.2 中测试)
  • UV 平铺的大小被认为是 (1,1)。如果需要更细的粒度(例如:0.1),则必须更改代码(但主要方法可以保持)
  • 场景被修改(创建临时平面几何体)。它在最后被清理干净,但这可能是不可接受的。
  • 该代码未在极端情况下进行全面测试,因此可能不应按原样使用。
  • 代码未优化,可以重写。
import math

def getBoundingBoxTilesRange(meshes):
    '''Determine the global bounding box of the UV shells.
        Single BBox englobing all.
    '''

    ((minX,maxX),(minY,maxY)) = cmds.polyEvaluate(meshes,boundingBox2d=True)

    minU = int(math.floor(minX))
    maxU = int(math.floor(maxX))
    minV = int(math.floor(minY))
    maxV = int(math.floor(maxY))

    return ((minU,minV),(maxU,maxV))


def createOverlapPlane(minU,minV,maxU,maxV):
    '''Create a plane covering the UV grid
        1 face per tile,covering the UV grid precisely
    '''

    # Create a plane with the same number of faces as the UV tiles
    # '+1' to include upper limits
    sizeX = maxU - minU + 1
    sizeY = maxV - minV + 1
    plane_trsf,plane_node = cmds.polyPlane(width=1,height=1,subdivisionsX=sizeX,subdivisionsY=sizeY,createUVs=2)

    # Scale/position the UVs of the plane to fit/cover the grid
    # '+2' to include last index AND upper limits
    count = 0
    for indexV in range(minV,maxV+2):
        for indexU in range(minU,maxU+2):
            uv = "%s.map[%d]" % (plane_trsf,count)
            cmds.polyEditUV(uv,relative=False,uValue=indexU,vValue=indexV)
            count += 1

    return plane_trsf


def getOverlappedTiles(meshes):
    '''Determine the UV tiles overlapped by the UV shells of the provided meshes.
    '''

    # Save scene status
    cmds.undoInfo(openChunk=True)


    # Get the global bounding box of the UV shells
    ((minU,maxV)) = getBoundingBoxTilesRange(selection)

    # Create a plane covering the UV grid (1 face per tile)
    plane_trsf = createOverlapPlane(minU,maxV)


    # Determine non-overlapped faces between the plane UV shell and the other meshes UV shells
    mesh_faces = cmds.polyListComponentConversion(meshes,tf=True)
    plane_faces = cmds.polyListComponentConversion(plane_trsf,tf=True)
    non_overlap = cmds.polyUVOverlap(mesh_faces + plane_faces,noc=1)


    # Determine UV tiles
    tiles = []
    for indexV in range(minV,maxV+1):
        for indexU in range(minU,maxU+1):
            tiles.append((indexU,indexV))

    # Flatten lists to separate each element
    non_overlap_list = cmds.ls(non_overlap,flatten=1)
    plane_faces_list = cmds.ls(plane_faces,flatten=1)

    # Get overlapped UV tiles
    overlapped_tiles = tiles[:]
    for face in non_overlap_list:
        #TODO: should find faster way
        index = plane_faces_list.index(face)
        overlapped_tiles.remove(tiles[index])


    # Restore scene status
    cmds.undoInfo(closeChunk=True)
    cmds.undo()

    return overlapped_tiles

示例用法:

# Get the desired meshes
# (here from selection as an example)
meshes = cmds.ls(sl=1)

# Get the overlapped UV tiles
overlapped_tiles = getOverlappedTiles(meshes)
print "Overlapped tiles:",overlapped_tiles