Unity人物隐身(半透明)处理

作者: 玄猫大人 | 来源:发表于2017-04-26 16:15 被阅读2569次

本文章只使用到简单的固定功能shader关闭光照后的效果。

在日常游戏开发中,一个3D模型需要隐身(半透明),常规步骤需要处理一下几个问题:

  • 透明Shader处理
  • 模型多部件网格合并
  • 模型多材质合并

1 透明Shader的处理

需要同时处理图片的透明和剔除,shader中一个pass是处理不了的。常规做法是进行两次渲染,一次渲染半透明效果,一次进行透明剔除。

首先,利用AlphaTest进行剔除处理,需要开启ZWrite选项,渲染一遍。

Pass   
{ 
    AlphaTest Greater [_CutOff]
    ZWrite On
    ColorMask 0
        SetTexture [_MainTex] 
        {
            ConstantColor [_Color]
            Combine Texture * constant
        }
}  

其次,半透明渲染的时候需要关闭ZWrite选项

Pass
{
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha
    ZTest LEqual
    SetTexture[_MainTex]
    {
        ConstantColor[_Color]
        Combine Texture * constant
    }
}

最终效果,飘带为半透明,裙边为剔除效果

模型有些糙,大家凑合看

2 模型多部件的网格合并

在一个3D游戏中,模型的换装是很常见的功能,没有换装也会有简单的武器,饰品类部件的绑定。这种情况下实现半透明隐身效果,就会出现模型间相互穿插的问题。

模型中支持换发功能,身体和头发属于两个部件,透明后相互穿插,效果十分不好

出现这种情况是因为两个部件之间的半透明后,并不知道彼此的深度关系(半透明效果在关闭ZWrite模式下渲染),只有将其合并到同一个Mesh(网格)中才能实现比较完美的透明效果。

unity官方mesh合并文档

当然只靠官方文档并没有什么卵用,unity官方文档的一贯风格,你们懂得~~我们还是要自己写代码,或者也可以使用像Mesh Baker这样的现成工具实现,对于Mesh Baker的使用这里就不累述了,有很详细的文档和例子。网格合并的同时还进行了材质合并,代码在下一部分以前给出。

3 模型多材质合并

多个部件一般都是在不同的材质中,这样在渲染一个3D模型的时候就需要同时处理多个材质球,打开Unity我们就会发现每使用一个材质球就会产生一个drawcall。合并多材质也是unity性能优化的一种方式。


未材质合并下的batches为4 材质合并后的batches为2

在模型的最外层,我挂载了一个Model3D.cs的脚本,用于处理模型和材质的合并,材质合并还需要对UV处理,代码中也已经包含。

void Combine()
    {
        List<CombineInstance> combineInstances = new List<CombineInstance>();
        List<Material> materials = new List<Material>();
        List<Transform> bones = new List<Transform>();
        Transform[] transforms = GetComponentsInChildren<Transform>();
        List<Texture2D> textures = new List<Texture2D>();
        int width = 0;
        int height = 0;

        int uvCount = 0;

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

        //蒙皮模型
        foreach (SkinnedMeshRenderer smr in GetComponentsInChildren<SkinnedMeshRenderer>())
        {
            if (_material == null)
                _material = Instantiate(smr.sharedMaterial) as Material;

            for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
            {
                CombineInstance ci = new CombineInstance();
                ci.mesh = smr.sharedMesh;
                ci.subMeshIndex = sub;
                ci.transform = smr.transform.localToWorldMatrix;
                combineInstances.Add(ci);
            }

            uvList.Add(smr.sharedMesh.uv);
            uvCount += smr.sharedMesh.uv.Length;

            if (smr.material.mainTexture != null)
            {
                //保存材质
                materials.AddRange(smr.GetComponent<Renderer>().materials);

                //保存贴图
                foreach (var mat in materials)
                {
                    textures.Add(mat.mainTexture as Texture2D);
                }
            }

            //保存骨骼信息
            foreach (Transform bone in smr.bones)
            {
                bones.Add(bone);
            }

            Destroy(smr.gameObject);
        }

        SkinnedMeshRenderer r = GetComponent<SkinnedMeshRenderer>();
        if (!r)
            r = gameObject.AddComponent<SkinnedMeshRenderer>();

        r.sharedMesh = new Mesh();

        //合并子网格
        r.sharedMesh.CombineMeshes(combineInstances.ToArray(),  true, false);
        r.bones = bones.ToArray();
        r.material = _material;

        Texture2D skinnedMeshAtlas = new Texture2D(width, height);
        Rect[] packingResult = skinnedMeshAtlas.PackTextures(textures.ToArray(), 0);
        Vector2[] atlasUVs = new Vector2[uvCount];


        //合并材质,处理uv
        int j = 0;
        for (int i = 0; i < uvList.Count; i++)
        {
            foreach (Vector2 uv in uvList[i])
            {
                atlasUVs[j].x = Mathf.Lerp(packingResult[i].xMin, packingResult[i].xMax, uv.x);
                atlasUVs[j].y = Mathf.Lerp(packingResult[i].yMin, packingResult[i].yMax, uv.y);
                j++;
            }
        }

        r.material.mainTexture = skinnedMeshAtlas;
        r.sharedMesh.uv = atlasUVs;
    }

上述代码中还存在一个问题,就是只合并了SkinnedMeshRenderer类型的网格,在unity中,带动作的模型FBX档案导入到项目中的时候,unity会默认导入为SkinnedMeshRenderer类型。但是如果当前的FBX不带动作(很多的武器是不需要动作,直接依靠绑点动作的),unity会默认导入为MeshRenderer类型,这时候这段代码就无法将该模型的网格进行合并。


SkinnedMeshRenderer(带动作包含骨骼信息)
MeshRenderer(不带动作不包含骨骼信息)
这种情况有两种解决办法:

1、浪费一根骨骼的资源,在所有不含动作的部件中加入一根骨骼,这样导入到unity中,就会默认统一导入为SkinnedMeshRenderer类型,也就不存在不同类型网格合并问题。
2、实际上SkinnedMeshRenderer和Mesh类型是可以进行合并的,如Mesh Baker中就可以实现,具体的方法我没有具体研究,有兴趣的朋友可以自己看看。


合并后的运行效果,没有穿帮现象

注意

1、使用该shader渲染的时候,如果是非透明情况,需要将_cutoff的数值调整为接近1,且小于1的数值,如0.95,这样的显示效果才正确。
2、修改透明度调低颜色的Alpha值时需要同步调低_cutoff,Alpha值略大于_cutoff值即可,否则会出现模型层级不对问题(渲染先后顺序)。

相关文章

  • Unity人物隐身(半透明)处理

    本文章只使用到简单的固定功能shader关闭光照后的效果。 在日常游戏开发中,一个3D模型需要隐身(半透明),常规...

  • 开启Allow unsafe code的影响

    1)开启Allow unsafe code的影响​2)Unity线性空间下,PhotoShop制作半透明图片,制作...

  • unity shader 半透明蒙版

  • 18072509 动画系统

    0725视频 9 动画系统 人物移动控制 - Unity动画系统 来源 -建模中制作 unity内制作 实例 移动...

  • Unity关于欧拉角判断负数的问题

    处理方式一: Unity中,比如欧拉角为-5度的时候,Unity的处理结果为355度,如果此时需要判断是否为负数的...

  • unity shader 透明设置 alpha 和 cutoff

    设置3D模型透明 alpha 目录 Unity默认使用的shader不能设置3D对象的透明和和半透明,我们只需要做...

  • 8章 透明效果

    Unity中两种方法实现透明效果: 1.透明度测试(Alpha Test),无法得到真正半透明效果,另外一种是透明...

  • unity的人物前后左右控制

    Unity survival shooter tutorial教程中控制人物移动的代码如下: 但是发现当人物掉头之...

  • URP屏幕后处理

    URP屏幕后处理 由于Unity对URP的屏幕后处理需集成要到2019.4版本后才稳定,所以升级Unity版本到2...

  • 静态批处理

    这是我在《Unity游戏优化 (第2版)》看的,记录一下~ Unity也提供了第二种批处理机制:静态批处理 其最大...

网友评论

  • 905b814ce12e:谢谢分享,很有用,对初学shader的人很有帮助
  • Alphazhu:还有这种操作~:smile:

本文标题:Unity人物隐身(半透明)处理

本文链接:https://www.haomeiwen.com/subject/aebtzttx.html