/**************************************************************************** Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. http://www.cocos.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "base/std/container/array.h" // #include "core/Director.h" #include "core/Root.h" #include "core/TypedArray.h" #include "core/assets/Material.h" #include "core/scene-graph/Scene.h" #include "core/scene-graph/SceneGlobals.h" #include "gfx-base/GFXTexture.h" #include "gi/light-probe/LightProbe.h" #include "gi/light-probe/SH.h" #include "profiler/Profiler.h" #include "renderer/pipeline/Define.h" #include "renderer/pipeline/InstancedBuffer.h" #include "renderer/pipeline/custom/RenderInterfaceTypes.h" #include "scene/Model.h" #include "scene/Pass.h" #include "scene/ReflectionProbe.h" #include "scene/ReflectionProbeManager.h" #include "scene/RenderScene.h" #include "scene/SubModel.h" namespace { const cc::gfx::SamplerInfo LIGHTMAP_SAMPLER_HASH{ cc::gfx::Filter::LINEAR, cc::gfx::Filter::LINEAR, cc::gfx::Filter::NONE, cc::gfx::Address::CLAMP, cc::gfx::Address::CLAMP, cc::gfx::Address::CLAMP, }; const cc::gfx::SamplerInfo LIGHTMAP_SAMPLER_WITH_MIP_HASH{ cc::gfx::Filter::LINEAR, cc::gfx::Filter::LINEAR, cc::gfx::Filter::LINEAR, cc::gfx::Address::CLAMP, cc::gfx::Address::CLAMP, cc::gfx::Address::CLAMP, }; const ccstd::vector SHADOW_MAP_PATCHES{{"CC_RECEIVE_SHADOW", true}}; const ccstd::vector LIGHT_PROBE_PATCHES{{"CC_USE_LIGHT_PROBE", true}}; const ccstd::string CC_USE_REFLECTION_PROBE = "CC_USE_REFLECTION_PROBE"; const ccstd::string CC_DISABLE_DIRECTIONAL_LIGHT = "CC_DISABLE_DIRECTIONAL_LIGHT"; const ccstd::vector STATIC_LIGHTMAP_PATHES{{"CC_USE_LIGHTMAP", 1}}; const ccstd::vector STATIONARY_LIGHTMAP_PATHES{{"CC_USE_LIGHTMAP", 2}}; const ccstd::vector HIGHP_LIGHTMAP_PATHES{{"CC_LIGHT_MAP_VERSION", 2}}; } // namespace namespace cc { namespace scene { Model::Model() { _device = Root::getInstance()->getDevice(); } Model::~Model() = default; void Model::initialize() { if (_inited) return; _receiveShadow = true; _castShadow = false; _enabled = true; _visFlags = Layers::Enum::NONE; _inited = true; _bakeToReflectionProbe = true; _reflectionProbeType = scene::UseReflectionProbeType::NONE; } void Model::destroy() { for (SubModel *subModel : _subModels) { CC_SAFE_DESTROY(subModel); } _subModels.clear(); CC_SAFE_DESTROY_NULL(_localBuffer); CC_SAFE_DESTROY_NULL(_localSHBuffer); CC_SAFE_DESTROY_NULL(_worldBoundBuffer); _worldBounds = nullptr; _modelBounds = nullptr; _inited = false; _localDataUpdated = true; _transform = nullptr; _node = nullptr; _isDynamicBatching = false; } void Model::updateTransform(uint32_t stamp) { CC_PROFILE(ModelUpdateTransform); if (isModelImplementedInJS()) { if (!_isCalledFromJS) { emit(stamp); _isCalledFromJS = false; return; } } Node *node = _transform; if (node->getChangedFlags() || node->isTransformDirty()) { node->updateWorldTransform(); _localDataUpdated = true; if (_modelBounds != nullptr && _modelBounds->isValid() && _worldBounds != nullptr) { _modelBounds->transform(node->getWorldMatrix(), _worldBounds); _worldBoundsDirty = true; } } } void Model::updateWorldBound() { Node *node = _transform; if (node) { node->updateWorldTransform(); _localDataUpdated = true; if (_modelBounds != nullptr && _modelBounds->isValid() && _worldBounds != nullptr) { _modelBounds->transform(node->getWorldMatrix(), _worldBounds); _worldBoundsDirty = true; } } } void Model::updateWorldBoundsForJSSkinningModel(const Vec3 &min, const Vec3 &max) { Node *node = _transform; if (node) { if (_modelBounds != nullptr && _modelBounds->isValid() && _worldBounds != nullptr) { geometry::AABB::fromPoints(min, max, _modelBounds); _modelBounds->transform(node->getWorldMatrix(), _worldBounds); _worldBoundsDirty = true; } } } void Model::updateWorldBoundsForJSBakedSkinningModel(geometry::AABB *aabb) { _worldBounds->center = aabb->center; _worldBounds->halfExtents = aabb->halfExtents; _worldBoundsDirty = true; } void Model::updateUBOs(uint32_t stamp) { CC_PROFILE(ModelUpdateUBOs); if (isModelImplementedInJS()) { if (!_isCalledFromJS) { emit(stamp); _isCalledFromJS = false; return; } } for (SubModel *subModel : _subModels) { subModel->update(); } _updateStamp = stamp; updateSHUBOs(); const auto *pipeline = Root::getInstance()->getPipeline(); const auto *shadowInfo = pipeline->getPipelineSceneData()->getShadows(); const auto forceUpdateUBO = shadowInfo->isEnabled() && shadowInfo->getType() == ShadowType::PLANAR; if (!_localDataUpdated) { return; } _localDataUpdated = false; getTransform()->updateWorldTransform(); const auto &worldMatrix = getTransform()->getWorldMatrix(); bool hasNonInstancingPass = false; for (const auto &subModel : _subModels) { const auto idx = subModel->getInstancedWorldMatrixIndex(); if (idx >= 0) { ccstd::vector &attrs = subModel->getInstancedAttributeBlock().views; subModel->updateInstancedWorldMatrix(worldMatrix, idx); } else { hasNonInstancingPass = true; } } if ((hasNonInstancingPass || forceUpdateUBO) && _localBuffer) { Mat4 mat4; Mat4::inverseTranspose(worldMatrix, &mat4); _localBuffer->write(worldMatrix, sizeof(float) * pipeline::UBOLocal::MAT_WORLD_OFFSET); _localBuffer->write(mat4, sizeof(float) * pipeline::UBOLocal::MAT_WORLD_IT_OFFSET); _localBuffer->write(_lightmapUVParam, sizeof(float) * pipeline::UBOLocal::LIGHTINGMAP_UVPARAM); _localBuffer->write(_shadowBias, sizeof(float) * (pipeline::UBOLocal::LOCAL_SHADOW_BIAS)); auto *probe = scene::ReflectionProbeManager::getInstance()->getReflectionProbeById(_reflectionProbeId); auto *blendProbe = scene::ReflectionProbeManager::getInstance()->getReflectionProbeById(_reflectionProbeBlendId); if (probe) { if (probe->getProbeType() == scene::ReflectionProbe::ProbeType::PLANAR) { const Vec4 plane = {probe->getNode()->getUp().x, probe->getNode()->getUp().y, probe->getNode()->getUp().z, 1.F}; _localBuffer->write(plane, sizeof(float) * (pipeline::UBOLocal::REFLECTION_PROBE_DATA1)); const Vec4 depthScale = {1.F, 0.F, 0.F, 1.F}; _localBuffer->write(depthScale, sizeof(float) * (pipeline::UBOLocal::REFLECTION_PROBE_DATA2)); } else { uint16_t mipAndUseRGBE = probe->isRGBE() ? 1000 : 0; const Vec4 pos = {probe->getNode()->getWorldPosition().x, probe->getNode()->getWorldPosition().y, probe->getNode()->getWorldPosition().z, 0.F}; _localBuffer->write(pos, sizeof(float) * (pipeline::UBOLocal::REFLECTION_PROBE_DATA1)); const Vec4 boxSize = {probe->getBoudingSize().x, probe->getBoudingSize().y, probe->getBoudingSize().z, static_cast(probe->getCubeMap() ? probe->getCubeMap()->mipmapLevel() + mipAndUseRGBE : 1 + mipAndUseRGBE)}; _localBuffer->write(boxSize, sizeof(float) * (pipeline::UBOLocal::REFLECTION_PROBE_DATA2)); } if (_reflectionProbeType == scene::UseReflectionProbeType::BLEND_PROBES || _reflectionProbeType == scene::UseReflectionProbeType::BLEND_PROBES_AND_SKYBOX) { if (blendProbe) { uint16_t mipAndUseRGBE = blendProbe->isRGBE() ? 1000 : 0; const Vec3 worldPos = blendProbe->getNode()->getWorldPosition(); Vec3 boudingBox = blendProbe->getBoudingSize(); const Vec4 pos = {worldPos.x, worldPos.y, worldPos.z, _reflectionProbeBlendWeight}; _localBuffer->write(pos, sizeof(float) * (pipeline::UBOLocal::REFLECTION_PROBE_BLEND_DATA1)); const Vec4 boxSize = {boudingBox.x, boudingBox.y, boudingBox.z, static_cast(blendProbe->getCubeMap() ? blendProbe->getCubeMap()->mipmapLevel() + mipAndUseRGBE : 1 + mipAndUseRGBE)}; _localBuffer->write(boxSize, sizeof(float) * (pipeline::UBOLocal::REFLECTION_PROBE_BLEND_DATA2)); } else if (_reflectionProbeType == scene::UseReflectionProbeType::BLEND_PROBES_AND_SKYBOX) { // blend with skybox const Vec4 pos = {0.F, 0.F, 0.F, _reflectionProbeBlendWeight}; _localBuffer->write(pos, sizeof(float) * (pipeline::UBOLocal::REFLECTION_PROBE_BLEND_DATA1)); } } } _localBuffer->update(); const bool enableOcclusionQuery = Root::getInstance()->getPipeline()->isOcclusionQueryEnabled(); if (enableOcclusionQuery) { updateWorldBoundUBOs(); } } } void Model::updateOctree() { if (_scene && _worldBoundsDirty) { _worldBoundsDirty = false; _scene->updateOctree(this); } } void Model::updateWorldBoundUBOs() { if (_worldBoundBuffer) { const Vec3 ¢er = _worldBounds ? _worldBounds->getCenter() : Vec3{0.0F, 0.0F, 0.0F}; const Vec3 &halfExtents = _worldBounds ? _worldBounds->getHalfExtents() : Vec3{1.0F, 1.0F, 1.0F}; const Vec4 worldBoundCenter{center.x, center.y, center.z, 0.0F}; const Vec4 worldBoundHalfExtents{halfExtents.x, halfExtents.y, halfExtents.z, 1.0F}; _worldBoundBuffer->write(worldBoundCenter, sizeof(float) * pipeline::UBOWorldBound::WORLD_BOUND_CENTER); _worldBoundBuffer->write(worldBoundHalfExtents, sizeof(float) * pipeline::UBOWorldBound::WORLD_BOUND_HALF_EXTENTS); _worldBoundBuffer->update(); } } void Model::createBoundingShape(const ccstd::optional &minPos, const ccstd::optional &maxPos) { if (!minPos.has_value() || !maxPos.has_value()) { return; } if (!_modelBounds) { _modelBounds = ccnew geometry::AABB(); } geometry::AABB::fromPoints(minPos.value(), maxPos.value(), _modelBounds); if (!_worldBounds) { _worldBounds = ccnew geometry::AABB(); } geometry::AABB::fromPoints(minPos.value(), maxPos.value(), _worldBounds); _worldBoundsDirty = true; } SubModel *Model::createSubModel() { return ccnew SubModel(); } void Model::initSubModel(index_t idx, cc::RenderingSubMesh *subMeshData, Material *mat) { initialize(); if (idx >= static_cast(_subModels.size())) { _subModels.resize(1 + idx, nullptr); } if (_subModels[idx] == nullptr) { _subModels[idx] = createSubModel(); } else { CC_SAFE_DESTROY(_subModels[idx]); } _subModels[idx]->initialize(subMeshData, mat->getPasses(), getMacroPatches(idx)); _subModels[idx]->setOwner(this); updateAttributesAndBinding(idx); } void Model::setSubModelMesh(index_t idx, cc::RenderingSubMesh *subMesh) const { if (idx < _subModels.size()) { _subModels[idx]->setSubMesh(subMesh); } } void Model::setSubModelMaterial(index_t idx, Material *mat) { if (idx < _subModels.size()) { _subModels[idx]->setPasses(mat->getPasses()); updateAttributesAndBinding(idx); } } void Model::onGlobalPipelineStateChanged() const { for (SubModel *subModel : _subModels) { subModel->onPipelineStateChanged(); } } void Model::onMacroPatchesStateChanged() { for (index_t i = 0; i < _subModels.size(); ++i) { _subModels[i]->onMacroPatchesStateChanged(getMacroPatches(i)); } } void Model::onGeometryChanged() { for (SubModel *subModel : _subModels) { subModel->onGeometryChanged(); } } void Model::initLightingmap(Texture2D *texture, const Vec4 &uvParam) { _lightmap = texture; _lightmapUVParam = uvParam; } void Model::updateLightingmap(Texture2D *texture, const Vec4 &uvParam) { _localDataUpdated = true; _lightmap = texture; _lightmapUVParam = uvParam; if (texture == nullptr) { texture = BuiltinResMgr::getInstance()->get(ccstd::string("empty-texture")); } gfx::Texture *gfxTexture = texture->getGFXTexture(); if (gfxTexture) { auto *sampler = _device->getSampler(texture->getMipmaps().size() > 1 ? LIGHTMAP_SAMPLER_WITH_MIP_HASH : LIGHTMAP_SAMPLER_HASH); for (SubModel *subModel : _subModels) { gfx::DescriptorSet *descriptorSet = subModel->getDescriptorSet(); // // TODO(Yun Hsiao Wu): should manage lightmap macro switches automatically // // USE_LIGHTMAP -> CC_USE_LIGHTMAP descriptorSet->bindTexture(pipeline::LIGHTMAPTEXTURE::BINDING, gfxTexture); descriptorSet->bindSampler(pipeline::LIGHTMAPTEXTURE::BINDING, sampler); descriptorSet->update(); } } } bool Model::isLightProbeAvailable() const { if (!_useLightProbe) { return false; } const auto *pipeline = Root::getInstance()->getPipeline(); const auto *lightProbes = pipeline->getPipelineSceneData()->getLightProbes(); if (!lightProbes || lightProbes->empty()) { return false; } if (!_worldBounds) { return false; } return true; } void Model::updateSHBuffer() { if (_localSHData.empty()) { return; } bool hasNonInstancingPass = false; for (const auto &subModel : _subModels) { const auto idx = subModel->getInstancedSHIndex(); if (idx >= 0) { subModel->updateInstancedSH(_localSHData, idx); } else { hasNonInstancingPass = true; } } if (hasNonInstancingPass && _localSHBuffer) { _localSHBuffer->update(_localSHData.buffer()->getData()); } } void Model::clearSHUBOs() { if (_localSHData.empty()) { return; } for (auto i = 0; i < pipeline::UBOSH::COUNT; i++) { _localSHData[i] = 0.0; } updateSHBuffer(); } void Model::updateSHUBOs() { if (!isLightProbeAvailable()) { return; } const auto center = _worldBounds->getCenter(); #if !CC_EDITOR if (center.approxEquals(_lastWorldBoundCenter, math::EPSILON)) { return; } #endif ccstd::vector coefficients; Vec4 weights(0.0F, 0.0F, 0.0F, 0.0F); const auto *pipeline = Root::getInstance()->getPipeline(); const auto *lightProbes = pipeline->getPipelineSceneData()->getLightProbes(); _lastWorldBoundCenter.set(center); _tetrahedronIndex = lightProbes->getData()->getInterpolationWeights(center, _tetrahedronIndex, weights); bool result = lightProbes->getData()->getInterpolationSHCoefficients(_tetrahedronIndex, weights, coefficients); if (!result) { return; } if (_localSHData.empty()) { return; } gi::SH::reduceRinging(coefficients, lightProbes->getReduceRinging()); gi::SH::updateUBOData(_localSHData, pipeline::UBOSH::SH_LINEAR_CONST_R_OFFSET, coefficients); updateSHBuffer(); } ccstd::vector Model::getMacroPatches(index_t subModelIndex) { if (isModelImplementedInJS()) { if (!_isCalledFromJS) { ccstd::vector macroPatches; emit(subModelIndex, ¯oPatches); _isCalledFromJS = false; return macroPatches; } } ccstd::vector patches; if (_receiveShadow) { for (const auto &patch : SHADOW_MAP_PATCHES) { patches.push_back(patch); } } if (_useLightProbe) { for (const auto &patch : LIGHT_PROBE_PATCHES) { patches.push_back(patch); } } patches.push_back({CC_USE_REFLECTION_PROBE, static_cast(_reflectionProbeType)}); if (_lightmap != nullptr) { bool stationary = false; if (getNode() != nullptr && getNode()->getScene() != nullptr) { stationary = getNode()->getScene()->getSceneGlobals()->getBakedWithStationaryMainLight(); } if (stationary) { for (const auto &patch : STATIONARY_LIGHTMAP_PATHES) { patches.push_back(patch); } } else { for (const auto &patch : STATIC_LIGHTMAP_PATHES) { patches.push_back(patch); } } // use highp lightmap if (getNode() != nullptr && getNode()->getScene() != nullptr) { if (getNode()->getScene()->getSceneGlobals()->getBakedWithHighpLightmap()) { for (const auto &patch : HIGHP_LIGHTMAP_PATHES) { patches.push_back(patch); } } } } patches.push_back({CC_DISABLE_DIRECTIONAL_LIGHT, !_receiveDirLight}); return patches; } void Model::updateAttributesAndBinding(index_t subModelIndex) { if (subModelIndex >= _subModels.size()) return; SubModel *subModel = _subModels[subModelIndex]; initLocalDescriptors(subModelIndex); updateLocalDescriptors(subModelIndex, subModel->getDescriptorSet()); initLocalSHDescriptors(subModelIndex); updateLocalSHDescriptors(subModelIndex, subModel->getDescriptorSet()); initWorldBoundDescriptors(subModelIndex); if (subModel->getWorldBoundDescriptorSet()) { updateWorldBoundDescriptors(subModelIndex, subModel->getWorldBoundDescriptorSet()); } ccstd::vector attributes; ccstd::unordered_map attributeMap; for (const auto &pass : *(subModel->getPasses())) { gfx::Shader *shader = pass->getShaderVariant(subModel->getPatches()); for (const auto &attr : shader->getAttributes()) { if (attributeMap.find(attr.name) == attributeMap.end()) { attributes.push_back(attr); attributeMap.insert({attr.name, attr}); } } } updateInstancedAttributes(attributes, subModel); } void Model::updateInstancedAttributes(const ccstd::vector &attributes, SubModel *subModel) { if (isModelImplementedInJS()) { if (!_isCalledFromJS) { emit(attributes, subModel); // FIXME _isCalledFromJS = false; return; } } subModel->updateInstancedAttributes(attributes); _localDataUpdated = true; } void Model::initLocalDescriptors(index_t /*subModelIndex*/) { if (!_localBuffer) { _localBuffer = _device->createBuffer({gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, gfx::MemoryUsageBit::DEVICE, pipeline::UBOLocal::SIZE, pipeline::UBOLocal::SIZE, gfx::BufferFlagBit::ENABLE_STAGING_WRITE}); } } void Model::initLocalSHDescriptors(index_t /*subModelIndex*/) { #if !CC_EDITOR if (!_useLightProbe) { return; } #endif if (_localSHData.empty()) { _localSHData.reset(pipeline::UBOSH::COUNT); } if (!_localSHBuffer) { _localSHBuffer = _device->createBuffer({ gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, gfx::MemoryUsageBit::DEVICE, pipeline::UBOSH::SIZE, pipeline::UBOSH::SIZE, }); } } void Model::initWorldBoundDescriptors(index_t /*subModelIndex*/) { if (!_worldBoundBuffer) { _worldBoundBuffer = _device->createBuffer({gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, gfx::MemoryUsageBit::DEVICE, pipeline::UBOWorldBound::SIZE, pipeline::UBOWorldBound::SIZE, gfx::BufferFlagBit::ENABLE_STAGING_WRITE}); } } void Model::updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) { if (isModelImplementedInJS()) { if (!_isCalledFromJS) { emit(subModelIndex, descriptorSet); _isCalledFromJS = false; return; } } if (_localBuffer) { descriptorSet->bindBuffer(pipeline::UBOLocal::BINDING, _localBuffer); } } void Model::updateLocalSHDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) { if (isModelImplementedInJS()) { if (!_isCalledFromJS) { emit(subModelIndex, descriptorSet); _isCalledFromJS = false; return; } } if (_localSHBuffer) { descriptorSet->bindBuffer(pipeline::UBOSH::BINDING, _localSHBuffer); } } void Model::updateWorldBoundDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) { if (isModelImplementedInJS()) { if (!_isCalledFromJS) { emit(subModelIndex, descriptorSet); _isCalledFromJS = false; return; } } if (_worldBoundBuffer) { descriptorSet->bindBuffer(pipeline::UBOLocal::BINDING, _worldBoundBuffer); } } void Model::updateLocalShadowBias() { _localDataUpdated = true; } void Model::updateReflectionProbeCubemap(TextureCube *texture) { _localDataUpdated = true; if (texture == nullptr) { texture = BuiltinResMgr::getInstance()->get(ccstd::string("default-cube-texture")); } gfx::Texture *gfxTexture = texture->getGFXTexture(); if (gfxTexture) { auto *sampler = _device->getSampler(texture->getSamplerInfo()); for (SubModel *subModel : _subModels) { gfx::DescriptorSet *descriptorSet = subModel->getDescriptorSet(); descriptorSet->bindTexture(pipeline::REFLECTIONPROBECUBEMAP::BINDING, gfxTexture); descriptorSet->bindSampler(pipeline::REFLECTIONPROBECUBEMAP::BINDING, sampler); descriptorSet->update(); } } } void Model::updateReflectionProbePlanarMap(gfx::Texture *texture) { _localDataUpdated = true; gfx::Texture *bindingTexture = texture; if (!bindingTexture) { bindingTexture = BuiltinResMgr::getInstance()->get(ccstd::string("empty-texture"))->getGFXTexture(); } if (bindingTexture) { gfx::SamplerInfo info{ cc::gfx::Filter::LINEAR, cc::gfx::Filter::LINEAR, cc::gfx::Filter::NONE, cc::gfx::Address::CLAMP, cc::gfx::Address::CLAMP, cc::gfx::Address::CLAMP, }; auto *sampler = _device->getSampler(info); for (SubModel *subModel : _subModels) { gfx::DescriptorSet *descriptorSet = subModel->getDescriptorSet(); descriptorSet->bindTexture(pipeline::REFLECTIONPROBEPLANARMAP::BINDING, bindingTexture); descriptorSet->bindSampler(pipeline::REFLECTIONPROBEPLANARMAP::BINDING, sampler); descriptorSet->update(); } } } void Model::updateReflectionProbeDataMap(Texture2D *texture) { _localDataUpdated = true; if (!texture) { texture = BuiltinResMgr::getInstance()->get(ccstd::string("empty-texture")); } gfx::Texture *gfxTexture = texture->getGFXTexture(); if (gfxTexture) { for (SubModel *subModel : _subModels) { gfx::DescriptorSet *descriptorSet = subModel->getDescriptorSet(); descriptorSet->bindTexture(pipeline::REFLECTIONPROBEDATAMAP::BINDING, gfxTexture); descriptorSet->bindSampler(pipeline::REFLECTIONPROBEDATAMAP::BINDING, texture->getGFXSampler()); descriptorSet->update(); } } } void Model::updateReflectionProbeBlendCubemap(TextureCube *texture) { _localDataUpdated = true; if (texture == nullptr) { texture = BuiltinResMgr::getInstance()->get(ccstd::string("default-cube-texture")); } gfx::Texture *gfxTexture = texture->getGFXTexture(); if (gfxTexture) { auto *sampler = _device->getSampler(texture->getSamplerInfo()); for (SubModel *subModel : _subModels) { gfx::DescriptorSet *descriptorSet = subModel->getDescriptorSet(); descriptorSet->bindTexture(pipeline::REFLECTIONPROBEBLENDCUBEMAP::BINDING, gfxTexture); descriptorSet->bindSampler(pipeline::REFLECTIONPROBEBLENDCUBEMAP::BINDING, sampler); descriptorSet->update(); } } } void Model::updateReflectionProbeId() { _localDataUpdated = true; } void Model::setInstancedAttribute(const ccstd::string &name, const float *value, uint32_t byteLength) { for (const auto &subModel : _subModels) { subModel->setInstancedAttribute(name, value, byteLength); } } void Model::setReflectionProbeType(UseReflectionProbeType val) { _reflectionProbeType = val; for (const auto &subModel : _subModels) { subModel->setReflectionProbeType(static_cast(val)); } onMacroPatchesStateChanged(); } } // namespace scene } // namespace cc