19 #include <nori/warp.h>
20 #include <nori/bsdf.h>
21 #include <nori/vector.h>
22 #include <nanogui/screen.h>
23 #include <nanogui/label.h>
24 #include <nanogui/window.h>
25 #include <nanogui/layout.h>
26 #include <nanogui/icons.h>
27 #include <nanogui/combobox.h>
28 #include <nanogui/slider.h>
29 #include <nanogui/textbox.h>
30 #include <nanogui/checkbox.h>
31 #include <nanogui/messagedialog.h>
32 #include <nanogui/renderpass.h>
33 #include <nanogui/shader.h>
34 #include <nanogui/texture.h>
35 #include <nanogui/screen.h>
36 #include <nanogui/opengl.h>
37 #include <nanogui/window.h>
39 #include <nanovg_gl.h>
42 #include <hypothesis.h>
43 #include <tinyformat.h>
45 #include <Eigen/Geometry>
54 using namespace nanogui;
57 using nori::NoriException;
58 using nori::NoriObjectFactory;
63 using nori::PropertyList;
65 using nori::BSDFQueryRecord;
69 enum PointType :
int {
87 static const std::string kWarpTypeNames[WarpTypeCount] = {
88 "square",
"disk",
"uniform_sphere",
"uniform_sphere_cap",
"uniform_hemisphere",
89 "cosine_hemisphere",
"beckmann",
"microfacet_brdf"
94 static const int kDefaultXres = 51;
95 static const int kDefaultYres = 51;
100 BSDFQueryRecord bRec;
104 std::unique_ptr<double[]> obsFrequencies, expFrequencies;
106 WarpTest(WarpType warpType_,
float parameterValue_, BSDF *bsdf_ =
nullptr,
107 BSDFQueryRecord bRec_ = BSDFQueryRecord(nori::Vector3f(), Point2f()),
108 int xres_ = kDefaultXres,
int yres_ = kDefaultYres)
109 : warpType(warpType_), parameterValue(parameterValue_), bsdf(bsdf_),
110 bRec(bRec_), xres(xres_), yres(yres_) {
112 if (warpType != Square && warpType != Disk)
117 std::pair<bool, std::string> run() {
118 int sampleCount = 1000 * res;
119 obsFrequencies.reset(
new double[res]);
120 expFrequencies.reset(
new double[res]);
121 memset(obsFrequencies.get(), 0, res*
sizeof(
double));
122 memset(expFrequencies.get(), 0, res*
sizeof(
double));
124 nori::MatrixXf points, values;
125 generatePoints(sampleCount,
Independent, points, values);
127 for (
int i=0; i<sampleCount; ++i) {
128 if (values(0, i) == 0)
130 nori::Vector3f sample = points.col(i);
133 if (warpType == Square) {
136 }
else if (warpType == Disk) {
137 x = sample.x() * 0.5f + 0.5f;
138 y = sample.y() * 0.5f + 0.5f;
140 x = std::atan2(sample.y(), sample.x()) * INV_TWOPI;
143 y = sample.z() * 0.5f + 0.5f;
146 int xbin = std::min(xres-1, std::max(0, (
int) std::floor(x * xres)));
147 int ybin = std::min(yres-1, std::max(0, (
int) std::floor(y * yres)));
148 obsFrequencies[ybin * xres + xbin] += 1;
151 auto integrand = [&](
double y,
double x) ->
double {
152 if (warpType == Square) {
154 }
else if (warpType == Disk) {
155 x = x * 2 - 1; y = y * 2 - 1;
161 double sinTheta = std::sqrt(1 - y * y);
162 double sinPhi = std::sin(x),
163 cosPhi = std::cos(x);
165 nori::Vector3f v((
float) (sinTheta * cosPhi),
166 (
float) (sinTheta * sinPhi),
169 if (warpType == UniformSphere)
171 else if (warpType == UniformSphereCap)
173 else if (warpType == UniformHemisphere)
175 else if (warpType == CosineHemisphere)
177 else if (warpType == Beckmann)
179 else if (warpType == MicrofacetBRDF) {
180 BSDFQueryRecord br(bRec);
182 br.measure = nori::ESolidAngle;
183 return bsdf->pdf(br);
185 throw NoriException(
"Invalid warp type");
190 double scale = sampleCount;
191 if (warpType == Square)
193 else if (warpType == Disk)
198 double *ptr = expFrequencies.get();
199 for (
int y=0; y<yres; ++y) {
200 double yStart = y / (double) yres;
201 double yEnd = (y+1) / (
double) yres;
202 for (
int x=0; x<xres; ++x) {
203 double xStart = x / (double) xres;
204 double xEnd = (x+1) / (
double) xres;
205 ptr[y * xres + x] = hypothesis::adaptiveSimpson2D(
206 integrand, yStart, xStart, yEnd, xEnd) * scale;
207 if (ptr[y * xres + x] < 0)
208 throw NoriException(
"The Pdf() function returned negative values!");
213 hypothesis::chi2_dump(yres, xres, obsFrequencies.get(), expFrequencies.get(),
"chi2test.py");
216 const int minExpFrequency = 5;
217 const float significanceLevel = 0.01f;
219 return hypothesis::chi2_test(yres*xres, obsFrequencies.get(),
220 expFrequencies.get(), sampleCount,
221 minExpFrequency, significanceLevel, 1);
225 std::pair<Point3f, float> warpPoint(
const Point2f &sample) {
235 case UniformSphereCap:
237 case UniformHemisphere:
239 case CosineHemisphere:
243 case MicrofacetBRDF: {
244 BSDFQueryRecord br(bRec);
245 float value = bsdf->sample(br, sample).getLuminance();
246 return std::make_pair(
248 value == 0 ? 0.f : bsdf->eval(br)[0]
252 throw std::runtime_error(
"Unsupported warp type.");
255 return std::make_pair(result, 1.f);
259 void generatePoints(
int &pointCount, PointType pointType,
260 nori::MatrixXf &positions, nori::MatrixXf &weights) {
262 int sqrtVal = (int) (std::sqrt((
float) pointCount) + 0.5f);
263 float invSqrtVal = 1.f / sqrtVal;
264 if (pointType == Grid || pointType == Stratified)
265 pointCount = sqrtVal*sqrtVal;
268 positions.resize(3, pointCount);
269 weights.resize(1, pointCount);
271 for (
int i=0; i<pointCount; ++i) {
272 int y = i / sqrtVal, x = i % sqrtVal;
277 sample = Point2f(rng.nextFloat(), rng.nextFloat());
281 sample = Point2f((x + 0.5f) * invSqrtVal, (y + 0.5f) * invSqrtVal);
285 sample = Point2f((x + rng.nextFloat()) * invSqrtVal,
286 (y + rng.nextFloat()) * invSqrtVal);
290 auto result = warpPoint(sample);
291 positions.col(i) = result.first;
292 weights(0, i) = result.second;
296 static std::pair<BSDF *, BSDFQueryRecord>
297 create_microfacet_bsdf(
float alpha,
float kd,
float bsdfAngle) {
299 list.setFloat(
"alpha", alpha);
300 list.setColor(
"kd", Color3f(kd));
303 nori::Vector3f wi(std::sin(bsdfAngle), 0.f,
304 std::max(std::cos(bsdfAngle), 1e-4f));
305 wi = wi.normalized();
306 BSDFQueryRecord bRec(wi, Point2f());
307 return { brdf, bRec };
313 using Quaternionf = Eigen::Quaternion<float, Eigen::DontAlign>;
315 Arcball(
float speedFactor = 2.0f)
316 : m_active(
false), m_lastPos(nori::Vector2i::Zero()), m_size(nori::Vector2i::Zero()),
317 m_quat(Quaternionf::Identity()),
318 m_incr(Quaternionf::Identity()),
319 m_speedFactor(speedFactor) { }
321 void setSize(nori::Vector2i size) { m_size = size; }
323 const nori::Vector2i &size()
const {
return m_size; }
325 void button(nori::Vector2i pos,
bool pressed) {
329 m_quat = (m_incr * m_quat).normalized();
330 m_incr = Quaternionf::Identity();
333 bool motion(nori::Vector2i pos) {
338 float invMinDim = 1.0f / m_size.minCoeff();
339 float w = (float) m_size.x(), h = (float) m_size.y();
341 float ox = (m_speedFactor * (2*m_lastPos.x() - w) + w) - w - 1.0f;
342 float tx = (m_speedFactor * (2*pos.x() - w) + w) - w - 1.0f;
343 float oy = (m_speedFactor * (h - 2*m_lastPos.y()) + h) - h - 1.0f;
344 float ty = (m_speedFactor * (h - 2*pos.y()) + h) - h - 1.0f;
346 ox *= invMinDim; oy *= invMinDim;
347 tx *= invMinDim; ty *= invMinDim;
349 nori::Vector3f v0(ox, oy, 1.0f), v1(tx, ty, 1.0f);
350 if (v0.squaredNorm() > 1e-4f && v1.squaredNorm() > 1e-4f) {
351 v0.normalize(); v1.normalize();
352 nori::Vector3f axis = v0.cross(v1);
353 float sa = std::sqrt(axis.dot(axis)),
355 angle = std::atan2(sa, ca);
356 if (tx*tx + ty*ty > 1.0f)
357 angle *= 1.0f + 0.2f * (std::sqrt(tx*tx + ty*ty) - 1.0f);
358 m_incr = Eigen::AngleAxisf(angle, axis.normalized());
359 if (!std::isfinite(m_incr.norm()))
360 m_incr = Quaternionf::Identity();
365 Eigen::Matrix4f matrix()
const {
366 Eigen::Matrix4f result2 = Eigen::Matrix4f::Identity();
367 result2.block<3,3>(0, 0) = (m_incr * m_quat).toRotationMatrix();
377 nori::Vector2i m_lastPos;
380 nori::Vector2i m_size;
399 EIGEN_MAKE_ALIGNED_OPERATOR_NEW
406 WarpTestScreen(): Screen(
Vector2i(800, 600),
"warptest: Sampling and Warping"), m_bRec(nori::Vector3f(), Point2f()) {
408 m_drawHistogram =
false;
412 static float mapParameter(WarpType warpType,
float parameterValue) {
413 if (warpType == Beckmann || warpType == MicrofacetBRDF)
414 parameterValue = std::exp(std::log(0.01f) * (1 - parameterValue) +
415 std::log(1.f) * parameterValue);
416 if (warpType == Beckmann)
417 parameterValue = std::max(parameterValue, 0.03f);
418 if (warpType == UniformSphereCap)
419 parameterValue = std::min(parameterValue, 0.97f);
420 return parameterValue;
424 PointType pointType = (PointType) m_pointTypeBox->selected_index();
425 WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
426 float parameterValue = mapParameter(warpType, m_parameterSlider->value());
427 float parameter2Value = mapParameter(warpType, m_parameter2Slider->value());
428 m_pointCount = (int) std::pow(2.f, 15 * m_pointCountSlider->value() + 5);
430 if (warpType == MicrofacetBRDF) {
432 float bsdfAngle = M_PI * (m_angleSlider->value() - 0.5f);
433 std::tie(ptr, m_bRec) = WarpTest::create_microfacet_bsdf(
434 parameterValue, parameter2Value, bsdfAngle);
439 nori::MatrixXf positions, values;
441 WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
442 tester.generatePoints(m_pointCount, pointType, positions, values);
443 }
catch (
const NoriException &e) {
444 m_warpTypeBox->set_selected_index(0);
446 new MessageDialog(
this, MessageDialog::Type::Warning,
"Error",
"An error occurred: " + std::string(e.what()));
450 float value_scale = 0.f;
451 for (
int i=0; i<m_pointCount; ++i)
452 value_scale = std::max(value_scale, values(0, i));
453 value_scale = 1.f / value_scale;
455 if (!m_brdfValueCheckBox->checked() || warpType != MicrofacetBRDF)
458 if (warpType != Square) {
459 for (
int i=0; i < m_pointCount; ++i) {
460 if (values(0, i) == 0.0f) {
461 positions.col(i) = nori::Vector3f::Constant(std::numeric_limits<float>::quiet_NaN());
465 ((value_scale == 0 ? 1.0f : (value_scale * values(0, i))) *
466 positions.col(i)) * 0.5f + nori::Vector3f(0.5f, 0.5f, 0.0f);
471 nori::MatrixXf colors(3, m_pointCount);
472 float colScale = 1.f / m_pointCount;
473 for (
int i=0; i<m_pointCount; ++i)
474 colors.col(i) << i*colScale, 1-i*colScale, 0;
477 m_pointShader->set_buffer(
"position", VariableType::Float32, {(size_t) m_pointCount, 3}, positions.data());
478 m_pointShader->set_buffer(
"color", VariableType::Float32, {(size_t) m_pointCount, 3}, colors.data());
481 if (m_gridCheckBox->checked()) {
482 int gridRes = (int) (std::sqrt((
float) m_pointCount) + 0.5f);
483 int fineGridRes = 16*gridRes, idx = 0;
484 m_lineCount = 4 * (gridRes+1) * (fineGridRes+1);
485 positions.resize(3, m_lineCount);
486 float coarseScale = 1.f / gridRes, fineScale = 1.f / fineGridRes;
488 WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
489 for (
int i=0; i<=gridRes; ++i) {
490 for (
int j=0; j<=fineGridRes; ++j) {
491 auto pt = tester.warpPoint(Point2f(j * fineScale, i * coarseScale));
492 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
493 pt = tester.warpPoint(Point2f((j+1) * fineScale, i * coarseScale));
494 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
495 pt = tester.warpPoint(Point2f(i*coarseScale, j * fineScale));
496 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
497 pt = tester.warpPoint(Point2f(i*coarseScale, (j+1) * fineScale));
498 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
501 if (warpType != Square) {
502 for (
int i=0; i<m_lineCount; ++i)
503 positions.col(i) = positions.col(i) * 0.5f + nori::Vector3f(0.5f, 0.5f, 0.0f);
505 m_gridShader->set_buffer(
"position", VariableType::Float32, {(size_t) m_lineCount, 3}, positions.data());
509 positions.resize(3, 106);
510 for (
int i=0; i<=50; ++i) {
511 float angle1 = i * 2 * M_PI / 50;
512 float angle2 = (i+1) * 2 * M_PI / 50;
513 positions.col(ctr++) << std::cos(angle1)*.5f + 0.5f, std::sin(angle1)*.5f + 0.5f, 0.f;
514 positions.col(ctr++) << std::cos(angle2)*.5f + 0.5f, std::sin(angle2)*.5f + 0.5f, 0.f;
516 positions.col(ctr++) << 0.5f, 0.5f, 0.f;
517 positions.col(ctr++) << -m_bRec.wi.x() * 0.5f + 0.5f, -m_bRec.wi.y() * 0.5f + 0.5f, m_bRec.wi.z() * 0.5f;
518 positions.col(ctr++) << 0.5f, 0.5f, 0.f;
519 positions.col(ctr++) << m_bRec.wi.x() * 0.5f + 0.5f, m_bRec.wi.y() * 0.5f + 0.5f, m_bRec.wi.z() * 0.5f;
520 m_arrowShader->set_buffer(
"position", VariableType::Float32, {106, 3}, positions.data());
524 if (m_pointCount > 1000000) {
525 m_pointCountBox->set_units(
"M");
526 str = tfm::format(
"%.2f", m_pointCount * 1e-6f);
527 }
else if (m_pointCount > 1000) {
528 m_pointCountBox->set_units(
"K");
529 str = tfm::format(
"%.2f", m_pointCount * 1e-3f);
531 m_pointCountBox->set_units(
" ");
532 str = tfm::format(
"%i", m_pointCount);
534 m_pointCountBox->set_value(str);
535 m_parameterBox->set_value(tfm::format(
"%.1g", parameterValue));
536 m_parameter2Box->set_value(tfm::format(
"%.1g", parameter2Value));
537 m_angleBox->set_value(tfm::format(
"%.1f", m_angleSlider->value() * 180-90));
538 m_parameterSlider->set_enabled(warpType == Beckmann || warpType == MicrofacetBRDF || warpType == UniformSphereCap);
539 m_parameterBox->set_enabled(warpType == Beckmann || warpType == MicrofacetBRDF || warpType == UniformSphereCap);
540 m_parameter2Slider->set_enabled(warpType == MicrofacetBRDF);
541 m_parameter2Box->set_enabled(warpType == MicrofacetBRDF);
542 m_angleBox->set_enabled(warpType == MicrofacetBRDF);
543 m_angleSlider->set_enabled(warpType == MicrofacetBRDF);
544 m_brdfValueCheckBox->set_enabled(warpType == MicrofacetBRDF);
545 m_pointCountSlider->set_value((std::log((
float) m_pointCount) / std::log(2.f) - 5) / 15);
549 int button,
int modifiers) {
550 if (!Screen::mouse_motion_event(p, rel, button, modifiers))
551 m_arcball.motion(nori::Vector2i(p.x(), p.y()));
555 bool mouse_button_event(
const Vector2i &p,
int button,
bool down,
int modifiers) {
556 if (down && !m_window->visible()) {
557 m_drawHistogram =
false;
558 m_window->set_visible(
true);
561 if (!Screen::mouse_button_event(p, button, down, modifiers)) {
562 if (button == GLFW_MOUSE_BUTTON_1)
563 m_arcball.button(nori::Vector2i(p.x(), p.y()), down);
568 void draw_contents() {
570 Matrix4f view, proj, model(1.f);
572 const float viewAngle = 30, near_clip = 0.01, far_clip = 100;
573 proj = Matrix4f::perspective(viewAngle / 360.0f * M_PI, near_clip, far_clip,
574 (
float) m_size.x() / (
float) m_size.y());
575 model = Matrix4f::translate(
Vector3f(-0.5f, -0.5f, 0.0f)) * model;
577 Matrix4f arcball_ng(1.f);
578 memcpy(arcball_ng.m, m_arcball.matrix().data(),
sizeof(
float) * 16);
579 model = arcball_ng * model;
580 m_renderPass->resize(framebuffer_size());
581 m_renderPass->begin();
583 if (m_drawHistogram) {
585 WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
586 const int spacer = 20;
587 const int histWidth = (width() - 3*spacer) / 2;
588 const int histHeight = (warpType == Square || warpType == Disk) ? histWidth : histWidth / 2;
589 const int verticalOffset = (height() - histHeight) / 2;
591 drawHistogram(
Vector2i(spacer, verticalOffset),
Vector2i(histWidth, histHeight), m_textures[0].get());
592 drawHistogram(
Vector2i(2*spacer + histWidth, verticalOffset),
Vector2i(histWidth, histHeight), m_textures[1].get());
594 auto ctx = m_nvg_context;
595 nvgBeginFrame(ctx, m_size[0], m_size[1], m_pixel_ratio);
597 nvgRect(ctx, spacer, verticalOffset + histHeight + spacer, width()-2*spacer, 70);
598 nvgFillColor(ctx, m_testResult.first ? Color(100, 255, 100, 100) : Color(255, 100, 100, 100));
600 nvgFontSize(ctx, 24.0f);
601 nvgFontFace(ctx,
"sans-bold");
602 nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
603 nvgFillColor(ctx, Color(255, 255));
604 nvgText(ctx, spacer + histWidth / 2, verticalOffset - 3 * spacer,
605 "Sample histogram",
nullptr);
606 nvgText(ctx, 2 * spacer + (histWidth * 3) / 2, verticalOffset - 3 * spacer,
607 "Integrated density",
nullptr);
608 nvgStrokeColor(ctx, Color(255, 255));
609 nvgStrokeWidth(ctx, 2);
611 nvgRect(ctx, spacer, verticalOffset, histWidth, histHeight);
612 nvgRect(ctx, 2 * spacer + histWidth, verticalOffset, histWidth,
615 nvgFontSize(ctx, 20.0f);
616 nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
619 nvgTextBoxBounds(ctx, 0, 0, width() - 2 * spacer,
620 m_testResult.second.c_str(),
nullptr, bounds);
622 ctx, spacer, verticalOffset + histHeight + spacer + (70 - bounds[3])/2,
623 width() - 2 * spacer, m_testResult.second.c_str(),
nullptr);
627 Matrix4f mvp = proj * view * model;
628 m_pointShader->set_uniform(
"mvp", mvp);
630 m_renderPass->set_depth_test(RenderPass::DepthTest::Less,
true);
631 m_pointShader->begin();
632 m_pointShader->draw_array(nanogui::Shader::PrimitiveType::Point, 0, m_pointCount);
633 m_pointShader->end();
635 bool drawGrid = m_gridCheckBox->checked();
637 m_gridShader->set_uniform(
"mvp", mvp);
638 m_gridShader->begin();
639 m_gridShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, m_lineCount);
642 if (m_warpTypeBox->selected_index() == MicrofacetBRDF) {
643 m_arrowShader->set_uniform(
"mvp", mvp);
644 m_arrowShader->begin();
645 m_arrowShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, 106);
646 m_arrowShader->end();
652 void drawHistogram(
const nanogui::Vector2i &pos_,
const nanogui::Vector2i &size_,
Texture *texture) {
655 Matrix4f mvp = Matrix4f::ortho(s.x(), e.x(), s.y(), e.y(), -1, 1);
656 m_renderPass->set_depth_test(RenderPass::DepthTest::Always,
true);
657 m_histogramShader->set_uniform(
"mvp", mvp);
658 m_histogramShader->set_texture(
"tex", texture);
659 m_histogramShader->begin();
660 m_histogramShader->draw_array(nanogui::Shader::PrimitiveType::Triangle, 0, 6,
true);
661 m_histogramShader->end();
666 WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
667 float parameterValue = mapParameter(warpType, m_parameterSlider->value());
669 WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
670 m_testResult = tester.run();
672 float maxValue = 0, minValue = std::numeric_limits<float>::infinity();
673 for (
int i=0; i<tester.res; ++i) {
674 maxValue = std::max(maxValue,
675 (
float) std::max(tester.obsFrequencies[i], tester.expFrequencies[i]));
676 minValue = std::min(minValue,
677 (
float) std::min(tester.obsFrequencies[i], tester.expFrequencies[i]));
680 float texScale = 1/(maxValue - minValue);
683 std::unique_ptr<float[]> buffer(
new float[tester.res]);
684 for (
int k=0; k<2; ++k) {
685 for (
int i=0; i<tester.res; ++i)
686 buffer[i] = ((k == 0 ? tester.obsFrequencies[i]
687 : tester.expFrequencies[i]) - minValue) * texScale;
688 m_textures[k] =
new Texture(Texture::PixelFormat::R,
689 Texture::ComponentFormat::Float32,
690 nanogui::Vector2i(tester.xres, tester.yres),
691 Texture::InterpolationMode::Nearest,
692 Texture::InterpolationMode::Nearest);
693 m_textures[k]->upload((uint8_t *) buffer.get());
695 m_drawHistogram =
true;
696 m_window->set_visible(
false);
699 void initializeGUI() {
700 m_window =
new Window(
this,
"Warp tester");
701 m_window->set_position(
Vector2i(15, 15));
702 m_window->set_layout(
new GroupLayout());
704 set_resize_callback([&](
Vector2i size) {
705 m_arcball.setSize(nori::Vector2i(size.x(), size.y()));
708 m_arcball.setSize(nori::Vector2i(m_size.x(), m_size.y()));
710 new Label(m_window,
"Input point set",
"sans-bold");
713 Widget *panel =
new Widget(m_window);
714 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
717 m_pointCountSlider =
new Slider(panel);
718 m_pointCountSlider->set_fixed_width(55);
719 m_pointCountSlider->set_callback([&](
float) { refresh(); });
722 m_pointCountBox =
new TextBox(panel);
723 m_pointCountBox->set_fixed_size(
Vector2i(80, 25));
725 m_pointTypeBox =
new ComboBox(m_window, {
"Independent",
"Grid",
"Stratified" });
726 m_pointTypeBox->set_callback([&](
int) { refresh(); });
728 new Label(m_window,
"Warping method",
"sans-bold");
729 m_warpTypeBox =
new ComboBox(m_window, {
"Square",
"Disk",
"Sphere",
"Spherical cap",
"Hemisphere (unif.)",
730 "Hemisphere (cos)",
"Beckmann distr.",
"Microfacet BRDF" });
731 m_warpTypeBox->set_callback([&](
int) { refresh(); });
733 panel =
new Widget(m_window);
734 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
735 m_parameterSlider =
new Slider(panel);
736 m_parameterSlider->set_fixed_width(55);
737 m_parameterSlider->set_callback([&](
float) { refresh(); });
738 m_parameterBox =
new TextBox(panel);
739 m_parameterBox->set_fixed_size(
Vector2i(80, 25));
740 panel =
new Widget(m_window);
741 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
742 m_parameter2Slider =
new Slider(panel);
743 m_parameter2Slider->set_fixed_width(55);
744 m_parameter2Slider->set_callback([&](
float) { refresh(); });
745 m_parameter2Box =
new TextBox(panel);
746 m_parameter2Box->set_fixed_size(
Vector2i(80, 25));
747 m_gridCheckBox =
new CheckBox(m_window,
"Visualize warped grid");
748 m_gridCheckBox->set_callback([&](
bool) { refresh(); });
750 new Label(m_window,
"BSDF parameters",
"sans-bold");
752 panel =
new Widget(m_window);
753 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
755 m_angleSlider =
new Slider(panel);
756 m_angleSlider->set_fixed_width(55);
757 m_angleSlider->set_callback([&](
float) { refresh(); });
758 m_angleBox =
new TextBox(panel);
759 m_angleBox->set_fixed_size(
Vector2i(80, 25));
760 m_angleBox->set_units(utf8(0x00B0).data());
762 m_brdfValueCheckBox =
new CheckBox(m_window,
"Visualize BRDF values");
763 m_brdfValueCheckBox->set_callback([&](
bool) { refresh(); });
766 std::string(utf8(0x03C7).data()) +
767 std::string(utf8(0x00B2).data()) +
" hypothesis test",
770 Button *testBtn =
new Button(m_window,
"Run", FA_CHECK);
771 testBtn->set_background_color(Color(0, 255, 0, 25));
772 testBtn->set_callback([&]{
775 }
catch (
const NoriException &e) {
776 new MessageDialog(
this, MessageDialog::Type::Warning,
"Error",
"An error occurred: " + std::string(e.what()));
780 m_renderPass =
new RenderPass({
this });
781 m_renderPass->set_clear_color(0, Color(0.f, 0.f, 0.f, 1.f));
785 m_pointShader =
new Shader(
796 gl_Position = mvp * vec4(position, 1.0);
797 if (isnan(position.r)) /* nan (missing value) */
798 frag_color = vec3(0.0);
808 if (frag_color == vec3(0.0))
810 out_color = vec4(frag_color, 1.0);
814 m_gridShader = new Shader(
823 gl_Position = mvp * vec4(position, 1.0);
830 out_color = vec4(vec3(1.0), 0.4);
831 })", Shader::BlendMode::AlphaBlend
834 m_arrowShader = new Shader(
843 gl_Position = mvp * vec4(position, 1.0);
850 out_color = vec4(vec3(1.0), 0.4);
854 m_histogramShader = new Shader(
864 gl_Position = mvp * vec4(position, 0.0, 1.0);
871 uniform sampler2D tex;
873 /* http://paulbourke.net/texture_colour/colourspace/ */
874 vec3 colormap(float v, float vmin, float vmax) {
880 float dv = vmax - vmin;
882 if (v < (vmin + 0.25 * dv)) {
884 c.g = 4.0 * (v - vmin) / dv;
885 } else if (v < (vmin + 0.5 * dv)) {
887 c.b = 1.0 + 4.0 * (vmin + 0.25 * dv - v) / dv;
888 } else if (v < (vmin + 0.75 * dv)) {
889 c.r = 4.0 * (v - vmin - 0.5 * dv) / dv;
892 c.g = 1.0 + 4.0 * (vmin + 0.75 * dv - v) / dv;
898 float value = texture(tex, uv).r;
899 out_color = vec4(colormap(value, 0.0, 1.0), 1.0);
904 uint32_t indices[3 * 2] = {
908 float positions[2 * 4] = {
914 m_histogramShader->set_buffer(
"indices", VariableType::UInt32, {3 * 2}, indices);
915 m_histogramShader->set_buffer(
"position", VariableType::Float32, {4, 2}, positions);
918 m_pointCountSlider->set_value(7.f / 15.f);
919 m_parameterSlider->set_value(.5f);
920 m_parameter2Slider->set_value(0.f);
921 m_angleSlider->set_value(.5f);
930 nanogui::ref<Shader> m_pointShader, m_gridShader, m_histogramShader, m_arrowShader;
932 Slider *m_pointCountSlider, *m_parameterSlider, *m_parameter2Slider, *m_angleSlider;
933 TextBox *m_pointCountBox, *m_parameterBox, *m_parameter2Box, *m_angleBox;
934 nanogui::ref<nanogui::Texture> m_textures[2];
935 ComboBox *m_pointTypeBox;
936 ComboBox *m_warpTypeBox;
937 CheckBox *m_gridCheckBox;
938 CheckBox *m_brdfValueCheckBox;
940 int m_pointCount, m_lineCount;
941 bool m_drawHistogram;
942 std::unique_ptr<BSDF> m_brdf;
943 BSDFQueryRecord m_bRec;
944 std::pair<bool, std::string> m_testResult;
945 nanogui::ref<RenderPass> m_renderPass;
949 std::tuple<WarpType, float, float> parse_arguments(
int argc,
char **argv) {
950 WarpType tp = WarpTypeCount;
951 for (
int i = 0; i < WarpTypeCount; ++i) {
952 if (strcmp(kWarpTypeNames[i].c_str(), argv[1]) == 0)
955 if (tp >= WarpTypeCount)
956 throw std::runtime_error(
"Invalid warp type!");
958 float value = 0.f, value2 = 0.f;
960 value = std::stof(argv[2]);
962 value2 = std::stof(argv[3]);
964 return { tp, value, value2 };
968 int main(
int argc,
char **argv) {
981 float paramValue, param2Value;
982 std::unique_ptr<BSDF> bsdf;
983 auto bRec = BSDFQueryRecord(nori::Vector3f(), Point2f());
984 std::tie(warpType, paramValue, param2Value) = parse_arguments(argc, argv);
985 if (warpType == MicrofacetBRDF) {
986 float bsdfAngle = M_PI * 0.f;
988 std::tie(ptr, bRec) = WarpTest::create_microfacet_bsdf(
989 paramValue, param2Value, bsdfAngle);
993 std::string extra =
"";
995 extra = tfm::format(
", second parameter value = %f", param2Value);
996 std::cout << tfm::format(
997 "Testing warp %s, parameter value = %f%s",
998 kWarpTypeNames[
int(warpType)], paramValue, extra
1000 WarpTest tester(warpType, paramValue, bsdf.get(), bRec);
1001 auto res = tester.run();
1005 std::cout << tfm::format(
"warptest failed: %s", res.second) << std::endl;
static NoriObject * createInstance(const std::string &name, const PropertyList &propList)
Construct an instance from the class of the given name.
Superclass of all texture.
static float squareToUniformDiskPdf(const Point2f &p)
Probability density of squareToUniformDisk()
static float squareToUniformHemispherePdf(const Vector3f &v)
Probability density of squareToUniformHemisphere()
static float squareToUniformSphereCapPdf(const Vector3f &v, float cosThetaMax)
Probability density of squareToUniformSphereCap()
static Point2f squareToUniformSquare(const Point2f &sample)
Dummy warping function: takes uniformly distributed points in a square and just returns them.
static Vector3f squareToCosineHemisphere(const Point2f &sample)
Uniformly sample a vector on the unit hemisphere around the pole (0,0,1) with respect to projected so...
static Vector3f squareToBeckmann(const Point2f &sample, float alpha)
Warp a uniformly distributed square sample to a Beckmann distribution * cosine for the given 'alpha' ...
static Vector3f squareToUniformSphere(const Point2f &sample)
Uniformly sample a vector on the unit sphere with respect to solid angles.
static float squareToCosineHemispherePdf(const Vector3f &v)
Probability density of squareToCosineHemisphere()
static Vector3f squareToUniformHemisphere(const Point2f &sample)
Uniformly sample a vector on the unit hemisphere around the pole (0,0,1) with respect to solid angles...
static Point2f squareToUniformDisk(const Point2f &sample)
Uniformly sample a vector on a 2D disk with radius 1, centered around the origin.
static float squareToBeckmannPdf(const Vector3f &m, float alpha)
Probability density of squareToBeckmann()
static Vector3f squareToUniformSphereCap(const Point2f &sample, float cosThetaMax)
Uniformly sample a vector on a spherical cap around (0, 0, 1)
static float squareToUniformSpherePdf(const Vector3f &v)
Probability density of squareToUniformSphere()
static float squareToUniformSquarePdf(const Point2f &p)
Probability density of squareToUniformSquare()