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

news2024/12/24 20:53:49

使用 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

比特币上的有状态多重签名

无需链下通信 介绍 随着区块链和加密货币空间的发展&#xff0c;越来越需要增强安全措施来保护数字资产。 应对这一挑战的突出解决方案之一是多重签名&#xff08;多重签名&#xff09;钱包。 这些钱包在执行交易之前需要多方签名&#xff0c;从而提供额外的安全层来防止未经授…

如何解决主从数据库同步延迟问题?

如何解决主从数据库同步延迟问题&#xff1f; 前言 最近&#xff0c;系统上频繁出现主从延迟的问题&#xff0c;因此针对主从架构、主从同步以及主从延迟问题进行了一次学习。 主从架构浅析 在了解主从延迟之前&#xff0c;我们有必要对主从架构有一些简单的认识。在如今的…

shell脚本正则表达式

目录 一. 正则表达式定义 二. 基本正则表达式 1. 元字符 2. 表示次数 3. 位置锚定 4. 分组或其他 二. 拓展正则表达式 1. 表示次数 2. 表示分组 一. 正则表达式定义 正则表达式&#xff08;REGEXP &#xff09;&#xff1a;由一类特殊字符及文本字符所编写的模式&…

使用 ChatGPT 创建 Makefile 构建系统:从 Docker 开始

使用 Docker 搭配 ChatGPT 创建 Makefile 构建系统 Makefile 构建系统是嵌入式软件团队实现其开发流程现代化的基础。构建系统不仅允许开发人员选择各种构建目标&#xff0c;还可以将这些构建集成到持续集成/持续部署 (CI/CD) 流程中。使用诸如 ChatGPT 这样的人工智能 (AI) 工…

vatee万腾的科技征途:Vatee数字化力量的新视野

在科技的浪潮中&#xff0c;Vatee万腾正展开一场引人注目的科技征途&#xff0c;以其独特的数字化力量描绘出一片新的视野。这不仅是一次技术的升级&#xff0c;更是一场对未来的全新探索&#xff0c;为我们带来了前所未有的数字化时代。 Vatee万腾以其卓越的技术实力和前瞻性的…

VR全景技术助力政务服务大厅数字化,打造全新政务服务体验

引言&#xff1a; 随着科技的飞速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术逐渐走进人们的视野。VR全景技术作为VR领域的一项重要应用&#xff0c;以其沉浸式、交互式的特点&#xff0c;正逐渐渗透到各行各业。政务服务大厅作为相关部门与民众之间的桥梁&#…

智控openwrt调试

1、移植openwrt 如何加入需要编译的内核。 由于内核与openwrt版本不对需要集成&#xff0c; 在/lib/modules/* 加载模块的目录搞错了。 2、从页面如何垂直调用 2.1页面 使用LUCI&#xff0c;LUCIUCILUA UCI 是 Openwrt 中为实现所有系统配置的一个统一接口&#xff0c;英…

vr红色教育虚拟展馆全景制作提升单位品牌形象

720全景展馆编辑平台以其独特的优势&#xff0c;为展览行业带来了革命性的变革。这种创新的技术应用为参展商提供了更高效、更便捷、更全面的展示解决方案&#xff0c;进一步提升了展览行业的水平和影响力。 一、提升展示效果&#xff0c;增强品牌形象 720全景展馆编辑平台通过…

AIGC系列之:Vision Transformer原理及论文解读

目录 相关资料 模型概述 Patch to Token Embedding Token Embedding Position Embedding ViT总结 相关资料 论文链接&#xff1a;https://arxiv.org/pdf/2010.11929.pdf 论文源码&#xff1a;https://github.com/google-research/vision_transformer PyTorch实现代码…

线上异步任务突然不能回写100%

项目场景&#xff1a; 需求是一个作业&#xff0c;需要运行一组sql&#xff0c;所有sql运行完成&#xff0c;更新作业进度为100%&#xff0c;状态为完成。sql需要是在大数据平台&#xff0c;通过yarn调度&#xff0c;异步执行。 kafka监听每个sql的执行状态&#xff0c;所有sql…

设计问卷调查问题的技巧二:确定问题的结构与顺序

上篇文章中&#xff0c;我们了解到设计问卷调查问卷的技巧有保持问题中立、少用开放式问题、保持全名平衡的答案集、谨慎设置单一回答。在这篇文章中&#xff0c;我们将继续深入探讨设计问卷调查问题的剩余5大技巧&#xff01; Tip5&#xff1a;注意问题的顺序 虽然您可以任意…

蓝桥杯刷题day01——字符串中的单词反转

题目描述 你在与一位习惯从右往左阅读的朋友发消息&#xff0c;他发出的文字顺序都与正常相反但单词内容正确&#xff0c;为了和他顺利交流你决定写一个转换程序&#xff0c;把他所发的消息 message 转换为正常语序。 注意&#xff1a;输入字符串 message 中可能会存在前导空…

机器学习中的概率与统计知识点汇总

引言 在学习高级知识时&#xff0c;理解基本概念至关重要。为什么&#xff1f;因为基础知识是您构建高级知识的基础。如果你把更多的东西放在薄弱的基础之上&#xff0c;它最终可能会分裂&#xff0c;这意味着你最终无法完全理解你所学的任何知识。因此&#xff0c;让我们尝试…

探索编程在现代社会的无限价值

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

有一种浪漫,叫接触Linux

大家好&#xff0c;我是五月。 嵌入式开发 嵌入式开发产品必须依赖硬件和软件。 硬件一般使用51单片机&#xff0c;STM32、ARM&#xff0c;做成的产品以平板&#xff0c;手机&#xff0c;智能机器人&#xff0c;智能小车居多。 软件用的当然是以linux系统为蓝本&#xff0c…

element table滚动条失效

问题描述:给el-table限制高度之后滚动条没了 给看看咋设置的&#xff1a; <el-table:data"tableData"style"width: 100%;"ref"table"max-height"400"sort-change"changeSort">对比了老半天找不出问题&#xff0c;最后…

时间序列预测 — LSTM实现多变量多步负荷预测(Keras)

目录 1 数据处理 1.1 数据集简介 1.2 数据集处理 2 模型训练与预测 2.1 模型训练 2.2 模型多步预测 2.3 结果可视化 1 数据处理 1.1 数据集简介 实验数据集采用数据集6&#xff1a;澳大利亚电力负荷与价格预测数据&#xff08;下载链接&#xff09;&#xff0c;包括数…

国内某知名半导体公司:实现虚拟化环境下的文件跨网安全交换

立足特定应用领域的创新型企业 上海某半导体公司是中国10大集成电路设计公司之一的子公司。该半导体公司是一家特色工艺集成电路芯片制造企业&#xff0c;专注模拟电路、功率器件所需的特色生产工艺研发与制造&#xff0c;。 该半导体公司不断追求创新&#xff0c;提高自身产…

Leetcode—907.子数组的最小值之和【中等】

2023每日刷题&#xff08;四十二&#xff09; Leetcode—907.子数组的最小值之和 算法思想 参考自y神思想 实现代码 class Solution { public:int sumSubarrayMins(vector<int>& arr) {long long ans 0;const int mod 1e97;int n arr.size();stack<int>…

万字详解,和你用RAG+LangChain实现chatpdf

像chatgpt这样的大语言模型(LLM)可以回答很多类型的问题,但是,如果只依赖LLM,它只知道训练过的内容,不知道你的私有数据:如公司内部没有联网的企业文档,或者在LLM训练完成后新产生的数据。(即使是最新的GPT-4 Turbo,训练的数据集也只更新到2023年4月)所以,如果我们…