I'm new to godot-cpp and I'm encountering a rendering issue after converting GLTF material textures to StandardMaterial3D and Texture in my project. Specifically, the textures are not rendering correctly after the conversion.

I've tried to look for an API in godot-cpp to set the WrapS and WrapT properties for the textures, but I couldn't find any.
And I've been inspired by the CesiumForUnity codebase and have borrowed several ideas from it for my project.
Now The baseColorTexture is not rendering correctly, while the baseColor is fine. When this amateur project is nearly completed, I want to share it with the open-source community for everyone to learn and progress together.
Could anyone more experienced with godot-cpp help me out? Below are the relevant code snippets and screenshots for reference.

  Ref<StandardMaterial3D> material =
                Ref<StandardMaterial3D>( memnew( StandardMaterial3D ) );

            const CesiumGltf::Material *pMaterial =
                Model::getSafe( &gltf.materials, primitive.material );
            if ( pMaterial )
            {
                setGltfMaterialParameterValues( gltf, primitiveInfo, *pMaterial, material,
                                                materialProperties );
            }
            Color color = material->get_albedo();
            SPDLOG_INFO( "material get_albedo {0}, {1}, {2}, {3}", color.r, color.g, color.b,
                         color.a );
            SPDLOG_INFO( "material_get_metallic {0}", material->get_metallic() );
            meshInstance->set_material_override( material );`
`static const CesiumGltf::MaterialPBRMetallicRoughness defaultPbrMetallicRoughness;
void setGltfMaterialParameterValues( const CesiumGltf::Model &model,
                                     const CesiumPrimitiveInfo &primitiveInfo,
                                     const CesiumGltf::Material &gltfMaterial,
                                     const Ref<StandardMaterial3D> material,
                                     const TilesetMaterialProperties &materialProperties )
{

    CESIUM_TRACE( "Cesium::CreateMaterials" );
    const CesiumGltf::MaterialPBRMetallicRoughness &pbr =
        gltfMaterial.pbrMetallicRoughness ? gltfMaterial.pbrMetallicRoughness.value()
                                          : defaultPbrMetallicRoughness;

    // Add base color factor and metallic-roughness factor regardless
    // of whether the textures are present.
    const std::vector<double> &baseColorFactor = pbr.baseColorFactor;
    material->set_albedo(
        Color( baseColorFactor[0], baseColorFactor[1], baseColorFactor[2], baseColorFactor[3] ) );
    material->set_metallic( pbr.metallicFactor );
    material->set_roughness( pbr.roughnessFactor );

    const std::optional<CesiumGltf::TextureInfo> &baseColorTexture = pbr.baseColorTexture;
    if ( baseColorTexture )
    {
        auto texCoordIndexIt = primitiveInfo.uvIndexMap.find( baseColorTexture->texCoord );
        if ( texCoordIndexIt != primitiveInfo.uvIndexMap.end() )
        {
            Ref<godot::Texture> gTexture = loadTexture( model, baseColorTexture->index, true );
            if ( gTexture.is_valid() )
            {
                material->set_texture( StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, gTexture );
                const CesiumGltf::Texture *pTexture =
                    Model::getSafe( &model.textures, baseColorTexture->index );
                const CesiumGltf::Sampler *pSampler =
                    CesiumGltf::Model::getSafe( &model.samplers, pTexture->source );
                if ( !pSampler->minFilter )
                {
                    if ( pSampler->magFilter &&
                         *pSampler->magFilter == Sampler::MagFilter::NEAREST )
                    {
                        material->set_texture_filter(
                            BaseMaterial3D::TextureFilter::TEXTURE_FILTER_NEAREST );
                    }
                    else
                    {
                        material->set_texture_filter(
                            BaseMaterial3D::TextureFilter::TEXTURE_FILTER_LINEAR );
                    }
                }
                else
                {
                    switch ( *pSampler->minFilter )
                    {
                        case Sampler::MinFilter::NEAREST:
                            material->set_texture_filter(
                                BaseMaterial3D::TextureFilter::TEXTURE_FILTER_NEAREST );
                            break;
                        case Sampler::MinFilter::NEAREST_MIPMAP_NEAREST:
                            material->set_texture_filter( BaseMaterial3D::TextureFilter::
                                                              TEXTURE_FILTER_NEAREST_WITH_MIPMAPS );
                            break;
                        case Sampler::MinFilter::LINEAR:
                            material->set_texture_filter(
                                BaseMaterial3D::TextureFilter::TEXTURE_FILTER_LINEAR );
                            break;
                        case Sampler::MinFilter::LINEAR_MIPMAP_NEAREST:
                            material->set_texture_filter(
                                BaseMaterial3D::TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS );
                            break;
                        default:
                            material->set_texture_filter(
                                BaseMaterial3D::TextureFilter::TEXTURE_FILTER_MAX );
                    }
                }
            }
        }
    }

    const std::optional<TextureInfo> &metallicRoughness = pbr.metallicRoughnessTexture;
    if ( metallicRoughness )
    {
        auto texCoordIndexIt = primitiveInfo.uvIndexMap.find( metallicRoughness->texCoord );
        if ( texCoordIndexIt != primitiveInfo.uvIndexMap.end() )
        {
            Ref<godot::Texture> gTexture = loadTexture( model, metallicRoughness->index, false );
            if ( gTexture.is_valid() )
            {
                material->set_texture( StandardMaterial3D::TextureParam::TEXTURE_METALLIC,
                                       gTexture );
            }
        }
    }

    const std::vector<double> &emissiveFactor = gltfMaterial.emissiveFactor;
    if ( gltfMaterial.emissiveTexture )
    {
        auto texCoordIndexIt =
            primitiveInfo.uvIndexMap.find( gltfMaterial.emissiveTexture->texCoord );
        if ( texCoordIndexIt != primitiveInfo.uvIndexMap.end() )
        {
            Ref<godot::Texture> gTexture =
                loadTexture( model, gltfMaterial.emissiveTexture->index, true );
            if ( gTexture.is_valid() )
            {
                material->set_texture( StandardMaterial3D::TextureParam::TEXTURE_EMISSION,
                                       gTexture );
            }
        }
    }

    if ( gltfMaterial.normalTexture )
    {
        auto texCoordIndexIt =
            primitiveInfo.uvIndexMap.find( gltfMaterial.normalTexture->texCoord );
        if ( texCoordIndexIt != primitiveInfo.uvIndexMap.end() )
        {
            Ref<godot::Texture> gTexture =
                loadTexture( model, gltfMaterial.normalTexture->index, true );
            if ( gTexture.is_valid() )
            {
                material->set_texture( StandardMaterial3D::TextureParam::TEXTURE_NORMAL, gTexture );
            }
        }
    }

    if ( gltfMaterial.occlusionTexture )
    {
        auto texCoordIndexIt =
            primitiveInfo.uvIndexMap.find( gltfMaterial.occlusionTexture->texCoord );
        if ( texCoordIndexIt != primitiveInfo.uvIndexMap.end() )
        {
            Ref<godot::Texture> gTexture =
                loadTexture( model, gltfMaterial.occlusionTexture->index, true );
            if ( gTexture.is_valid() )
            {
                material->set_texture( StandardMaterial3D::TextureParam::TEXTURE_AMBIENT_OCCLUSION,
                                       gTexture );
            }
        }
    }

    // Handle KHR_texture_transform for each available texture.
    CesiumGltf::KhrTextureTransform textureTransform;
    const CesiumGltf::ExtensionKhrTextureTransform *pBaseColorTextureTransform =
        pbr.baseColorTexture
            ? pbr.baseColorTexture->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
            : nullptr;
    if ( pBaseColorTextureTransform )
    {
        textureTransform = CesiumGltf::KhrTextureTransform( *pBaseColorTextureTransform );
        if ( textureTransform.status() == CesiumGltf::KhrTextureTransformStatus::Valid )
        {
            const glm::dvec2 &scale = textureTransform.scale();
            const glm::dvec2 &offset = textureTransform.offset();
            material->set_uv1_scale( Vector3( scale[0], scale[1], 0 ) );
            material->set_uv1_offset( Vector3( offset[0], offset[1], 0 ) );
            // const glm::dvec2& rotationSineCosine =
            //     textureTransform.rotationSineCosine();
            // unityMaterial.SetVector(
            //     materialProperties.getBaseColorTextureRotationID(),
            //     { static_cast<float>(rotationSineCosine[0]),
            //      static_cast<float>(rotationSineCosine[1]),
            //      0.0f,
            //      0.0f });
        }
    }

    const CesiumGltf::ExtensionKhrTextureTransform *pMetallicRoughnessTextureTransform =
        pbr.metallicRoughnessTexture
            ? pbr.metallicRoughnessTexture->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
            : nullptr;
    if ( pMetallicRoughnessTextureTransform )
    {
        textureTransform = CesiumGltf::KhrTextureTransform( *pMetallicRoughnessTextureTransform );
        if ( textureTransform.status() == CesiumGltf::KhrTextureTransformStatus::Valid )
        {
            const glm::dvec2 &scale = textureTransform.scale();
            const glm::dvec2 &offset = textureTransform.offset();
            material->set_uv1_scale( Vector3( scale[0], scale[1], 0 ) );
            material->set_uv1_offset( Vector3( offset[0], offset[1], 0 ) );
            // const glm::dvec2& rotationSineCosine =
            //     textureTransform.rotationSineCosine();
            // unityMaterial.SetVector(
            //     materialProperties.getMetallicRoughnessTextureRotationID(),
            //     { static_cast<float>(rotationSineCosine[0]),
            //      static_cast<float>(rotationSineCosine[1]),
            //      0.0f,
            //      0.0f });
        }
    }

    const CesiumGltf::ExtensionKhrTextureTransform *pNormalTextureTransform =
        gltfMaterial.normalTexture
            ? gltfMaterial.normalTexture->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
            : nullptr;
    if ( pNormalTextureTransform )
    {
        textureTransform = CesiumGltf::KhrTextureTransform( *pNormalTextureTransform );
        if ( textureTransform.status() == CesiumGltf::KhrTextureTransformStatus::Valid )
        {
            const glm::dvec2 &scale = textureTransform.scale();
            const glm::dvec2 &offset = textureTransform.offset();
            material->set_uv1_scale( Vector3( scale[0], scale[1], 0 ) );
            material->set_uv1_offset( Vector3( offset[0], offset[1], 0 ) );
            // const glm::dvec2& rotationSineCosine =
            //     textureTransform.rotationSineCosine();
            // unityMaterial.SetVector(
            //     materialProperties.getNormalMapTextureRotationID(),
            //     { static_cast<float>(rotationSineCosine[0]),
            //      static_cast<float>(rotationSineCosine[1]),
            //      0.0f,
            //      0.0f });
        }
    }

    const CesiumGltf::ExtensionKhrTextureTransform *pEmissiveTextureTransform =
        gltfMaterial.emissiveTexture
            ? gltfMaterial.emissiveTexture->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
            : nullptr;
    if ( pEmissiveTextureTransform )
    {
        textureTransform = CesiumGltf::KhrTextureTransform( *pEmissiveTextureTransform );
        if ( textureTransform.status() == CesiumGltf::KhrTextureTransformStatus::Valid )
        {
            const glm::dvec2 &scale = textureTransform.scale();
            const glm::dvec2 &offset = textureTransform.offset();
            material->set_uv1_scale( Vector3( scale[0], scale[1], 0 ) );
            material->set_uv1_offset( Vector3( offset[0], offset[1], 0 ) );
            // const glm::dvec2& rotationSineCosine =
            //     textureTransform.rotationSineCosine();
            // unityMaterial.SetVector(
            //     materialProperties.getEmissiveTextureRotationID(),
            //     { static_cast<float>(rotationSineCosine[0]),
            //      static_cast<float>(rotationSineCosine[1]),
            //      0.0f,
            //      0.0f });
        }
    }

    const CesiumGltf::ExtensionKhrTextureTransform *pOcclusionTextureTransform =
        gltfMaterial.occlusionTexture
            ? gltfMaterial.occlusionTexture
                  ->getExtension<CesiumGltf::ExtensionKhrTextureTransform>()
            : nullptr;
    if ( pOcclusionTextureTransform )
    {
        textureTransform = CesiumGltf::KhrTextureTransform( *pOcclusionTextureTransform );
        if ( textureTransform.status() == CesiumGltf::KhrTextureTransformStatus::Valid )
        {
            const glm::dvec2 &scale = textureTransform.scale();
            const glm::dvec2 &offset = textureTransform.offset();
            material->set_uv1_scale( Vector3( scale[0], scale[1], 0 ) );
            material->set_uv1_offset( Vector3( offset[0], offset[1], 0 ) );
            // const glm::dvec2& rotationSineCosine =
            //     textureTransform.rotationSineCosine();
            // unityMaterial.SetVector(
            //     materialProperties.getOcclusionTextureRotationID(),
            //     { static_cast<float>(rotationSineCosine[0]),
            //      static_cast<float>(rotationSineCosine[1]),
            //      0.0f,
            //      0.0f });
        }
    }
}

Ref<godot::Image> loadImageFromCesiumImage( const CesiumGltf::ImageCesium &imageCesium, bool sRGB )
{
    int32_t width = imageCesium.width;
    int32_t height = imageCesium.height;
    int32_t channels = imageCesium.channels;
    godot::Image::Format format;
    Ref<godot::Image> image;

    switch ( channels )
    {
        case 1:
            format = godot::Image::FORMAT_L8; // 灰度图
            break;
        case 2:
            format = godot::Image::FORMAT_LA8; // 灰度+透明度
            break;
        case 3:
            format = godot::Image::FORMAT_RGB8; // RGB
            break;
        case 4:
            format = godot::Image::FORMAT_RGBA8; // RGBA
            break;
        default:
            format = godot::Image::FORMAT_RGBA8;
            break;
    }
    std::vector<uint8_t> pixelDataBuffer( width * height * channels * imageCesium.bytesPerChannel );

    if ( imageCesium.mipPositions.empty() )
    {
        std::memcpy( pixelDataBuffer.data(), imageCesium.pixelData.data(),
                     imageCesium.pixelData.size() );

        godot::PackedByteArray packedData;
        packedData.resize( pixelDataBuffer.size() );
        std::memcpy( packedData.ptrw(), pixelDataBuffer.data(), pixelDataBuffer.size() );
        image.instantiate();
        image->set_data( width, height, false, format, packedData );
    }
    else
    {
        size_t totalMipSize = 0;
        for ( const auto &mip : imageCesium.mipPositions )
        {
            totalMipSize += mip.byteSize;
        }

        if ( totalMipSize > pixelDataBuffer.size() )
        {
            pixelDataBuffer.resize( totalMipSize, 0 );
        }

        uint8_t *writePos = pixelDataBuffer.data();
        for ( const auto &mip : imageCesium.mipPositions )
        {
            std::memcpy( writePos, imageCesium.pixelData.data() + mip.byteOffset, mip.byteSize );
            writePos += mip.byteSize;
        }

        godot::PackedByteArray packedData;
        packedData.resize( imageCesium.mipPositions[0].byteSize );
        std::memcpy( packedData.ptrw(), pixelDataBuffer.data(),
                     imageCesium.mipPositions[0].byteSize );

        image.instantiate();
        image->set_data( width, height, false, format, packedData );
    }

    return image;
}

Ref<godot::Texture> loadTexture( const CesiumGltf::Model &model, int32_t textureInfoIndex,
                                 bool sRGB )
{
    const CesiumGltf::Texture *pTexture = Model::getSafe( &model.textures, textureInfoIndex );
    const CesiumGltf::Image *pImage = CesiumGltf::Model::getSafe( &model.images, pTexture->source );
    if ( !pImage )
    {
        return Ref<godot::Texture>( nullptr );
    }

    const ImageCesium &imageCesium = pImage->cesium;
    Ref<godot::Image> image = loadImageFromCesiumImage( imageCesium, sRGB );
    if ( !image.is_valid() )
    {
        return Ref<godot::Texture>();
    }

    Ref<ImageTexture> godotTexture = ImageTexture::create_from_image( image );

    return godotTexture;
}

  • xyz replied to this.

    I wrote the texture image to a file, but most of the area's pixel color is black. It seems there is something wrong with the image processing code.
    Here is the code for the image processing function along with saved image file:

    Ref<godot::Image> loadImageFromCesiumImage( const CesiumGltf::ImageCesium &imageCesium, bool sRGB )
    {
        int32_t width = imageCesium.width;
        int32_t height = imageCesium.height;
        int32_t channels = imageCesium.channels;
        godot::Image::Format format;
        Ref<godot::Image> image;
    
        switch ( channels )
        {
            case 1:
                format = godot::Image::FORMAT_L8; // 灰度图
                break;
            case 2:
                format = godot::Image::FORMAT_LA8; // 灰度+透明度
                break;
            case 3:
                format = godot::Image::FORMAT_RGB8; // RGB
                break;
            case 4:
                format = godot::Image::FORMAT_RGBA8; // RGBA
                break;
            default:
                format = godot::Image::FORMAT_RGBA8;
                break;
        }
        std::vector<uint8_t> pixelDataBuffer( width * height * channels * imageCesium.bytesPerChannel );
    
        if ( imageCesium.mipPositions.empty() )
        {
            std::memcpy( pixelDataBuffer.data(), imageCesium.pixelData.data(),
                         imageCesium.pixelData.size() );
    
            godot::PackedByteArray packedData;
            packedData.resize( pixelDataBuffer.size() );
            std::memcpy( packedData.ptrw(), pixelDataBuffer.data(), pixelDataBuffer.size() );
            image.instantiate();
            image->set_data( width, height, false, format, packedData );
        }
        else
        {
            size_t totalMipSize = 0;
            for ( const auto &mip : imageCesium.mipPositions )
            {
                totalMipSize += mip.byteSize;
            }
    
            if ( totalMipSize > pixelDataBuffer.size() )
            {
                pixelDataBuffer.resize( totalMipSize, 0 );
            }
    
            uint8_t *writePos = pixelDataBuffer.data();
            for ( const auto &mip : imageCesium.mipPositions )
            {
                std::memcpy( writePos, imageCesium.pixelData.data() + mip.byteOffset, mip.byteSize );
                writePos += mip.byteSize;
            }
    
            godot::PackedByteArray packedData;
            packedData.resize( imageCesium.mipPositions[0].byteSize );
            std::memcpy( packedData.ptrw(), pixelDataBuffer.data(),
                         imageCesium.mipPositions[0].byteSize );
    
            image.instantiate();
            image->set_data( width, height, false, format, packedData );
        }
    
        return image;
    }

    xuwzen2024 Specifically, the textures are not rendering correctly after the conversion

    Not rendered correctly in what way? Post the before/after comparison images.

    Due to improper adjustment of the LOD (Level of Detail) in the Cesium Godot plugin, the lowest-level model is being loaded. Below is the effect of loading the same level of detail model using CesiumJS.

    and godot:

    • xyz replied to this.

      Totally agree. It's very likely an issue related to UV mapping. When I tested with a random color texture image, I could clearly see the effect.

      Thank you so much for your help.