MyPal3(7)

Renderer

StaticMeshRenderer

cs 复制代码
public sealed class StaticMeshRenderer : MonoBehaviour, IDisposable
{
    private MeshRenderer _meshRenderer;
    private MeshFilter _meshFilter;
    private Material[] _materials;
    private bool _receiveShadows = true;
    
    public bool ReceiveShadows
    {
        get => _receiveShadows;
        set
        {
            _receiveShadows = value;
            if (_meshRenderer != null)
            {
                _meshRenderer.receiveShadows = value;
            }
        }
    }
}

MeshFilter IndexBuffer和VertexBuffer的持有者,即持有对mesh对象的引用

sharedMesh:指向原始网格资源的指针。多个物体可以共享同一个 sharedMesh 以节省内存。这里我们在初始化的时候就是对MeshFilter的sharedMesh赋值,这是安全的。但是如果过在运行时对其进行修改那么就会对其他的网格造成影响,应该使用下面的mesh。

mesh :访问该属性会触发一次 Deep Copy(深拷贝),为你创建一个该网格的独立副本。在复刻版中,由于我们是动态生成网格,通常直接操作 sharedMesh。

MeshRenderer 相当于是渲染器

Materials (材质):引用了 Shader 和贴图,定义了物体表面的光学特性(反射、透明度等)。

Lighting (光照控制):决定物体是否接收/投射阴影(如你之前问的 receiveShadows)。

Culling(剔除):利用 bounds(包围盒)判断物体是否需要被渲染。

_meshRenderer.receiveShadows,如果为true则会读取深度缓冲区自动为网格生成阴影。


cs 复制代码
public Mesh Render(Vector3[] vertices,
    int[] triangles,
    Vector3[] normals,
    (int channel, Vector2[] uvs) mainTextureUvs,
    (int channel, Vector2[] uvs) secondaryTextureUvs,
    Material[] materials,
    bool isDynamic)
{
    Dispose();

    _materials = materials;
    _meshRenderer = gameObject.AddComponent<MeshRenderer>();
    _meshRenderer.sharedMaterials = materials;
    _meshRenderer.receiveShadows = _receiveShadows;

    _meshFilter = gameObject.AddComponent<MeshFilter>();

    Mesh mesh = new();

    if (isDynamic)
    {
        mesh.MarkDynamic();
    }

    mesh.SetVertices(vertices);

    if (triangles != null)
    {
        // 这里只是单材质网格
        mesh.SetTriangles(triangles, 0);
    }

    if (normals != null)
    {
        mesh.SetNormals(normals);
    }

    if (mainTextureUvs != default)
    {
        mesh.SetUVs(mainTextureUvs.channel, mainTextureUvs.uvs);
    }

    if (secondaryTextureUvs != default)
    {
        mesh.SetUVs(secondaryTextureUvs.channel, secondaryTextureUvs.uvs);
    }

    if (triangles == null)
    {
        mesh.RecalculateBounds();
    }
    
    _meshFilter.sharedMesh = mesh;

    return mesh;
}

mesh.SetTriangles(triangles, 0); 0指的是第一个子网格索引,对应materials[0]

如果是多材质:

cs 复制代码
// 假设有一个角色模型,包含身体和武器两部分

Mesh mesh = new Mesh();

// 设置顶点(所有部件共享)
mesh.SetVertices(allVertices);

// 身体部分 - 子网格 0
mesh.SetTriangles(bodyTriangles, 0);

// 武器部分 - 子网格 1
mesh.SetTriangles(weaponTriangles, 1);

// 设置对应的材质
meshRenderer.sharedMaterials = new Material[] 
{ 
    bodyMaterial,    // 对应 submesh 0
    weaponMaterial   // 对应 submesh 1
};

// 必须设置子网格数量
mesh.subMeshCount = 2;

cs 复制代码
public Bounds GetRendererBounds() => _meshRenderer.bounds;

public Bounds GetMeshBounds() => _meshFilter.sharedMesh.bounds;

PolyModelRenderer

https://blog.csdn.net/z2251226240z/article/details/158284953?spm=1001.2014.3001.5501

之前做的笔记还挺详细的,够用了。


Mv3ModelRenderer

初始化子网格

cs 复制代码
private void InitSubMeshes(int index, ref Mv3Mesh mv3Mesh, ref GameBoxMaterial material)
{
    // 1. 加载纹理
    _textures[index] = _textureProvider.GetTexture(textureName, out bool hasAlphaChannel);
    
    // 2. 创建材质
    _materials[index] = _materialManager.CreateStandardMaterials(...);
    
    // 3. 分配缓冲区 (复用内存)
    _renderMeshComponents[index].MeshDataBuffer.AllocateOrResizeBuffers(...);
    
    // 4. 填充初始顶点数据
    mv3Mesh.KeyFrames[0].GameBoxVertices.ToUnityPositions(...);
    mv3Mesh.GameBoxTriangles.ToUnityTriangles(...);
    
    // 5. 创建静态网格渲染器
    StaticMeshRenderer meshRenderer = _meshEntities[index].AddComponent<StaticMeshRenderer>();
    Mesh renderMesh = meshRenderer.Render(vertices, triangles, normals, uvs, materials, isDynamic: true);
}

动画更新

cs 复制代码
private IEnumerator PlayOneTimeAnimationInternalAsync(uint startTick, uint endTick, CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        // 1. 计算当前 tick
        uint tick = elapsedSeconds.SecondsToGameBoxTick() + startTick;
        
        // 2. 检查动画事件 (如 "hold")
        CheckForAnimationEvents(tick);
        
        // 3. 如果动画结束,退出
        if (tick >= endTick) yield break;
        
        // 4. 不可见时跳过更新 (优化)
        if (!IsVisibleToCamera()) continue;
        
        // 5. 更新每个子网格的顶点
        for (int i = 0; i < _meshCount; i++)
        {
            // 使用二分查找定位当前帧
            int currentFrameIndex = CoreUtility.GetFloorIndex(frameTicks, tick);
            int nextFrameIndex = currentFrameIndex + 1;
            
            // 计算插值权重
            float influence = (tick - currentFrameTick) / (nextFrameTick - currentFrameTick);
            
            // 顶点位置线性插值 (Lerp)
            for (int j = 0; j < vertices.Length; j++)
            {
                vertices[j] = Vector3.Lerp(
                    keyFrames[currentFrameIndex].Vertices[j],
                    keyFrames[nextFrameIndex].Vertices[j],
                    influence);
            }
            
            // 更新 Mesh
            mesh.SetVertices(vertices);
            mesh.RecalculateBounds();
        }
        
        // 6. 更新挂载点 (武器) 位置和旋转
        if (_tagNodePolFile != null)
        {
            // 对位置和旋转分别使用 Lerp 和 Slerp
            position = Vector3.Lerp(currentPos, nextPos, influence);
            rotation = Quaternion.Slerp(currentRot, nextRot, influence);
            _tagNodes[i].Transform.SetLocalPositionAndRotation(position, rotation);
        }
        
        yield return null;
    }
}

值得一提的是这里由两个EventHandler

第一个是每次动画播放完成都会进行触发,会调用所有在该EventHandler注册的事件/函数

第二个会在动画播放到特定的时间点触发


CvdModelRenderer

cs 复制代码
public void Init(CvdFile cvdFile, ITextureResourceProvider textureProvider, IMaterialManager materialManager, ...)
{
    // 1. 保存动画时长
    _animationDuration = cvdFile.AnimationDuration;
    
    // 2. 递归构建纹理缓存
    foreach (CvdGeometryNode node in cvdFile.RootNodes)
    {
        BuildTextureCache(node, textureProvider, _textureCache);
    }
    
    // 3. 创建根节点容器
    IGameEntity root = GameEntityFactory.Create("Cvd Mesh", GameEntity, false);
    
    // 4. 递归渲染所有节点
    for (int i = 0; i < cvdFile.RootNodes.Length; i++)
    {
        RenderMeshInternal(initTime, hashKey, cvdFile.RootNodes[i], _textureCache, root);
    }
}

递归渲染节点

cs 复制代码
private void RenderMeshInternal(float initTime, string meshName, CvdGeometryNode node, ...)
{
    // 创建节点 GameObject
    IGameEntity meshEntity = GameEntityFactory.Create(meshName, parent, false);

    if (node.IsGeometryNode)
    {
        // 获取当前帧索引
        int frameIndex = GetFrameIndex(node.Mesh.AnimationTimeKeys, initTime);
        
        // 计算节点变换矩阵
        Matrix4x4 frameMatrix = GetFrameMatrix(initTime, node);
        
        // 计算插值权重
        float influence = (initTime - currentKeyTime) / (nextKeyTime - currentKeyTime);
        
        // 渲染每个网格分区
        foreach (CvdMeshSection meshSection in node.Mesh.MeshSections)
        {
            // 创建顶点缓冲区
            MeshDataBuffer meshDataBuffer = new(...);
            
            // 填充三角形索引 (所有帧共享)
            meshSection.GameBoxTriangles.ToUnityTriangles(meshDataBuffer.TriangleBuffer);
            
            // 更新顶点数据 (应用变换矩阵)
            UpdateMeshDataBuffer(ref meshDataBuffer, meshSection, frameIndex, influence, frameMatrix);
            
            // 创建渲染器
            StaticMeshRenderer meshRenderer = meshSectionEntity.AddComponent<StaticMeshRenderer>();
            meshRenderer.Render(vertices, triangles, normals, uvs, materials, isDynamic: true);
        }
    }
    
    // 递归处理子节点
    foreach (CvdGeometryNode childNode in node.Children)
    {
        RenderMeshInternal(initTime, childMeshName, childNode, textureCache, meshEntity);
    }
}

计算变换矩阵

cs 复制代码
private Matrix4x4 GetFrameMatrix(float time, CvdGeometryNode node)
{
    // 获取当前位置、旋转、缩放 (通过关键帧插值)
    Vector3 position = GetPosition(time, node.PositionKeyInfos);
    Quaternion rotation = GetRotation(time, node.RotationKeyInfos);
    (Vector3 scale, Quaternion scaleRotation) = GetScale(time, node.ScaleKeyInfos);
    
    // 计算复合变换矩阵 (TRS + 缩放旋转)
    Quaternion scalePreRotation = scaleRotation;
    Quaternion scaleInverseRotation = Quaternion.Inverse(scalePreRotation);
    
    return Matrix4x4.Translate(position)                    // T: 平移
           * Matrix4x4.Scale(new Vector3(node.Scale, ...))   // S: 节点缩放
           * Matrix4x4.Rotate(rotation)                       // R: 旋转
           * Matrix4x4.Rotate(scalePreRotation)              // 缩放预旋转
           * Matrix4x4.Scale(scale)                          // 非均匀缩放
           * Matrix4x4.Rotate(scaleInverseRotation);         // 缩放逆旋转
}

更新网格

cs 复制代码
private void UpdateMesh(float time)
{
    foreach ((CvdGeometryNode node, Dictionary<int, RenderMeshComponent> components) in _renderers)
    {
        // 计算当前帧
        int frameIndex = GetFrameIndex(node.Mesh.AnimationTimeKeys, time);
        
        // 计算变换矩阵
        Matrix4x4 frameMatrix = GetFrameMatrix(time, node);
        
        // 计算插值权重
        float influence = (time - currentKeyTime) / (nextKeyTime - currentKeyTime);
        
        // 更新每个网格分区
        foreach (var meshSection in node.Mesh.MeshSections)
        {
            // 更新顶点缓冲区
            UpdateMeshDataBuffer(ref meshDataBuffer, meshSection, frameIndex, influence, frameMatrix);
            
            // 应用到 Mesh
            mesh.SetVertices(meshDataBuffer.VertexBuffer);
            mesh.SetUVs(0, meshDataBuffer.UvBuffer);
            mesh.RecalculateBounds();
        }
    }
}

顶点数据更新

cs 复制代码
private void UpdateMeshDataBuffer(ref MeshDataBuffer meshDataBuffer, CvdMeshSection meshSection, 
    int frameIndex, float influence, Matrix4x4 matrix)
{
    CvdVertex[] frameVertices = meshSection.FrameVertices[frameIndex];
    
    if (influence < float.Epsilon)  // 无需插值
    {
        for (int i = 0; i < frameVertices.Length; i++)
        {
            // 直接应用变换矩阵
            meshDataBuffer.VertexBuffer[i] = matrix.MultiplyPoint3x4(
                frameVertices[i].GameBoxPosition.CvdPositionToUnityPosition());
            meshDataBuffer.UvBuffer[i] = frameVertices[i].Uv.ToUnityVector2();
        }
    }
    else  // 需要插值
    {
        CvdVertex[] toFrameVertices = meshSection.FrameVertices[frameIndex + 1];
        
        for (int i = 0; i < frameVertices.Length; i++)
        {
            // 顶点位置插值
            Vector3 lerpPosition = Vector3.Lerp(
                frameVertices[i].GameBoxPosition.CvdPositionToUnityPosition(),
                toFrameVertices[i].GameBoxPosition.CvdPositionToUnityPosition(), 
                influence);
            
            // 应用变换矩阵
            meshDataBuffer.VertexBuffer[i] = matrix.MultiplyPoint3x4(lerpPosition);
            
            // UV 插值
            meshDataBuffer.UvBuffer[i] = Vector2.Lerp(
                frameVertices[i].Uv.ToUnityVector2(),
                toFrameVertices[i].Uv.ToUnityVector2(), 
                influence);
        }
    }
}

动画播放

cs 复制代码
// 循环播放
public void LoopAnimation(float timeScale = 1f)
{
    StartCoroutine(PlayAnimationAsync(timeScale, loopCount: -1, durationPercentage: 1f, startFromBeginning: true));
}

// 单次播放
public void StartOneTimeAnimation(bool startFromBeginning, float timeScale = 1f, Action onFinished = null)
{
    StartCoroutine(PlayAnimationAsync(timeScale, loopCount: 1, durationPercentage: 1f, startFromBeginning, onFinished));
}

// 核心播放协程
private IEnumerator PlayOneTimeAnimationInternalAsync(float timeScale, float duration, 
    bool startFromBeginning, CancellationToken cancellationToken)
{
    double startTime = GameTimeProvider.Instance.TimeSinceStartup - (startFromBeginning ? 0 : _currentAnimationTime);
    
    while (!cancellationToken.IsCancellationRequested)
    {
        // 计算经过时间
        float elapsed = (float)(GameTimeProvider.Instance.TimeSinceStartup - startTime);
        
        // 根据时间缩放调整 (支持反向播放)
        float adjustedTime = timeScale > 0 
            ? elapsed * timeScale 
            : (duration - elapsed) * -timeScale;
        
        // 检查是否完成
        bool isComplete = timeScale > 0 ? adjustedTime >= duration : adjustedTime <= 0;
        if (isComplete) yield break;
        
        // 更新网格
        UpdateMesh(adjustedTime);
        
        yield return null;
    }
}

关键帧插值(二分查找)

cs 复制代码
// 位置插值
private Vector3 GetPosition(float time, CvdAnimationPositionKeyFrame[] nodePositionInfo)
{
    if (nodePositionInfo.Length == 1) 
        return nodePositionInfo[0].GameBoxPosition.CvdPositionToUnityPosition();
    
    // 二分查找定位两帧
    int startIndex = 0, endIndex = nodePositionInfo.Length - 1;
    while (endIndex - startIndex > 1)
    {
        int midIndex = (startIndex + endIndex) / 2;
        if (nodePositionInfo[midIndex].Time > time)
            endIndex = midIndex;
        else
            startIndex = midIndex;
    }
    
    // 线性插值
    float influence = (time - fromKeyFrame.Time) / (toKeyFrame.Time - fromKeyFrame.Time);
    return Vector3.Lerp(fromKeyFrame.Position, toKeyFrame.Position, influence);
}

// 旋转插值
private Quaternion GetRotation(float time, CvdAnimationRotationKeyFrame[] nodeRotationInfo)
{
    // ... 同样的二分查找 ...
    
    // 球面线性插值
    return Quaternion.Slerp(fromKeyFrame.Rotation, toKeyFrame.Rotation, influence);
}

相关推荐
被AI抢饭碗的人1 小时前
高并发内存池实现
开发语言·c++
不光头强2 小时前
object所有方法及知识点
java·开发语言·jvm
予枫的编程笔记2 小时前
【面试专栏|JVM虚拟机】CMS vs 其他垃圾收集器:核心差异+适用场景
java·jvm·java面试·后端开发·垃圾回收机制·cmv垃圾回收器·jvm性能优化
渡过晚枫2 小时前
[第十六届蓝桥杯/java/算法]1.偏蓝
java·算法·蓝桥杯
zhaoyin19942 小时前
JavaScript面试题笔记
java·javascript·笔记
.小小陈.2 小时前
C++进阶7:深入理解哈希表,从原理到 C++ 实践
开发语言·c++·学习·哈希算法
码云数智-大飞2 小时前
排序算法的终极博弈:从复杂度推导到工程选型实战
开发语言
计算机学姐2 小时前
基于SpringBoot的宠物诊所管理系统
java·vue.js·spring boot·后端·spring·elementui·宠物
2501_940315262 小时前
【无标题】1302 层数最深叶子节点的和
java·数据结构·算法