Utility to prefilter an environment at runtime

This adds a utility function on IndirectLight populate the reflection
map from an environment at runtime. This performs some processing
similar to cmgen, albeit at a reduced quality.
This commit is contained in:
Mathias Agopian
2019-06-20 18:53:27 -07:00
committed by Mathias Agopian
parent 902e7021fd
commit 56e9d8eda5
24 changed files with 396 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Google Configuration Checker Style" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@@ -14,6 +14,10 @@ add_library(utils STATIC IMPORTED)
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
add_library(ibl STATIC IMPORTED)
set_target_properties(ibl PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl.a)
add_library(filaflat STATIC IMPORTED)
set_target_properties(filaflat PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilaflat.a)
@@ -86,6 +90,7 @@ target_link_libraries(filament-jni
filaflat
filabridge
geometry
ibl
utils
log
GLESv3

View File

@@ -17,6 +17,9 @@
#include <jni.h>
#include <filament/IndirectLight.h>
#include <filament/Texture.h>
#include <common/NioUtils.h>
#include <common/CallbackUtils.h>
using namespace filament;

View File

@@ -329,6 +329,48 @@ Java_com_google_android_filament_Texture_nIsStreamValidForTexture(JNIEnv*, jclas
return (jboolean) (texture->getTarget() == SamplerType::SAMPLER_EXTERNAL);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGeneratePrefilterMipmap(JNIEnv *env, jclass,
jlong nativeTexture, jlong nativeEngine, jint width, jint height,
jobject storage, jint remaining, jint left,
jint top, jint type, jint alignment, jint stride, jint format,
jintArray faceOffsetsInBytes_, jobject handler, jobject runnable, jint sampleCount,
jboolean mirror) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
jint *faceOffsetsInBytes = env->GetIntArrayElements(faceOffsetsInBytes_, NULL);
Texture::FaceOffsets faceOffsets;
std::copy_n(faceOffsetsInBytes, 6, faceOffsets.offsets);
env->ReleaseIntArrayElements(faceOffsetsInBytes_, faceOffsetsInBytes, JNI_ABORT);
stride = stride ? stride : width;
size_t sizeInBytes = 6 *
Texture::computeTextureDataSize((Texture::Format) format, (Texture::Type) type,
(size_t) stride, (size_t) height, (size_t) alignment);
AutoBuffer nioBuffer(env, storage, 0);
if (sizeInBytes > (remaining << nioBuffer.getShift())) {
// BufferOverflowException
return -1;
}
void *buffer = nioBuffer.getData();
auto *callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer));
Texture::PixelBufferDescriptor desc(buffer, sizeInBytes, (backend::PixelDataFormat) format,
(backend::PixelDataType) type, (uint8_t) alignment, (uint32_t)0, (uint32_t)0,
(uint32_t) stride, &JniBufferCallback::invoke, callback);
Texture::PrefilterOptions options;
options.sampleCount = sampleCount;
options.mirror = mirror;
texture->generatePrefilterMipmap(*engine, std::move(desc), faceOffsets, &options);
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// ANDROID SPECIFIC BITS
////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -92,7 +92,7 @@ public class IndirectLight {
}
@NonNull
public Builder rotation(@NonNull @Size(min = 9) float rotation[]) {
public Builder rotation(@NonNull @Size(min = 9) float[] rotation) {
nRotation(mNativeBuilder,
rotation[0], rotation[1], rotation[2],
rotation[3], rotation[4], rotation[5],
@@ -132,7 +132,7 @@ public class IndirectLight {
return nGetIntensity(getNativeObject());
}
public void setRotation(@NonNull @Size(min = 9) float rotation[]) {
public void setRotation(@NonNull @Size(min = 9) float[] rotation) {
nSetRotation(getNativeObject(),
rotation[0], rotation[1], rotation[2],
rotation[3], rotation[4], rotation[5],
@@ -164,5 +164,4 @@ public class IndirectLight {
private static native void nSetIntensity(long nativeIndirectLight, float intensity);
private static native float nGetIntensity(long nativeIndirectLight);
private static native void nSetRotation(long nativeIndirectLight, float v0, float v1, float v2, float v3, float v4, float v5, float v6, float v7, float v8);
}

View File

@@ -287,6 +287,11 @@ public class Texture {
}
}
public static class PrefilterOptions {
public int sampleCount = 8;
public boolean mirror = true;
}
public static boolean isTextureFormatSupported(@NonNull Engine engine,
@NonNull InternalFormat format) {
return nIsTextureFormatSupported(engine.getNativeObject(), format.ordinal());
@@ -490,6 +495,33 @@ public class Texture {
nGenerateMipmaps(getNativeObject(), engine.getNativeObject());
}
public void generatePrefilterMipmap(@NonNull Engine engine,
@NonNull PixelBufferDescriptor buffer, @NonNull @Size(min = 6) int[] faceOffsetsInBytes,
PrefilterOptions options) {
int width = getWidth(0);
int height= getHeight(0);
int sampleCount = 8;
boolean mirror = true;
if (options != null) {
sampleCount = options.sampleCount;
mirror = options.mirror;
}
int result = nGeneratePrefilterMipmap(getNativeObject(), engine.getNativeObject(),
width, height,
buffer.storage, buffer.storage.remaining(),
buffer.left, buffer.top, buffer.type.ordinal(), buffer.alignment,
buffer.stride, buffer.format.ordinal(), faceOffsetsInBytes,
buffer.handler, buffer.callback,
sampleCount, mirror);
if (result < 0) {
throw new BufferOverflowException();
}
}
@UsedByReflection("TextureHelper.java")
long getNativeObject() {
if (mNativeObject == 0) {
@@ -554,4 +586,9 @@ public class Texture {
private static native void nGenerateMipmaps(long nativeTexture, long nativeEngine);
private static native boolean nIsStreamValidForTexture(long nativeTexture, long nativeStream);
private static native int nGeneratePrefilterMipmap(long nativeIndirectLight, long nativeEngine,
int width, int height, Buffer storage, int remaining, int left, int top,
int type, int alignment, int stride, int format, int[] faceOffsetsInBytes,
Object handler, Runnable callback, int sampleCount, boolean mirror);
}

View File

@@ -274,6 +274,7 @@ target_link_libraries(${TARGET} PUBLIC geometry) # TODO: remove this dependency
target_link_libraries(${TARGET} PUBLIC filaflat)
target_link_libraries(${TARGET} PUBLIC filabridge)
target_link_libraries(${TARGET} PUBLIC image_headers)
target_link_libraries(${TARGET} PUBLIC ibl)
# Android, iOS, and WebGL do not use bluegl.
if(NOT IOS AND NOT ANDROID AND NOT WEBGL)

View File

@@ -511,6 +511,11 @@ inline constexpr TextureUsage operator^(TextureUsage lhs, TextureUsage rhs) noex
return TextureUsage(uint8_t(lhs) ^ uint8_t(rhs));
}
//! returns whether this format a compressed format
static constexpr bool isCompressedFormat(TextureFormat format) noexcept {
return format >= TextureFormat::EAC_R11;
}
//! returns whether this format is an ETC2 compressed format
static constexpr bool isETC2Compression(TextureFormat format) noexcept {
return format >= TextureFormat::EAC_R11 && format <= TextureFormat::ETC2_EAC_SRGBA8;

View File

@@ -36,6 +36,8 @@ public:
using PixelDataFormat = backend::PixelDataFormat;
using PixelDataType = backend::PixelDataType;
PixelBufferDescriptor() = default;
PixelBufferDescriptor(void const* buffer, size_t size,
PixelDataFormat format, PixelDataType type, uint8_t alignment = 1,
uint32_t left = 0, uint32_t top = 0, uint32_t stride = 0,

View File

@@ -92,6 +92,7 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI {
struct BuilderDetails;
public:
//! Use Builder to construct an IndirectLight object instance
class Builder : public BuilderBase<BuilderDetails> {
friend struct BuilderDetails;

View File

@@ -85,6 +85,20 @@ public:
static size_t computeTextureDataSize(Texture::Format format, Texture::Type type,
size_t stride, size_t height, size_t alignment) noexcept;
/**
* Options for enviornment prefiltering into reflection map
*
* @see generatePrefilterMipmap()
*/
struct UTILS_PUBLIC PrefilterOptions {
uint16_t sampleCount = 8; //!< sample count used for filtering
bool mirror = true; //!< whether the environment must be mirrored
private:
UTILS_UNUSED uintptr_t reserved[3] = {};
};
//! Use Builder to construct a Texture object instance
class Builder : public BuilderBase<BuilderDetails> {
friend struct BuilderDetails;
@@ -353,6 +367,42 @@ public:
* @attention This Texture instance must NOT use Sampler::SAMPLER_CUBEMAP or it has no effect
*/
void generateMipmaps(Engine& engine) const noexcept;
/**
* Creates a reflection map from an environment map.
*
* This is a utility function that replaces calls to Texture::setImage().
* The provided environment map is processed and all mipmap levels are populated. The
* processing is similar to the offline tool `cmgen` as a lower quality setting.
*
* This function is intended to be used when the environment cannot be processed offline,
* for instance if it's generated at runtime.
*
* The source data must obey to some constraints:
* - the data type must be PixelDataFormat::RGB
* - the data format must be one of
* - PixelDataType::FLOAT
* - PixelDataType::HALF
*
* The current texture must be a cubemap
*
* The reflections cubemap's internal format cannot be a compressed format.
*
* The reflections cubemap's dimension must be a power-of-two.
*
* @warning This operation is computationally intensive, especially with large environments.
*
* @param engine Reference to the filament::Engine to associate this IndirectLight with.
* @param buffer Client-side buffer containing the images to set.
* @param faceOffsets Offsets in bytes into \p buffer for all six images. The offsets
* are specified in the following order: +x, -x, +y, -y, +z, -z
*
* @exception utils::PreConditionPanic if the source data constraints are not respected.
*
*/
void generatePrefilterMipmap(Engine& engine,
PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets,
PrefilterOptions const* options = nullptr);
};
} // namespace filament

View File

@@ -23,6 +23,9 @@
#include <utils/Panic.h>
#include <backend/DriverEnums.h>
#include <filament/IndirectLight.h>
#define IBL_INTEGRATION_PREFILTERED_CUBEMAP 0
#define IBL_INTEGRATION_IMPORTANCE_SAMPLING 1
#define IBL_INTEGRATION IBL_INTEGRATION_PREFILTERED_CUBEMAP

View File

@@ -21,6 +21,11 @@
#include "FilamentAPI-impl.h"
#include <ibl/Cubemap.h>
#include <ibl/CubemapIBL.h>
#include <ibl/CubemapUtils.h>
#include <ibl/Image.h>
#include <utils/Panic.h>
namespace filament {
@@ -333,6 +338,180 @@ size_t FTexture::getFormatSize(InternalFormat format) noexcept {
}
}
// this is a hack to be able to create a std::function<> with a non-copyable closure
template<class F>
auto make_copyable_function(F&& f) {
using dF = std::decay_t<F>;
auto spf = std::make_shared<dF>(std::forward<F>(f));
return [spf](auto&& ... args) -> decltype(auto) {
return (*spf)(decltype(args)(args)...);
};
}
void FTexture::generatePrefilterMipmap(FEngine& engine,
PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets,
PrefilterOptions const* options) {
using namespace ibl;
using namespace backend;
using namespace math;
const size_t size = getWidth();
const size_t stride = buffer.stride ? buffer.stride : size;
/* validate input data */
if (!ASSERT_PRECONDITION_NON_FATAL(buffer.format == PixelDataFormat::RGB,
"input data format must be RGB")) {
return;
}
if (!ASSERT_PRECONDITION_NON_FATAL(
buffer.type == PixelDataType::FLOAT ||
buffer.type == PixelDataType::HALF ||
buffer.type == PixelDataType::UINT_10F_11F_11F_REV,
"input data type must be FLOAT, HALF or UINT_10F_11F_11F_REV")) {
return;
}
/* validate texture */
if (!ASSERT_PRECONDITION_NON_FATAL(!(size & (size-1)),
"input data cubemap dimensions must be a power-of-two")) {
return;
}
if (!ASSERT_PRECONDITION_NON_FATAL(!isCompressed(),
"reflections texture cannot be compressed")) {
return;
}
PrefilterOptions defaultOptions;
options = options ? options : &defaultOptions;
utils::JobSystem& js = engine.getJobSystem();
FEngine::DriverApi& driver = engine.getDriverApi();
auto generateMipmaps = [](utils::JobSystem& js,
std::vector<Cubemap>& levels, std::vector<Image>& images) {
Image temp;
const Cubemap& base(levels[0]);
size_t dim = base.getDimensions();
size_t mipLevel = 0;
while (dim > 1) {
dim >>= 1u;
Cubemap dst = CubemapUtils::create(temp, dim);
const Cubemap& src(levels[mipLevel++]);
CubemapUtils::downsampleCubemapLevelBoxFilter(js, dst, src);
dst.makeSeamless();
images.push_back(std::move(temp));
levels.push_back(std::move(dst));
}
};
/*
* Create a Cubemap data structure
*/
Image temp;
Cubemap cml = CubemapUtils::create(temp, size);
for (size_t j = 0; j < 6; j++) {
Cubemap::Face face = (Cubemap::Face)j;
Image const& image = cml.getImageForFace(face);
for (size_t y = 0; y < size; y++) {
Cubemap::Texel* out = (Cubemap::Texel*)image.getPixelRef(0, y);
if (buffer.type == PixelDataType::FLOAT) {
float3 const* src = reinterpret_cast<float3 const*>(
static_cast<char const*>(buffer.buffer) + faceOffsets[j]) + y * stride;
for (size_t x = 0; x < size; x++, out++, src++) {
Cubemap::writeAt(out, *src);
}
} else if (buffer.type == PixelDataType::HALF) {
half3 const* src = reinterpret_cast<half3 const*>(
static_cast<char const*>(buffer.buffer) + faceOffsets[j]) + y * stride;
for (size_t x = 0; x < size; x++, out++, src++) {
Cubemap::writeAt(out, *src);
}
} else if (buffer.type == PixelDataType::UINT_10F_11F_11F_REV) {
uint32_t const* src = reinterpret_cast<uint32_t const*>(
static_cast<char const*>(buffer.buffer) + faceOffsets[j]) + y * stride;
for (size_t x = 0; x < size; x++, out++, src++) {
using fp10 = math::fp<0, 5, 5>;
using fp11 = math::fp<0, 5, 6>;
fp11 r{ uint16_t( *src & 0x7FFu) };
fp11 g{ uint16_t((*src >> 11u) & 0x7FFu) };
fp10 b{ uint16_t((*src >> 22u) & 0x3FFu) };
Cubemap::Texel texel{ fp11::tof(r), fp11::tof(g), fp10::tof(b) };
Cubemap::writeAt(out, texel);
}
}
}
}
/*
* Create the mipmap chain
*/
std::vector<Image> images;
std::vector<Cubemap> levels;
images.reserve(getLevels());
levels.reserve(getLevels());
images.push_back(std::move(temp));
levels.push_back(std::move(cml));
if (options->mirror) {
Image mirrorImage;
Cubemap cubemap(CubemapUtils::create(mirrorImage, size));
CubemapUtils::mirrorCubemap(js, cubemap, levels[0]);
std::swap(levels[0], cubemap);
std::swap(images[0], mirrorImage);
}
// make the cubemap seamless
levels[0].makeSeamless();
// Now generate all the mipmap levels
generateMipmaps(js, levels, images);
// Finally generate each pre-filtered mipmap level
const size_t baseExp = utils::ctz(size);
size_t numSamples = options->sampleCount;
const size_t numLevels = baseExp + 1;
for (ssize_t i = baseExp; i >= 0; --i) {
const size_t dim = 1U << i;
const size_t level = baseExp - i;
const float lod = saturate(level / (numLevels - 1.0));
const float linearRoughness = lod * lod;
Image image;
Cubemap dst = CubemapUtils::create(image, dim);
CubemapIBL::roughnessFilter(js, dst, levels, linearRoughness, numSamples);
Texture::PixelBufferDescriptor pbd(image.getData(), image.getSize(),
Texture::PixelBufferDescriptor::PixelDataFormat::RGB,
Texture::PixelBufferDescriptor::PixelDataType::FLOAT, 1, 0, 0, image.getStride());
uintptr_t base = uintptr_t(image.getData());
backend::FaceOffsets offsets{};
for (size_t j = 0; j < 6; j++) {
Image const& faceImage = dst.getImageForFace((Cubemap::Face)j);
offsets[j] = uintptr_t(faceImage.getData()) - base;
}
// upload all 6 faces into the texture
driver.updateCubeImage(mHandle, level, std::move(pbd), offsets);
// enqueue a commands that holds the image data until it's executed
driver.queueCommand(make_copyable_function([data = image.detach()]() {}));
}
// no need to call the user callback because buffer is a reference and it'll be destroyed
// by the caller (without being move()d here).
}
} // namespace details
// ------------------------------------------------------------------------------------------------
@@ -405,4 +584,9 @@ size_t Texture::computeTextureDataSize(Texture::Format format, Texture::Type typ
return FTexture::computeTextureDataSize(format, type, stride, height, alignment);
}
void Texture::generatePrefilterMipmap(Engine& engine, Texture::PixelBufferDescriptor&& buffer,
const Texture::FaceOffsets& faceOffsets, PrefilterOptions const* options) {
upcast(this)->generatePrefilterMipmap(upcast(engine), std::move(buffer), faceOffsets, options);
}
} // namespace filament

View File

@@ -22,6 +22,7 @@
#include <backend/Handle.h>
#include <filament/IndirectLight.h>
#include <filament/Texture.h>
#include <utils/compiler.h>

View File

@@ -60,6 +60,10 @@ public:
void setImage(FEngine& engine, size_t level,
PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const noexcept;
void generatePrefilterMipmap(FEngine& engine,
PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets,
PrefilterOptions const* options);
void setExternalImage(FEngine& engine, void* image) noexcept;
void setExternalStream(FEngine& engine, FStream* stream) noexcept;
@@ -68,6 +72,7 @@ public:
void setSampleCount(size_t sampleCount) noexcept { mSampleCount = uint8_t(sampleCount); }
size_t getSampleCount() const noexcept { return mSampleCount; }
bool isMultisample() const noexcept { return mSampleCount > 1; }
bool isCompressed() const noexcept { return backend::isCompressedFormat(mFormat); }
bool isCubemap() const noexcept { return mTarget == Sampler::SAMPLER_CUBEMAP; }

View File

@@ -44,7 +44,7 @@ target_link_libraries(${TARGET} math utils)
if (MSVC OR CLANG_CL)
target_compile_options(${TARGET} PRIVATE /fp:fast)
else()
target_compile_options(${TARGET} PRIVATE -ffast-math)
target_compile_options(${TARGET} PRIVATE -ffast-math $<$<PLATFORM_ID:Linux>:-fPIC>)
endif()

View File

@@ -27,7 +27,6 @@
#include <algorithm>
namespace filament {
namespace ibl {
@@ -60,12 +59,12 @@ public:
enum class Face : uint8_t {
NX = 0, // left +----+
PX, // right | PY |
NY, // bottom +----+----+----+----+
PY, // top | NX | PZ | PX | NZ |
NZ, // back +----+----+----+----+
PZ // front | NY |
PX = 0, // left +----+
NX, // right | PY |
PY, // bottom +----+----+----+----+
NY, // top | NX | PZ | PX | NZ |
PZ, // back +----+----+----+----+
NZ // front | NY |
// +----+
};

View File

@@ -17,9 +17,6 @@
#ifndef IBL_CUBEMAPIBL_H
#define IBL_CUBEMAPIBL_H
namespace utils {
class JobSystem;
} // namespace utils
#include <functional>
#include <vector>
@@ -27,6 +24,10 @@ class JobSystem;
#include <stdint.h>
#include <stddef.h>
namespace utils {
class JobSystem;
} // namespace utils
namespace filament {
namespace ibl {

View File

@@ -41,6 +41,8 @@ public:
size_t getWidth() const { return mWidth; }
size_t getStride() const { return mBpr / getBytesPerPixel(); }
size_t getHeight() const { return mHeight; }
size_t getBytesPerRow() const { return mBpr; }
@@ -49,8 +51,12 @@ public:
void* getData() const { return mData; }
size_t getSize() const { return mBpr * mHeight; }
void* getPixelRef(size_t x, size_t y) const;
std::unique_ptr<uint8_t[]> detach() { return std::move(mOwnedData); }
private:
size_t mBpr = 0;
size_t mWidth = 0;

View File

@@ -44,11 +44,11 @@ inline bool isPOT(size_t x) {
inline filament::math::double2 hammersley(uint32_t i, float iN) {
constexpr float tof = 0.5f / 0x80000000U;
uint32_t bits = i;
bits = (bits << 16) | (bits >> 16);
bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return { i * iN, bits * tof };
}

View File

@@ -300,8 +300,8 @@ void CubemapIBL::roughnessFilter(JobSystem& js, Cubemap& dst, const std::vector<
if (linearRoughness == 0) {
CubemapUtils::process<CubemapUtils::EmptyState>(dst, js, [&]
(CubemapUtils::EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
if (updater) {
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
updater(0, (float)p / (dim * 6));
}
const Cubemap& cm = levels[0];
@@ -395,8 +395,8 @@ void CubemapIBL::roughnessFilter(JobSystem& js, Cubemap& dst, const std::vector<
[&](CubemapUtils::EmptyState&, size_t y,
Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
if (updater) {
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
updater(0, (float)p / (dim * 6));
}
@@ -563,8 +563,8 @@ void CubemapIBL::diffuseIrradiance(JobSystem& js, Cubemap& dst, const std::vecto
[&](CubemapUtils::EmptyState&, size_t y,
Cubemap::Face f, Cubemap::Texel* data, size_t dim) {
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
if (updater) {
size_t p = progress.fetch_add(1, std::memory_order_relaxed) + 1;
updater(0, (float)p / (dim * 6));
}

View File

@@ -241,7 +241,7 @@ void CubemapUtils::downsampleCubemapLevelBoxFilter(JobSystem& js, Cubemap& dst,
// ------------------------------------------------------------------------------------------------
void CubemapUtils::setFaceFromCross(Cubemap& cm, Cubemap::Face face, const Image& image) {
size_t dim = cm.getDimensions() + 2; // 2 extra per image, for seemlessness
size_t dim = cm.getDimensions() + 2; // 2 extra per image, for seamlessness
size_t x = 0;
size_t y = 0;
switch (face) {
@@ -322,12 +322,12 @@ void CubemapUtils::mirrorCubemap(JobSystem& js, Cubemap& dst, const Cubemap& src
void CubemapUtils::generateUVGrid(JobSystem& js, Cubemap& cml, size_t gridFrequencyX, size_t gridFrequencyY) {
Cubemap::Texel const colors[6] = {
{ 1, 0, 0 }, // -X / l - red
{ 1, 1, 1 }, // +X / r - white
{ 0, 1, 0 }, // -Y / b - green
{ 1, 0, 0 }, // -X / l - red
{ 0, 0, 1 }, // +Y / t - blue
{ 1, 0, 1 }, // -Z / bk - magenta
{ 0, 1, 0 }, // -Y / b - green
{ 1, 1, 0 }, // +z / fr - yellow
{ 1, 0, 1 }, // -Z / bk - magenta
};
const float uvGridHDRIntensity = 5.0f;
size_t gridSizeX = cml.getDimensions() / gridFrequencyX;

View File

@@ -121,6 +121,7 @@ bool IBL::loadFromDirectory(const utils::Path& path) {
// Read mip-mapped cubemap
const std::string prefix = "m";
if (!loadCubemapLevel(&mTexture, path, 0, prefix + "0_")) return false;
size_t numLevels = mTexture->getLevels();
for (size_t i = 1; i<numLevels; i++) {
const std::string levelPrefix = prefix + std::to_string(i) + "_";
@@ -142,6 +143,19 @@ bool IBL::loadFromDirectory(const utils::Path& path) {
bool IBL::loadCubemapLevel(filament::Texture** texture, const utils::Path& path, size_t level,
std::string const& levelPrefix) const {
Texture::FaceOffsets offsets;
Texture::PixelBufferDescriptor buffer;
bool success = loadCubemapLevel(texture, &buffer, &offsets, path, level, levelPrefix);
if (!success) return false;
(*texture)->setImage(mEngine, level, std::move(buffer), offsets);
return true;
}
bool IBL::loadCubemapLevel(
filament::Texture** texture,
Texture::PixelBufferDescriptor* outBuffer,
Texture::FaceOffsets* outOffsets,
const utils::Path& path, size_t level, std::string const& levelPrefix) const {
static const char* faceSuffix[6] = { "px", "nx", "py", "ny", "pz", "nz" };
size_t size = 0;
@@ -223,7 +237,8 @@ bool IBL::loadCubemapLevel(filament::Texture** texture, const utils::Path& path,
if (!success) return false;
(*texture)->setImage(mEngine, level, std::move(buffer), offsets);
*outBuffer = std::move(buffer);
*outOffsets = offsets;
return true;
}

View File

@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_SAMPLE_IBL_H
#define TNT_FILAMENT_SAMPLE_IBL_H
#include <filament/Texture.h>
#include <math/vec3.h>
#include <string>
@@ -56,6 +58,13 @@ private:
bool loadCubemapLevel(filament::Texture** texture, const utils::Path& path,
size_t level = 0, std::string const& levelPrefix = "") const;
bool loadCubemapLevel(filament::Texture** texture,
filament::Texture::PixelBufferDescriptor* outBuffer,
filament::Texture::FaceOffsets* outOffsets,
const utils::Path& path,
size_t level = 0, std::string const& levelPrefix = "") const;
filament::Engine& mEngine;
filament::math::float3 mBands[9] = {};