3D模型人物换装系统三(优化合批处理,提取合批配置可,封装)

3D模型人物换装系统三(优化合批处理,提取合批配置可,封装)

介绍

本文使用2018.4.4和2020.3.26进行的测试
这里先说一下我上一篇没有解决的问题,法线贴图不正确,如果按照我之前的文章创建其他贴图的方式去创建法线贴图,你会发现法线贴图不正确。

如果这里不太明白换装的流程可以参考我之前
3D模型人物换装系统一
3D模型人物换装系统二

法线贴图问题

之前创建贴图的方式如下所示

这里能看到我创建所有图片的时候的格式都是使用的RGBA32,但是后来我在网上查了以后才发现,其实法线贴图是不带A通道的图片,所以这里应该使用的是RGB24而不是使用的RGBA32,前后对比图我就不放了,因为我对这里的方法进行了封装,已经不是原来的方式创建和合并贴图了。

csharp 复制代码
newAlbedoMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);
newNormalMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);
newMaskMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);

规划以及封装

这里我大概讲一下我之前脚本设计的缺陷,以及我想修改成的样子,在文章最后面我会把我修改完成的脚本和资源都放过来,如果大家在后面使用脚本的过程中遇到问题可以在询问我。

缺陷

  1. 原来合并设计的是太过于死板,材质球的属性是直接提取出来的并没有动态去做,也就是说我这里只能手动修改,如果换一个四个贴图的材质还需要修改下面的代码,这样太不方便,需要封装一下这里
csharp 复制代码
    private const string COMBINE_ALBEDOMAP_TEXTURE = "_AlbedoMap";
    private const string COMBINE_NORMALMAP_TEXTURE = "_NormalMap";
    private const string COMBINE_MASKMAP_TEXTURE = "_MaskMap";
  1. 创建贴图的大小也是固定死的现在,这里也是有缺陷,没法动态的调整
csharp 复制代码
   /// <summary>
    /// Only for merge materials.
    /// </summary>
	private const int COMBINE_TEXTURE_MAX = 256;
  1. 合并这里或多或少有点缺陷,但是不算是有问题,这里传入的如果是true则就是需要合并,但是如果合并不成功的话就直接报错
csharp 复制代码
if (combine)
        {
            Shader tmpShader = Shader.Find("E3D/Actor/PBR-MaskRG-Normal");
            newMaterial = new Material(tmpShader);
            oldUV = new List<Vector2[]>();

            // merge the texture
            List<Texture2D> AlbedoTextures = new List<Texture2D>();
            List<Texture2D> NormalTextures = new List<Texture2D>();
            List<Texture2D> MaskTextures = new List<Texture2D>();

            for (int i = 0; i < materials.Count; i++)
            {
                AlbedoTextures.Add(materials[i].GetTexture(COMBINE_ALBEDOMAP_TEXTURE) as Texture2D);
                NormalTextures.Add(materials[i].GetTexture(COMBINE_NORMALMAP_TEXTURE) as Texture2D);
                MaskTextures.Add(materials[i].GetTexture(COMBINE_MASKMAP_TEXTURE) as Texture2D);
            }
            newAlbedoMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);
            newNormalMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);
            newMaskMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);

            Rect[] uvs = newAlbedoMapTex.PackTextures(AlbedoTextures.ToArray(), 0);
            //newNormalMapTex.PackTextures(NormalTextures.ToArray(), 0);
            newMaskMapTex.PackTextures(MaskTextures.ToArray(), 0);

            newMaterial.SetTexture(COMBINE_ALBEDOMAP_TEXTURE, newAlbedoMapTex);
            newMaterial.SetTexture(COMBINE_NORMALMAP_TEXTURE, newNormalMapTex);
            newMaterial.SetTexture(COMBINE_MASKMAP_TEXTURE, newMaskMapTex);

            // reset uv
            Vector2[] uva, uvb;
            for (int i = 0; i < combineInstances.Count; i++)
            {
                uva = combineInstances[i].mesh.uv;
                uvb = new Vector2[uva.Length];
                for (int k = 0; k < uva.Length; k++)
                {
                    uvb[k] = new Vector2((uva[k].x * uvs[i].width) + uvs[i].x, (uva[k].y * uvs[i].height) + uvs[i].y);
                }
                oldUV.Add(uva);
                combineInstances[i].mesh.uv = uvb;
            }
        }

		if (combine)
		{
    		r.material = newMaterial;
        	for (int i = 0; i < combineInstances.Count; i++)
        	{
            	combineInstances[i].mesh.uv = oldUV[i];
        	}
   		}
  1. Shader也是写死的,不是动态的
csharp 复制代码
 Shader tmpShader = Shader.Find("E3D/Actor/PBR-MaskRG-Normal");

修改整理

  1. 创建一个配置类,当创建模型的时候根据自己需要传入定制化配置文件,这样可以动态调整创建贴图的大小、动态调整合并贴图的数量、动态修改想要合并成的材质Shader。
csharp 复制代码
/// <summary>
/// 贴图属性
/// </summary>
public class CombineClass
{
    /// <summary>
    /// 新Shader属性名
    /// </summary>
    public string NewShaderPropertiesName = "_AlbedoMap";

    /// <summary>
    /// 原始Shader属性名
    /// </summary>
    public string OriginalPropertiesName = "_AlbedoMap";

    /// <summary>
    /// 图片格式
    /// </summary>
    public TextureFormat Format = TextureFormat.RGBA32;

    /// <summary>
    /// 
    /// </summary>
    public bool MipChain = true;

    /// <summary>
    /// 实例化
    /// </summary>
    /// <param name="propertiesName"></param>
    /// <param name="format"></param>
    /// <param name="mipChain"></param>
    public CombineClass(string newPropertiesName, string orinalPropertiesName, TextureFormat format, bool mipChain)
    {
        this.NewShaderPropertiesName = newPropertiesName;
        this.OriginalPropertiesName = orinalPropertiesName;
        this.Format = format;
        this.MipChain = mipChain;
    }
}


public class CombineConfig
{
    /// <summary>
    /// Only for merge materials.
    /// 仅适用于合并材质.
    /// </summary>
    private int COMBINE_TEXTURE_MAX = 256;

    /// <summary>
    /// Shader名称
    /// </summary>
    private string ShaderName = "";

    /// <summary>
    /// 贴图设置集合
    /// </summary>
    private List<CombineClass> combineClasses = new List<CombineClass>();

    /// <summary>
    /// 实例化
    /// </summary>
    /// <param name="COMBINE_TEXTURE_MAX"></param>
    /// <param name="combineClasses"></param>
    public CombineConfig(int COMBINE_TEXTURE_MAX, string ShaderName, List<CombineClass> combineClasses)
    {
        this.COMBINE_TEXTURE_MAX = COMBINE_TEXTURE_MAX;
        this.ShaderName = ShaderName;
        this.combineClasses = combineClasses;
    }

    /// <summary>
    /// 获取合并贴图大小
    /// </summary>
    /// <returns></returns>
    public int CombineTextureMax() { return this.COMBINE_TEXTURE_MAX; }

    /// <summary>
    /// 获取Shader名
    /// </summary>
    /// <returns></returns>
    public string GetShaderName() { return this.ShaderName; }

    /// <summary>
    /// 获取贴图设置集合数量
    /// </summary>
    /// <returns></returns>
    public int GetCombineCount() { return this.combineClasses.Count; }

    /// <summary>
    /// 获取贴图设置集合
    /// </summary>
    /// <returns></returns>
    public List<CombineClass> GetCombineList() { return combineClasses; }
}
  1. 获取需要合并的模型的所有的所有Shader,如果是只有一个Shader则你combine为true时会进行合批处理,如果Shader数量多了哪怕你combine为true则也不进行合批。
  2. 加入了一个材质回调,这样在调用的时候可以对材质球进行设置

整合

CombineClass.cs

csharp 复制代码
/// <summary>
/// 贴图属性
/// </summary>
public class CombineClass
{
    /// <summary>
    /// 新Shader属性名
    /// </summary>
    public string NewShaderPropertiesName = "_AlbedoMap";

    /// <summary>
    /// 原始Shader属性名
    /// </summary>
    public string OriginalPropertiesName = "_AlbedoMap";

    /// <summary>
    /// 图片格式
    /// </summary>
    public TextureFormat Format = TextureFormat.RGBA32;

    /// <summary>
    /// 
    /// </summary>
    public bool MipChain = true;

    /// <summary>
    /// 实例化
    /// </summary>
    /// <param name="propertiesName"></param>
    /// <param name="format"></param>
    /// <param name="mipChain"></param>
    public CombineClass(string newPropertiesName, string orinalPropertiesName, TextureFormat format, bool mipChain)
    {
        this.NewShaderPropertiesName = newPropertiesName;
        this.OriginalPropertiesName = orinalPropertiesName;
        this.Format = format;
        this.MipChain = mipChain;
    }
}

CombineConfig.cs

csharp 复制代码
public class CombineConfig
{
    /// <summary>
    /// Only for merge materials.
    /// 仅适用于合并材质.
    /// </summary>
    private int COMBINE_TEXTURE_MAX = 256;

    /// <summary>
    /// Shader名称
    /// </summary>
    private string ShaderName = "";

    /// <summary>
    /// 贴图设置集合
    /// </summary>
    private List<CombineClass> combineClasses = new List<CombineClass>();

    /// <summary>
    /// 实例化
    /// </summary>
    /// <param name="COMBINE_TEXTURE_MAX"></param>
    /// <param name="combineClasses"></param>
    public CombineConfig(int COMBINE_TEXTURE_MAX, string ShaderName, List<CombineClass> combineClasses)
    {
        this.COMBINE_TEXTURE_MAX = COMBINE_TEXTURE_MAX;
        this.ShaderName = ShaderName;
        this.combineClasses = combineClasses;
    }

    /// <summary>
    /// 获取合并贴图大小
    /// </summary>
    /// <returns></returns>
    public int CombineTextureMax() { return this.COMBINE_TEXTURE_MAX; }

    /// <summary>
    /// 获取Shader名
    /// </summary>
    /// <returns></returns>
    public string GetShaderName() { return this.ShaderName; }

    /// <summary>
    /// 获取贴图设置集合数量
    /// </summary>
    /// <returns></returns>
    public int GetCombineCount() { return this.combineClasses.Count; }

    /// <summary>
    /// 获取贴图设置集合
    /// </summary>
    /// <returns></returns>
    public List<CombineClass> GetCombineList() { return combineClasses; }
}

UCombineSkinnedMgr.cs

csharp 复制代码
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System;

public class UCombineSkinnedMgr
{
    /// <summary>
    /// Combine SkinnedMeshRenderers together and share one skeleton.
    /// 将蒙皮网格渲染器组合在一起并共享一个骨架.(优化合批报错,如果无法合批但是combine为true,则进行不分批处理)
    /// Merge materials will reduce the drawcalls, but it will increase the size of memory.
    /// 合并材质会减少drawcalls,但会增加内存大小.
    /// </summary>
    /// <param name="skeleton">combine meshes to this skeleton(a gameobject)</param>
    /// <param name="meshes">meshes need to be merged</param>
    /// <param name="combine">merge materials or not</param>
    public void CombineObject(CombineConfig config, GameObject skeleton, SkinnedMeshRenderer[] meshes, bool combine = false, Action<Material> action = null)
    {
        //Fetch all bones of the skeleton
        //获取所有的骨骼
        List<Transform> transforms = new List<Transform>();
        transforms.AddRange(skeleton.GetComponentsInChildren<Transform>(true));

        //the list of materials
        List<Shader> shaders = new List<Shader>();

        //the list of materials
        //所有材质球
        List<Material> materials = new List<Material>();

        //the list of meshes
        //所有网格
        List<CombineInstance> combineInstances = new List<CombineInstance>();

        //the list of bones
        //所有骨骼节点
        List<Transform> bones = new List<Transform>();

        #region 合批使用

        // Below informations only are used for merge materilas(bool combine = true)
        //以下信息仅用于合并材料(bool-combine=true)原始UV坐标
        List<Vector2[]> oldUV = null;
        //合并之后得新材质球
        Material newMaterial = null;
        //创建新贴图集合
        List<Texture2D> MapTex = new List<Texture2D>();
        for (int i = 0; i < config.GetCombineCount(); i++)
        {
            MapTex.Add(null);
        }
        #endregion

        // Collect information from meshes and shader
        // 获取网格和shader信息
        for (int i = 0; i < meshes.Length; i++)
        {
            SkinnedMeshRenderer smr = meshes[i];
            materials.AddRange(smr.materials);
            for (int j = 0; j < smr.materials.Length; j++)
            {
                if (!shaders.Contains(smr.materials[j].shader))
                {
                    shaders.Add(smr.materials[j].shader);
                }
            }
            // Collect meshes
            for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
            {
                CombineInstance ci = new CombineInstance();
                ci.mesh = smr.sharedMesh;
                ci.subMeshIndex = sub;
                combineInstances.Add(ci);
            }
            // Collect bones
            for (int j = 0; j < smr.bones.Length; j++)
            {
                int tBase = 0;
                for (tBase = 0; tBase < transforms.Count; tBase++)
                {
                    if (smr.bones[j].name.Equals(transforms[tBase].name))
                    {
                        bones.Add(transforms[tBase]);
                        break;
                    }
                }
            }
        }
        // merge materials
        //合并材质
        if (combine && shaders.Count == 1)
        {
            if (config.GetShaderName() == "") newMaterial = new Material(shaders[0]);
            else newMaterial = new Material(Shader.Find(config.GetShaderName()));

            oldUV = new List<Vector2[]>();

            // merge the texture
            //合并贴图
            List<List<Texture2D>> texture2Ds = new List<List<Texture2D>>();
            for (int i = 0; i < config.GetCombineCount(); i++)
            {
                texture2Ds.Add(new List<Texture2D>());
            }

            for (int i = 0; i < materials.Count; i++)
            {
                Material mat = materials[i];
                for (int j = 0; j < config.GetCombineCount(); j++)
                {
                    texture2Ds[j].Add(mat.GetTexture(config.GetCombineList()[j].OriginalPropertiesName) as Texture2D);
                }
                //int ind = 0;
                //texture2Ds.ForEach((txtLst) =>
                //{
                //    txtLst.Add(mat.GetTexture(config.GetCombineList()[ind].OriginalPropertiesName) as Texture2D);
                //    ind++;
                //});
            }

            Rect[] uvs = new Rect[config.GetCombineCount()];
            for (int i = 0; i < config.GetCombineCount(); i++)
            {
                MapTex[i] = new Texture2D(config.CombineTextureMax(), config.CombineTextureMax(), config.GetCombineList()[i].Format, config.GetCombineList()[i].MipChain);
                uvs = MapTex[i].PackTextures(texture2Ds[i].ToArray(), 0);
                newMaterial.SetTexture(config.GetCombineList()[i].NewShaderPropertiesName, MapTex[i]);
            }

            action?.Invoke(newMaterial);

            #region 导出图片

            //WriteIntoPic(TextureToTexture2D(newMaterial.GetTexture(COMBINE_ALBEDOMAP_TEXTURE)), "albedo");
            //WriteIntoPic(TextureToTexture2D(newMaterial.GetTexture(COMBINE_NORMALMAP_TEXTURE)), "normal");
            //WriteIntoPic(TextureToTexture2D(newMaterial.GetTexture(COMBINE_MASKMAP_TEXTURE)), "mask");

            #endregion

            // reset uv
            Vector2[] uva, uvb;
            for (int i = 0; i < combineInstances.Count; i++)
            {
                uva = combineInstances[i].mesh.uv;
                uvb = new Vector2[uva.Length];
                for (int k = 0; k < uva.Length; k++)
                {
                    uvb[k] = new Vector2((uva[k].x * uvs[i].width) + uvs[i].x, (uva[k].y * uvs[i].height) + uvs[i].y);
                }
                oldUV.Add(uva);
                combineInstances[i].mesh.uv = uvb;
            }
        }

        // Create a new SkinnedMeshRenderer
        SkinnedMeshRenderer oldSKinned = skeleton.GetComponent<SkinnedMeshRenderer>();
        if (oldSKinned != null)
        {
            GameObject.DestroyImmediate(oldSKinned);
        }
        SkinnedMeshRenderer r = skeleton.AddComponent<SkinnedMeshRenderer>();
        r.sharedMesh = new Mesh();
        r.sharedMesh.CombineMeshes(combineInstances.ToArray(), combine && shaders.Count == 1, false);// Combine meshes
        r.bones = bones.ToArray();// Use new bones
        if (combine && shaders.Count == 1)
        {
            r.material = newMaterial;
            for (int i = 0; i < combineInstances.Count; i++)
            {
                combineInstances[i].mesh.uv = oldUV[i];
            }
        }
        else
        {
            r.materials = materials.ToArray();
        }
    }

    #region 导出图片

    private Texture2D TextureToTexture2D(Texture texture)
    {
        Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
        RenderTexture currentRT = RenderTexture.active;
        RenderTexture renderTexture = RenderTexture.GetTemporary(texture.width, texture.height, 32);
        Graphics.Blit(texture, renderTexture);

        RenderTexture.active = renderTexture;
        texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
        texture2D.Apply();

        RenderTexture.active = currentRT;
        RenderTexture.ReleaseTemporary(renderTexture);

        return texture2D;
    }

    public void WriteIntoPic(Texture2D tex, string name)
    {
        //编码纹理为PNG格式 
        var bytes = tex.EncodeToPNG();
        File.WriteAllBytes(Application.dataPath + "/" + name + ".png", bytes);
    }

    #endregion
}

UCharacterController.cs

csharp 复制代码
using UnityEngine;

public class UCharacterController
{
    /// <summary>
    /// GameObject reference
    /// </summary>
	public GameObject Instance = null;

    /// <summary>
    /// 换装总组装数量
    /// </summary>
    public int m_MeshCount = 7;

    public string Role_Skeleton;
    public string Role_Body;
    public string Role_Clothes;
    public string Role_Hair;
    public string Role_Head;
    public string Role_Pants;
    public string Role_Shoes;
    public string Role_Socks;

    /// <summary>
    /// 创建对象
    /// </summary>
    /// <param name="job"></param>
    /// <param name="skeleton"></param>
    /// <param name="body"></param>
    /// <param name="cloak"></param>
    /// <param name="face"></param>
    /// <param name="hair"></param>
    /// <param name="hand"></param>
    /// <param name="leg"></param>
    /// <param name="mainweapon"></param>
    /// <param name="retina"></param>
    /// <param name="subweapon"></param>
    /// <param name="combine"></param>
    public UCharacterController(CombineConfig config, string job, string skeleton, string body, string clothes, string hair, string head, string pants, string shoes, string socks, bool combine = false, System.Action<Material> action = null)
    {
        Object res = Resources.Load("RoleMesh/" + job + "/" + job + "/" + skeleton);
        this.Instance = GameObject.Instantiate(res) as GameObject;
        this.Role_Skeleton = skeleton;
        this.Role_Body = body;
        this.Role_Clothes = clothes;
        this.Role_Hair = hair;
        this.Role_Head = head;
        this.Role_Pants = pants;
        this.Role_Shoes = shoes;
        this.Role_Socks = socks;

        string[] equipments = new string[m_MeshCount];
        equipments[0] = "Body/" + Role_Body;
        equipments[1] = "Clothes/" + Role_Clothes;
        equipments[2] = "Hair/" + Role_Hair;
        equipments[3] = "Head/" + Role_Head;
        equipments[4] = "Pants/" + Role_Pants;
        equipments[5] = "Shoes/" + Role_Shoes;
        equipments[6] = "Socks/" + Role_Socks;

        SkinnedMeshRenderer[] meshes = new SkinnedMeshRenderer[m_MeshCount];
        GameObject[] objects = new GameObject[m_MeshCount];
        for (int i = 0; i < equipments.Length; i++)
        {
            res = Resources.Load("RoleMesh/" + job + "/" + equipments[i]);
            objects[i] = GameObject.Instantiate(res) as GameObject;
            meshes[i] = objects[i].GetComponentInChildren<SkinnedMeshRenderer>();
        }

        UCharacterManager.Instance.CombineSkinnedMgr.CombineObject(config, Instance, meshes, combine, action);

        for (int i = 0; i < objects.Length; i++)
        {
            GameObject.DestroyImmediate(objects[i].gameObject);
        }
    }

    public void Delete()
    {
        GameObject.Destroy(Instance);
    }
}

测试代码
UCharacterManager.cs

csharp 复制代码
using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// 换装管理器
/// </summary>
public class UCharacterManager : MonoBehaviour
{
    public static UCharacterManager Instance;

    private UCombineSkinnedMgr skinnedMgr = null;
    public UCombineSkinnedMgr CombineSkinnedMgr { get { return skinnedMgr; } }

    private int characterIndex = 0;
    private Dictionary<int, UCharacterController> characterDic = new Dictionary<int, UCharacterController>();
    public UCharacterManager()
    {

        skinnedMgr = new UCombineSkinnedMgr();
    }

    private void Awake()
    {
        Instance = this;
    }

    public UCharacterController mine;

    private void Start()
    {
        //mine = Generatecharacter(new CombineConfig(256, "", new List<CombineClass> {
        //    new CombineClass("_AlbedoMap","_AlbedoMap",TextureFormat.RGBA32,true),
        //    new CombineClass("_NormalMap","_NormalMap",TextureFormat.RGB24,true),
        //    new CombineClass("_MaskMap","_MaskMap", TextureFormat.RGBA32,true),
        //}), "MaTa", "MaTa", "Body1", "Clothes1", "Hair1", "Head1", "Pants1", "Shoes1", "Socks1", true, (mat) =>
        //{
        //    mat.SetFloat("_SideLightScale", 0);
        //});
        mine = Generatecharacter(new CombineConfig(256, "Standard", new List<CombineClass> {
            new CombineClass("_MainTex","_AlbedoMap",TextureFormat.RGBA32,true),
            new CombineClass("_BumpMap","_NormalMap",TextureFormat.RGB24,true),
            new CombineClass("_DetailMask","_MaskMap", TextureFormat.RGBA32,true),
        }), "MaTa", "MaTa", "Body1", "Clothes1", "Hair1", "Head1", "Pants1", "Shoes1", "Socks1", true, (mat) =>
        {
            mat.SetFloat("_SideLightScale", 0);
        });
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ChangeRole();
        }
    }

    public void ChangeRole()
    {
        //if (mine != null)
        //{
        //    mine.Delete();
        //}

        //int a = Random.Range(1, 4);

        //mine = Generatecharacter("MaTa", "MaTa", "Body" + a, "Clothes" + a, "Hair" + a, "Head" + a, "Pants" + a, "Shoes" + a, "Socks" + a, true);
    }

    #region 创建人物模型骨骼

    public UCharacterController Generatecharacter(CombineConfig config, string job, string skeleton, string body, string clothes, string hair, string hand, string pants, string shoes, string socks, bool combine = false, System.Action<Material> action = null)
    {

        UCharacterController instance = new UCharacterController(config, job, skeleton, body, clothes, hair, hand, pants, shoes, socks, combine, action);
        characterDic.Add(characterIndex, instance);
        characterIndex++;

        return instance;
    }

    #endregion
}

完整资源

总结

人物换装的优化这篇文章后面应该不会在出了,预计得优化大概就是这样,如果有别的好点子可以私信我交流一下。希望这篇文章能给大家带来帮助,感谢大家的支持和关注,感谢点赞。

相关推荐
ellis19709 小时前
Unity插件SafeArea Helper适配异形屏详解
unity
nnsix10 小时前
Unity Physics.Raycast的 QueryTriggerInteraction枚举作用
unity·游戏引擎
地狱为王10 小时前
Cesium for Unity叠加行政区划线
unity·gis·cesium
小贺儿开发19 小时前
Unity3D 八大菜系连连看
游戏·unity·互动·传统文化
在路上看风景19 小时前
25. 屏幕像素和纹理像素不匹配
unity
ۓ明哲ڪ21 小时前
Unity功能——创建新脚本时自动添加自定义头注释
unity·游戏引擎
熬夜敲代码的小N21 小时前
Unity大场景卡顿“急救包”:从诊断到落地的全栈优化方案
java·unity·游戏引擎
派葛穆1 天前
Unity-realvirtual-S7通讯快速配置(未完结)
unity·游戏引擎
w-白兰地1 天前
【Addressable远端加载资源】
unity·addressable·资源加载
小张不爱写代码2 天前
[Unity 技巧] 如何自定义 Inspector 变量显示名称 (CustomLabel)
unity·游戏引擎