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:
committed by
Mathias Agopian
parent
902e7021fd
commit
56e9d8eda5
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <filament/IndirectLight.h>
|
||||
#include <filament/Texture.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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 |
|
||||
// +----+
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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] = {};
|
||||
|
||||
Reference in New Issue
Block a user