插件编写 | Unity3D渲染到CubeMap 的切图保存工具
Unity3DCubeMapThepoly 40242 0
实名

通过了实名认证的内容创造者

发布于 2022-3-24 19:52:52

您需要 登录 才可以下载或查看,没有账号?注册

x

174551k9r66ex7z719e99h.jpg
Hello . 大家好
今天给大家带来的是U3D渲染到CubeMap的切图保存工具
我是麦田

a8e7f8990925571c6f19c9a600dbce26.png



1
需求


因为自研引擎写GLSL暂时不支持直接采样HDR,只能够支持原生的6图模式的图片合成,如果通过手动切太浪费时间了,目前本插件还没有完善到完全自动化的程度,但是实现了基本的功能。
2
实现需求


Cube Map 通常在渲染引擎中充当天空盒、反射贴图等重要角色。Cube Map的制作往往通过采集的HDR图或者在3D渲染软件中渲染生成。一张完整的Cube Map 包含前后左右上下六张图。但是在一些特定平台并不能直接使用exr等其他HDR图,例如three.js H5 平台(使用纯原生threejs引擎的是支持的),所以就需要生成通用的6张PNG(不一定是PNG)的图。


如下图,这一个是Unity烘焙面板用来烘焙环境贴图的参数面板,本着实时渲染在手机端能省就省的原则,Unity这里采取的也是一个预先烘焙好环境贴图的方式(预积分),为了让贴图分辨率高一点,我这里设置成了1024。


Unity烘焙后的probe如下图,是被排列成一个6*1的矩阵形式的。Unity烘焙后的probe如下图,是被排列成一个6*1的矩阵形式的。


如何在Unity3D引擎中快速方便的生成六张图呢?下面,我们就开始编写。

我们先搭建一个用于渲染的场景:


通过引擎烘焙出一张 EXR 图(也可以直接使用Reflection Probe 来生成),插件还没有做到一件自动,这一步其实可以通过脚本调用的。

先添加一个 Reflection Probe 并调整好位置与角度。



然后在Unity中渲染反射贴图,渲染效果如下。


在Inspector面板将贴图标记为可读,否则会无法访问。


选择插件并切分:


插件主要功能是读取Cube Map 并生成六张图。


选择菜单栏后选择一张CubeMap(标记可读取后的),弹出路径选择面板后保存



建立对应的六张图片数组并将六张图片命名,方便调用:(代码都写了注释,纯API调用,没有难点,这里不再赘述)

  1. var idx = (int)CubemapFace.PositiveX;
  2. var count = (int)CubemapFace.NegativeZ + 1;
  3. //使用一个 list 来存储cubemap的图片
  4.         List<PickupCubemapTexInfo> texs = new List<PickupCubemapTexInfo>();
  5.         Dictionary<CubemapFace, string> nameMap = new Dictionary<CubemapFace, string>();

  6. //设置存储的名称
  7.         nameMap[CubemapFace.PositiveX] = "Right";
  8.         nameMap[CubemapFace.NegativeX] = "Left";
  9.         nameMap[CubemapFace.PositiveY] = "Upwards";
  10.         nameMap[CubemapFace.NegativeY] = "Downward";
  11.         nameMap[CubemapFace.PositiveZ] = "Forward";
  12.         nameMap[CubemapFace.NegativeZ] = "Backward";
点击此处复制文本
使用字节流对每个图片进行编码与存储,注意填写图片大小,防止失真。

  1. if (string.IsNullOrEmpty(foldername) || !Directory.Exists(foldername))
  2.             {
  3. // noop
  4.             }
  5. else
  6.             {
  7. //循环读出图片数据
  8. for (; idx < count; idx++)
  9.                 {
  10. var face = (CubemapFace)idx;
  11. var ps = cubemap.GetPixels(face);
  12. // 注意这里的texture2d 的width, height对应cubemap中的face size,但类中没定义,所以这里匹配好你的cubemap来使用就好了
  13. var newTex = new Texture2D(cubemap.width, cubemap.height);
  14.                     newTex.SetPixels(ps);

  15.                     texs.Add(new PickupCubemapTexInfo { tex = newTex, name = nameMap[face] });
  16.                 }

  17.                 foldername = foldername.Replace("\", "/");
  18. var cd = Directory.GetParent(Path.GetFullPath("Assets")).FullName;
  19.                 cd = cd.Replace("\", "/");
  20.                 Debug.Log([        DISCUZ_CODE_1        ]quot;cd:{cd}");
  21.                 foldername = Path.Combine(foldername, Selection.activeObject.name);
  22. if (!Directory.Exists(foldername))
  23.                 {
  24.                     Directory.CreateDirectory(foldername);
  25.                 }

  26.                 foldername = foldername.Replace("\", "/");
  27.                 Debug.Log([        DISCUZ_CODE_1        ]quot;append assetsname folder:{foldername}");
  28. //循环并输出图片资源
  29. foreach (var item in texs)
  30.                 {

  31. var fullname = Path.Combine(foldername, item.name + ".png");
  32.                     fullname = fullname.Replace("\", "/");
  33.                     fullname = fullname.Replace(cd + "/", "");
  34.                     Debug.Log([        DISCUZ_CODE_1        ]quot;fullname:{fullname}");

  35. //注意:图片可能上下反转 ,此步是将图片反转回来
  36. var tex = VerticalFlipTexture(item.tex);
  37. var bs = tex.EncodeToPNG();

  38. var fs = File.Open(fullname, FileMode.Create, FileAccess.Write);
  39.                     fs.Write(bs, 0, bs.Length);
  40.                     fs.Close();
  41.                     fs.Dispose();
  42.                 }

  43.                 AssetDatabase.SaveAssets();//保存资源
  44.                 AssetDatabase.Refresh();//刷新资源面板
  45.             }
  46.         }
点击此处复制文本


最后可以加一个进度条,方便查看进度。

  1. //加载一个可视化的进度条
  2. var title = "Select the directory which stored CubeMap Tex.";
  3. var filepath = AssetDatabase.GetAssetPath(Selection.activeObject);
  4. var folder = Directory.GetParent(filepath);
  5. var foldername = EditorUtility.OpenFolderPanel(title, folder.ToString(), string.Empty);
点击此处复制文本
之后使用插件将六张图片输出。


这六张图片可以作为反射贴图,也可以作为天空盒。下图是天空盒以及金属反射效果,使用Unity自带的CubeMapShader即可显示。


最后贴出插件代码,在Unity本地新建一个Editor文件后放入,即可使用。



  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEditor;
  5. using System.IO;

  6. public class ReadEXRToPNG : Editor
  7. {
  8.     [MenuItem("CustomTool/Cut CubeMap", false, 2)]
  9.     public static void EXR2PNG()
  10.     {
  11.         Cubemap cubemap = null;
  12.         Debug.Log(Selection.activeObject.GetType());

  13.         //判断所选资源类型并过滤 Cubemap 资源
  14.         if (Selection.activeObject.GetType() == typeof(Cubemap))
  15.         {
  16.             cubemap = Selection.activeObject as Cubemap;
  17.         }

  18.         //为空则跳出并报警告
  19.         if (cubemap == null)
  20.         {
  21.             Debug.LogWarning([        DISCUZ_CODE_3        ]quot;Selecting one cubemap.");
  22.             return;
  23.         }

  24.         var idx = (int)CubemapFace.PositiveX;
  25.         var count = (int)CubemapFace.NegativeZ + 1;
  26.         //使用一个 list 来存储cubemap的图片
  27.         List<PickupCubemapTexInfo> texs = new List<PickupCubemapTexInfo>();
  28.         Dictionary<CubemapFace, string> nameMap = new Dictionary<CubemapFace, string>();

  29.         //设置存储的名称
  30.         nameMap[CubemapFace.PositiveX] = "Right";
  31.         nameMap[CubemapFace.NegativeX] = "Left";
  32.         nameMap[CubemapFace.PositiveY] = "Upwards";
  33.         nameMap[CubemapFace.NegativeY] = "Downward";
  34.         nameMap[CubemapFace.PositiveZ] = "Forward";
  35.         nameMap[CubemapFace.NegativeZ] = "Backward";

  36.         try
  37.         {
  38.             //加载一个可视化的进度条
  39.             var title = "Select the directory which stored CubeMap Tex.";
  40.             var filepath = AssetDatabase.GetAssetPath(Selection.activeObject);
  41.             var folder = Directory.GetParent(filepath);
  42.             var foldername = EditorUtility.OpenFolderPanel(title, folder.ToString(), string.Empty);

  43.             if (string.IsNullOrEmpty(foldername) || !Directory.Exists(foldername))
  44.             {
  45.                 // noop
  46.             }
  47.             else
  48.             {
  49.                 //循环读出图片数据
  50.                 for (; idx < count; idx++)
  51.                 {
  52.                     var face = (CubemapFace)idx;
  53.                     var ps = cubemap.GetPixels(face);
  54.                     // 注意这里的texture2d 的width, height对应cubemap中的face size,但类中没定义,所以这里匹配好你的cubemap来使用就好了
  55.                     var newTex = new Texture2D(cubemap.width, cubemap.height);
  56.                     newTex.SetPixels(ps);

  57.                     texs.Add(new PickupCubemapTexInfo { tex = newTex, name = nameMap[face] });
  58.                 }

  59.                 foldername = foldername.Replace("\", "/");
  60.                 var cd = Directory.GetParent(Path.GetFullPath("Assets")).FullName;
  61.                 cd = cd.Replace("\", "/");
  62.                 Debug.Log([        DISCUZ_CODE_3        ]quot;cd:{cd}");
  63.                 foldername = Path.Combine(foldername, Selection.activeObject.name);
  64.                 if (!Directory.Exists(foldername))
  65.                 {
  66.                     Directory.CreateDirectory(foldername);
  67.                 }

  68.                 foldername = foldername.Replace("\", "/");
  69.                 Debug.Log([        DISCUZ_CODE_3        ]quot;append assetsname folder:{foldername}");
  70.                 //循环并输出图片资源
  71.                 foreach (var item in texs)
  72.                 {

  73.                     var fullname = Path.Combine(foldername, item.name + ".png");
  74.                     fullname = fullname.Replace("\", "/");
  75.                     fullname = fullname.Replace(cd + "/", "");
  76.                     Debug.Log([        DISCUZ_CODE_3        ]quot;fullname:{fullname}");

  77.                     //注意:图片可能上下反转 ,此步是将图片反转回来
  78.                     var tex = VerticalFlipTexture(item.tex);
  79.                     var bs = tex.EncodeToPNG();

  80.                     var fs = File.Open(fullname, FileMode.Create, FileAccess.Write);
  81.                     fs.Write(bs, 0, bs.Length);
  82.                     fs.Close();
  83.                     fs.Dispose();
  84.                 }

  85.                 AssetDatabase.SaveAssets();//保存资源
  86.                 AssetDatabase.Refresh();//刷新资源面板
  87.             }
  88.         }
  89.         catch (Exception er)
  90.         {
  91.             Debug.LogError(er);
  92.         }
  93.         finally
  94.         {
  95.             foreach (var item in texs)
  96.             {
  97.                 Texture2D.DestroyImmediate(item.tex);
  98.             }

  99.             texs.Clear();
  100.         }
  101.     }

  102.     //垂直翻转
  103.     public static Texture2D VerticalFlipTexture(Texture2D texture)
  104.     {
  105.         //得到图片的宽高
  106.         int width = texture.width;
  107.         int height = texture.height;

  108.         Texture2D flipTexture = new Texture2D(width, height);
  109.         for (int i = 0; i < height; i++)
  110.         {
  111.             flipTexture.SetPixels(0, i, width, 1, texture.GetPixels(0, height - i - 1, width, 1));
  112.         }
  113.         flipTexture.Apply();
  114.         return flipTexture;
  115.     }
  116. }

  117. public class PickupCubemapTexInfo
  118. {
  119.     public Texture2D tex;
  120.     public string name;
  121. }
点击此处复制文本
- End -




评分

参与人数 3元素币 +40 活跃度 +19 展开 理由
闭户先生... + 10 + 10 资源哪里好,肯定元素找。
淼煮鱼 + 20 + 6 原创作品发元素,日积月累成大触。
angelanany... + 10 + 3 给你一个666

查看全部评分

本帖被以下画板推荐:

还没有设置签名!您可以在此展示你的链接,或者个人主页!
使用道具 <
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表