Add animation controller system, material assets and skeleton visualization
- .animctrl state machine asset (params/transitions/blend trees, 1D+2D directional blending) with Unity Animator-style editor panel: state list, transitions, params, directional pad preview driving live CrossFade on the selected entity - Split animation evaluation out of SceneRenderer into AnimationSystem/AnimationEvaluator; skin palettes with rest-pose fallback and blend-weight epsilon filtering - MaterialAsset (.material) with reload-safe preview tile pool (DropMaterial clears dirty refs before instance destruction) - EditorGizmoPass skeleton wires + viewport joint overlay; camera projection rework (baked Vulkan Y-flip, ortho support) - Editor/game build split via HELLOVULKAN_EDITOR option
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include <Engine/Renderer/Frustum.h>
|
||||
#include <Engine/Renderer/Model.h>
|
||||
#include <Engine/Renderer/Mesh.h>
|
||||
#include <Engine/Renderer/MaterialAsset.h>
|
||||
#include <Engine/Assets/AssetManager.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
@@ -18,6 +19,7 @@
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
@@ -31,17 +33,37 @@ namespace Engine
|
||||
|
||||
namespace
|
||||
{
|
||||
// 相机裁剪范围(UE 编辑器视口语义:near = 10cm;far 拉远到
|
||||
// "无限"级——UE 的 far clip 默认就是不裁剪,深度精度由 near 主导,
|
||||
// far 取多大都不损近处精度)。阴影距离独立(UE DirectionalLight
|
||||
// 的 shadow distance 同构):级联分割只覆盖 kShadowDistance,
|
||||
// 更远无阴影,避免级联跟着 far 拉爆近处阴影分辨率
|
||||
constexpr f32 kCameraNear = 0.1f;
|
||||
constexpr f32 kCameraFar = 10000.0f;
|
||||
// 相机裁剪范围不再走常量:每实体相机自带 near/far(Camera.h
|
||||
// 默认值 0.1/10000,UE 语义:near 主导深度精度,far 拉远到
|
||||
// "无限"级)。阴影距离独立(UE DirectionalLight 的 shadow
|
||||
// distance 同构):级联分割只覆盖 kShadowDistance,更远无阴影,
|
||||
// 避免级联跟着 far 拉爆近处阴影分辨率
|
||||
constexpr f32 kShadowDistance = 100.0f;
|
||||
// 实用分割插值因子(0=均匀,1=对数;0.6 是业界常用值)
|
||||
constexpr f32 kCascadeLambda = 0.6f;
|
||||
|
||||
#ifdef HELLO_EDITOR
|
||||
// ---- Editor gizmo 收集常量(CollectEditorGizmos 专用)----
|
||||
// 图标屏幕占比(相对视口高):恒定屏幕尺寸 = dist·tanHalf·frac
|
||||
constexpr f32 kIconScreenFrac = 0.04f;
|
||||
// 代理棱边屏宽(像素):HitProxy 可点选的 ribbon 宽度
|
||||
constexpr f32 kProxyWidthPx = 4.0f;
|
||||
// 视锥线框可视远平面截断:far clip(10000m)全尺寸线框横贯场景
|
||||
// 无信息量,可视化统一用固定短距离(渲染语义不变,纯显示)
|
||||
constexpr f32 kGizmoFrustumVisFar = 3.0f;
|
||||
// 视锥棱 ribbon 细分段数(恒定屏宽的折线逼近)
|
||||
constexpr u32 kRibbonSegsPerEdge = 12;
|
||||
// 点光选中时的衰减半径圆段数
|
||||
constexpr u32 kRadiusCircleSegs = 24;
|
||||
|
||||
// billboard 形状 id(gizmo_billboard.frag 的 SDF 分支约定)
|
||||
constexpr f32 kShapeDirLight = 0.0f;
|
||||
constexpr f32 kShapePointLight = 1.0f;
|
||||
constexpr f32 kShapeCamera = 2.0f;
|
||||
constexpr f32 kShapeSkybox = 3.0f;
|
||||
constexpr f32 kShapeSkyLight = 4.0f;
|
||||
#endif
|
||||
|
||||
// Halton 低差异序列(TAA 子像素抖动)
|
||||
f32 Halton(u32 index, u32 base)
|
||||
{
|
||||
@@ -53,134 +75,20 @@ namespace Engine
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---- 骨骼动画采样(BuildFrame 第 2.5 节专用)----
|
||||
|
||||
// 静止 local TRS 分解(列主序 SRT 矩阵;假设无负缩放/剪切)。
|
||||
// 被动画轨道触碰的节点用关键帧重建,未触碰的保持原矩阵——
|
||||
// FBX PreRotation 折在 localTransform 里,拆 TRS 重建会丢
|
||||
void DecomposeTRS(const glm::mat4& m, glm::vec3& pos, glm::quat& rot,
|
||||
glm::vec3& scl)
|
||||
{
|
||||
pos = glm::vec3(m[3]);
|
||||
scl = {glm::length(glm::vec3(m[0])),
|
||||
glm::length(glm::vec3(m[1])),
|
||||
glm::length(glm::vec3(m[2]))};
|
||||
const glm::mat3 r(glm::vec3(m[0]) / scl.x,
|
||||
glm::vec3(m[1]) / scl.y,
|
||||
glm::vec3(m[2]) / scl.z);
|
||||
rot = glm::quat_cast(r);
|
||||
}
|
||||
|
||||
// 关键帧采样:前后键插值(位置/缩放 lerp,旋转 slerp)。
|
||||
// key 数通常几十个,线性扫描对 cache 更友好
|
||||
glm::vec3 SampleKeys(const std::vector<PosKey>& keys, f32 t,
|
||||
const glm::vec3& rest)
|
||||
{
|
||||
if (keys.empty()) return rest;
|
||||
if (t <= keys.front().time) return keys.front().value;
|
||||
for (size_t i = 0; i + 1 < keys.size(); ++i) {
|
||||
if (t < keys[i + 1].time) {
|
||||
const f32 a = keys[i].time, b = keys[i + 1].time;
|
||||
return glm::mix(keys[i].value, keys[i + 1].value,
|
||||
b > a ? (t - a) / (b - a) : 0.0f);
|
||||
}
|
||||
}
|
||||
return keys.back().value;
|
||||
}
|
||||
|
||||
glm::vec3 SampleKeys(const std::vector<SclKey>& keys, f32 t,
|
||||
const glm::vec3& rest)
|
||||
{
|
||||
if (keys.empty()) return rest;
|
||||
if (t <= keys.front().time) return keys.front().value;
|
||||
for (size_t i = 0; i + 1 < keys.size(); ++i) {
|
||||
if (t < keys[i + 1].time) {
|
||||
const f32 a = keys[i].time, b = keys[i + 1].time;
|
||||
return glm::mix(keys[i].value, keys[i + 1].value,
|
||||
b > a ? (t - a) / (b - a) : 0.0f);
|
||||
}
|
||||
}
|
||||
return keys.back().value;
|
||||
}
|
||||
|
||||
glm::quat SampleKeys(const std::vector<RotKey>& keys, f32 t,
|
||||
const glm::quat& rest)
|
||||
{
|
||||
if (keys.empty()) return rest;
|
||||
if (t <= keys.front().time) return keys.front().value;
|
||||
for (size_t i = 0; i + 1 < keys.size(); ++i) {
|
||||
if (t < keys[i + 1].time) {
|
||||
const f32 a = keys[i].time, b = keys[i + 1].time;
|
||||
return glm::slerp(keys[i].value, keys[i + 1].value,
|
||||
b > a ? (t - a) / (b - a) : 0.0f);
|
||||
}
|
||||
}
|
||||
return keys.back().value;
|
||||
}
|
||||
|
||||
// 单 clip 在 time 的全节点局部 TRS(无轨道分量 = 静止分解值)。
|
||||
// touched = 被任意轨道触碰的节点标记(ComposeGlobals 决定重建或原样)
|
||||
void SampleLocalPose(const Model& model, const AnimationClip& clip,
|
||||
f32 time, std::vector<glm::vec3>& pos,
|
||||
std::vector<glm::quat>& rot,
|
||||
std::vector<glm::vec3>& scl,
|
||||
std::vector<u8>& touched)
|
||||
{
|
||||
const auto& nodes = model.GetNodes();
|
||||
pos.resize(nodes.size());
|
||||
rot.resize(nodes.size());
|
||||
scl.resize(nodes.size());
|
||||
touched.assign(nodes.size(), 0);
|
||||
for (u32 i = 0; i < nodes.size(); ++i) {
|
||||
DecomposeTRS(nodes[i].localTransform, pos[i], rot[i], scl[i]);
|
||||
}
|
||||
if (clip.duration <= 0.0f) return;
|
||||
const f32 t = glm::mod(time, clip.duration);
|
||||
for (const auto& ch : clip.channels) {
|
||||
if (ch.nodeIdx >= nodes.size()) continue;
|
||||
touched[ch.nodeIdx] = 1;
|
||||
if (!ch.positions.empty())
|
||||
pos[ch.nodeIdx] = SampleKeys(ch.positions, t, pos[ch.nodeIdx]);
|
||||
if (!ch.rotations.empty())
|
||||
rot[ch.nodeIdx] = SampleKeys(ch.rotations, t, rot[ch.nodeIdx]);
|
||||
if (!ch.scales.empty())
|
||||
scl[ch.nodeIdx] = SampleKeys(ch.scales, t, scl[ch.nodeIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
// 局部 TRS → 全节点 global 矩阵(层级累乘;父序 DFS 保证父先算)
|
||||
void ComposeGlobals(const Model& model, const std::vector<glm::vec3>& pos,
|
||||
const std::vector<glm::quat>& rot,
|
||||
const std::vector<glm::vec3>& scl,
|
||||
const std::vector<u8>& touched,
|
||||
std::vector<glm::mat4>& globals)
|
||||
{
|
||||
const auto& nodes = model.GetNodes();
|
||||
globals.resize(nodes.size());
|
||||
for (u32 i = 0; i < nodes.size(); ++i) {
|
||||
const glm::mat4 local = touched[i]
|
||||
? glm::translate(glm::mat4(1.0f), pos[i]) *
|
||||
glm::mat4_cast(rot[i]) * glm::scale(glm::mat4(1.0f), scl[i])
|
||||
: nodes[i].localTransform;
|
||||
globals[i] = nodes[i].parent >= 0
|
||||
? globals[static_cast<u32>(nodes[i].parent)] * local
|
||||
: local;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SceneRenderer::ComputeCascades(const glm::vec3& shadowDir)
|
||||
void SceneRenderer::ComputeCascades(const glm::vec3& shadowDir,
|
||||
const f32 camNear, const f32 camFar)
|
||||
{
|
||||
constexpr u32 N = kMaxCascades;
|
||||
|
||||
// ---- 1. 实用分割:均匀与对数分割按 λ 插值(视图深度,正值)----
|
||||
//(分割覆盖 [near, kShadowDistance];kCameraFar 只管主投影裁剪)
|
||||
//(分割覆盖 [camNear, kShadowDistance];camFar 只管主投影裁剪)
|
||||
f32 splits[N];
|
||||
for (u32 i = 1; i <= N; ++i) {
|
||||
const f32 t = static_cast<f32>(i) / N;
|
||||
const f32 uniform = kCameraNear + (kShadowDistance - kCameraNear) * t;
|
||||
const f32 log = kCameraNear * glm::pow(kShadowDistance / kCameraNear, t);
|
||||
const f32 uniform = camNear + (kShadowDistance - camNear) * t;
|
||||
const f32 log = camNear * glm::pow(kShadowDistance / camNear, t);
|
||||
splits[i - 1] = glm::mix(uniform, log, kCascadeLambda);
|
||||
}
|
||||
for (u32 c = 0; c < N; ++c) {
|
||||
@@ -203,14 +111,14 @@ namespace Engine
|
||||
? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
|
||||
for (u32 c = 0; c < N; ++c) {
|
||||
const f32 d0 = (c == 0) ? kCameraNear : splits[c - 1];
|
||||
const f32 d0 = (c == 0) ? camNear : splits[c - 1];
|
||||
const f32 d1 = splits[c];
|
||||
// 视图深度 → 视锥棱边参数:棱边上 z_view 线性(near/far 角点
|
||||
// 都在各自平面上),farC 反投影自主投影 far 面,故 t 相对
|
||||
// kCameraFar 归一;splits 只覆盖 [near, kShadowDistance],
|
||||
// camFar 归一;splits 只覆盖 [camNear, kShadowDistance],
|
||||
// t 必然远小于 1 → 自然截出阴影范围内的切片
|
||||
const f32 t0 = (d0 - kCameraNear) / (kCameraFar - kCameraNear);
|
||||
const f32 t1 = (d1 - kCameraNear) / (kCameraFar - kCameraNear);
|
||||
const f32 t0 = (d0 - camNear) / (camFar - camNear);
|
||||
const f32 t1 = (d1 - camNear) / (camFar - camNear);
|
||||
|
||||
glm::vec3 slice[8];
|
||||
for (u32 i = 0; i < 4; ++i) {
|
||||
@@ -258,6 +166,297 @@ namespace Engine
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HELLO_EDITOR
|
||||
// ---- Editor gizmo 收集(编辑形态专属,只读场景写 editor 段)----
|
||||
void SceneRenderer::CollectEditorGizmos(ECS::Scene& scene,
|
||||
const glm::vec3& camPos,
|
||||
const glm::vec3& camRight,
|
||||
const glm::vec3& camUp,
|
||||
f32 aspect, f32 viewHeightPx,
|
||||
f32 viewTanHalf)
|
||||
{
|
||||
auto& icons = m_Frame.gizmoIconVerts;
|
||||
auto& wires = m_Frame.gizmoWireVerts;
|
||||
auto& proxies = m_Frame.gizmoProxyVerts;
|
||||
icons.clear();
|
||||
wires.clear();
|
||||
proxies.clear();
|
||||
|
||||
constexpr f32 kTwoPi = 6.28318530718f;
|
||||
// viewTanHalf:编辑器视角相机的 tan(fov/2)(BuildFrame 传入),
|
||||
// 只服务 billboard 恒定屏占比与 proxy ribbon 恒定像素宽——与
|
||||
// 相机实体自己的 fov 无关
|
||||
const f32 tanHalf = viewTanHalf;
|
||||
const bool anySelected = m_Frame.selectedEntityValid;
|
||||
const u32 selBits = m_Frame.selectedEntityBits;
|
||||
auto IsSel = [&](u32 bits)
|
||||
{ return anySelected && bits == selBits; };
|
||||
|
||||
// 预算哨兵(一次性 WARN 复用截断标志语义)
|
||||
bool truncated = false;
|
||||
auto Fit = [](u32 cur, u32 add, u32 cap)
|
||||
{ return cur + add <= cap; };
|
||||
|
||||
// 图标 billboard:面向相机 quad(2 三角)+ 同角点进代理流(可点选)
|
||||
auto EmitIcon = [&](const glm::vec3& pos, f32 shape,
|
||||
const glm::vec4& color, u32 bits, f32 scaleMul)
|
||||
{
|
||||
if (!Fit(static_cast<u32>(icons.size()), 6, kMaxGizmoIconVerts) ||
|
||||
!Fit(static_cast<u32>(proxies.size()), 6,
|
||||
kMaxGizmoProxyVerts))
|
||||
{
|
||||
truncated = true;
|
||||
return;
|
||||
}
|
||||
const f32 dist =
|
||||
std::max(glm::length(pos - camPos), 0.1f);
|
||||
const f32 half = dist * tanHalf * kIconScreenFrac * scaleMul;
|
||||
const glm::vec3 r = camRight * half;
|
||||
const glm::vec3 u = camUp * half;
|
||||
const glm::vec3 p00 = pos - r - u, p10 = pos + r - u;
|
||||
const glm::vec3 p11 = pos + r + u, p01 = pos - r + u;
|
||||
const auto push = [&](std::vector<GizmoQuadVertex>& v,
|
||||
const glm::vec3& p, f32 lu, f32 lv)
|
||||
{
|
||||
v.push_back({p, color, glm::vec4(shape, lu, lv, 0.0f), bits});
|
||||
};
|
||||
// 三角表:v00 v10 v11 / v00 v11 v01(uv 对应角点)
|
||||
push(icons, p00, -1, -1); push(icons, p10, 1, -1);
|
||||
push(icons, p11, 1, 1);
|
||||
push(icons, p00, -1, -1); push(icons, p11, 1, 1);
|
||||
push(icons, p01, -1, 1);
|
||||
// 代理壳颜色占位(拾取 frag 只写 entityId)
|
||||
const glm::vec4 any{1.0f};
|
||||
const auto pushP = [&](const glm::vec3& p)
|
||||
{ proxies.push_back({p, any, glm::vec4(shape, 0, 0, 0), bits}); };
|
||||
pushP(p00); pushP(p10); pushP(p11);
|
||||
pushP(p00); pushP(p11); pushP(p01);
|
||||
};
|
||||
|
||||
// 线段(LineList 成对 push)
|
||||
auto EmitLine = [&](const glm::vec3& a, const glm::vec3& b,
|
||||
const glm::vec4& color)
|
||||
{
|
||||
if (!Fit(static_cast<u32>(wires.size()), 2, kMaxGizmoWireVerts))
|
||||
{
|
||||
truncated = true;
|
||||
return;
|
||||
}
|
||||
wires.push_back({a, color});
|
||||
wires.push_back({b, color});
|
||||
};
|
||||
|
||||
// 拾取代理 ribbon:棱边 ab 细分 N 段,每段以段中点为基准的面向
|
||||
// 相机 quad(半宽恒定 ~kProxyWidthPx 屏幕像素)。线段在 R32Uint
|
||||
// 光栅化里点不中,quad 才可拾取
|
||||
auto EmitRibbon = [&](const glm::vec3& a, const glm::vec3& b,
|
||||
u32 bits)
|
||||
{
|
||||
const glm::vec3 ab = b - a;
|
||||
const f32 len = glm::length(ab);
|
||||
if (len < 1e-4f) return;
|
||||
const glm::vec3 dir = ab / len;
|
||||
for (u32 s = 0; s < kRibbonSegsPerEdge; ++s)
|
||||
{
|
||||
const f32 t0 = static_cast<f32>(s) / kRibbonSegsPerEdge;
|
||||
const f32 t1 = static_cast<f32>(s + 1) / kRibbonSegsPerEdge;
|
||||
const glm::vec3 m =
|
||||
a + ab * (0.5f * (t0 + t1)); // 段中点
|
||||
const glm::vec3 toCam = camPos - m;
|
||||
const f32 dist = std::max(glm::length(toCam), 0.1f);
|
||||
glm::vec3 side = glm::cross(dir, toCam / dist);
|
||||
const f32 sideLen = glm::length(side);
|
||||
if (!(sideLen > 1e-6f) ||
|
||||
!(std::isfinite(side.x) && std::isfinite(side.y) &&
|
||||
std::isfinite(side.z)))
|
||||
{
|
||||
side = camUp; // 棱正对相机退化:用屏幕 up 兜底
|
||||
}
|
||||
else
|
||||
{
|
||||
side /= sideLen;
|
||||
}
|
||||
const f32 half =
|
||||
dist * tanHalf * (kProxyWidthPx / viewHeightPx);
|
||||
const glm::vec3 halfDir = dir * (len / kRibbonSegsPerEdge * 0.5f);
|
||||
const glm::vec3 halfSide = side * half;
|
||||
if (!Fit(static_cast<u32>(proxies.size()), 6,
|
||||
kMaxGizmoProxyVerts))
|
||||
{
|
||||
truncated = true;
|
||||
return;
|
||||
}
|
||||
const glm::vec4 any{1.0f};
|
||||
const glm::vec3 p0 = m - halfDir - halfSide;
|
||||
const glm::vec3 p1 = m + halfDir - halfSide;
|
||||
const glm::vec3 p2 = m + halfDir + halfSide;
|
||||
const glm::vec3 p3 = m - halfDir + halfSide;
|
||||
proxies.push_back({p0, any, glm::vec4(0, 0, 0, 0), bits});
|
||||
proxies.push_back({p1, any, glm::vec4(0, 0, 0, 0), bits});
|
||||
proxies.push_back({p2, any, glm::vec4(0, 0, 0, 0), bits});
|
||||
proxies.push_back({p0, any, glm::vec4(0, 0, 0, 0), bits});
|
||||
proxies.push_back({p2, any, glm::vec4(0, 0, 0, 0), bits});
|
||||
proxies.push_back({p3, any, glm::vec4(0, 0, 0, 0), bits});
|
||||
}
|
||||
};
|
||||
|
||||
// 相机实体视锥线框(near 真值 / 远平面可视截断)+ 棱 ribbon。
|
||||
// 几何取实体相机自己的 fov/near/orthoSize(Inspector 参数改动
|
||||
// 即时反映到线框),aspect 用编辑器视口比例(可视化近似)
|
||||
auto EmitFrustum = [&](const ECS::TransformComponent& t,
|
||||
const ECS::CameraComponent& cc,
|
||||
const glm::vec4& lineColor, u32 bits)
|
||||
{
|
||||
const glm::mat4 W = t.World();
|
||||
const auto Xform = [&](f32 x, f32 y, f32 z)
|
||||
{ return glm::vec3(W * glm::vec4(x, y, z, 1.0f)); };
|
||||
// 本地 -Z 为视线方向(Camera 单一事实源约定)
|
||||
const f32 camNear = cc.camera.GetNearZ();
|
||||
// 可视远平面不小于 near 的 1.5 倍:用户把 near 拖大时防止
|
||||
// 远矩形反而落在 near 之前(线框翻转)
|
||||
const f32 visFar =
|
||||
glm::max(kGizmoFrustumVisFar, camNear * 1.5f);
|
||||
f32 nH = 0.0f, vH = 0.0f;
|
||||
if (cc.camera.GetProjectionMode() ==
|
||||
Camera::ProjectionMode::Orthographic)
|
||||
{
|
||||
// 正交视锥 = 长方体:near/远矩形同宽高(无透视收缩)
|
||||
nH = cc.camera.GetOrthoSize();
|
||||
vH = nH;
|
||||
}
|
||||
else
|
||||
{
|
||||
const f32 tanHalfEnt =
|
||||
std::tan(glm::radians(cc.camera.GetFovDeg()) * 0.5f);
|
||||
nH = tanHalfEnt * camNear;
|
||||
vH = tanHalfEnt * visFar;
|
||||
}
|
||||
const f32 nW = nH * aspect;
|
||||
const f32 vW = vH * aspect;
|
||||
const glm::vec3 n0 = Xform(-nW, -nH, -camNear);
|
||||
const glm::vec3 n1 = Xform(nW, -nH, -camNear);
|
||||
const glm::vec3 n2 = Xform(nW, nH, -camNear);
|
||||
const glm::vec3 n3 = Xform(-nW, nH, -camNear);
|
||||
const glm::vec3 f0 = Xform(-vW, -vH, -visFar);
|
||||
const glm::vec3 f1 = Xform(vW, -vH, -visFar);
|
||||
const glm::vec3 f2 = Xform(vW, vH, -visFar);
|
||||
const glm::vec3 f3 = Xform(-vW, vH, -visFar);
|
||||
// near 矩形 + 可视远矩形 + 4 棱 + 上轴刻度
|
||||
EmitLine(n0, n1, lineColor); EmitLine(n1, n2, lineColor);
|
||||
EmitLine(n2, n3, lineColor); EmitLine(n3, n0, lineColor);
|
||||
EmitLine(f0, f1, lineColor); EmitLine(f1, f2, lineColor);
|
||||
EmitLine(f2, f3, lineColor); EmitLine(f3, f0, lineColor);
|
||||
EmitLine(n0, f0, lineColor); EmitLine(n1, f1, lineColor);
|
||||
EmitLine(n2, f2, lineColor); EmitLine(n3, f3, lineColor);
|
||||
EmitLine(Xform(0.0f, nH, -camNear),
|
||||
Xform(0.0f, nH + 0.3f, -camNear), lineColor);
|
||||
// 12 棱的拾取 ribbon(图标 quad 之外的可点选面积)
|
||||
EmitRibbon(n0, n1, bits); EmitRibbon(n1, n2, bits);
|
||||
EmitRibbon(n2, n3, bits); EmitRibbon(n3, n0, bits);
|
||||
EmitRibbon(f0, f1, bits); EmitRibbon(f1, f2, bits);
|
||||
EmitRibbon(f2, f3, bits); EmitRibbon(f3, f0, bits);
|
||||
EmitRibbon(n0, f0, bits); EmitRibbon(n1, f1, bits);
|
||||
EmitRibbon(n2, f2, bits); EmitRibbon(n3, f3, bits);
|
||||
};
|
||||
|
||||
// 灯光:方向光 = 太阳盘 + 照射方向箭头;点光 = 光晕盘
|
||||
//(选中追加衰减半径三正交圆,UE 语义——常显是视觉噪音)
|
||||
scene.Each<TransformComponent, LightComponent>(
|
||||
[&](Entity e, TransformComponent& t, LightComponent& light)
|
||||
{
|
||||
if (!light.active) return;
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
const glm::vec3 pos = t.GetWorldPosition();
|
||||
const glm::vec3 rgb(light.color);
|
||||
const glm::vec4 color =
|
||||
IsSel(bits) ? kGizmoSelectedColor
|
||||
: glm::vec4(rgb * (0.6f + 0.4f * light.color.a),
|
||||
1.0f);
|
||||
if (light.type == LightComponent::Type::Point)
|
||||
{
|
||||
EmitIcon(pos, kShapePointLight, color, bits, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitIcon(pos, kShapeDirLight, color, bits, 1.1f);
|
||||
glm::vec3 dir = light.direction;
|
||||
if (glm::length(dir) > 1e-4f)
|
||||
{
|
||||
dir = glm::normalize(dir);
|
||||
const glm::vec4 lineColor =
|
||||
IsSel(bits) ? kGizmoSelectedColor
|
||||
: glm::vec4(rgb, 0.9f);
|
||||
EmitLine(pos, pos + dir * 0.6f, lineColor);
|
||||
}
|
||||
}
|
||||
if (IsSel(bits) && light.type == LightComponent::Type::Point)
|
||||
{
|
||||
const glm::vec4 ring = glm::vec4(rgb, 0.35f);
|
||||
const f32 R = light.radius;
|
||||
for (u32 s = 0; s < kRadiusCircleSegs; ++s)
|
||||
{
|
||||
const f32 a0 = kTwoPi * s / kRadiusCircleSegs;
|
||||
const f32 a1 = kTwoPi * (s + 1) /
|
||||
kRadiusCircleSegs;
|
||||
const glm::vec3 d0(std::cos(a0), std::sin(a0), 0.0f);
|
||||
const glm::vec3 d1(std::cos(a1), std::sin(a1), 0.0f);
|
||||
EmitLine(pos + d0 * R, pos + d1 * R, ring); // XY
|
||||
EmitLine(pos + glm::vec3(d0.x, 0, d0.y) * R,
|
||||
pos + glm::vec3(d1.x, 0, d1.y) * R, ring); // XZ
|
||||
EmitLine(pos + glm::vec3(0, d0.y, d0.x) * R,
|
||||
pos + glm::vec3(0, d1.y, d1.x) * R, ring); // YZ
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 相机实体:图标 + 视锥线框(primary 提亮放大)
|
||||
scene.Each<TransformComponent, CameraComponent>(
|
||||
[&](Entity e, TransformComponent& t, CameraComponent& cam)
|
||||
{
|
||||
if (!cam.active) return;
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
const bool sel = IsSel(bits);
|
||||
const glm::vec4 color =
|
||||
sel ? kGizmoSelectedColor
|
||||
: glm::vec4(cam.primary ? 1.0f : 0.55f, 0.8f, 1.0f, 1.0f);
|
||||
EmitIcon(t.GetWorldPosition(), kShapeCamera, color, bits,
|
||||
cam.primary ? 1.3f : 1.0f);
|
||||
EmitFrustum(t, cam, sel ? kGizmoSelectedColor
|
||||
: glm::vec4(0.55f, 0.8f, 1.0f, 0.85f),
|
||||
bits);
|
||||
});
|
||||
|
||||
// 环境组件:位置无关天体的空间锚点标记
|
||||
scene.Each<TransformComponent, SkyboxComponent>(
|
||||
[&](Entity e, TransformComponent& t, SkyboxComponent& sb)
|
||||
{
|
||||
if (!sb.active) return;
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
EmitIcon(t.GetWorldPosition(), kShapeSkybox,
|
||||
IsSel(bits) ? kGizmoSelectedColor
|
||||
: glm::vec4(0.45f, 0.55f, 0.9f, 1.0f),
|
||||
bits, 1.0f);
|
||||
});
|
||||
scene.Each<TransformComponent, SkyLightComponent>(
|
||||
[&](Entity e, TransformComponent& t, SkyLightComponent& sl)
|
||||
{
|
||||
if (!sl.active) return;
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
EmitIcon(t.GetWorldPosition(), kShapeSkyLight,
|
||||
IsSel(bits) ? kGizmoSelectedColor
|
||||
: glm::vec4(0.5f, 0.75f, 0.95f, 1.0f),
|
||||
bits, 1.0f);
|
||||
});
|
||||
|
||||
if (truncated && !m_GizmoTruncWarned)
|
||||
{
|
||||
m_GizmoTruncWarned = true;
|
||||
ENGINE_WARN("SceneRenderer: editor gizmo vertex budget "
|
||||
"exceeded, clamped");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
|
||||
vk::Extent2D targetExtent, f32 dt)
|
||||
{
|
||||
@@ -303,19 +502,34 @@ namespace Engine
|
||||
const f32 aspect = static_cast<f32>(targetExtent.width) /
|
||||
static_cast<f32>(targetExtent.height);
|
||||
ubo.view = camera->GetView(*camTransform);
|
||||
ubo.proj = camera->GetProjection(aspect, kCameraNear, kCameraFar);
|
||||
// 投影参数(模式/fov/near/far/orthoSize)由相机自身承载:
|
||||
// 编辑器形态 = 编辑器相机,游戏形态 = primary 相机实体
|
||||
ubo.proj = camera->GetProjection(aspect);
|
||||
|
||||
// 未抖动投影:velocity 的当前帧 clip 位置用它(抖动不能进 velocity)
|
||||
ubo.projNoJitter = ubo.proj;
|
||||
|
||||
// 子像素抖动(Halton×8):光栅化采样位置逐帧偏移,TAA 累积去锯齿。
|
||||
// NDC 偏移 = 像素偏移 × 2 / 尺寸。TAA 关闭时停抖(直通画面静止)
|
||||
// NDC 偏移 = 像素偏移 × 2 / 尺寸。TAA 关闭时停抖(直通画面静止)。
|
||||
// 抖动分量必须按投影模式选(w_clip 语义不同):
|
||||
// 透视 w_clip = -z_eye → 加 z 列([2][x]),被 w 除后 NDC 偏移
|
||||
// 恒定(z_eye 约掉);正交 w_clip = 1 → z 列偏移会随视图深度
|
||||
// 放大(far=10000 时抖动放大上万倍 = 切正交后画面狂震的根因),
|
||||
// 必须加平移列([3][x]),与 w=1 相乘才是恒定 NDC 偏移
|
||||
if (m_TAAEnabled) {
|
||||
const u32 idx = static_cast<u32>(m_FrameIndex % 8);
|
||||
const f32 jx = Halton(idx + 1, 2) - 0.5f;
|
||||
const f32 jy = Halton(idx + 1, 3) - 0.5f;
|
||||
ubo.proj[2][0] += jx * 2.0f / static_cast<f32>(targetExtent.width);
|
||||
ubo.proj[2][1] += jy * 2.0f / static_cast<f32>(targetExtent.height);
|
||||
const f32 sx = 2.0f / static_cast<f32>(targetExtent.width);
|
||||
const f32 sy = 2.0f / static_cast<f32>(targetExtent.height);
|
||||
if (camera->GetProjectionMode() ==
|
||||
Camera::ProjectionMode::Orthographic) {
|
||||
ubo.proj[3][0] += jx * sx;
|
||||
ubo.proj[3][1] += jy * sy;
|
||||
} else {
|
||||
ubo.proj[2][0] += jx * sx;
|
||||
ubo.proj[2][1] += jy * sy;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 环境组件落帧(UE SkyLight/Skybox Actor 语义):场景环境
|
||||
@@ -353,6 +567,13 @@ namespace Engine
|
||||
m_Frame.lodEnabled = m_LodEnabled;
|
||||
m_Frame.debugView = m_DebugView;
|
||||
m_Frame.displayMode = m_DisplayMode;
|
||||
#ifdef HELLO_EDITOR
|
||||
// 骨骼可视化设置(ImGui 经 Renderer 门面 → 此处落帧)
|
||||
m_Frame.showBoneWires = m_ShowBoneWires;
|
||||
m_Frame.boneAllNodes = m_BoneAllNodes;
|
||||
m_Frame.boneJointPx = m_BoneJointPx;
|
||||
m_Frame.pieActive = m_GameView; // PIE 态骨骼可视化整体停产
|
||||
#endif
|
||||
ubo.cascadeSplits.w = m_ShadowsEnabled ? 1.0f : 0.0f;
|
||||
|
||||
// 天空盒:视图去平移(只留旋转)× 投影 → 逆矩阵,push 给 Skybox pass;
|
||||
@@ -422,111 +643,37 @@ namespace Engine
|
||||
collected[0].params.x < 0.5f) {
|
||||
shadowDir = glm::vec3(collected[0].direction);
|
||||
}
|
||||
ComputeCascades(shadowDir);
|
||||
ComputeCascades(shadowDir, camera->GetNearZ(), camera->GetFarZ());
|
||||
ubo.cameraPos = glm::vec4(camTransform->GetWorldPosition(), 1.0f);
|
||||
|
||||
// ---- 2.5 骨骼动画推进 + 姿态采样(与可见性无关:播放头恒走,
|
||||
// 否则被剔除的实体回屏时动画跳变)----
|
||||
// palette 槽 0 固定 identity:静态模型/无动画实例的蒙皮直通
|
||||
m_Frame.skinPalettes.clear();
|
||||
m_Frame.skinPalettes.push_back(glm::mat4(1.0f));
|
||||
m_Frame.skinOffsets.clear();
|
||||
m_Frame.skinPaletteTruncated = false;
|
||||
m_EntitySkinBase.clear();
|
||||
scene.Each<MeshRendererComponent, AnimationComponent>(
|
||||
[&](Entity entity, MeshRendererComponent& mr, AnimationComponent& anim) {
|
||||
if (!anim.active) return; // 组件禁用 = 暂停采样(播放头不动)
|
||||
if (!mr.model.Valid()) return; // 未指派/手清 = 无效句柄(Get 对无效 FATAL)
|
||||
const Model* model = assets.GetModel(mr.model);
|
||||
if (!model || !model->HasAnimations()) return;
|
||||
const auto& clips = model->GetClips();
|
||||
// ---- 2.5 骨骼动画(调度收口 Animation::AnimationSystem:推进
|
||||
// 播放头/crossfade + 姿态求值 + palette 产出。与可见性无关:
|
||||
// 播放头恒走,否则被剔除的实体回屏时动画跳变)----
|
||||
// 渲染侧零动画逻辑:收集段只查 frame.entitySkinBases(查不到 =
|
||||
// identity 槽 = 非蒙皮静态;蒙皮实体由 AnimationSystem 兜底
|
||||
// rest-pose palette,无动画组件也成立)
|
||||
m_AnimSystem.Update(scene, assets, dt, m_Frame);
|
||||
|
||||
// clip 下标合法性(模型资产可能已换)
|
||||
if (anim.clipIndex < 0 ||
|
||||
anim.clipIndex >= static_cast<int>(clips.size())) {
|
||||
anim.clipIndex = 0;
|
||||
anim.fadeFrom = -1;
|
||||
if (clips.empty()) return;
|
||||
}
|
||||
if (anim.fadeFrom >= static_cast<int>(clips.size())) {
|
||||
anim.fadeFrom = -1;
|
||||
}
|
||||
|
||||
// ---- 播放头推进(wrap/clamp)----
|
||||
if (anim.playing && anim.speed != 0.0f) {
|
||||
const f32 d = dt * anim.speed;
|
||||
anim.time += d;
|
||||
if (anim.fadeFrom >= 0) anim.fadeFromTime += d;
|
||||
}
|
||||
const auto wrapTime = [&](f32 t, const AnimationClip& c) {
|
||||
if (c.duration <= 0.0f) return 0.0f;
|
||||
return anim.loop ? glm::mod(t, c.duration)
|
||||
: glm::clamp(t, 0.0f, c.duration - 1e-3f);
|
||||
};
|
||||
const AnimationClip& cur = clips[anim.clipIndex];
|
||||
anim.time = wrapTime(anim.time, cur);
|
||||
const AnimationClip* src = nullptr;
|
||||
f32 srcTime = 0.0f;
|
||||
f32 wCur = 1.0f; // 当前 clip 权重(crossfade 0→1)
|
||||
if (anim.fadeFrom >= 0) {
|
||||
// 淡出计时(暂停时冻结,与播放头一致)
|
||||
if (anim.playing) anim.fadeElapsed += dt;
|
||||
const f32 f = glm::min(
|
||||
anim.fadeElapsed / glm::max(anim.fadeDuration, 1e-4f),
|
||||
1.0f);
|
||||
wCur = f * f * (3.0f - 2.0f * f); // smoothstep
|
||||
if (f >= 1.0f) {
|
||||
anim.fadeFrom = -1; // 过渡完成
|
||||
} else {
|
||||
src = &clips[anim.fadeFrom];
|
||||
anim.fadeFromTime = wrapTime(anim.fadeFromTime, *src);
|
||||
srcTime = anim.fadeFromTime;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 采样 + 局部 TRS 层混合 → global → palette ----
|
||||
SampleLocalPose(*model, cur, anim.time,
|
||||
m_PosePosA, m_PoseRotA, m_PoseSclA, m_TouchedA);
|
||||
if (src) {
|
||||
SampleLocalPose(*model, *src, srcTime,
|
||||
m_PosePosB, m_PoseRotB, m_PoseSclB,
|
||||
m_TouchedB);
|
||||
const size_t n = m_PosePosA.size();
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
// touched = 两层轨道的并集(重建集合)
|
||||
m_TouchedA[i] = static_cast<u8>(
|
||||
m_TouchedA[i] | m_TouchedB[i]);
|
||||
m_PosePosA[i] = glm::mix(m_PosePosB[i], m_PosePosA[i], wCur);
|
||||
m_PoseSclA[i] = glm::mix(m_PoseSclB[i], m_PoseSclA[i], wCur);
|
||||
// 旋转必须 slerp(lerp 单位四元数会走样)
|
||||
m_PoseRotA[i] = glm::slerp(m_PoseRotB[i], m_PoseRotA[i], wCur);
|
||||
}
|
||||
}
|
||||
ComposeGlobals(*model, m_PosePosA, m_PoseRotA, m_PoseSclA,
|
||||
m_TouchedA, m_Globals);
|
||||
|
||||
// palette bump 分配(按实体,不按模型去重——同模型两实体
|
||||
// 可不同播放状态)。预算打满 → 该实体退回绑定姿势
|
||||
const auto& bones = model->GetBones();
|
||||
const u32 base = static_cast<u32>(m_Frame.skinPalettes.size());
|
||||
if (base + bones.size() > kMaxSkinMatrices) {
|
||||
m_Frame.skinPaletteTruncated = true;
|
||||
if (!m_SkinTruncWarned) {
|
||||
m_SkinTruncWarned = true;
|
||||
ENGINE_WARN("GPUDriven: skin palette full ({}), "
|
||||
"animated entities fall back to bind pose",
|
||||
kMaxSkinMatrices);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const auto& b : bones) {
|
||||
const u32 ni = b.nodeIdx < m_Globals.size()
|
||||
? b.nodeIdx : 0;
|
||||
m_Frame.skinPalettes.push_back(m_Globals[ni] * b.invBind);
|
||||
}
|
||||
const u32 entKey = static_cast<u32>(entt::to_integral(entity));
|
||||
m_EntitySkinBase[entKey] = base;
|
||||
});
|
||||
#ifdef HELLO_EDITOR
|
||||
// ---- 2.7 Editor gizmo 收集(编辑形态专属;纯 CPU 只读场景)。
|
||||
// billboard right/up 从视图矩阵行向量提取(相机无缩放)----
|
||||
if (m_DisplayMode == kDisplayViewport && !m_GameView) {
|
||||
CollectEditorGizmos(scene, camTransform->GetWorldPosition(),
|
||||
glm::vec3(ubo.view[0][0], ubo.view[1][0], ubo.view[2][0]),
|
||||
glm::vec3(ubo.view[0][1], ubo.view[1][1], ubo.view[2][1]),
|
||||
aspect, static_cast<f32>(targetExtent.height),
|
||||
std::tan(glm::radians(
|
||||
m_EditorCamera.GetController().GetFovDeg()) * 0.5f));
|
||||
} else {
|
||||
m_Frame.gizmoIconVerts.clear();
|
||||
m_Frame.gizmoWireVerts.clear();
|
||||
m_Frame.gizmoProxyVerts.clear();
|
||||
// 骨骼可视化数组与 gizmo 同批清(AnimationSystem.Update 开头
|
||||
// 也会清,此处双保险:PIE 态/全屏直渲绝无残留绘制)
|
||||
m_Frame.boneWireVerts.clear();
|
||||
m_Frame.boneJoints.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- 3. GPUDriven:单趟收集 + 多视图剔除 ----
|
||||
// 同作用域第二个 zone:ZoneScopedN 变量名固定会重声明,用 ZoneNamedN
|
||||
@@ -576,10 +723,12 @@ namespace Engine
|
||||
}
|
||||
m_CurWorlds.emplace_back(entKey, t.World());
|
||||
|
||||
// palette 基址(第 2.5 节预采样;无动画实体 = identity 槽)
|
||||
// palette 基址(AnimationSystem 预采样:动画实体=当帧
|
||||
// 姿态、蒙皮无动画实体=rest 兜底;查不到=identity 槽,
|
||||
// 只可能是非蒙皮静态模型)
|
||||
u32 skinBase = kIdentityPaletteSlot;
|
||||
if (const auto it = m_EntitySkinBase.find(entKey);
|
||||
it != m_EntitySkinBase.end()) {
|
||||
if (const auto it = m_Frame.entitySkinBases.find(entKey);
|
||||
it != m_Frame.entitySkinBases.end()) {
|
||||
skinBase = it->second;
|
||||
}
|
||||
|
||||
@@ -606,12 +755,38 @@ namespace Engine
|
||||
}
|
||||
if (!anyVis) return;
|
||||
|
||||
for (auto& mesh : model->GetMeshes()) {
|
||||
const auto& meshes = model->GetMeshes();
|
||||
for (size_t meshIdx = 0; meshIdx < meshes.size(); ++meshIdx) {
|
||||
auto& mesh = meshes[meshIdx];
|
||||
const auto& bounds = mesh->GetBounds();
|
||||
const glm::vec3 worldCenter =
|
||||
glm::vec3(t.World() * glm::vec4(bounds.center, 1.0f));
|
||||
const f32 worldRadius = bounds.radius * worldScale;
|
||||
|
||||
// ---- 实体级材质覆盖 resolve(UE Material Slot)----
|
||||
// 覆盖路径命中已加载资产 → 用其 GPU 材质;未加载/
|
||||
// 解析失败回退内建(一次性告警)。收集期只查缓存
|
||||
// 不建 GPU 资源——加载点收敛在场景预载(游戏形态
|
||||
// 唯一加载点)与编辑器指派(UI 只是编辑接口)
|
||||
Material* resolved = mesh->GetMaterial();
|
||||
if (!mr.materialOverrides.empty()) {
|
||||
if (const auto oIt = mr.materialOverrides.find(
|
||||
static_cast<u32>(meshIdx));
|
||||
oIt != mr.materialOverrides.end()) {
|
||||
if (MaterialAsset* asset =
|
||||
assets.FindMaterialAsset(oIt->second);
|
||||
asset != nullptr) {
|
||||
resolved = asset->GetMaterial();
|
||||
}
|
||||
else if (m_WarnedMaterialMisses.insert(
|
||||
oIt->second).second) {
|
||||
ENGINE_WARN("GPUDriven: material asset "
|
||||
"'{}' not loaded, falling back "
|
||||
"to built-in", oIt->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LOD 选级:屏占比近似 = radius/距离(主视图按实例距离;
|
||||
// 阈值 10/20 倍半径,可调)。开关关闭 = 恒 LOD0
|
||||
const f32 dist =
|
||||
@@ -634,20 +809,38 @@ namespace Engine
|
||||
instanceTruncated = true;
|
||||
return;
|
||||
}
|
||||
// batch 查找 = 数组寻址(Mesh::GetSlot 槽位 + LOD 槽)
|
||||
// batchIdx 存储 +1 偏移(0 = 该槽位尚无 batch)
|
||||
const u32 slot = mesh->GetSlot();
|
||||
if (slot >= s.lookup.size()) {
|
||||
s.lookup.resize(slot + 1);
|
||||
// batch 查找:内建材质 = 数组寻址快路径
|
||||
//(Mesh::GetSlot 槽位 + LOD 槽);覆盖材质 =
|
||||
// hash 寻址(键含材质变体:同 mesh 不同覆盖天然
|
||||
// 分批,同覆盖多实体仍合并——材质资产指针仅帧内
|
||||
// 有效,变体表每帧随 ViewScratch::Clear 清空)
|
||||
const bool builtin = (resolved == mesh->GetMaterial());
|
||||
std::array<u32, kMeshLodCount>* lodSlots = nullptr;
|
||||
if (builtin) {
|
||||
const u32 slot = mesh->GetSlot();
|
||||
if (slot >= s.lookup.size()) {
|
||||
s.lookup.resize(slot + 1);
|
||||
}
|
||||
lodSlots = &s.lookup[slot];
|
||||
}
|
||||
auto& lodSlots = s.lookup[slot];
|
||||
u32 batchIdx = lodSlots[lod];
|
||||
else {
|
||||
const auto [vIt, inserted] = s.matVariants.emplace(
|
||||
resolved,
|
||||
static_cast<u32>(s.matVariants.size()));
|
||||
const u64 key =
|
||||
(static_cast<u64>(mesh->GetSlot()) << 32) |
|
||||
static_cast<u64>(vIt->second);
|
||||
lodSlots = &s.ovrLookup[key];
|
||||
}
|
||||
// batchIdx 存储 +1 偏移(0 = 该槽位尚无 batch)
|
||||
u32 batchIdx = (*lodSlots)[lod];
|
||||
if (batchIdx == 0) {
|
||||
batchIdx = static_cast<u32>(s.result.batches.size()) + 1;
|
||||
lodSlots[lod] = batchIdx;
|
||||
(*lodSlots)[lod] = batchIdx;
|
||||
// first 是占位:真实基址在 appendView 聚合时
|
||||
// 回填(收集序下 batch 实例散布,见链表注释)
|
||||
s.result.batches.push_back({mesh.get(), 0, 0, lod});
|
||||
s.result.batches.push_back(
|
||||
{mesh.get(), resolved, 0, 0, lod});
|
||||
s.batchDistSum.push_back(0.0f);
|
||||
s.batchHead.push_back(ViewScratch::kNoInstance);
|
||||
}
|
||||
@@ -668,9 +861,10 @@ namespace Engine
|
||||
};
|
||||
|
||||
// 透明材质 → 独立队列(主视图,混合绘制 + 排序;
|
||||
// 不进阴影——alpha 阴影复杂度高收益低,注释后续演进)
|
||||
const bool isTransparentMesh =
|
||||
mesh->GetMaterial()->IsTransparent();
|
||||
// 不进阴影——alpha 阴影复杂度高收益低,注释后续演进)。
|
||||
// 队列归属按 resolve 后的实际材质:同一 mesh 可因
|
||||
// 覆盖不同而分属不透明/透明两队列
|
||||
const bool isTransparentMesh = resolved->IsTransparent();
|
||||
ViewScratch& mainTarget = isTransparentMesh
|
||||
? m_TranslucentScratch : m_MainScratch;
|
||||
|
||||
@@ -851,16 +1045,16 @@ namespace Engine
|
||||
const u64 vb = reinterpret_cast<u64>(static_cast<VkBuffer>(
|
||||
b.mesh->GetVertexBuffer()));
|
||||
if (va != vb) return va < vb;
|
||||
if (byMaterial && a.mesh->GetMaterial() != b.mesh->GetMaterial()) {
|
||||
return reinterpret_cast<u64>(a.mesh->GetMaterial()) <
|
||||
reinterpret_cast<u64>(b.mesh->GetMaterial());
|
||||
if (byMaterial && a.material != b.material) {
|
||||
return reinterpret_cast<u64>(a.material) <
|
||||
reinterpret_cast<u64>(b.material);
|
||||
}
|
||||
return a.mesh->GetSlot() < b.mesh->GetSlot();
|
||||
});
|
||||
|
||||
for (const auto& b : view.batches) {
|
||||
const vk::Buffer vb = b.mesh->GetVertexBuffer();
|
||||
Material* mat = byMaterial ? b.mesh->GetMaterial() : nullptr;
|
||||
Material* mat = byMaterial ? b.material : nullptr;
|
||||
|
||||
bool newGroup = view.indirectGroups.empty();
|
||||
if (!newGroup) {
|
||||
|
||||
Reference in New Issue
Block a user