使用多边形网格的 UV 瓦片

Posted

技术标签:

【中文标题】使用多边形网格的 UV 瓦片【英文标题】:Get used UV tiles of a Polygonal Mesh 【发布时间】:2021-05-25 12:51:37 【问题描述】:

背景资料

多边形网格使用 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 可以仅与 Tile 重叠/相交或完全包含在 Tile 中。然后应该包括图块。

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

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

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

到目前为止我所拥有的

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

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)

相关

How to check intersections with two rotated rectangles - 这将是一个潜在的解决方案。但是,从技术上讲,单个 UV 多边形在此实现失败的地方可能是凹的。我还假设,因为我们正在检查轴对齐的整数图块,所以算法可能更简单。 AABB and Concave Polygon Intersection - 仅适用于凹多边形 - 但除了类似的问题。

【问题讨论】:

【参考方案1】:

这是 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, minV),(maxU, maxV)) = getBoundingBoxTilesRange(selection)

    # Create a plane covering the UV grid (1 face per tile)
    plane_trsf = createOverlapPlane(minU, minV, maxU, 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

【讨论】:

好主意!我发现 maya.cmds.polyUVCoverage 给了我 this working prototype 松散地基于您的版本,而不需要中间网格。

以上是关于使用多边形网格的 UV 瓦片的主要内容,如果未能解决你的问题,请参考以下文章

用opengl实现网格[重复]

是否可以从 Blender 中的渲染对象创建 UV 贴图

Unity MeshMeshFilterMeshRenderer扫盲

谷歌地图下载瓦片拼接地图

Java对点线面生成栅格瓦片jpg,并渲染呈现

3dsmax-uv展开