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);
}

