好的,我会将这个内容翻译成简体中文。

这是我在制作生存游戏的过程中遇到的一个非常具体但是又很聪明的解决方案(这就是为什么我要在这里发布它)。基本上,我有一个用Fuse制作的玩家模型,每个部分都有不同的网格模型(一个头部网格模型、一个胳膊网格模型等等),并且我想尝试复制Rust的玩家模型处理方式。在Rust中,玩家模型在仪表板UI中有一个统一的模式,而当你看向下时,你还能看到你的玩家身体。我想尝试实现这个功能。

因此,我决定使用两个玩家模型,一个模型有一个相对于它的摄像头,输出到一个渲染文本,而另一个模型则位于玩家的碰撞体边缘,其手臂和头部网格模型设置为阴影模式。但是,这引发了一些问题。

  1. 这样就不方便地使用模块化装备系统与Rust相同,因为现在你还得考虑两个玩家模型。
  2. 当你看向下时看到的玩家模型,脚部IK不会正常工作,因为现在你看下的是一个位置在玩家碰撞体边缘的玩家模型。

因此,我决定不去处理这些问题,而是寻找一个更加聪明的解决方案。因此,我想“好吧,让我们只使用一个玩家模型,不过这次我们将它设置在一个在主摄像头不渲染的层面,而另一个摄像头只渲染这个层面的玩家模型”。现在又有另一个问题。阴影。当一个摄像头在Unity中设置为不渲染一个层面时,会忽略层面上的所有阴影信息。因此,现在你只看到一个在仪表板UI中渲染的玩家模型,而玩家在实际游戏世界中完全消失了,包括阴影。

所以,我最后在网上找到了解决方案,并向Unity的Gemini询问,Gemini建议了一种相当独特和聪明的方法来解决这个问题。

基本上,我们现在是在主摄像头渲染的时候,设置玩家模型对应的renderer为阴影模式,只渲染玩家模型最重要的部分,如身体。因此,在主摄像头停止渲染或另一个摄像头开始渲染时,我们可以把所有东西恢复原位。通过这个方式,我们可以在玩家身上实现想要的效果,而不用两个不同的玩家模型。通过脚本,我们也不需要考虑不同的玩家模型,因此非常方便。

这个解决方案使用Unity的OnPreRender和OnPostRender函数实现了。OnPreRender函数在主摄像头开始渲染之前被调用,OnPostRender函数在主摄像头渲染之后被调用。

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class PlayerModelRenderer : MonoBehaviour
{
    /// <summary>
    /// 这个脚本让主摄像头渲染时,设置玩家模型对应的renderer为阴影模式。
    /// 然后,主摄像头渲染后或另一个摄像头开始渲染时,恢复renderer的原有状态。
    /// 这样一来,就能在玩家身上实现想要的效果,而不用两个不同的玩家模型。
    /// 通过脚本,我们也不需要考虑不同的玩家模型,因此非常方便。
    /// </summary>

    [Header("References")]
    public GameObject playerRoot;

    [Header("Renderer Settings")]
    public List<Renderer> renderersToExclude;

    [Header("Model Position Settings")]
    public Vector3 targetPlayerModelPos;

    private Renderer[] playerRenderers;
    private Renderer[] finalRenderers;
    private int rendererCount;

    void Start()
    {
        RefreshRenderers();
    }

    // 当有新的renderer加入到玩家身上时,调用这个方法。
    public void RefreshRenderers()
    {
        // 缓存所有的renderer,以提升性能(适用于模块化装备)
        playerRenderers = playerRoot.GetComponentsInChildren<Renderer>();

        finalRenderers = playerRenderers.Except(renderersToExclude).ToArray();
        rendererCount = finalRenderers.Length;
    }

    // 在主摄像头开始渲染时,设置玩家模型对应的renderer为阴影模式。
    void OnPreRender()
    {
        playerRoot.transform.localPosition = targetPlayerModelPos;

        for (int i = 0; i < rendererCount; i++)
        {
            if (finalRenderers[i] != null)
                finalRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly;
        }
    }

    // 在主摄像头渲染后或另一个摄像头开始渲染时,恢复renderer的原有状态。
    void OnPostRender()
    {
        playerRoot.transform.localPosition = Vector3.zero;

        for (int i = 0; i < rendererCount; i++)
        {
            if (finalRenderers[i] != null)
                finalRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
        }
    }
}