纹理烘焙:原理及实现

纹理烘焙是计算机图形学中常见的技术,用于将着色器的细节传输到纹理中。 如果你的着色器计算量很大,但会产生静态结果,例如,这非常有用。 复杂的噪音。

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

许多建模应用程序都有此功能。 如果你有从 Blender/Houdini/Maya 传输资源的管道,那么你可能需要在那里进行纹理烘焙。 但是,如果你主要使用 Unity 进行工作,那么本教程适合你。 通过这种技术,你可以有效地保存实时操作的自定义着色器结果,并在任何其他具有默认着色器的应用程序中使用生成的纹理(Sketchfab?等)。

首先,让我们从列出问题开始:

  • 将应用了着色器的网格展开到 uv 坐标空间中。
  • 将展开的网格渲染为纹理。
  • 将纹理保存到磁盘。

仅供参考,如果你只是想查看一些代码(就像我一直做的那样),就在这里。

1、展开网格

展开网格是将 3D 顶点坐标映射到其 2D uv 坐标空间的过程。 要在顶点着色器中执行此操作,我们可以将顶点的 x 和 y 分量设置为其 uv 坐标。 将 z 分量设置为 0 将使我们在 XY 平面中 z=0 处留下展开的网格。

要尝试此操作,请复制要烘焙的着色器,并使用以下行调整顶点位置:

v.vertex = float4(v.uv.xy, 0.0, 1.0);

应用于原始网格的这个新着色器将向你显示展开的网格:

展开的unity球体,应用了 fbm 噪声着色器

解开的树,应用了 fbm 噪声着色器

2、如何将这个展开的网格渲染为纹理?

正交相机! 对于正交投影,没有深度线索。 无论距相机的距离如何,渲染图像中的对象尺寸都保持不变。 这将使我们能够绘制完美的 2D UV 网格而不失真。

透视与正交投影

要开始烘焙过程,我们需要将一个脚本附加到相机来处理烘焙逻辑。 我们将其附加到相机,因为我们想要在完成渲染场景后使用 Graphics.DrawMeshNow 绘制网格。 为此,我们需要访问 OnPostRender 函数。

在示例项目中,我在 ShaderBaker.cs 中的“M”键按下事件上发生以下代码(因为将事物绑定到随机键非常简单,哈哈)。

Mesh M = objectToBake.GetComponent<MeshFilter>().mesh;
// create a new render texture 
RenderTexture rt = RenderTexture.GetTemporary(width, height);// set the active render target 
Graphics.SetRenderTarget(rt);
// save the last camera state
GL.PushMatrix(); 
// load an orthographic camera 
GL.LoadOrtho(); 
// set the active material to be the unwrapping material we made earlier
uvMaterial.SetPass(0);
// draw the mesh, the matrix does not matter because we are not using it for any projection in the shader
Graphics.DrawMeshNow(M, Matrix4x4.identity);
// ** save to disk  here **
// reset state
Graphics.SetRenderTarget(null);
RenderTexture.ReleaseTemporary(rt);
GL.PopMatrix();

关于上面发生的事情有很多话要说,但如何说取决于你对图形管道的熟悉程度……我想这里最有趣的两件事是加载正交相机和激活适当的 UV 展开材质 使用 SetPass 进行渲染。

UV 展开着色器将与你要烘焙的着色器相同,除了顶点着色器中的两条线更改之外。 前面提到的第一个变化是将顶点坐标重新映射到 UV 空间。

v.vertex = float4(v.uv.xy, 0.0, 1.0);

其次,我们需要将标准行:

o.vertex = UnityObjectToClipPos(v.vertex)

替换为:

o.vertex = mul(UNITY_MATRIX_P, v.vertex);

UnityObjectToClipPos 本质上是将顶点与其世界矩阵和投影矩阵相乘。 我们不关心该对象的世界矩阵,因为我们现在渲染的 UV 坐标应始终以 0 为中心。但是,我们仍然需要将 UV 坐标投影到屏幕空间,这只需使用投影矩阵 UNITY_MATRIX_P即可。

另一件需要注意的事情是,Unity 文档指出 DrawMeshNow 不包含光照信息,因此这意味着此实现仅适用于无光照着色器。 如果想要烘焙一个完全集成光照和阴影的着色器,你必须考虑使用 DrawMesh 函数。

3、将纹理保存到磁盘

此时,我们应该在渲染纹理中拥有烘焙的着色器纹理贴图。 只需要谷歌一下就能解决这个问题,因为这部分过程很常见。 这些步骤涉及将 RenderTexture 转换为 Texture2D ,然后将结果编码为 PNG。 此代码可以在 ShaderBaker.cs 中找到。

WOW! 现在我们有一个 .png 纹理代表选择的未光照着色器。 然而,像往常一样,总会出现一些根本性的问题。 如果你尝试在任何默认 Unity 材质上使用新纹理作为 _MainTex,可能会在 UV 岛边缘看到黑色伪像:

由精确 UV 接缝周围的黑色造成的伪影

这是因为纹理是使用精确的 UV 坐标烘焙的,并且没有考虑在边缘采样纹理时发生的采样方差。 为了解决这个问题,我发现其他建模软件在新烘焙的纹理上实现了“岛边界扩展”,这只是扩展边界的混合操作。

我决定通过在输出纹理上实施第二次扩张来实现“UV岛边界扩展”。

4、解决边缘伪影问题

膨胀(dilation)是一种扩展形状的形态学方法,最初是为二值(黑色或白色)图像定义的:

该算法采用结构元素(在本例中为 3x3 图块),并通过将图块置于像素中心来迭代图像的每个像素。 如果图块中有白色元素,我们会将图块转换为白色。

要将其扩展到彩色图像,我们需要一个不同的因素来转换图块。 我决定让结构元素对像素颜色与预定义背景颜色的差异进行操作。 如果结构元素中有一个像素的颜色值与背景颜色足够远,我们可以转换给定的像素。

这个实现可以在 Dilate.shader 中找到。 为了将其合并到我们现有的设置中,我们将以下几行添加到烘焙代码中:

...
Graphics.DrawMeshNow(M, Matrix4x4.identity);
Graphics.SetRenderTarget(null);
...
// create a second render target 
RenderTexture rt2 = RenderTexture.GetTemporary(width, height);
// use the dilate shader on our first render target, output to rt2
Graphics.Blit(rt, rt2, dilateMat);
// save rt2 to png
SaveTexture(rt2, objectToBake.name);
// reset 
RenderTexture.ReleaseTemporary(rt);
RenderTexture.ReleaseTemporary(rt2);
GL.PopMatrix();

膨胀前和膨胀后

这很好地扩展了纹理! 需要注意的是,这种膨胀实现仅在纹理的背景颜色与着色器的颜色不同时才有效。 我在 ShaderBaker.cs 中添加了一个属性来设置背景颜色:)

另外,接缝处仍然有非常小的伪影,我还没有弄清楚那里发生了什么。

5、结束语

由于本文中的代码相当断断续续,我强烈建议你查看 github 项目中的代码。 以下是代码的摘要:

  • UVUnwrap.shader — 此着色器与你要烘焙的着色器重复,但顶点着色器已修改为在 UV 空间中渲染顶点。
  • Dilate.shader — 此着色器负责输出纹理的膨胀后处理。
  • ShaderBaker.cs — 将此脚本附加到相机,它负责将网格渲染为纹理。 它具有公共字段,你可以在其中放置要烘焙的对象、具有要烘焙的着色器的展开版本的材质 (UVUnwrap.shader) 以及具有扩张着色器的材质 (Dilate.shader)。 你还应该将 backgroundColor 属性设置为与着色器中的颜色不同的颜色。

这应该就是全部了~~对我来说是一次有趣的探索,希望它能帮助开发者:)


原文链接:纹理烘焙原理及实现 - BimAnt

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

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

相关文章

Ajax的使用方法

1,什么是Ajax&#xff1f; Ajax&#xff08;异步Javascript和XML&#xff09;&#xff0c;是指一种创建交互式网页应用的网页开发技术。 2&#xff0c;Ajax的作用 Ajax可以使网页实现异步更新----即在不更新整个页面的情况下实现对某一部分进行更新。 简单来说Ajax就是用于连接…

【Python】yaml.safe_load()函数详解和示例

在Python中&#xff0c;PyYAML库提供了对YAML&#xff08;YAML Ain’t Markup Language&#xff09;文件的强大支持。YAML是一种直观的数据序列化标准&#xff0c;可以方便地存储和加载配置文件、数据日志等。 yaml.safe_load和yaml.load是Python的PyYAML库提供的两个函数&…

从零搭建AlibabaCloud微服务项目

1&#xff0c;创建maven项目工程如下 equipment-admin 后台equipment-applet 前台或小程序端或app、h5equipment-common 公共模块equipment-gateway 网关equipment-mapper mapper层操作数据库equipment-model 实体类对应数据库表 2&#xff0c;在父pom文件引入依赖 <proper…

基于Java SSM框架实现美食推荐管理系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现美食推荐管理系统演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&a…

【人生苦短,我学 Python】(3)Python 常用内置数据类型 I —— 数值数据类型(int、float、complex、bool)

目录 简述 / 前言1. int 类型1.1 创建int对象1.2 整数的运算 2. float 类型2.1 创建float对象 3. complex 类型3.1 创建 complex 对象3.2 complex对象属性和方法3.3 cmath 模块中复数运算 4. bool数据类型和相关运算符4.1 bool 对象 文章传送门 简述 / 前言 前面主要了解了 Py…

国内首个农业开源鸿蒙操作系统联合华为正式发布

2023年11月29日&#xff0c;在中国国际供应链促进博览会上&#xff0c;中信农业科技股份有限公司&#xff08;简称“中信农业”&#xff09;与深圳开鸿数字产业发展有限公司&#xff08;简称“深开鸿”&#xff09;以及华为技术有限公司&#xff08;简称“华为”&#xff09;联…

UniWebView 版本3 版本4 版本5介绍

一、介绍 UniWebView是iOS/Android上的web视图组件的包装器&#xff0c;所以运行时拥有与原生web相似性能。是针对Unity所写的插件&#xff0c;节省了项目的开发时间。 官网地址&#xff1a;UniWebView 二、下载&使用 1、下载 &#xff08;1&#xff09;、Unity Asset …

GAN:PacGAN-生成对抗网络中两个样本的威力

论文&#xff1a;https://arxiv.org/pdf/1712.04086.pdf 代码&#xff1a;GitHub - fjxmlzn/PacGAN: [NeurIPS 2018] [JSAIT] PacGAN: The power of two samples in generative adversarial networks 发表&#xff1a;2016 一、摘要 1&#xff1a;GAN最重大的缺陷是&#xf…

深入ArkTS:应用状态管理与LocalStorage装饰器详解【鸿蒙专栏-11】

文章目录 ArkTS 应用状态管理详解LocalStorage: 页面级 UI 状态存储使用规则概述:装饰器详解:限制条件:使用场景:1. 应用逻辑使用 LocalStorage2. 从 UI 内部使用 LocalStorageArkTS 应用状态管理进阶LocalStorage 装饰器详解1. @LocalStorageProp2. @LocalStorageLink观察…

【算法】离散化 与 哈希 之间的区别

离散化&#xff08;Discretization&#xff09;和哈希&#xff08;Hashing&#xff09;是两种不同的数据处理技术&#xff0c;用于处理不同类型的问题。 1. 离散化&#xff08;Discretization&#xff09;&#xff1a; 离散化是将一组连续的数据映射到有限个离散值的过程。主要…

自己动手写 chatgpt: Attention 机制的原理与实现

chatgpt等大模型之所以成功都有赖于一种算法突破&#xff0c;那就是 attention 机制。这种机制能让神经网络更有效的从语言中抽取识别其内含的规律&#xff0c;同时它支持多路并行运算&#xff0c;因此相比于原来的自然语言处理算法&#xff0c;它能够通过并发的方式将训练的速…

leetcode 11. 盛最多水的容器(优质解法)

代码&#xff1a; class Solution {public int maxArea(int[] height) {int nheight.length;int left0;int rightn-1;int max0;while (left<right){//计算当前 left 和 right 所在位置的面积int areaMath.min(height[left],height[right])*(right-left);//重置最大值if(are…

HTML中使用JavaScript实现一个简单的鼠标悬停特效。

代码中&#xff0c;我们首先在CSS中定义了一个红色的方块&#xff0c;然后使用:hover伪类定义了鼠标悬停时的样式。接着&#xff0c;在JavaScript中定义了一个函数showMessage()&#xff0c;用于在控制台输出一条消息。最后&#xff0c;在HTML中使用<div>标签定义了一个i…

uniapp 监测不到 ios 设备

引言 UniApp 是一款跨平台的应用开发框架,它能够让开发者使用一套代码开发出同时在 iOS 和 Android 上运行的应用程序。然而,有时候我们可能会遇到一个问题:UniApp 无法正确地检测到 iOS 设备。本文将介绍这个问题的原因,并提供一种解决方案。 问题描述 当我们使用 UniA…

力扣二叉树--第三十五天

前言 二叉搜索树&#xff0c;写了一道题&#xff0c;第二题没写出来。明天再写吧。。。 内容 一、二叉搜索树中的搜索 700. 二叉搜索树中的搜索 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。…

进程间通信基础知识【Linux】——上篇

目录 一&#xff0c;理解进程之间的通信 1. 进程间通信目的 2. 进程间通信的技术背景 3&#xff0c;常见的进程间通信 二&#xff0c;管道 1. 尝试建立一个管道 管道的特点&#xff1a; 管道提供的访问控制&#xff1a; 2. 扩展&#xff1a;进程池 阶段一&#xff1a…

sqli-labs靶场详解(less32-less37)

宽字节注入 原理在下方 目录 less-32 less-33 less-34 less-35 less-36 less-37 less-32 正常页面 ?id1 下面有提示 获取到了Hint: The Query String you input is escaped as : 1\ ?id1 看来是把参数中的非法字符就加上了转义 从而在数据库中只能把单引号当成普通的字…

asla四大开源组件应用示例(alsa-lib、alsa-utils、alsa-tools、alsa-plugins)

文章目录 alsa设备文件/dev/snd//sys/class/sound/proc/asoundalsa-lib示例1alsa-utilsalsa-toolsalsa-plugins参考alsa设备文件 /dev/snd/ alsa设备文件目录位于,/dev/snd,如下所示 root@xboard:~#ls /dev/snd -l total 0 drwxr-xr-x 2 root root 60 Nov 6 2023 …

学习python类的构造

目录 1、Object类 2、初始化方法 3、双下划线方法 4、传递参数 5、装饰器 6、assert 7、yield关键字 1、Object类 class NodeBase(object): 在Python中&#xff0c;Object是所有其他类的基类。换句话说&#xff0c;所有的类都是Object类的子类。Object类定义了所有对象…

springboot基础配置及maven运行

目录 1、spring快速开始&#xff1a; 2、通过idea工具打开导入包 3、maven打包 1、springboot快速开始&#xff1a; 环境依赖&#xff1a;jdk17 Spring | Quickstart spring初始化包下载&#xff1a; 点击generate&#xff0c;下载包 2、通过idea工具打开导入包 我之前写了…