#include "MetaCoreRendering/MetaCoreRendering.h" #include "MetaCoreFoundation/MetaCoreArchive.h" #include "MetaCoreFoundation/MetaCoreHash.h" #include #include #include #include #include #include #include #include #include #include namespace MetaCore { namespace { template bool WriteDocument(const std::filesystem::path& path, const T& document, const MetaCoreTypeRegistry& registry) { const auto bytes = MetaCoreSerializeToBytes(document, registry); if (!bytes) return false; std::error_code error; std::filesystem::create_directories(path.parent_path(), error); std::ofstream output(path, std::ios::binary | std::ios::trunc); if (!output) return false; output.write(reinterpret_cast(bytes->data()), static_cast(bytes->size())); return output.good(); } template std::optional ReadDocument(const std::filesystem::path& path, const MetaCoreTypeRegistry& registry) { std::ifstream input(path, std::ios::binary | std::ios::ate); if (!input) return std::nullopt; const auto end = input.tellg(); if (end < 0) return std::nullopt; std::vector bytes(static_cast(end)); input.seekg(0); if (!bytes.empty() && !input.read(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) return std::nullopt; T value{}; if (!MetaCoreDeserializeFromBytes(std::span(bytes.data(), bytes.size()), value, registry)) return std::nullopt; return value; } bool IsFinite(float value) { return std::isfinite(value); } } MetaCoreRenderSettingsDocument MetaCoreBuildDefaultRenderSettings() { return {}; } bool MetaCoreValidateRenderSettings(MetaCoreRenderSettingsDocument& document, std::vector* issues) { bool valid = true; const auto report = [&](std::string issue) { valid = false; if (issues) issues->push_back(std::move(issue)); }; if (document.Version == 0U || document.Version > MetaCoreRenderingSettingsVersion) { report("不支持的渲染设置版本"); document.Version = MetaCoreRenderingSettingsVersion; } if (document.DirectionalCascadeCount != 1U && document.DirectionalCascadeCount != 2U && document.DirectionalCascadeCount != 4U) { report("级联阴影数量必须为 1、2 或 4"); document.DirectionalCascadeCount = 4U; } if (!IsFinite(document.DirectionalShadowDistance) || document.DirectionalShadowDistance <= 0.0F) { report("方向光阴影距离必须为正有限值"); document.DirectionalShadowDistance = 150.0F; } if (document.DirectionalCascadeSplits.size() != document.DirectionalCascadeCount) { report("级联分割数量与级联数量不一致"); document.DirectionalCascadeSplits.resize(document.DirectionalCascadeCount); for (std::uint32_t i=0;i(i+1U)/static_cast(document.DirectionalCascadeCount); } float previous = 0.0F; for (float& split : document.DirectionalCascadeSplits) { if (!IsFinite(split) || split <= previous || split > 1.0F) { report("级联分割必须递增且位于 (0,1]"); split = std::min(1.0F, previous + 1.0F/static_cast(document.DirectionalCascadeCount)); } previous = split; } if (!document.DirectionalCascadeSplits.empty()) document.DirectionalCascadeSplits.back() = 1.0F; if (document.ShadowAtlasSize != 1024U && document.ShadowAtlasSize != 2048U && document.ShadowAtlasSize != 4096U && document.ShadowAtlasSize != 8192U) { report("阴影图集必须为 1024/2048/4096/8192"); document.ShadowAtlasSize = 4096U; } if (document.MaxShadowCastingPointSpotLights > 64U) { report("点光/聚光阴影预算不能超过 64"); document.MaxShadowCastingPointSpotLights = 64U; } if (document.MsaaSamples != 1U && document.MsaaSamples != 2U && document.MsaaSamples != 4U && document.MsaaSamples != 8U) { report("MSAA 必须为 1/2/4/8"); document.MsaaSamples = 1U; } if (document.OcclusionGraceFrames > 8U) { report("遮挡宽限帧不能超过 8"); document.OcclusionGraceFrames = 8U; } if (document.GlobalParticleBudget == 0U || document.GlobalParticleBudget > 1000000U) { report("全局粒子预算必须位于 [1,1000000]"); document.GlobalParticleBudget = 100000U; } if (document.DynamicLinePointBudget == 0U) { report("动态线点预算必须大于零"); document.DynamicLinePointBudget = 262144U; } return valid; } bool MetaCoreValidatePostProcessProfile(MetaCorePostProcessProfileDocument& document, std::vector* issues) { bool valid = true; const auto report=[&](std::string issue){valid=false;if(issues)issues->push_back(std::move(issue));}; if (document.Version == 0U || document.Version > MetaCorePostProcessProfileVersion) { report("不支持的后处理 Profile 版本"); document.Version=MetaCorePostProcessProfileVersion; } if (!IsFinite(document.ManualEv100)) { report("手动曝光必须为有限值"); document.ManualEv100=15.0F; } if (!IsFinite(document.AutoExposureMinEv100) || !IsFinite(document.AutoExposureMaxEv100) || document.AutoExposureMinEv100 > document.AutoExposureMaxEv100) { report("自动曝光范围无效"); document.AutoExposureMinEv100=-4.0F;document.AutoExposureMaxEv100=16.0F; } if (!IsFinite(document.BloomStrength) || document.BloomStrength < 0.0F) { report("Bloom 强度不能为负"); document.BloomStrength=0.1F; } if (document.MsaaSamples!=1U&&document.MsaaSamples!=2U&&document.MsaaSamples!=4U&&document.MsaaSamples!=8U) { report("Profile MSAA 必须为 1/2/4/8"); document.MsaaSamples=1U; } document.Saturation=std::clamp(document.Saturation,0.0F,2.0F); document.Contrast=std::clamp(document.Contrast,0.0F,2.0F); return valid; } bool MetaCoreValidateEnvironmentProfile(MetaCoreEnvironmentProfileDocument& document, std::vector* issues) { bool valid=true;const auto report=[&](std::string issue){valid=false;if(issues)issues->push_back(std::move(issue));}; if(document.Version==0U||document.Version>MetaCoreEnvironmentProfileVersion){report("不支持的环境 Profile 版本");document.Version=MetaCoreEnvironmentProfileVersion;} if(!IsFinite(document.IndirectLightIntensity)||document.IndirectLightIntensity<0.0F){report("环境光强度无效");document.IndirectLightIntensity=30000.0F;} if(!IsFinite(document.SkyboxIntensity)||document.SkyboxIntensity<0.0F){report("天空盒强度无效");document.SkyboxIntensity=30000.0F;} if(!IsFinite(document.RotationDegrees)){report("环境旋转无效");document.RotationDegrees=0.0F;} document.RotationDegrees=std::fmod(document.RotationDegrees,360.0F);if(document.RotationDegrees<0.0F)document.RotationDegrees+=360.0F; return valid; } bool MetaCoreWriteRenderSettings(const std::filesystem::path& path, const MetaCoreRenderSettingsDocument& document, const MetaCoreTypeRegistry& registry) { MetaCoreRenderSettingsDocument value=document;if(!MetaCoreValidateRenderSettings(value))return false;return WriteDocument(path,value,registry); } std::optional MetaCoreReadRenderSettings(const std::filesystem::path& path, const MetaCoreTypeRegistry& registry) { auto value=ReadDocument(path,registry);if(!value)return std::nullopt;if(value->Version>MetaCoreRenderingSettingsVersion)return std::nullopt;(void)MetaCoreValidateRenderSettings(*value);return value; } bool MetaCoreWriteEnvironmentProfile(const std::filesystem::path& path,const MetaCoreEnvironmentProfileDocument& document,const MetaCoreTypeRegistry& registry){auto value=document;if(!value.AssetGuid.IsValid()||!MetaCoreValidateEnvironmentProfile(value))return false;return WriteDocument(path,value,registry);} std::optional MetaCoreReadEnvironmentProfile(const std::filesystem::path& path,const MetaCoreTypeRegistry& registry){auto value=ReadDocument(path,registry);if(!value||value->Version>MetaCoreEnvironmentProfileVersion||!value->AssetGuid.IsValid())return std::nullopt;(void)MetaCoreValidateEnvironmentProfile(*value);return value;} bool MetaCoreWritePostProcessProfile(const std::filesystem::path& path,const MetaCorePostProcessProfileDocument& document,const MetaCoreTypeRegistry& registry){auto value=document;if(!value.AssetGuid.IsValid()||!MetaCoreValidatePostProcessProfile(value))return false;return WriteDocument(path,value,registry);} std::optional MetaCoreReadPostProcessProfile(const std::filesystem::path& path,const MetaCoreTypeRegistry& registry){auto value=ReadDocument(path,registry);if(!value||value->Version>MetaCorePostProcessProfileVersion||!value->AssetGuid.IsValid())return std::nullopt;(void)MetaCoreValidatePostProcessProfile(*value);return value;} std::int32_t MetaCoreSelectLod(const MetaCoreLodGroupComponent& group, float screenHeight, std::int32_t previousLevel) { if(!group.Enabled||group.Levels.empty())return -1; screenHeight=std::max(0.0F,screenHeight); std::int32_t selected=group.CullBelowLastLevel?-1:static_cast(group.Levels.size()-1U); for(std::size_t i=0;i=group.Levels[i].ScreenHeight){selected=static_cast(i);break;} if(previousLevel>=0&&static_cast(previousLevel)(boundaryLevel)].ScreenHeight;if(std::abs(screenHeight-boundary)<=std::max(0.0F,group.Hysteresis))selected=previousLevel;} return selected; } std::vector MetaCoreAllocateShadowBudget(const std::vector& candidates, const MetaCoreRenderSettingsDocument& settings) { std::vector eligible;for(const auto& candidate:candidates)if(candidate.Enabled&&candidate.CastShadows)eligible.push_back(candidate); std::stable_sort(eligible.begin(),eligible.end(),[](const auto& a,const auto& b){if(a.Type==MetaCoreLightType::Directional&&b.Type!=MetaCoreLightType::Directional)return true;if(a.Type!=MetaCoreLightType::Directional&&b.Type==MetaCoreLightType::Directional)return false;if(a.Priority!=b.Priority)return a.Priority>b.Priority;if(a.DistanceToCamera!=b.DistanceToCamera)return a.DistanceToCamera result;std::uint32_t localCount=0U;for(const auto& candidate:eligible){if(candidate.Type!=MetaCoreLightType::Directional&&localCount>=settings.MaxShadowCastingPointSpotLights)continue;result.push_back(candidate.ObjectId);if(candidate.Type!=MetaCoreLightType::Directional)++localCount;}return result; } std::uint32_t MetaCoreAllocateParticleBudget(std::uint32_t requested,std::uint32_t emitterMaximum,std::uint32_t globalRemaining){return std::min({requested,emitterMaximum,globalRemaining});} std::vector MetaCoreSelectReflectionProbes(const std::vector& candidates,const glm::vec3& worldPosition){ struct Match{const MetaCoreReflectionProbeCandidate* Candidate=nullptr;float Weight=0.0F;float Distance=0.0F;};std::vector matches; for(const auto& candidate:candidates){const auto& probe=candidate.Probe;if(!probe.Enabled||!probe.BakedCubemapGuid.IsValid())continue;const glm::vec3 offset=worldPosition-candidate.Position;float distance=0.0F;float edge=0.0F; if(probe.Shape==MetaCoreReflectionProbeShape::Sphere){distance=glm::length(offset);if(distance>std::max(0.0F,probe.Radius))continue;edge=probe.Radius-distance;} else{const glm::vec3 extents=glm::max(glm::abs(probe.Extents),glm::vec3(0.0001F));const glm::vec3 outside=glm::max(glm::abs(offset)-extents,glm::vec3(0.0F));if(glm::dot(outside,outside)>0.0F)continue;distance=glm::length(offset);const glm::vec3 inside=extents-glm::abs(offset);edge=std::min({inside.x,inside.y,inside.z});} const float weight=probe.BlendDistance<=0.0F?1.0F:std::clamp(edge/probe.BlendDistance,0.0F,1.0F);matches.push_back({&candidate,weight,distance});} std::stable_sort(matches.begin(),matches.end(),[](const Match& lhs,const Match& rhs){if(lhs.Candidate->Probe.Priority!=rhs.Candidate->Probe.Priority)return lhs.Candidate->Probe.Priority>rhs.Candidate->Probe.Priority;if(lhs.Weight!=rhs.Weight)return lhs.Weight>rhs.Weight;if(lhs.Distance!=rhs.Distance)return lhs.DistanceObjectIdObjectId;}); if(matches.size()>2U)matches.resize(2U);float total=0.0F;for(const auto& match:matches)total+=match.Weight;if(total<=0.0F&&!matches.empty())total=static_cast(matches.size());std::vector result;for(const auto& match:matches)result.push_back({match.Candidate->ObjectId,match.Candidate->Probe.BakedCubemapGuid,total>0.0F?(match.Weight>0.0F?match.Weight:1.0F)/total:0.0F});return result; } bool MetaCoreOcclusionHistory::IsVisible(MetaCoreId objectId,bool occlusionQueryVisible,bool cameraCut,bool dynamicObject,bool newlyVisible,std::uint32_t graceFrames){auto& entry=Entries_[objectId];entry.LastTouchedFrame=FrameIndex_;if(cameraCut||dynamicObject||newlyVisible)entry.GraceRemaining=graceFrames;if(occlusionQueryVisible){entry.GraceRemaining=graceFrames;return true;}if(entry.GraceRemaining>0U){--entry.GraceRemaining;return true;}return false;} void MetaCoreOcclusionHistory::BeginFrame(){++FrameIndex_;for(auto iterator=Entries_.begin();iterator!=Entries_.end();){if(FrameIndex_-iterator->second.LastTouchedFrame>120U)iterator=Entries_.erase(iterator);else ++iterator;}} void MetaCoreOcclusionHistory::Reset(){Entries_.clear();FrameIndex_=0U;} std::vector MetaCoreBuildInstanceBatches(const std::vector& candidates,bool instancingEnabled){ struct Key{MetaCoreAssetGuid Mesh{};std::vector Materials{};std::uint64_t Parameters=0U,State=0U,Object=0U;bool operator==(const Key&)const=default;}; struct Hasher{std::size_t operator()(const Key& key)const noexcept{std::size_t hash=MetaCoreAssetGuidHasher{}(key.Mesh);const auto combine=[&](std::size_t value){hash^=value+0x9e3779b97f4a7c15ULL+(hash<<6U)+(hash>>2U);};for(const auto& material:key.Materials)combine(MetaCoreAssetGuidHasher{}(material));combine(std::hash{}(key.Parameters));combine(std::hash{}(key.State));combine(std::hash{}(key.Object));return hash;}}; std::unordered_map indices;indices.reserve(candidates.size());std::vector result;result.reserve(candidates.size()); for(const auto& candidate:candidates){if(!candidate.Visible)continue;Key key{candidate.MeshAssetGuid,candidate.MaterialAssetGuids,candidate.ParameterHash,candidate.RenderStateHash,instancingEnabled?0U:candidate.ObjectId};const auto [iterator,inserted]=indices.emplace(key,result.size());if(inserted)result.push_back({candidate.MeshAssetGuid,candidate.MaterialAssetGuids,candidate.ParameterHash,candidate.RenderStateHash,{}});result[iterator->second].ObjectIds.push_back(candidate.ObjectId);}result.shrink_to_fit();return result; } void MetaCoreAccumulateVisibilityResult(MetaCoreVisibilityStatistics& statistics,MetaCoreVisibilityResult result,std::uint64_t triangleCount){++statistics.Submitted;switch(result){case MetaCoreVisibilityResult::Visible:++statistics.Visible;statistics.VisibleTriangleCount+=triangleCount;break;case MetaCoreVisibilityResult::LayerCulled:++statistics.LayerCulled;break;case MetaCoreVisibilityResult::DistanceCulled:++statistics.DistanceCulled;break;case MetaCoreVisibilityResult::FrustumCulled:++statistics.FrustumCulled;break;case MetaCoreVisibilityResult::LodCulled:++statistics.LodCulled;break;case MetaCoreVisibilityResult::OcclusionCulled:++statistics.OcclusionCulled;break;}} float MetaCoreCpuParticleEmitter::NextRandom(){RandomState_=RandomState_*1664525U+1013904223U;return static_cast(RandomState_>>8U)*(1.0F/16777216.0F);} void MetaCoreCpuParticleEmitter::Reset(std::uint32_t seed){Particles_.clear();RandomState_=seed==0U?1U:seed;DroppedParticleCount_=0U;EmissionAccumulator_=0.0F;Elapsed_=0.0F;BurstEmitted_=false;} void MetaCoreCpuParticleEmitter::Spawn(const MetaCoreParticleEmitterComponent& component){MetaCoreParticleState particle;particle.Lifetime=std::max(0.001F,component.Lifetime);particle.Size=std::max(0.0F,component.StartSize);particle.Color=component.StartColor;glm::vec3 direction{NextRandom()*2.0F-1.0F,NextRandom()*2.0F-1.0F,NextRandom()*2.0F-1.0F};if(glm::dot(direction,direction)<0.0001F)direction={0.0F,0.0F,1.0F};direction=glm::normalize(direction);if(component.Shape==MetaCoreParticleEmitterShape::Box)particle.Position={NextRandom()-0.5F,NextRandom()-0.5F,NextRandom()-0.5F};else if(component.Shape==MetaCoreParticleEmitterShape::Sphere)particle.Position=direction*NextRandom()*0.5F;else if(component.Shape==MetaCoreParticleEmitterShape::Cone)direction=glm::normalize(glm::vec3((NextRandom()-0.5F)*0.6F,(NextRandom()-0.5F)*0.6F,1.0F));particle.Velocity=direction*component.StartSpeed;Particles_.push_back(particle);} void MetaCoreCpuParticleEmitter::Simulate(const MetaCoreParticleEmitterComponent& component,float deltaSeconds,std::uint32_t globalRemaining){if(deltaSeconds<=0.0F)return;for(auto& particle:Particles_){particle.Age+=deltaSeconds;particle.Velocity+=component.Gravity*deltaSeconds;particle.Position+=particle.Velocity*deltaSeconds;const float t=std::clamp(particle.Age/particle.Lifetime,0.0F,1.0F);particle.Color=component.StartColor+(component.EndColor-component.StartColor)*t;particle.Size=component.StartSize+(component.EndSize-component.StartSize)*t;}std::erase_if(Particles_,[](const auto& particle){return particle.Age>=particle.Lifetime;});if(!component.Enabled)return;Elapsed_+=deltaSeconds;const bool emitting=component.Loop||Elapsed_<=std::max(0.0F,component.Duration);std::uint32_t requested=0U;if(emitting){EmissionAccumulator_+=std::max(0.0F,component.EmissionRate)*deltaSeconds;requested=static_cast(EmissionAccumulator_);EmissionAccumulator_-=static_cast(requested);if(!BurstEmitted_){requested+=component.BurstCount;BurstEmitted_=true;}}const std::uint32_t capacity=component.MaxParticles>Particles_.size()?component.MaxParticles-static_cast(Particles_.size()):0U;const std::uint32_t allocated=MetaCoreAllocateParticleBudget(requested,capacity,globalRemaining);for(std::uint32_t index=0;index0.0F&&Elapsed_>=component.Duration){Elapsed_=std::fmod(Elapsed_,component.Duration);BurstEmitted_=false;}} const MetaCorePostProcessProfileDocument& MetaCoreResolvePostProcessProfile(const MetaCorePostProcessProfileDocument& engineDefault,const MetaCorePostProcessProfileDocument* sceneProfile,const MetaCorePostProcessProfileDocument* cameraOverride){if(cameraOverride)return *cameraOverride;if(sceneProfile)return *sceneProfile;return engineDefault;} MetaCoreMaterialTemplateRegistry::MetaCoreMaterialTemplateRegistry() { const auto floatParameter=[](std::string name,float value,float minimum,float maximum){MetaCoreMaterialTemplateParameter parameter;parameter.Name=name;parameter.Type=MetaCoreMaterialParameterType::Float;parameter.Minimum=minimum;parameter.Maximum=maximum;parameter.DefaultValue.Name=parameter.Name;parameter.DefaultValue.Type=parameter.Type;parameter.DefaultValue.FloatValue=value;return parameter;}; const auto colorParameter=[](std::string name,glm::vec3 value){MetaCoreMaterialTemplateParameter parameter;parameter.Name=name;parameter.Type=MetaCoreMaterialParameterType::Color;parameter.DefaultValue.Name=parameter.Name;parameter.DefaultValue.Type=parameter.Type;parameter.DefaultValue.VectorValue=value;return parameter;}; const auto textureParameter=[](std::string name){MetaCoreMaterialTemplateParameter parameter;parameter.Name=name;parameter.Type=MetaCoreMaterialParameterType::Texture;parameter.DefaultValue.Name=parameter.Name;parameter.DefaultValue.Type=parameter.Type;return parameter;}; Templates_.push_back({"metacore.pbr",1U,"pbr.filamat",{colorParameter("baseColor",{1.0F,1.0F,1.0F}),textureParameter("baseColorTexture"),floatParameter("metallic",0.0F,0.0F,1.0F),floatParameter("roughness",1.0F,0.0F,1.0F),textureParameter("normalTexture"),textureParameter("emissiveTexture")}}); Templates_.push_back({"metacore.unlit",1U,"unlit.filamat",{colorParameter("color",{1.0F,1.0F,1.0F}),textureParameter("texture")}}); Templates_.push_back({"metacore.transparent",1U,"transparent.filamat",{colorParameter("color",{1.0F,1.0F,1.0F}),textureParameter("texture"),floatParameter("opacity",1.0F,0.0F,1.0F)}}); Templates_.push_back({"metacore.decal",1U,"decal.filamat",{colorParameter("baseColor",{1.0F,1.0F,1.0F}),textureParameter("baseColorTexture"),textureParameter("normalTexture"),floatParameter("opacity",1.0F,0.0F,1.0F)}}); Templates_.push_back({"metacore.terrain",1U,"terrain.filamat",{textureParameter("heightmap"),textureParameter("splatMap")}}); Templates_.push_back({"metacore.particle",1U,"particle.filamat",{textureParameter("texture"),colorParameter("tint",{1.0F,1.0F,1.0F})}}); Templates_.push_back({"metacore.line",1U,"line.filamat",{textureParameter("texture"),colorParameter("tint",{1.0F,1.0F,1.0F})}}); } const MetaCoreMaterialTemplateDescriptor* MetaCoreMaterialTemplateRegistry::Find(std::string_view templateId) const {const auto iterator=std::find_if(Templates_.begin(),Templates_.end(),[&](const auto& value){return value.TemplateId==templateId;});return iterator==Templates_.end()?nullptr:&*iterator;} bool MetaCoreMaterialTemplateRegistry::ValidateAndMigrate(MetaCoreMaterialAssetDocument& material,std::vector* issues) const { if(material.TemplateId.empty()){material.TemplateId=material.ShaderModel==MetaCoreMaterialShaderModel::PbrMetalRough?"metacore.pbr":"metacore.unlit";} const auto* descriptor=Find(material.TemplateId);if(!descriptor){if(issues)issues->push_back("未知材质模板: "+material.TemplateId);return false;} if(material.TemplateVersion>descriptor->Version){if(issues)issues->push_back("材质模板版本高于当前引擎: "+material.TemplateId+"@"+std::to_string(material.TemplateVersion));return false;} bool valid=true;std::vector migrated;std::unordered_set used; for(const auto& schema:descriptor->Parameters){const auto existing=std::find_if(material.Parameters.begin(),material.Parameters.end(),[&](const auto& parameter){return parameter.Name==schema.Name&¶meter.Type==schema.Type;});auto value=existing==material.Parameters.end()?schema.DefaultValue:*existing;if(existing==material.Parameters.end()&&issues)issues->push_back("补充材质参数默认值: "+schema.Name);if(schema.Type==MetaCoreMaterialParameterType::Float)value.FloatValue=std::clamp(value.FloatValue,schema.Minimum,schema.Maximum);migrated.push_back(std::move(value));used.insert(schema.Name);} for(const auto& parameter:material.Parameters)if(!used.contains(parameter.Name)){valid=false;if(issues)issues->push_back("模板不接受材质参数: "+parameter.Name);} material.Parameters=std::move(migrated);material.TemplateVersion=descriptor->Version;return valid; } namespace { MetaCoreRenderBounds MergeBounds(const MetaCoreRenderBounds& a, const MetaCoreRenderBounds& b) { return {glm::min(a.Minimum, b.Minimum), glm::max(a.Maximum, b.Maximum)}; } MetaCoreRenderBounds BoundsForRange(const std::vector& items, std::uint32_t first, std::uint32_t count) { MetaCoreRenderBounds result{glm::vec3(std::numeric_limits::max()), glm::vec3(std::numeric_limits::lowest())}; for (std::uint32_t index = first; index < first + count; ++index) result = MergeBounds(result, items[index].Bounds); return result; } glm::vec3 TransformPoint(const glm::mat4& matrix, const glm::vec3& point) { const glm::vec4 transformed = matrix * glm::vec4(point, 1.0F); return glm::vec3(transformed) / std::max(0.000001F, std::abs(transformed.w)); } } void MetaCoreStaticVisibilityBvh::Build(std::vector items) { Items_ = std::move(items); Nodes_.clear(); if (!Items_.empty()) (void)BuildNode(0U, static_cast(Items_.size())); } std::int32_t MetaCoreStaticVisibilityBvh::BuildNode(std::uint32_t first, std::uint32_t count) { const std::int32_t nodeIndex = static_cast(Nodes_.size()); Nodes_.push_back({BoundsForRange(Items_, first, count), first, count, -1, -1}); if (count <= 8U) return nodeIndex; const glm::vec3 extent = Nodes_[static_cast(nodeIndex)].Bounds.Extents(); const int axis = extent.y > extent.x ? (extent.z > extent.y ? 2 : 1) : (extent.z > extent.x ? 2 : 0); const std::uint32_t middle = first + count / 2U; std::nth_element(Items_.begin() + first, Items_.begin() + middle, Items_.begin() + first + count, [axis](const MetaCoreBvhItem& lhs, const MetaCoreBvhItem& rhs) { if (lhs.Bounds.Center()[axis] != rhs.Bounds.Center()[axis]) return lhs.Bounds.Center()[axis] < rhs.Bounds.Center()[axis]; return lhs.ObjectId < rhs.ObjectId; }); const std::int32_t left = BuildNode(first, middle - first); const std::int32_t right = BuildNode(middle, first + count - middle); Nodes_[static_cast(nodeIndex)].Left = left; Nodes_[static_cast(nodeIndex)].Right = right; return nodeIndex; } std::vector MetaCoreStaticVisibilityBvh::Query( const MetaCoreViewFrustum& frustum, const glm::vec3& cameraPosition, std::uint32_t cameraMask, MetaCoreVisibilityStatistics* statistics) const { std::vector visible; if (Nodes_.empty()) return visible; std::vector stack{0}; while (!stack.empty()) { const Node& node = Nodes_[static_cast(stack.back())]; stack.pop_back(); if (!MetaCoreIntersectsFrustum(frustum, node.Bounds)) { if (statistics) for (std::uint32_t index=0; index= 0) { stack.push_back(node.Right); stack.push_back(node.Left); continue; } for (std::uint32_t offset = 0U; offset < node.Count; ++offset) { const MetaCoreBvhItem& item = Items_[node.First + offset]; if ((item.LayerMask & cameraMask) == 0U) { if (statistics) MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::LayerCulled); continue; } if (item.MaxDrawDistance > 0.0F && glm::distance(cameraPosition, item.Bounds.Center()) > item.MaxDrawDistance) { if (statistics) MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::DistanceCulled); continue; } if (!MetaCoreIntersectsFrustum(frustum, item.Bounds)) { if (statistics) MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::FrustumCulled); continue; } visible.push_back(item.ObjectId); } } std::sort(visible.begin(), visible.end()); return visible; } MetaCoreViewFrustum MetaCoreExtractFrustum(const glm::mat4& matrix) { const auto row = [&matrix](int index) { return glm::vec4(matrix[0][index], matrix[1][index], matrix[2][index], matrix[3][index]); }; const glm::vec4 r0=row(0), r1=row(1), r2=row(2), r3=row(3); const std::array raw{r3+r0, r3-r0, r3+r1, r3-r1, r3+r2, r3-r2}; MetaCoreViewFrustum result; for (std::size_t index=0; index linearDepth) { Reset(); if (width==0U || height==0U || linearDepth.size()!=static_cast(width)*height) return false; Mips_.push_back({width,height,std::vector(linearDepth.begin(),linearDepth.end())}); while (width>1U || height>1U) { const Mip& source=Mips_.back(); width=std::max(1U,(source.Width+1U)/2U); height=std::max(1U,(source.Height+1U)/2U); Mip target{width,height,std::vector(static_cast(width)*height,0.0F)}; for(std::uint32_t y=0;y(sy)*source.Width+sx]); } target.Depth[static_cast(y)*width+x]=farthest; } Mips_.push_back(std::move(target)); } return true; } bool MetaCoreConservativeHzb::IsVisible(const glm::vec4& inputBounds, float nearestLinearDepth) const { if(Mips_.empty() || !std::isfinite(nearestLinearDepth) || nearestLinearDepth<=0.0F) return true; const glm::vec4 bounds{std::clamp(inputBounds.x,0.0F,1.0F),std::clamp(inputBounds.y,0.0F,1.0F),std::clamp(inputBounds.z,0.0F,1.0F),std::clamp(inputBounds.w,0.0F,1.0F)}; if(bounds.z<=bounds.x || bounds.w<=bounds.y) return true; const float pixelWidth=(bounds.z-bounds.x)*static_cast(Mips_[0].Width); const float pixelHeight=(bounds.w-bounds.y)*static_cast(Mips_[0].Height); const std::uint32_t mip=std::min(static_cast(Mips_.size()-1U), static_cast(std::max(0.0F,std::floor(std::log2(std::max(1.0F,std::max(pixelWidth,pixelHeight))))))); const Mip& level=Mips_[mip]; const std::uint32_t minX=std::min(level.Width-1U,static_cast(bounds.x*level.Width)); const std::uint32_t maxX=std::min(level.Width-1U,static_cast(bounds.z*level.Width)); const std::uint32_t minY=std::min(level.Height-1U,static_cast(bounds.y*level.Height)); const std::uint32_t maxY=std::min(level.Height-1U,static_cast(bounds.w*level.Height)); float farthest=0.0F; for(std::uint32_t y=minY;y<=maxY;++y)for(std::uint32_t x=minX;x<=maxX;++x)farthest=std::max(farthest,level.Depth[static_cast(y)*level.Width+x]); return nearestLinearDepth<=farthest+0.001F; } void MetaCoreConservativeHzb::Reset(){Mips_.clear();} MetaCoreDynamicGeometry MetaCoreBuildParticleBillboards( std::span particles, const glm::vec3& cameraRight, const glm::vec3& cameraUp, const glm::mat4& emitterWorldMatrix) { MetaCoreDynamicGeometry geometry; geometry.Vertices.reserve(particles.size()*4U);geometry.Indices.reserve(particles.size()*6U); const glm::vec3 right=glm::normalize(cameraRight), up=glm::normalize(cameraUp); for(const MetaCoreParticleState& particle:particles){ const glm::vec3 center=TransformPoint(emitterWorldMatrix,particle.Position);const float half=std::max(0.0F,particle.Size)*0.5F; const glm::vec4 color{particle.Color, std::clamp(1.0F-particle.Age/std::max(0.001F,particle.Lifetime),0.0F,1.0F)}; const std::uint32_t base=static_cast(geometry.Vertices.size()); geometry.Vertices.push_back({center-right*half-up*half,color,{0,0}});geometry.Vertices.push_back({center+right*half-up*half,color,{1,0}}); geometry.Vertices.push_back({center+right*half+up*half,color,{1,1}});geometry.Vertices.push_back({center-right*half+up*half,color,{0,1}}); geometry.Indices.insert(geometry.Indices.end(),{base,base+1U,base+2U,base,base+2U,base+3U}); } return geometry; } MetaCoreDynamicGeometry MetaCoreBuildLineRibbon(std::span points,float width,const glm::vec4& startColor,const glm::vec4& endColor,const glm::vec3& cameraForward,std::uint32_t pointBudget){ MetaCoreDynamicGeometry geometry;const std::size_t count=std::min(points.size(),pointBudget);if(count<2U||width<=0.0F)return geometry; geometry.Vertices.reserve(count*2U);geometry.Indices.reserve((count-1U)*6U);const glm::vec3 view=glm::normalize(cameraForward); for(std::size_t index=0;index(index)/static_cast(count-1U);const glm::vec4 color=startColor+(endColor-startColor)*t;geometry.Vertices.push_back({points[index]-side,color,{0,t}});geometry.Vertices.push_back({points[index]+side,color,{1,t}});} for(std::uint32_t index=0;index+1U heights,std::uint32_t width,std::uint32_t height,const glm::vec3& worldSize,std::uint32_t sampleStep){ MetaCoreDynamicGeometry geometry;if(width<2U||height<2U||heights.size()!=static_cast(width)*height)return geometry;sampleStep=std::max(1U,sampleStep); std::vector xs,ys;for(std::uint32_t x=0;x(x)/static_cast(width-1U),v=static_cast(y)/static_cast(height-1U),h=static_cast(heights[static_cast(y)*width+x])/65535.0F;geometry.Vertices.push_back({{(u-0.5F)*worldSize.x,(v-0.5F)*worldSize.y,h*worldSize.z},{h,h,h,1},{u,v}});} for(std::uint32_t y=0;y+1U(xs.size())+x,b=a+1U,c=a+static_cast(xs.size()),d=c+1U;geometry.Indices.insert(geometry.Indices.end(),{a,b,d,a,d,c});}return geometry; } void MetaCoreTrailHistory::Update(const glm::vec3& position,float deltaSeconds,float lifetime,float minimumDistance,std::uint32_t maxPoints){for(auto& point:Points_)point.Age+=std::max(0.0F,deltaSeconds);std::erase_if(Points_,[lifetime](const auto& point){return point.Age>std::max(0.0F,lifetime);});if(maxPoints==0U)return;if(Points_.empty()||glm::distance(Points_.back().Position,position)>=std::max(0.0F,minimumDistance))Points_.push_back({position,0.0F});if(Points_.size()>maxPoints)Points_.erase(Points_.begin(),Points_.begin()+static_cast(Points_.size()-maxPoints));} bool MetaCoreIsDecalVisible(const MetaCoreDecalProjectorComponent& decal,const glm::mat4& projectorWorld,const MetaCoreRenderBounds& receiverBounds,std::uint32_t receiverLayerMask,const glm::vec3& cameraPosition){if(!decal.Enabled||(decal.LayerMask&receiverLayerMask)==0U)return false;const glm::vec3 center=glm::vec3(projectorWorld[3]);if(decal.MaxDistance>0.0F&&glm::distance(center,cameraPosition)>decal.MaxDistance)return false;const glm::vec3 extents=glm::abs(decal.Size)*0.5F;const MetaCoreRenderBounds projector{center-extents,center+extents};return projector.Minimum.x<=receiverBounds.Maximum.x&&projector.Maximum.x>=receiverBounds.Minimum.x&&projector.Minimum.y<=receiverBounds.Maximum.y&&projector.Maximum.y>=receiverBounds.Minimum.y&&projector.Minimum.z<=receiverBounds.Maximum.z&&projector.Maximum.z>=receiverBounds.Minimum.z;} std::string MetaCoreBuildMaterialCookKey(const MetaCoreMaterialCookRequest& request){const std::string canonical=request.TemplateId+"\n"+std::to_string(request.TemplateVersion)+"\n"+request.SourceHash+"\n"+request.TargetPlatform+"\n"+request.GraphicsApi+"\n"+request.CompilerOptions;return MetaCoreSha256Bytes(std::as_bytes(std::span(canonical.data(),canonical.size())));} bool MetaCoreWriteReflectionProbeDerivedHeader(const std::filesystem::path& path,const MetaCoreReflectionProbeDerivedHeader& header){if(!header.ProbeGuid.IsValid()||header.Version!=MetaCoreReflectionProbeDerivedVersion)return false;std::error_code error;std::filesystem::create_directories(path.parent_path(),error);std::ofstream output(path,std::ios::trunc);if(!output)return false;output<<"MCRPROBE "< MetaCoreReadReflectionProbeDerivedHeader(const std::filesystem::path& path){std::ifstream input(path);std::string magic,guid;MetaCoreReflectionProbeDerivedHeader header;if(!(input>>magic>>header.Version>>guid>>std::quoted(header.SourceHash)>>std::quoted(header.IblRelativePath)>>std::quoted(header.SkyboxRelativePath))||magic!="MCRPROBE"||header.Version!=MetaCoreReflectionProbeDerivedVersion)return std::nullopt;const auto parsed=MetaCoreAssetGuid::Parse(guid);if(!parsed)return std::nullopt;header.ProbeGuid=*parsed;return header;} std::string_view MetaCoreRenderDebugViewName(MetaCoreRenderDebugView value){static constexpr std::array names{"lit","unlit","wireframe","overdraw","world-normal","albedo","roughness","metallic","shadow-cascades","lod","occlusion","reflection-probe"};const auto index=static_cast(value);return index MetaCoreParseRenderDebugView(std::string_view value){for(int index=0;index<=static_cast(MetaCoreRenderDebugView::ReflectionProbe);++index){const auto mode=static_cast(index);if(MetaCoreRenderDebugViewName(mode)==value)return mode;}return std::nullopt;} } // namespace MetaCore