349 lines
38 KiB
C++
349 lines
38 KiB
C++
#include "MetaCoreRendering/MetaCoreRendering.h"
|
|
|
|
#include "MetaCoreFoundation/MetaCoreArchive.h"
|
|
#include "MetaCoreFoundation/MetaCoreHash.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <tuple>
|
|
#include <unordered_set>
|
|
|
|
#include <glm/common.hpp>
|
|
#include <glm/geometric.hpp>
|
|
|
|
namespace MetaCore {
|
|
namespace {
|
|
template <typename T>
|
|
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<const char*>(bytes->data()), static_cast<std::streamsize>(bytes->size()));
|
|
return output.good();
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<T> 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<std::byte> bytes(static_cast<std::size_t>(end));
|
|
input.seekg(0);
|
|
if (!bytes.empty() && !input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()))) return std::nullopt;
|
|
T value{};
|
|
if (!MetaCoreDeserializeFromBytes(std::span<const std::byte>(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<std::string>* 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<document.DirectionalCascadeCount;++i) document.DirectionalCascadeSplits[i]=static_cast<float>(i+1U)/static_cast<float>(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<float>(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<std::string>* 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<std::string>* 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<MetaCoreRenderSettingsDocument> MetaCoreReadRenderSettings(const std::filesystem::path& path, const MetaCoreTypeRegistry& registry) { auto value=ReadDocument<MetaCoreRenderSettingsDocument>(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<MetaCoreEnvironmentProfileDocument> MetaCoreReadEnvironmentProfile(const std::filesystem::path& path,const MetaCoreTypeRegistry& registry){auto value=ReadDocument<MetaCoreEnvironmentProfileDocument>(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<MetaCorePostProcessProfileDocument> MetaCoreReadPostProcessProfile(const std::filesystem::path& path,const MetaCoreTypeRegistry& registry){auto value=ReadDocument<MetaCorePostProcessProfileDocument>(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<std::int32_t>(group.Levels.size()-1U);
|
|
for(std::size_t i=0;i<group.Levels.size();++i)if(screenHeight>=group.Levels[i].ScreenHeight){selected=static_cast<std::int32_t>(i);break;}
|
|
if(previousLevel>=0&&static_cast<std::size_t>(previousLevel)<group.Levels.size()&&selected!=previousLevel){const std::int32_t boundaryLevel=selected<0?previousLevel:std::min(selected,previousLevel);const float boundary=group.Levels[static_cast<std::size_t>(boundaryLevel)].ScreenHeight;if(std::abs(screenHeight-boundary)<=std::max(0.0F,group.Hysteresis))selected=previousLevel;}
|
|
return selected;
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreAllocateShadowBudget(const std::vector<MetaCoreShadowCandidate>& candidates, const MetaCoreRenderSettingsDocument& settings) {
|
|
std::vector<MetaCoreShadowCandidate> 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<b.DistanceToCamera;return a.ObjectId<b.ObjectId;});
|
|
std::vector<MetaCoreId> 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<MetaCoreReflectionProbeBlend> MetaCoreSelectReflectionProbes(const std::vector<MetaCoreReflectionProbeCandidate>& candidates,const glm::vec3& worldPosition){
|
|
struct Match{const MetaCoreReflectionProbeCandidate* Candidate=nullptr;float Weight=0.0F;float Distance=0.0F;};std::vector<Match> 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.Distance<rhs.Distance;return lhs.Candidate->ObjectId<rhs.Candidate->ObjectId;});
|
|
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<float>(matches.size());std::vector<MetaCoreReflectionProbeBlend> 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<MetaCoreInstanceBatch> MetaCoreBuildInstanceBatches(const std::vector<MetaCoreInstanceCandidate>& candidates,bool instancingEnabled){
|
|
struct Key{MetaCoreAssetGuid Mesh{};std::vector<MetaCoreAssetGuid> 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<std::uint64_t>{}(key.Parameters));combine(std::hash<std::uint64_t>{}(key.State));combine(std::hash<std::uint64_t>{}(key.Object));return hash;}};
|
|
std::unordered_map<Key,std::size_t,Hasher> indices;indices.reserve(candidates.size());std::vector<MetaCoreInstanceBatch> 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<float>(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<std::uint32_t>(EmissionAccumulator_);EmissionAccumulator_-=static_cast<float>(requested);if(!BurstEmitted_){requested+=component.BurstCount;BurstEmitted_=true;}}const std::uint32_t capacity=component.MaxParticles>Particles_.size()?component.MaxParticles-static_cast<std::uint32_t>(Particles_.size()):0U;const std::uint32_t allocated=MetaCoreAllocateParticleBudget(requested,capacity,globalRemaining);for(std::uint32_t index=0;index<allocated;++index)Spawn(component);DroppedParticleCount_+=requested-allocated;if(component.Loop&&component.Duration>0.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<std::string>* 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<MetaCoreMaterialParameterDocument> migrated;std::unordered_set<std::string> 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<MetaCoreBvhItem>& items, std::uint32_t first, std::uint32_t count) {
|
|
MetaCoreRenderBounds result{glm::vec3(std::numeric_limits<float>::max()), glm::vec3(std::numeric_limits<float>::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<MetaCoreBvhItem> items) {
|
|
Items_ = std::move(items);
|
|
Nodes_.clear();
|
|
if (!Items_.empty()) (void)BuildNode(0U, static_cast<std::uint32_t>(Items_.size()));
|
|
}
|
|
|
|
std::int32_t MetaCoreStaticVisibilityBvh::BuildNode(std::uint32_t first, std::uint32_t count) {
|
|
const std::int32_t nodeIndex = static_cast<std::int32_t>(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<std::size_t>(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<std::size_t>(nodeIndex)].Left = left;
|
|
Nodes_[static_cast<std::size_t>(nodeIndex)].Right = right;
|
|
return nodeIndex;
|
|
}
|
|
|
|
std::vector<MetaCoreId> MetaCoreStaticVisibilityBvh::Query(
|
|
const MetaCoreViewFrustum& frustum, const glm::vec3& cameraPosition,
|
|
std::uint32_t cameraMask, MetaCoreVisibilityStatistics* statistics) const {
|
|
std::vector<MetaCoreId> visible;
|
|
if (Nodes_.empty()) return visible;
|
|
std::vector<std::int32_t> stack{0};
|
|
while (!stack.empty()) {
|
|
const Node& node = Nodes_[static_cast<std::size_t>(stack.back())];
|
|
stack.pop_back();
|
|
if (!MetaCoreIntersectsFrustum(frustum, node.Bounds)) {
|
|
if (statistics) for (std::uint32_t index=0; index<node.Count; ++index)
|
|
MetaCoreAccumulateVisibilityResult(*statistics, MetaCoreVisibilityResult::FrustumCulled);
|
|
continue;
|
|
}
|
|
if (node.Left >= 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<glm::vec4, 6> raw{r3+r0, r3-r0, r3+r1, r3-r1, r3+r2, r3-r2};
|
|
MetaCoreViewFrustum result;
|
|
for (std::size_t index=0; index<raw.size(); ++index) {
|
|
const float length=glm::length(glm::vec3(raw[index]));
|
|
result.Planes[index]={glm::vec3(raw[index])/std::max(length,0.000001F), raw[index].w/std::max(length,0.000001F)};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool MetaCoreIntersectsFrustum(const MetaCoreViewFrustum& frustum, const MetaCoreRenderBounds& bounds) {
|
|
const glm::vec3 center=bounds.Center(), extents=bounds.Extents();
|
|
for (const MetaCoreFrustumPlane& plane : frustum.Planes) {
|
|
const float radius=glm::dot(glm::abs(plane.Normal), extents);
|
|
if (glm::dot(plane.Normal, center)+plane.Distance+radius < 0.0F) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MetaCoreConservativeHzb::Build(std::uint32_t width, std::uint32_t height, std::span<const float> linearDepth) {
|
|
Reset();
|
|
if (width==0U || height==0U || linearDepth.size()!=static_cast<std::size_t>(width)*height) return false;
|
|
Mips_.push_back({width,height,std::vector<float>(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<float>(static_cast<std::size_t>(width)*height,0.0F)};
|
|
for(std::uint32_t y=0;y<height;++y)for(std::uint32_t x=0;x<width;++x){
|
|
float farthest=0.0F;
|
|
for(std::uint32_t oy=0;oy<2U;++oy)for(std::uint32_t ox=0;ox<2U;++ox){
|
|
const std::uint32_t sx=std::min(source.Width-1U,x*2U+ox), sy=std::min(source.Height-1U,y*2U+oy);
|
|
farthest=std::max(farthest,source.Depth[static_cast<std::size_t>(sy)*source.Width+sx]);
|
|
}
|
|
target.Depth[static_cast<std::size_t>(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<float>(Mips_[0].Width);
|
|
const float pixelHeight=(bounds.w-bounds.y)*static_cast<float>(Mips_[0].Height);
|
|
const std::uint32_t mip=std::min<std::uint32_t>(static_cast<std::uint32_t>(Mips_.size()-1U),
|
|
static_cast<std::uint32_t>(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<std::uint32_t>(bounds.x*level.Width));
|
|
const std::uint32_t maxX=std::min(level.Width-1U,static_cast<std::uint32_t>(bounds.z*level.Width));
|
|
const std::uint32_t minY=std::min(level.Height-1U,static_cast<std::uint32_t>(bounds.y*level.Height));
|
|
const std::uint32_t maxY=std::min(level.Height-1U,static_cast<std::uint32_t>(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<std::size_t>(y)*level.Width+x]);
|
|
return nearestLinearDepth<=farthest+0.001F;
|
|
}
|
|
|
|
void MetaCoreConservativeHzb::Reset(){Mips_.clear();}
|
|
|
|
MetaCoreDynamicGeometry MetaCoreBuildParticleBillboards(
|
|
std::span<const MetaCoreParticleState> 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<std::uint32_t>(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<const glm::vec3> 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<std::size_t>(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<count;++index){const glm::vec3 tangent=glm::normalize(points[std::min(index+1U,count-1U)]-points[index==0U?0U:index-1U]);glm::vec3 side=glm::cross(view,tangent);if(glm::dot(side,side)<0.00001F)side=glm::cross(glm::vec3(0,0,1),tangent);side=glm::normalize(side)*width*0.5F;const float t=static_cast<float>(index)/static_cast<float>(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<count;++index){const std::uint32_t base=index*2U;geometry.Indices.insert(geometry.Indices.end(),{base,base+1U,base+3U,base,base+3U,base+2U});}return geometry;
|
|
}
|
|
|
|
MetaCoreDynamicGeometry MetaCoreBuildTerrainGeometry(std::span<const std::uint16_t> 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<std::size_t>(width)*height)return geometry;sampleStep=std::max(1U,sampleStep);
|
|
std::vector<std::uint32_t> xs,ys;for(std::uint32_t x=0;x<width-1U;x+=sampleStep)xs.push_back(x);xs.push_back(width-1U);for(std::uint32_t y=0;y<height-1U;y+=sampleStep)ys.push_back(y);ys.push_back(height-1U);
|
|
geometry.Vertices.reserve(xs.size()*ys.size());for(std::uint32_t y:ys)for(std::uint32_t x:xs){const float u=static_cast<float>(x)/static_cast<float>(width-1U),v=static_cast<float>(y)/static_cast<float>(height-1U),h=static_cast<float>(heights[static_cast<std::size_t>(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<ys.size();++y)for(std::uint32_t x=0;x+1U<xs.size();++x){const std::uint32_t a=y*static_cast<std::uint32_t>(xs.size())+x,b=a+1U,c=a+static_cast<std::uint32_t>(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<std::ptrdiff_t>(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 "<<header.Version<<' '<<header.ProbeGuid.ToString()<<' '<<std::quoted(header.SourceHash)<<' '<<std::quoted(header.IblRelativePath)<<' '<<std::quoted(header.SkyboxRelativePath)<<'\n';return output.good();}
|
|
std::optional<MetaCoreReflectionProbeDerivedHeader> 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<std::size_t>(value);return index<names.size()?names[index]:names[0];}
|
|
std::optional<MetaCoreRenderDebugView> MetaCoreParseRenderDebugView(std::string_view value){for(int index=0;index<=static_cast<int>(MetaCoreRenderDebugView::ReflectionProbe);++index){const auto mode=static_cast<MetaCoreRenderDebugView>(index);if(MetaCoreRenderDebugViewName(mode)==value)return mode;}return std::nullopt;}
|
|
|
|
} // namespace MetaCore
|