Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Differentiable Mipmap #599

Draft
wants to merge 33 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a8b350d
mipmap constructor
Feb 25, 2023
d2a2701
eval_1
Feb 26, 2023
477a96d
running mipmap for 1 channel
Feb 27, 2023
3fa88b7
test with ray differential
Feb 27, 2023
0d1a660
minor change
ziyi-zhang Feb 27, 2023
19f0df2
working mipmap under vector mode
Feb 27, 2023
0503de4
merge
Feb 27, 2023
202c7df
optimize eval level
Feb 28, 2023
baf8a79
test with pbrt
Feb 28, 2023
bf47cfb
trilinear sanity check
Mar 2, 2023
f81e67f
box and max(duv_dxy) seems to be better
Mar 2, 2023
20be5f4
spp=8 with pbrt
Mar 2, 2023
c5a92cc
mipmap ewa
Mar 8, 2023
78cb540
use pbrt way to calculate radius
Mar 9, 2023
337046e
working but slow ewa
Mar 13, 2023
16e265d
tensor downsampling
Mar 17, 2023
1aabb65
working differentiable trilinear
Mar 17, 2023
9032a10
trying to grad_replace ewa
Mar 17, 2023
b9511d9
2D mipmap without implementing query
Mar 20, 2023
c376564
back to array of textures
Mar 21, 2023
dd7be27
failing vcall
Mar 21, 2023
dc03a18
compiled texture vcall
Mar 22, 2023
282b251
working mipmap with vcall, but failed with ad
Mar 22, 2023
aa9bbc4
failing backward vcall
Mar 23, 2023
d05a311
change template
Mar 23, 2023
0f32c4e
working vcall ewa and trilinear
Mar 24, 2023
127b6b2
working 3 channel
Mar 24, 2023
26f912c
working 3 channel ewa
Mar 28, 2023
9c03f17
add interpolate_spectral
Mar 28, 2023
49edec3
no spectral support for mipmap; add clamp to downsampling
Mar 30, 2023
624b7fa
fix evalEWA3 bug
Mar 31, 2023
470b51b
working mipmap and replace_grad ewa
Jun 27, 2023
c152f39
Merge branch 'master' into mip
Axiwa Jun 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
eval_1
Axiwa authored and Axiwa committed Feb 26, 2023
commit d2a270151327214a1d2bb01fed09e8d585bdad38
161 changes: 121 additions & 40 deletions include/mitsuba/render/mipmap.h
Original file line number Diff line number Diff line change
@@ -11,9 +11,9 @@
#include <mitsuba/core/transform.h>
#include <mitsuba/render/interaction.h>
#include <mitsuba/render/shape.h>

#include <drjit/tensor.h>
#include <drjit/texture.h>
#include <drjit/struct.h>


NAMESPACE_BEGIN(mitsuba)
@@ -37,77 +37,158 @@ class MIPMap : public Object{
public:
MI_IMPORT_TYPES(Texture)

MIPMap(Bitmap *bitmap,
MIPMap(ref<Bitmap> bitmap,
Bitmap::PixelFormat pixelFormat, // channels
Struct::Type componentFormat, // channel format
const ReconstructionFilter<Float, Spectrum> *rfilter,
FilterBoundaryCondition bc,
MIPFilterType filterType,
const ref<Bitmap::ReconstructionFilter> rfilter,
dr::WrapMode wrap_mode,
dr::FilterMode tex_filter,
MIPFilterType mip_filter,
size_t channels = 3,
Float maxAnisotropy = 20.0f,
bool accel = true
bool accel = true,
ScalarFloat maxValue = 1.0f
):
m_pixelFormat(pixelFormat),
m_filterType(filterType),
m_bc(bc),
m_texture_filter(tex_filter),
m_mipmap_filter(mip_filter),
m_bc(wrap_mode),
m_weightLut(NULL),
m_maxAnisotropy(maxAnisotropy),
m_accel(accel){
m_accel(accel),
m_channels(channels){

// TODO: resize to be power of two : seems to be done by bitmap resample!!
// Compuate the total levles
channels = bitmap->channel_count();
m_levels = 1;

ScalarVector2u size = ScalarVector2u(bitmap->size());
ScalarUInt64 pyramid_height = size.y;
ScalarUInt64 pyramid_width = size.x;
if (m_filterType != Nearest && m_filterType != Bilinear){
while(size.x > 1 || size.y > 1){
size.x = dr::max(1, (size.x + 1) / 2);
size.y = dr::max(1, (size.y + 1) / 2);
if (mip_filter != MIPFilterType::Nearest && mip_filter != MIPFilterType::Bilinear){
ScalarVector2u size = ScalarVector2u(bitmap->size());
while(size.x() > 1 || size.y() > 1){
size.x() = dr::maximum(1, (size.x() + 1) / 2);
size.y() = dr::maximum(1, (size.y() + 1) / 2);
++m_levels;
}
}

// allocate pyramid
// Allocate pyramid
m_pyramid.init_(m_levels);
std::cout<<m_pyramid.size()<<std::endl;

dr::FilterMode filter_mode;
dr::WrapMode wrap_mode;
size_t channels = bitmap->channel_count();

ScalarVector2i res = ScalarVector2i(bitmap->size());

// Initialize level 0
ScalarVector2u res = ScalarVector2u(bitmap->size());
size_t shape[3] = { (size_t) res.y(), (size_t) res.x(), channels };
m_pyramid[0] = Texture2f(TensorXf(bitmap->data(), 3, shape), m_accel, m_accel, filter_mode, wrap_mode);
m_pyramid[0] = Texture2f(TensorXf(bitmap->data(), 3, shape), m_accel, m_accel, tex_filter, wrap_mode);

if (m_filterType != Nearest && m_filterType != Bilinear){
Vector2i size = bitmap->size();
// Downsample until 1x1
if (mip_filter != MIPFilterType::Nearest && mip_filter != MIPFilterType::Bilinear){
ScalarVector2u size = bitmap->size();
m_levels = 1;
while (size.x > 1 || size.y > 1) {
while (size.x() > 1 || size.y() > 1) {
/* Compute the size of the next downsampled layer */
size.x = std::max(1, (size.x + 1) / 2);
size.y = std::max(1, (size.y + 1) / 2);
size.x() = dr::maximum(1, (size.x() + 1) / 2);
size.y() = dr::maximum(1, (size.y() + 1) / 2);

// Resample to be new size
bitmap = bitmap->resample(size, rfilter);

bitmap = bitmap->resample(size, rfilter, m_bc, {m_minimum, m_maximum});
ScalarVector2i res = ScalarVector2i(bitmap->size());
size_t shape[3] = { (size_t) res.y(), (size_t) res.x(), channels };
size_t shape[3] = { (size_t) size.y(), (size_t) size.x(), channels };
m_pyramid[m_levels] = Texture2f(TensorXf(bitmap->data(), 3, shape), m_accel, m_accel, tex_filter, wrap_mode);

// Test if the pyramid is built
// FileStream* str = new FileStream(std::to_string(m_levels)+".exr", FileStream::ETruncReadWrite);
// bitmap->write(str);

m_pyramid[m_levels] = Texture2f(TensorXf(bitmap->data(), 3, shape), m_accel, m_accel, filter_mode, wrap_mode);
++m_levels;
}
}

if (m_mipmap_filter == MIPFilterType::EWA){
// TODO: ewa weights
}
}

void rebuild_pyramid(){
// TODO:
std::cout<<"Not implemented yet..."<<std::endl;
}

Float evalTexel(int level, Point2f uv, Mask active){
Color3f out;
if (m_accel){
m_pyramid[level].eval(uv, out.data(), active);
}
else{
m_pyramid[level].eval_nonaccel(uv, out.data(), active);
}
}

Float eval_1(const Point2f &uv, const Vector2f &d0, const Vector2f &d1, Mask active) const{
const Vector2i &size = m_pyramid[0].tensor().size();
Float du0 = d0.x() * size.x(), dv0 = d0.y() * size.y(),
du1 = d1.x() * size.x(), dv1 = d1.y() * size.y();

Float A = dv0*dv0 + dv1*dv1,
B = -2.0f * (du0*dv0 + du1*dv1),
C = du0*du0 + du1*du1,
F = A*C - B*B*0.25f;

Float root = dr::hypot(A-C, B),
Aprime = 0.5f * (A + C - root),
Cprime = 0.5f * (A + C + root),
majorRadius = dr::select(Aprime != 0, dr::sqrt(F / Aprime), 0),
minorRadius = dr::select(Cprime != 0, dr::sqrt(F / Cprime), 0);

// If isTri, perform trilinear filter
Mask isTri = (m_mipmap_filter == Trilinear || !(minorRadius > 0) || !(majorRadius > 0) || F < 0);

Float level = dr::log2(dr::maximum(majorRadius, dr::Epsilon<Float>));
Int32 ilevel = dr::floor2int<Int32>(level);

Mask isZero = false;
isZero = dr::select(ilevel < 0, true, false);


/* Bilinear interpolation (lookup is smaller than 1 pixel) */
// Float out_zero;
// if (m_accel)
// dr::gather<Texture2f>(m_pyramid, Int32(0), active & isZero); //.eval(uv, &out_zero, active);
// else
// dr::gather<Texture2f>(m_pyramid, Int32(0), active & isZero); //.eval_nonaccel(uv, &out_zero, active);


// /* Trilinear interpolation between two mipmap levels */
// Float a = level - ilevel;
// Float out_upper;
// if (m_accel)
// dr::gather<Texture2f>(m_pyramid, ilevel+1, active & !isZero); //.eval(uv, &out_upper, active);
// else
// dr::gather<Texture2f>(m_pyramid, ilevel+1, active & !isZero); //.eval_nonaccel(uv, &out_upper, active);

// Float out_lower;
// if (m_accel)
// dr::gather<Texture2f>(m_pyramid, ilevel, active & !isZero); //.eval(uv, &out_lower, active);
// else
// dr::gather<Texture2f>(m_pyramid, ilevel, active & !isZero); //.eval_nonaccel(uv, &out_lower, active);

// Float out = out_upper * (1.0f - a) + out_lower * a;
// dr::select(out, isZero, out_zero);

Float out;
return out;

}



private:
Bitmap::PixelFormat m_pixelFormat;
MIPFilterType m_filterType;
FilterBoundaryCondition m_bc;
dr::FilterMode m_texture_filter;
MIPFilterType m_mipmap_filter;
dr::WrapMode m_bc;
Float m_maxAnisotropy;
bool m_accel;
size_t m_channels;

dr::DynamicArray<Texture2f> m_pyramid;
Float *m_weightLut;
dr::DynamicArray<Float> m_weightLut;
int m_levels;

ScalarFloat m_minimum; // this is got from bitmap
90 changes: 74 additions & 16 deletions src/textures/bitmap2.cpp
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
#include <mitsuba/render/interaction.h>
#include <mitsuba/render/texture.h>
#include <mitsuba/render/srgb.h>
#include <mitsuba/render/mipmap.h>
#include <drjit/tensor.h>
#include <drjit/texture.h>
#include <mutex>
@@ -112,11 +113,11 @@ at all.
*/

template <typename Float, typename Spectrum>
class BitmapTexture final : public Texture<Float, Spectrum> {
class BitmapTexture2 final : public Texture<Float, Spectrum> {
public:
MI_IMPORT_TYPES(Texture)

BitmapTexture(const Properties &props) : Texture(props) {
BitmapTexture2(const Properties &props) : Texture(props) {
m_transform = props.get<ScalarTransform4f>("to_uv", ScalarTransform4f())
.extract();
if (m_transform != ScalarTransform3f())
@@ -200,6 +201,7 @@ class BitmapTexture final : public Texture<Float, Spectrum> {
m_bitmap =
m_bitmap->convert(pixel_format, struct_type_v<ScalarFloat>, false);

// Upsampling to 2x2
if (dr::any(m_bitmap->size() < 2)) {
Log(Warn,
"Image must be at least 2x2 pixels in size, up-sampling..");
@@ -250,7 +252,7 @@ class BitmapTexture final : public Texture<Float, Spectrum> {

if (exceed_unit_range && !m_raw)
Log(Warn,
"BitmapTexture: texture named \"%s\" contains pixels that "
"BitmapTexture2: texture named \"%s\" contains pixels that "
"exceed the [0, 1] range!",
m_name);

@@ -261,6 +263,43 @@ class BitmapTexture final : public Texture<Float, Spectrum> {
size_t shape[3] = { (size_t) res.y(), (size_t) res.x(), channels };
m_texture = Texture2f(TensorXf(m_bitmap->data(), 3, shape), m_accel,
m_accel, filter_mode, wrap_mode);


// Generate MIP map hierarchy; downsample using a 2-lobed Lanczos reconstruction filter
std::string mip_filter_str = props.string("mipmap_filter_type", "trilinear");
if (mip_filter_str == "nearest")
m_mip_filter = MIPFilterType::Nearest;
else if (mip_filter_str == "bilinear")
m_mip_filter = MIPFilterType::Bilinear;
else if (mip_filter_str == "trilinear"){
if (filter_mode_str == "nearest"){
Log(Warn, "Mipmap filter may not be compatible with texture filter");
}
m_mip_filter = MIPFilterType::Trilinear;
}
else if (mip_filter_str == "ewa")
m_mip_filter = MIPFilterType::EWA;
else
Throw("Invalid filter type \"%s\", must be one of: \"nearest\", or "
"\"bilinear\"! or \"trilinear\", or \"ewa\" ", filter_mode_str);

// get Anisotropy of EWA
m_maxAnisotropy = props.get<ScalarFloat>("maxAnisotropy", 20);

// get mipmap filter
using ReconstructionFilter = Bitmap::ReconstructionFilter;
Properties rfilterProps("lanczos");
rfilterProps.set_int("lobes", 2);
ref<ReconstructionFilter> rfilter = static_cast<ReconstructionFilter *> (
PluginManager::instance()->create_object<ReconstructionFilter>(rfilterProps));

if (pixel_format == Bitmap::PixelFormat::Y){
m_mipmap = new MIPMap<Float, Spectrum>(m_bitmap, pixel_format, struct_type_v<ScalarFloat>, rfilter, wrap_mode, filter_mode, m_mip_filter);
}
else{
m_mipmap = new MIPMap<Float, Spectrum>(m_bitmap, pixel_format, struct_type_v<ScalarFloat>, rfilter, wrap_mode, filter_mode, m_mip_filter);
}

}

void traverse(TraversalCallback *callback) override {
@@ -560,7 +599,7 @@ class BitmapTexture final : public Texture<Float, Spectrum> {

std::string to_string() const override {
std::ostringstream oss;
oss << "BitmapTexture[" << std::endl
oss << "BitmapTexture2[" << std::endl
<< " name = \"" << m_name << "\"," << std::endl
<< " resolution = \"" << resolution() << "\"," << std::endl
<< " raw = " << (int) m_raw << "," << std::endl
@@ -637,13 +676,24 @@ class BitmapTexture final : public Texture<Float, Spectrum> {

Point2f uv = m_transform.transform_affine(si.uv);

Float out;
if (m_accel)
m_texture.eval(uv, &out, active);
else
m_texture.eval_nonaccel(uv, &out, active);
if (m_mip_filter == MIPFilterType::Nearest || m_mip_filter == MIPFilterType::Bilinear){
Float out;
if (m_accel)
m_texture.eval(uv, &out, active);
else
m_texture.eval_nonaccel(uv, &out, active);

return out;
}
else{
Vector2f duvdx = si.duv_dx;
Vector2f duvdy = si.duv_dy;
// TODO: get correctly transformed dst/dxy

m_mipmap->eval_1(uv, duvdx, duvdy, active);

}

return out;
}

/**
@@ -671,7 +721,7 @@ class BitmapTexture final : public Texture<Float, Spectrum> {
* \brief Recompute mean and 2D sampling distribution (if requested)
* following an update
*/
void rebuild_internals(bool init_mean, bool init_distr) {
void rebuild_internals(bool init_mean, bool init_distr, bool init_pyramid = true) {
auto&& data = dr::migrate(m_texture.value(), AllocType::Host);

if constexpr (dr::is_jit_v<Float>)
@@ -726,9 +776,13 @@ class BitmapTexture final : public Texture<Float, Spectrum> {
if (init_mean)
m_mean = dr::opaque<Float>(ScalarFloat(mean / pixel_count));

if (init_pyramid){
m_mipmap->rebuild_pyramid();
}

if (exceed_unit_range && !m_raw)
Log(Warn,
"BitmapTexture: texture named \"%s\" contains pixels that "
"BitmapTexture2: texture named \"%s\" contains pixels that "
"exceed the [0, 1] range!",
m_name);
}
@@ -737,8 +791,8 @@ class BitmapTexture final : public Texture<Float, Spectrum> {
MI_INLINE void init_distr() const {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_distr2d) {
auto self = const_cast<BitmapTexture *>(this);
self->rebuild_internals(false, true);
auto self = const_cast<BitmapTexture2 *>(this);
self->rebuild_internals(false, true, false);
}
}

@@ -754,9 +808,13 @@ class BitmapTexture final : public Texture<Float, Spectrum> {
// Optional: distribution for importance sampling
mutable std::mutex m_mutex;
std::unique_ptr<DiscreteDistribution2D<Float>> m_distr2d;

ScalarFloat m_maxAnisotropy;
ref<MIPMap<Float, Spectrum>> m_mipmap;
MIPFilterType m_mip_filter;
};

MI_IMPLEMENT_CLASS_VARIANT(BitmapTexture, Texture)
MI_EXPORT_PLUGIN(BitmapTexture, "Bitmap texture")
MI_IMPLEMENT_CLASS_VARIANT(BitmapTexture2, Texture)
MI_EXPORT_PLUGIN(BitmapTexture2, "Bitmap texture")

NAMESPACE_END(mitsuba)