3D模型纹理集合并【Python|C#】

使用 Substance Painter 时,将模型的各个部分分成不同的纹理集非常有用。 这可以帮助遮罩,或者只是保持层栈干净。 不幸的是,Painter 无法将多个纹理集中的所有贴图导出为单个图集,即使在创建单独对象的 UV 时考虑到了这一点。 显然,在游戏设计领域,最好将加载到内存中的大纹理数量保持在最低限度,因此,如果可以的话,我们当然不应该为游戏中的单个对象使用五个纹理集。 公平地说,首先可能有一百万种方法可以防止这个问题。 我怀疑,通过一些巧妙的 ID 屏蔽,你可以相当轻松地仅使用一组纹理来对整个模型进行纹理处理。 但事后解决这个问题应该不会那么烦人。

NSDT在线工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器

组合这些纹理是一个相对简单的过程:你只需将 UV 岛与任何背景颜色物质分开,然后将它们叠加在一起。 分离图像的哪些部分被 UV 岛覆盖可能听起来很困难,但不用担心,我们实际上不需要从模型中获取 UV。 我们只需要获取与 Substance 用于该贴图的背景填充颜色不同的所有像素,然后从给定纹理组(即所有法线贴图)中的所有贴图复制所有这些像素并将它们粘贴到一个贴图中 ,即最终纹理。 这是一个简单的过程,但手动完成仍然相当乏味,特别是当你需要频繁迭代纹理时。

所以我写了一个 python 脚本来帮我做这件事。 但这(非常)慢,所以我还编写了另一个更快的 C# 脚本。 这不仅是因为 C# 一般来说是一种更快的语言,而且还因为我对它进行了多线程处理,因此它可以立即组合我需要的所有纹理组。

我已经提供了我的代码,欢迎您使用,但请注意,我在 Substance Painter 中使用 Unity HDRP 工作流程; 如果你希望它能够在任何其他用例中正常工作,将需要进行一些更改。

using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.Threading;

public class Atlaser
{
    public static void Main(string[] args)
    {
        /*Here I'm creating a thread to combine the maps for each texture set component.
        You'll want to add or remove threads depending on what kinds of maps you're using in your workflow.
        You'll also need to change the filename endings to match those of your textures.
        Finally, change the colors to match the default background color of the maps. Remember that C# formats colors as ARGB (alpha, red, green, blue) for some reason.
        */
        Console.WriteLine("Opening Threads...");
        Thread normal = new Thread(Atlaser.Atlas);
        Thread baseColor = new Thread(Atlaser.Atlas);
        Thread Mask = new Thread(Atlaser.Atlas);
        Thread Emissive = new Thread(Atlaser.Atlas);
        normal.Start(new FileAndCol("_Normal.png", Color.FromArgb(255, 127, 127, 255)));
        baseColor.Start(new FileAndCol("_BaseMap.png", Color.FromArgb(255, 0, 0, 0)));
        Mask.Start(new FileAndCol("_MaskMap.png", Color.FromArgb(178, 0, 0, 0)));
        Emissive.Start(new FileAndCol("_Emissive.png", Color.FromArgb(255, 0, 0, 0)));
    }

    /*
    Atlas() grabs all the files in the current directory with the provided file extension, creates a new texture of the same size filled with the baseline/background color, then moves the deltas from the opened maps over to the new texture, and finally saves the new texture to the disk.
    */
    private static void Atlas(object? fcobj)
    {
        if(fcobj == null)
            return;

        FileAndCol fc = fcobj as FileAndCol;

        var watch = new Stopwatch();
        watch.Start();

        Console.WriteLine("Beginning thread for " + fc.fileExtension + " images.");

        List<Bitmap> maps = new List<Bitmap>();

        string[] files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*" + fc.fileExtension);

        string o = "Found " + files.Length + " maps : \n ";
        foreach (string file in files)
        {
            o += file + "\n ";
            maps.Add(new Bitmap(file));
        }
        Console.WriteLine(o);

        Bitmap finalMap = new Bitmap(maps[0].Width, maps[0].Height);
        using (Graphics g = Graphics.FromImage(finalMap))
        using (SolidBrush brush = new SolidBrush(Color.FromArgb(fc.col.ToArgb())))
        {
            Rectangle rect = new Rectangle(0, 0, finalMap.Width, finalMap.Height);
            g.FillRectangle(brush, rect);
        }


        //Console.WriteLine("Iterating through maps...");

        for (int i = 0; i < maps.Count; i++)
        {
            Console.WriteLine(" Beginning map: " + files[i] + " (" + fc.fileExtension + ") " + (i+1) + " of " + maps.Count);
            for (int x = 0; x < finalMap.Width; x++)
            {
                for (int y = 0; y < finalMap.Height; y++)
                {
                    Color px = maps[i].GetPixel(x, y);
                    if (!px.Equals(fc.col))
                    {
                        finalMap.SetPixel(x, y, px);
                    }
                }
            }
        }

        finalMap.Save(fc.fileExtension.Remove(fc.fileExtension.Length - 4) + "_Combined.png", ImageFormat.Png);

        watch.Stop();
        Console.WriteLine(" **" + fc.fileExtension + " thread completed in " + watch.ElapsedMilliseconds + " ms");
    }
}
public class FileAndCol
{
    public string fileExtension;
    public Color col;

    public FileAndCol(string s, Color c)
    {
        fileExtension = s;
        col = c;
    }
}

总而言之,这并不是一个非常复杂的脚本。 如果你是 .NET 新手(或者只在 Unity 内部使用过 C#),那么运行它非常简单:

  • 在 Visual Studio 或 VS Code(或 Rider 或任何其他 .NET IDE)中创建新的 C# 控制台应用程序
  • 创建一个新的空 C# 脚本
  • 将上面的代码复制并粘贴到那里
  • 进行处理特定纹理所需的任何编辑,保存文件
  • 将单独的纹理贴图移动到与脚本相同的目录中
  • 通过按顶部的绿色大播放按钮 (Visual Studio) 运行脚本,或者在终端中打开目录并输入以下命令: dotnet run

一旦确定其按照你想要的方式工作,甚至可以将项目构建为可执行文件,你只需将其拖放到 Substance Painter 导出文件夹中即可。

构建一个小型 GUI 或 CLI 来精确定制哪些贴图到图集以及如何映射也很方便,但它对我来说已经足够好了,所以这就是我留下的地方。 希望你学到了一些东西,或者至少获得了一个有用的工具!

顺便说一句,如果你对我编写的 Python 版本感到好奇,这里是:

import glob, os
from PIL import Image, ImageColor
​
textures = []
​
for img in glob.glob("*BaseMap.png"):
    textures.append(Image.open(img))
    print('Found ' + img)
​
neutralColor = (0,0,0,1)
​
finalImg = Image.new('RGBA', textures[0].size, neutralColor)
​
print('Iterating through maps...')
i = 0
for img in textures:
    print('  Beginning map: ' + img.filename)
    for x in range(img.size[0]):
        #print("     Current col: ", x)
        for y in range(img.size[1]):
            px = img.getpixel((x,y))
            if px != neutralColor:
                finalImg.putpixel((x,y), px)
    
    
    i += 1
    
finalImg.show()
finalImg.save('combinedBlackMaskTexture.png')

这是一个更简单的脚本。 它使用 Pillow 来创建图像,并遵循相同的基本算法:循环遍历每个图像的每个像素,并复制与给定基线颜色不匹配的部分。

我只得到了一个纹理集的图集,并认为 Python 对于我的需求来说有点太慢了。 事实上,绘制单个纹理组所需的时间比 C# 脚本长 2.5 倍。 由于单个集合中可以有四个或更多纹理组,并且 Python 中的并行处理可能有点困难,因此我认为切换到另一种语言是一个好主意。 尽管如此,编写这个脚本要容易得多。


原文链接:合并多个纹理集 - BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/194269.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Django创建基本的app应用并配置URL路径-成功运行服务

开发环境&#xff1a;Pycharm2021 Win11 首先创建虚拟环境: 可参考&#xff1a; Pycharm开发环境下创建python运行的虚拟环境&#xff08;自动执行安装依赖包&#xff09;_pycharm自动下载依赖包_heda3的博客-CSDN博客 1、安装 Django 在虚拟环境下安装pip install django …

ES 8.x开始(docker-compose安装、kibana使用、java操作)

学习文档地址 一、Docker安装 这里使用docker-compose来安装&#xff0c;方便后续迁移&#xff0c;Elasticserach和kibina一起安装。 1、创建安装目录 configdataplugins 2、配置文件 配置文件有两个&#xff0c;一个是ES的配置文件&#xff0c;一个docker-compose的配置文件 …

DS图—图的最短路径/Dijkstra算法【数据结构】

DS图—图的最短路径/Dijkstra算法【数据结构】 题目描述 给出一个图的邻接矩阵&#xff0c;输入顶点v&#xff0c;用迪杰斯特拉算法求顶点v到其它顶点的最短路径。 输入 第一行输入t&#xff0c;表示有t个测试实例 第二行输入顶点数n和n个顶点信息 第三行起&#xff0c;每行…

龙芯loongarch64服务器编译安装pyarrow

1、简介 pyarrow是一个高效的Python库,用于在Python应用程序和Apache Arrow之间进行交互。Arrow是一种跨语言的内存格式,可以快速高效地转移大型数据集合。它提供了一种通用的数据格式,将数据在内存中表示为表格,并支持诸如序列化和分布式读取等功能。 龙芯的Python仓库安…

Mo0n(月亮) MCGS触摸屏在野0day利用,强制卡死锁屏

项目:https://github.com/MartinxMax/Mo0n 后面还会不会在,我可就不知道了奥…还不收藏点赞关注 扫描存在漏洞的设备 #python3 Mo0n.py -scan 192.168.0.0/24 入侵锁屏 #python3 Mo0n.py -rhost 192.168.0.102 -lock 解锁 #python3 Mo0n.py -rhost 192.168.0.102 -unlock …

Linux(10):Shell scripts

什么是 Shell scripts shell script&#xff08;程序化脚本&#xff09;&#xff1a;shell 部分是一个文字接口下让我们与系统沟通的一个工具接口&#xff1b;script 是脚本的意思&#xff0c;shell script 就是针对 shell 写的脚本。 shell script 是利用 shell 的功能所写的…

yolov8-pose 推理流程

目录 一、关键点预测 二、图像预处理 二、推理 三、后处理与可视化 3.1、后处理 3.2、特征点可视化 四、完整pytorch代码 yolov8-pose tensorrt 一、关键点预测 注&#xff1a;本篇只是阐述推理流程&#xff0c;tensorrt实现后续跟进。 yolov8-pose的tensorrt部署代码…

FPGA模块——DA转换模块(AD9708类)

FPGA模块——DA转换模块&#xff08;AD9708类&#xff09; AD9708/3PD9708代码 AD9708/3PD9708 由于电路接了反相器&#xff0c;所以对应就不一样了。 电路图&#xff1a; 代码 在ROM中存入要输出的波形数据&#xff1a; 用软件生成各个对应的点。 给DA转换器一个时钟&…

智能优化算法应用:基于樽海鞘群算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于樽海鞘群算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于樽海鞘群算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.樽海鞘群算法4.实验参数设定5.算法结果6.参考…

Junos webauth_operation.php 文件上传漏洞复现(CVE-2023-36844)

0x01 产品简介 Junos 是 Juniper Networks 生产的一款可靠的高性能网络操作系统。 0x02 漏洞概述 Junos webauth_operation.php接口处存在文件上传漏洞&#xff0c;未经身份认证的攻击者可利用 Junos 操作系统的 J-Web 服务 /webauth_operation.php 路由上传 php webshell&…

C语言第三十四弹--矩形逆置

C语言实现矩阵逆置 逆置结果如图 思路&#xff1a;通过观察逆置结果&#xff0c;首先发现行数和列数都发生了调换。其次观察逆置前后数字对应的下标&#xff0c;逆置前数字对应下标为:[x][j] 逆置后数字对应下标为&#xff1a;[y][x]。综上&#xff0c;就可以实现矩阵逆置。 …

人才“塔尖城市”,长沙如何炼成?

文 | 智能相对论 作者 | 范柔丝 长沙在人才吸引力上&#xff0c;近几年来可谓风头无二。 自2022年长沙人才政策“升级版45条”实施以来&#xff0c;越来越多的人才因为长沙真金白银的政策与城市发展机遇&#xff0c;奔赴长沙安居乐业。 随着2023互联网岳麓峰会吹响长沙全力…

用函数初始化数组

将数组全部初始化为相同值 对于一般情况 一般是用函数&#xff0c;传什么数就初始化为什么数 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void init(int arr[], int len, int num) {int i;for (i 0; i < len; i){arr[i] num;} } int main() {int arr[…

网页设计--第5次课后作业

1、快速学习JavaScript的基本知识第1-10章 JavaScript入门 - 绿叶学习网 2、使用所学的知识完成以下练习。需求如下3个&#xff1a; 1&#xff09;点亮灯泡 2&#xff09;将所有的div标签的标签体内容后面加上&#xff1a; very good 3&#xff09;使所有的复选框呈现被选…

OpenHarmony模块化编译

一、环境配置 OpenHarmony版本&#xff1a;OpenHarmony 4.0 Release 编译环境&#xff1a;WSL2 Ubuntu 18.04 平台设备&#xff1a;RK3568 二、配置hb OpenHarmony 代码构建有build.sh和hb两种方式: #方式一、build.sh ./build.sh --product-name rk3568 --ccache#方式二、…

人工智能关键技术决定机器人产业的前途

人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;是指让计算机或机器具有类似于人类的智能和学习能力的技术。人工智能技术与机器人技术的结合将改变传统的机器人行业格局&#xff0c;就像智能手机对传统手机的颠覆一样。本文从人工智能技术的发展趋势、…

QT基础实践之简易计算器

文章目录 简易计算器源码分享演示图第一步 界面设计第二步 设置槽第三步 计算功能实现 简易计算器 源码分享 链接&#xff1a;https://pan.baidu.com/s/1Jn5fJLYOZUq77eNJ916Kig 提取码&#xff1a;qwer 演示图 第一步 界面设计 这里直接用了ui界面&#xff0c;如果想要自己…

ITIL4中自动化测试和质量保障的重要性

点击进入IT管理资料库 在迅速变革的科技世界中&#xff0c;IT服务管理的关键要素之一是自动化测试和质量保障。随着ITIL 4的崭新框架崛起&#xff0c;这两者不仅成为服务管理的重要组成部分&#xff0c;更是组织提高服务质量和效率的不可或缺的利器。 自动化测试和质量保障如何…

MySQL备份与恢复(重点)

MySQL备份与恢复&#xff08;重点&#xff09; 一、用户管理与权限管理 ☆ 用户管理 1、创建MySQL用户 注意&#xff1a;MySQL中不能单纯通过用户名来说明用户&#xff0c;必须要加上主机。如jack10.1.1.1 基本语法&#xff1a; mysql> create user 用户名被允许连接的主…

DM8数据库版本升级

DM数据库版本升级说明 DM数据库的版本一直在不断的的迭代。 对于DM 的数据库版本&#xff0c;分大版本和小版本。 1)大版本&#xff1a;指DM6&#xff0c;DM7&#xff0c;DM8 这种。2)小版本&#xff1a;指同一个大版本子版本的变化&#xff0c;比如DM8的&#xff1a;8.1.0.1…