From 23806ed67d73bf870b0a98a336581679bd94e165 Mon Sep 17 00:00:00 2001 From: sabrina-shanman <sabrina@highfidelity.io> Date: Fri, 8 Feb 2019 14:33:55 -0800 Subject: [PATCH] Add support for GLTF roughness/metallic --- libraries/fbx/src/GLTFSerializer.cpp | 2 + libraries/hfm/src/hfm/HFM.h | 2 + libraries/image/src/image/Image.cpp | 46 ++++++++++++++++++- libraries/image/src/image/Image.h | 3 +- .../src/model-networking/ModelCache.cpp | 2 +- .../src/model-networking/TextureCache.cpp | 36 ++++++++++----- .../src/model-networking/TextureCache.h | 6 ++- .../shared/src/shared/ColorChannelMapping.h | 37 +++++++++++++++ 8 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 libraries/shared/src/shared/ColorChannelMapping.h diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 96c236f703..f7493ad88b 100644 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -1057,8 +1057,10 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& mat } if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) { fbxmat.roughnessTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.roughnessTexture.channelMapping = ColorChannelMapping::GREEN; fbxmat.useRoughnessMap = true; fbxmat.metallicTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.metallicTexture.channelMapping = ColorChannelMapping::BLUE; fbxmat.useMetallicMap = true; } if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 1bd87332a1..f733e4ef69 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -24,6 +24,7 @@ #include <graphics/Geometry.h> #include <graphics/Material.h> +#include <shared/ColorChannelMapping.h> #if defined(Q_OS_ANDROID) #define HFM_PACK_NORMALS 0 @@ -125,6 +126,7 @@ public: QString name; QByteArray filename; QByteArray content; + ColorChannelMapping channelMapping { ColorChannelMapping::NONE }; Transform transform; int maxNumPixels { MAX_NUM_PIXELS_FOR_FBX_TEXTURE }; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index ac2813667f..0f7acabf85 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -16,6 +16,7 @@ #include <QtCore/QtGlobal> #include <QUrl> #include <QImage> +#include <QRgb> #include <QBuffer> #include <QImageReader> @@ -221,7 +222,45 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { return QImage(); } -gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& filename, +void mapToRedChannel(QImage& image, ColorChannelMapping sourceChannel) { + // Change format of image so we know exactly how to process it + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + + for (int i = 0; i < image.height(); i++) { + QRgb* pixel = reinterpret_cast<QRgb*>(image.scanLine(i)); + // Past end pointer + QRgb* lineEnd = pixel + image.width(); + + // Transfer channel data from source to target + for (; pixel < lineEnd; pixel++) { + int colorValue; + switch (sourceChannel) { + case ColorChannelMapping::RED: + colorValue = qRed(*pixel); + break; + case ColorChannelMapping::GREEN: + colorValue = qGreen(*pixel); + break; + case ColorChannelMapping::BLUE: + colorValue = qBlue(*pixel); + break; + case ColorChannelMapping::ALPHA: + colorValue = qAlpha(*pixel); + break; + default: + colorValue = qRed(*pixel); + break; + } + + // Dump the color in the red channel, ignore the rest + *pixel = qRgba(colorValue, 0, 0, 0); + } + } +} + +gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& filename, ColorChannelMapping channelMapping, int maxNumPixels, TextureUsage::Type textureType, bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) { @@ -252,6 +291,11 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std:: QSize(originalWidth, originalHeight) << " to " << QSize(imageWidth, imageHeight) << ")"; } + + // Re-map to image with single red channel texture if requested + if (channelMapping != ColorChannelMapping::NONE) { + mapToRedChannel(image, channelMapping); + } auto loader = TextureUsage::getTextureLoaderForType(textureType); auto texture = loader(std::move(image), filename, compress, target, abortProcessing); diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index ae72a183b3..cc68ef6718 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -15,6 +15,7 @@ #include <QVariant> #include <gpu/Texture.h> +#include <shared/ColorChannelMapping.h> class QByteArray; class QImage; @@ -81,7 +82,7 @@ gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const st const QStringList getSupportedFormats(); -gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url, +gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url, ColorChannelMapping channelMapping, int maxNumPixels, TextureUsage::Type textureType, bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 1535f5cfad..0da683789e 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -599,7 +599,7 @@ graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl } const auto url = getTextureUrl(baseUrl, hfmTexture); - const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels); + const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels, hfmTexture.channelMapping); _textures[channel] = Texture { hfmTexture.name, texture }; auto map = std::make_shared<graphics::TextureMap>(); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 910de258f9..fe68c4ca6e 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -192,6 +192,7 @@ public: image::TextureUsage::Type type; const QByteArray& content; int maxNumPixels; + ColorChannelMapping channelMapping; }; namespace std { @@ -206,19 +207,19 @@ namespace std { struct hash<TextureExtra> { size_t operator()(const TextureExtra& a) const { size_t result = 0; - hash_combine(result, (int)a.type, a.content, a.maxNumPixels); + hash_combine(result, (int)a.type, a.content, a.maxNumPixels, a.channelMapping); return result; } }; } -ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels) { +ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels, ColorChannelMapping channelMapping) { auto byteArray = QByteArray(); - TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels }; + TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels, channelMapping }; return ResourceCache::prefetch(url, &extra, std::hash<TextureExtra>()(extra)); } -NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) { +NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels, ColorChannelMapping channelMapping) { if (url.scheme() == RESOURCE_SCHEME) { return getResourceTexture(url); } @@ -228,7 +229,7 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUs query.addQueryItem("skybox", ""); modifiedUrl.setQuery(query.toString()); } - TextureExtra extra = { type, content, maxNumPixels }; + TextureExtra extra = { type, content, maxNumPixels, channelMapping }; return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash<TextureExtra>()(extra)).staticCast<NetworkTexture>(); } @@ -346,6 +347,7 @@ NetworkTexture::NetworkTexture(const QUrl& url) : NetworkTexture::NetworkTexture(const NetworkTexture& other) : Resource(other), _type(other._type), + _channelMapping(other._channelMapping), _currentlyLoadingResourceType(other._currentlyLoadingResourceType), _originalWidth(other._originalWidth), _originalHeight(other._originalHeight), @@ -353,6 +355,11 @@ NetworkTexture::NetworkTexture(const NetworkTexture& other) : _height(other._height), _maxNumPixels(other._maxNumPixels) { + if (_width == 0 || _height == 0 || + other._currentlyLoadingResourceType == ResourceType::META || + (other._currentlyLoadingResourceType == ResourceType::KTX && other._ktxResourceState != KTXResourceState::WAITING_FOR_MIP_REQUEST)) { + _startedLoading = false; + } } static bool isLocalUrl(const QUrl& url) { @@ -364,6 +371,7 @@ void NetworkTexture::setExtra(void* extra) { const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra); _type = textureExtra ? textureExtra->type : image::TextureUsage::DEFAULT_TEXTURE; _maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; + _channelMapping = textureExtra ? textureExtra->channelMapping : ColorChannelMapping::NONE; _textureSource = std::make_shared<gpu::TextureSource>(_url, (int)_type); _lowestRequestedMipLevel = 0; @@ -425,7 +433,8 @@ gpu::TexturePointer NetworkTexture::getFallbackTexture() const { class ImageReader : public QRunnable { public: ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url, - const QByteArray& data, int maxNumPixels); + const QByteArray& data, size_t extraHash, int maxNumPixels, + ColorChannelMapping channelMapping); void run() override final; void read(); @@ -435,7 +444,9 @@ private: QWeakPointer<Resource> _resource; QUrl _url; QByteArray _content; + size_t _extraHash; int _maxNumPixels; + ColorChannelMapping _channelMapping; }; NetworkTexture::~NetworkTexture() { @@ -1068,7 +1079,7 @@ void NetworkTexture::loadTextureContent(const QByteArray& content) { return; } - QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels)); + QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _extraHash, _maxNumPixels, _channelMapping)); } void NetworkTexture::refresh() { @@ -1093,11 +1104,13 @@ void NetworkTexture::refresh() { Resource::refresh(); } -ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url, const QByteArray& data, int maxNumPixels) : +ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url, const QByteArray& data, size_t extraHash, int maxNumPixels, const ColorChannelMapping channelMapping) : _resource(resource), _url(url), _content(data), - _maxNumPixels(maxNumPixels) + _extraHash(extraHash), + _maxNumPixels(maxNumPixels), + _channelMapping(channelMapping) { DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing"); listSupportedImageFormats(); @@ -1152,11 +1165,12 @@ void ImageReader::read() { } auto networkTexture = resource.staticCast<NetworkTexture>(); - // Hash the source image to for KTX caching + // Hash the source image and extra to for KTX caching std::string hash; { QCryptographicHash hasher(QCryptographicHash::Md5); hasher.addData(_content); + hasher.addData(std::to_string(_extraHash).c_str()); hash = hasher.result().toHex().toStdString(); } @@ -1204,7 +1218,7 @@ void ImageReader::read() { constexpr bool shouldCompress = false; #endif auto target = getBackendTarget(); - texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); + texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _channelMapping, _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); if (!texture) { QMetaObject::invokeMethod(resource.data(), "setImage", diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index d744d060b6..44f7a0034c 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -96,6 +96,7 @@ private: friend class ImageReader; image::TextureUsage::Type _type; + ColorChannelMapping _channelMapping; enum class ResourceType { META, @@ -178,7 +179,8 @@ public: /// Loads a texture from the specified URL. NetworkTexturePointer getTexture(const QUrl& url, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE, - const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); + const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, + ColorChannelMapping channelMapping = ColorChannelMapping::NONE); gpu::TexturePointer getTextureByHash(const std::string& hash); gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture); @@ -201,7 +203,7 @@ signals: protected: // Overload ResourceCache::prefetch to allow specifying texture type for loads - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, ColorChannelMapping channelMapping = ColorChannelMapping::NONE); virtual QSharedPointer<Resource> createResource(const QUrl& url) override; QSharedPointer<Resource> createResourceCopy(const QSharedPointer<Resource>& resource) override; diff --git a/libraries/shared/src/shared/ColorChannelMapping.h b/libraries/shared/src/shared/ColorChannelMapping.h new file mode 100644 index 0000000000..c400ec1414 --- /dev/null +++ b/libraries/shared/src/shared/ColorChannelMapping.h @@ -0,0 +1,37 @@ +// +// ColorChannelMapping.h +// libraries/shared/src +// +// Created by Sabrina Shanman on 2019/02/05. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ColorChannelMapping_h +#define hifi_ColorChannelMapping_h + +#include "../RegisteredMetaTypes.h" + +enum class ColorChannelMapping { + NONE, + RED, + GREEN, + BLUE, + ALPHA, + COUNT +}; + +namespace std { + template <> + struct hash<ColorChannelMapping> { + size_t operator()(const ColorChannelMapping& a) const { + size_t result = 0; + hash_combine(result, (int)a); + return result; + } + }; +}; + +#endif // hifi_ColorChannelMapping_h