插件编写 | Unity3D渲染到CubeMap 的切图保存工具

文章 此情可待成追忆
2021-5-10 10:57 129人浏览 0人回复
Hello . 大家好
今天给大家带来的是U3D渲染到CubeMap的切图保存工具

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调用,没有难点,这里不再赘述)

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

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

使用字节流对每个图片进行编码与存储,注意填写图片大小,防止失真。

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

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

                foldername = foldername.Replace("\\", "/");
var cd = Directory.GetParent(Path.GetFullPath("Assets")).FullName;
                cd = cd.Replace("\\", "/");
                Debug.Log($"cd:{cd}");
                foldername = Path.Combine(foldername, Selection.activeObject.name);
if (!Directory.Exists(foldername))
                {
                    Directory.CreateDirectory(foldername);
                }

                foldername = foldername.Replace("\\", "/");
                Debug.Log($"append assetsname folder:{foldername}");
//循环并输出图片资源
foreach (var item in texs)
                {

var fullname = Path.Combine(foldername, item.name + ".png");
                    fullname = fullname.Replace("\\", "/");
                    fullname = fullname.Replace(cd + "/", "");
                    Debug.Log($"fullname:{fullname}");

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

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

                AssetDatabase.SaveAssets();//保存资源
                AssetDatabase.Refresh();//刷新资源面板
            }
        }

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

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

之后使用插件将六张图片输出。


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


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

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

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

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

        //为空则跳出并报警告
        if (cubemap == null)
        {
            Debug.LogWarning($"Selecting one cubemap.");
            return;
        }

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

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

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

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

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

                foldername = foldername.Replace("\\", "/");
                var cd = Directory.GetParent(Path.GetFullPath("Assets")).FullName;
                cd = cd.Replace("\\", "/");
                Debug.Log($"cd:{cd}");
                foldername = Path.Combine(foldername, Selection.activeObject.name);
                if (!Directory.Exists(foldername))
                {
                    Directory.CreateDirectory(foldername);
                }

                foldername = foldername.Replace("\\", "/");
                Debug.Log($"append assetsname folder:{foldername}");
                //循环并输出图片资源
                foreach (var item in texs)
                {

                    var fullname = Path.Combine(foldername, item.name + ".png");
                    fullname = fullname.Replace("\\", "/");
                    fullname = fullname.Replace(cd + "/", "");
                    Debug.Log($"fullname:{fullname}");

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

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

                AssetDatabase.SaveAssets();//保存资源
                AssetDatabase.Refresh();//刷新资源面板
            }
        }
        catch (Exception er)
        {
            Debug.LogError(er);
        }
        finally
        {
            foreach (var item in texs)
            {
                Texture2D.DestroyImmediate(item.tex);
            }

            texs.Clear();
        }
    }

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

        Texture2D flipTexture = new Texture2D(width, height);
        for (int i = 0; i < height; i++)
        {
            flipTexture.SetPixels(0, i, width, 1, texture.GetPixels(0, height - i - 1, width, 1));
        }
        flipTexture.Apply();
        return flipTexture;
    }
}

public class PickupCubemapTexInfo
{
    public Texture2D tex;
    public string name;
}


- End -

1

路过

雷人

握手

鲜花

鸡蛋

刚表态过的朋友 (1 人)

最新评论

相关分类
热门教程
返回顶部
客服