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()),
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(),
"chitest.m");
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);
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()) {
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 return parameterValue;
420 PointType pointType = (PointType) m_pointTypeBox->selected_index();
421 WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
422 float parameterValue = mapParameter(warpType, m_parameterSlider->value());
423 float parameter2Value = mapParameter(warpType, m_parameter2Slider->value());
424 m_pointCount = (int) std::pow(2.f, 15 * m_pointCountSlider->value() + 5);
426 if (warpType == MicrofacetBRDF) {
428 float bsdfAngle = M_PI * (m_angleSlider->value() - 0.5f);
429 std::tie(ptr, m_bRec) = WarpTest::create_microfacet_bsdf(
430 parameterValue, parameter2Value, bsdfAngle);
435 nori::MatrixXf positions, values;
437 WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
438 tester.generatePoints(m_pointCount, pointType, positions, values);
439 }
catch (
const NoriException &e) {
440 m_warpTypeBox->set_selected_index(0);
442 new MessageDialog(
this, MessageDialog::Type::Warning,
"Error",
"An error occurred: " + std::string(e.what()));
446 float value_scale = 0.f;
447 for (
int i=0; i<m_pointCount; ++i)
448 value_scale = std::max(value_scale, values(0, i));
449 value_scale = 1.f / value_scale;
451 if (!m_brdfValueCheckBox->checked() || warpType != MicrofacetBRDF)
454 if (warpType != Square) {
455 for (
int i=0; i < m_pointCount; ++i) {
456 if (values(0, i) == 0.0f) {
457 positions.col(i) = nori::Vector3f::Constant(std::numeric_limits<float>::quiet_NaN());
461 ((value_scale == 0 ? 1.0f : (value_scale * values(0, i))) *
462 positions.col(i)) * 0.5f + nori::Vector3f(0.5f, 0.5f, 0.0f);
467 nori::MatrixXf colors(3, m_pointCount);
468 float colScale = 1.f / m_pointCount;
469 for (
int i=0; i<m_pointCount; ++i)
470 colors.col(i) << i*colScale, 1-i*colScale, 0;
473 m_pointShader->set_buffer(
"position", VariableType::Float32, {(size_t) m_pointCount, 3}, positions.data());
474 m_pointShader->set_buffer(
"color", VariableType::Float32, {(size_t) m_pointCount, 3}, colors.data());
477 if (m_gridCheckBox->checked()) {
478 int gridRes = (int) (std::sqrt((
float) m_pointCount) + 0.5f);
479 int fineGridRes = 16*gridRes, idx = 0;
480 m_lineCount = 4 * (gridRes+1) * (fineGridRes+1);
481 positions.resize(3, m_lineCount);
482 float coarseScale = 1.f / gridRes, fineScale = 1.f / fineGridRes;
484 WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
485 for (
int i=0; i<=gridRes; ++i) {
486 for (
int j=0; j<=fineGridRes; ++j) {
487 auto pt = tester.warpPoint(Point2f(j * fineScale, i * coarseScale));
488 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
489 pt = tester.warpPoint(Point2f((j+1) * fineScale, i * coarseScale));
490 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
491 pt = tester.warpPoint(Point2f(i*coarseScale, j * fineScale));
492 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
493 pt = tester.warpPoint(Point2f(i*coarseScale, (j+1) * fineScale));
494 positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
497 if (warpType != Square) {
498 for (
int i=0; i<m_lineCount; ++i)
499 positions.col(i) = positions.col(i) * 0.5f + nori::Vector3f(0.5f, 0.5f, 0.0f);
501 m_gridShader->set_buffer(
"position", VariableType::Float32, {(size_t) m_lineCount, 3}, positions.data());
505 positions.resize(3, 106);
506 for (
int i=0; i<=50; ++i) {
507 float angle1 = i * 2 * M_PI / 50;
508 float angle2 = (i+1) * 2 * M_PI / 50;
509 positions.col(ctr++) << std::cos(angle1)*.5f + 0.5f, std::sin(angle1)*.5f + 0.5f, 0.f;
510 positions.col(ctr++) << std::cos(angle2)*.5f + 0.5f, std::sin(angle2)*.5f + 0.5f, 0.f;
512 positions.col(ctr++) << 0.5f, 0.5f, 0.f;
513 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;
514 positions.col(ctr++) << 0.5f, 0.5f, 0.f;
515 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;
516 m_arrowShader->set_buffer(
"position", VariableType::Float32, {106, 3}, positions.data());
520 if (m_pointCount > 1000000) {
521 m_pointCountBox->set_units(
"M");
522 str = tfm::format(
"%.2f", m_pointCount * 1e-6f);
523 }
else if (m_pointCount > 1000) {
524 m_pointCountBox->set_units(
"K");
525 str = tfm::format(
"%.2f", m_pointCount * 1e-3f);
527 m_pointCountBox->set_units(
" ");
528 str = tfm::format(
"%i", m_pointCount);
530 m_pointCountBox->set_value(str);
531 m_parameterBox->set_value(tfm::format(
"%.1g", parameterValue));
532 m_parameter2Box->set_value(tfm::format(
"%.1g", parameter2Value));
533 m_angleBox->set_value(tfm::format(
"%.1f", m_angleSlider->value() * 180-90));
534 m_parameterSlider->set_enabled(warpType == Beckmann || warpType == MicrofacetBRDF || warpType == UniformSphereCap);
535 m_parameterBox->set_enabled(warpType == Beckmann || warpType == MicrofacetBRDF || warpType == UniformSphereCap);
536 m_parameter2Slider->set_enabled(warpType == MicrofacetBRDF);
537 m_parameter2Box->set_enabled(warpType == MicrofacetBRDF);
538 m_angleBox->set_enabled(warpType == MicrofacetBRDF);
539 m_angleSlider->set_enabled(warpType == MicrofacetBRDF);
540 m_brdfValueCheckBox->set_enabled(warpType == MicrofacetBRDF);
541 m_pointCountSlider->set_value((std::log((
float) m_pointCount) / std::log(2.f) - 5) / 15);
545 int button,
int modifiers) {
546 if (!Screen::mouse_motion_event(p, rel, button, modifiers))
547 m_arcball.motion(nori::Vector2i(p.x(), p.y()));
551 bool mouse_button_event(
const Vector2i &p,
int button,
bool down,
int modifiers) {
552 if (down && !m_window->visible()) {
553 m_drawHistogram =
false;
554 m_window->set_visible(
true);
557 if (!Screen::mouse_button_event(p, button, down, modifiers)) {
558 if (button == GLFW_MOUSE_BUTTON_1)
559 m_arcball.button(nori::Vector2i(p.x(), p.y()), down);
564 void draw_contents() {
566 Matrix4f view, proj, model(1.f);
568 const float viewAngle = 30, near_clip = 0.01, far_clip = 100;
569 proj = Matrix4f::perspective(viewAngle / 360.0f * M_PI, near_clip, far_clip,
570 (
float) m_size.x() / (
float) m_size.y());
571 model = Matrix4f::translate(
Vector3f(-0.5f, -0.5f, 0.0f)) * model;
573 Matrix4f arcball_ng(1.f);
574 memcpy(arcball_ng.m, m_arcball.matrix().data(),
sizeof(
float) * 16);
575 model = arcball_ng * model;
576 m_renderPass->resize(framebuffer_size());
577 m_renderPass->begin();
579 if (m_drawHistogram) {
581 WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
582 const int spacer = 20;
583 const int histWidth = (width() - 3*spacer) / 2;
584 const int histHeight = (warpType == Square || warpType == Disk) ? histWidth : histWidth / 2;
585 const int verticalOffset = (height() - histHeight) / 2;
587 drawHistogram(
Vector2i(spacer, verticalOffset),
Vector2i(histWidth, histHeight), m_textures[0].get());
588 drawHistogram(
Vector2i(2*spacer + histWidth, verticalOffset),
Vector2i(histWidth, histHeight), m_textures[1].get());
590 auto ctx = m_nvg_context;
591 nvgBeginFrame(ctx, m_size[0], m_size[1], m_pixel_ratio);
593 nvgRect(ctx, spacer, verticalOffset + histHeight + spacer, width()-2*spacer, 70);
594 nvgFillColor(ctx, m_testResult.first ? Color(100, 255, 100, 100) : Color(255, 100, 100, 100));
596 nvgFontSize(ctx, 24.0f);
597 nvgFontFace(ctx,
"sans-bold");
598 nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
599 nvgFillColor(ctx, Color(255, 255));
600 nvgText(ctx, spacer + histWidth / 2, verticalOffset - 3 * spacer,
601 "Sample histogram",
nullptr);
602 nvgText(ctx, 2 * spacer + (histWidth * 3) / 2, verticalOffset - 3 * spacer,
603 "Integrated density",
nullptr);
604 nvgStrokeColor(ctx, Color(255, 255));
605 nvgStrokeWidth(ctx, 2);
607 nvgRect(ctx, spacer, verticalOffset, histWidth, histHeight);
608 nvgRect(ctx, 2 * spacer + histWidth, verticalOffset, histWidth,
611 nvgFontSize(ctx, 20.0f);
612 nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
615 nvgTextBoxBounds(ctx, 0, 0, width() - 2 * spacer,
616 m_testResult.second.c_str(),
nullptr, bounds);
618 ctx, spacer, verticalOffset + histHeight + spacer + (70 - bounds[3])/2,
619 width() - 2 * spacer, m_testResult.second.c_str(),
nullptr);
623 Matrix4f mvp = proj * view * model;
624 m_pointShader->set_uniform(
"mvp", mvp);
626 m_renderPass->set_depth_test(RenderPass::DepthTest::Less,
true);
627 m_pointShader->begin();
628 m_pointShader->draw_array(nanogui::Shader::PrimitiveType::Point, 0, m_pointCount);
629 m_pointShader->end();
631 bool drawGrid = m_gridCheckBox->checked();
633 m_gridShader->set_uniform(
"mvp", mvp);
634 m_gridShader->begin();
635 m_gridShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, m_lineCount);
638 if (m_warpTypeBox->selected_index() == MicrofacetBRDF) {
639 m_arrowShader->set_uniform(
"mvp", mvp);
640 m_arrowShader->begin();
641 m_arrowShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, 106);
642 m_arrowShader->end();
648 void drawHistogram(
const nanogui::Vector2i &pos_,
const nanogui::Vector2i &size_,
Texture *texture) {
651 Matrix4f mvp = Matrix4f::ortho(s.x(), e.x(), s.y(), e.y(), -1, 1);
652 m_renderPass->set_depth_test(RenderPass::DepthTest::Always,
true);
653 m_histogramShader->set_uniform(
"mvp", mvp);
654 m_histogramShader->set_texture(
"tex", texture);
655 m_histogramShader->begin();
656 m_histogramShader->draw_array(nanogui::Shader::PrimitiveType::Triangle, 0, 6,
true);
657 m_histogramShader->end();
662 WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
663 float parameterValue = mapParameter(warpType, m_parameterSlider->value());
665 WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
666 m_testResult = tester.run();
668 float maxValue = 0, minValue = std::numeric_limits<float>::infinity();
669 for (
int i=0; i<tester.res; ++i) {
670 maxValue = std::max(maxValue,
671 (
float) std::max(tester.obsFrequencies[i], tester.expFrequencies[i]));
672 minValue = std::min(minValue,
673 (
float) std::min(tester.obsFrequencies[i], tester.expFrequencies[i]));
676 float texScale = 1/(maxValue - minValue);
679 std::unique_ptr<float[]> buffer(
new float[tester.res]);
680 for (
int k=0; k<2; ++k) {
681 for (
int i=0; i<tester.res; ++i)
682 buffer[i] = ((k == 0 ? tester.obsFrequencies[i]
683 : tester.expFrequencies[i]) - minValue) * texScale;
684 m_textures[k] =
new Texture(Texture::PixelFormat::R,
685 Texture::ComponentFormat::Float32,
686 nanogui::Vector2i(tester.xres, tester.yres),
687 Texture::InterpolationMode::Nearest,
688 Texture::InterpolationMode::Nearest);
689 m_textures[k]->upload((uint8_t *) buffer.get());
691 m_drawHistogram =
true;
692 m_window->set_visible(
false);
695 void initializeGUI() {
696 m_window =
new Window(
this,
"Warp tester");
697 m_window->set_position(
Vector2i(15, 15));
698 m_window->set_layout(
new GroupLayout());
700 set_resize_callback([&](
Vector2i size) {
701 m_arcball.setSize(nori::Vector2i(size.x(), size.y()));
704 m_arcball.setSize(nori::Vector2i(m_size.x(), m_size.y()));
706 new Label(m_window,
"Input point set",
"sans-bold");
709 Widget *panel =
new Widget(m_window);
710 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
713 m_pointCountSlider =
new Slider(panel);
714 m_pointCountSlider->set_fixed_width(55);
715 m_pointCountSlider->set_callback([&](
float) { refresh(); });
718 m_pointCountBox =
new TextBox(panel);
719 m_pointCountBox->set_fixed_size(
Vector2i(80, 25));
721 m_pointTypeBox =
new ComboBox(m_window, {
"Independent",
"Grid",
"Stratified" });
722 m_pointTypeBox->set_callback([&](
int) { refresh(); });
724 new Label(m_window,
"Warping method",
"sans-bold");
725 m_warpTypeBox =
new ComboBox(m_window, {
"Square",
"Disk",
"Sphere",
"Spherical cap",
"Hemisphere (unif.)",
726 "Hemisphere (cos)",
"Beckmann distr.",
"Microfacet BRDF" });
727 m_warpTypeBox->set_callback([&](
int) { refresh(); });
729 panel =
new Widget(m_window);
730 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
731 m_parameterSlider =
new Slider(panel);
732 m_parameterSlider->set_fixed_width(55);
733 m_parameterSlider->set_callback([&](
float) { refresh(); });
734 m_parameterBox =
new TextBox(panel);
735 m_parameterBox->set_fixed_size(
Vector2i(80, 25));
736 panel =
new Widget(m_window);
737 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
738 m_parameter2Slider =
new Slider(panel);
739 m_parameter2Slider->set_fixed_width(55);
740 m_parameter2Slider->set_callback([&](
float) { refresh(); });
741 m_parameter2Box =
new TextBox(panel);
742 m_parameter2Box->set_fixed_size(
Vector2i(80, 25));
743 m_gridCheckBox =
new CheckBox(m_window,
"Visualize warped grid");
744 m_gridCheckBox->set_callback([&](
bool) { refresh(); });
746 new Label(m_window,
"BSDF parameters",
"sans-bold");
748 panel =
new Widget(m_window);
749 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
751 m_angleSlider =
new Slider(panel);
752 m_angleSlider->set_fixed_width(55);
753 m_angleSlider->set_callback([&](
float) { refresh(); });
754 m_angleBox =
new TextBox(panel);
755 m_angleBox->set_fixed_size(
Vector2i(80, 25));
756 m_angleBox->set_units(utf8(0x00B0).data());
758 m_brdfValueCheckBox =
new CheckBox(m_window,
"Visualize BRDF values");
759 m_brdfValueCheckBox->set_callback([&](
bool) { refresh(); });
762 std::string(utf8(0x03C7).data()) +
763 std::string(utf8(0x00B2).data()) +
" hypothesis test",
766 Button *testBtn =
new Button(m_window,
"Run", FA_CHECK);
767 testBtn->set_background_color(Color(0, 255, 0, 25));
768 testBtn->set_callback([&]{
771 }
catch (
const NoriException &e) {
772 new MessageDialog(
this, MessageDialog::Type::Warning,
"Error",
"An error occurred: " + std::string(e.what()));
776 m_renderPass =
new RenderPass({
this });
777 m_renderPass->set_clear_color(0, Color(0.f, 0.f, 0.f, 1.f));
781 m_pointShader =
new Shader(
792 gl_Position = mvp * vec4(position, 1.0);
793 if (isnan(position.r)) /* nan (missing value) */
794 frag_color = vec3(0.0);
804 if (frag_color == vec3(0.0))
806 out_color = vec4(frag_color, 1.0);
810 m_gridShader = new Shader(
819 gl_Position = mvp * vec4(position, 1.0);
826 out_color = vec4(vec3(1.0), 0.4);
827 })", Shader::BlendMode::AlphaBlend
830 m_arrowShader = new Shader(
839 gl_Position = mvp * vec4(position, 1.0);
846 out_color = vec4(vec3(1.0), 0.4);
850 m_histogramShader = new Shader(
860 gl_Position = mvp * vec4(position, 0.0, 1.0);
867 uniform sampler2D tex;
869 /* http://paulbourke.net/texture_colour/colourspace/ */
870 vec3 colormap(float v, float vmin, float vmax) {
876 float dv = vmax - vmin;
878 if (v < (vmin + 0.25 * dv)) {
880 c.g = 4.0 * (v - vmin) / dv;
881 } else if (v < (vmin + 0.5 * dv)) {
883 c.b = 1.0 + 4.0 * (vmin + 0.25 * dv - v) / dv;
884 } else if (v < (vmin + 0.75 * dv)) {
885 c.r = 4.0 * (v - vmin - 0.5 * dv) / dv;
888 c.g = 1.0 + 4.0 * (vmin + 0.75 * dv - v) / dv;
894 float value = texture(tex, uv).r;
895 out_color = vec4(colormap(value, 0.0, 1.0), 1.0);
900 uint32_t indices[3 * 2] = {
904 float positions[2 * 4] = {
910 m_histogramShader->set_buffer(
"indices", VariableType::UInt32, {3 * 2}, indices);
911 m_histogramShader->set_buffer(
"position", VariableType::Float32, {4, 2}, positions);
914 m_pointCountSlider->set_value(7.f / 15.f);
915 m_parameterSlider->set_value(.5f);
916 m_parameter2Slider->set_value(0.f);
917 m_angleSlider->set_value(.5f);
926 nanogui::ref<Shader> m_pointShader, m_gridShader, m_histogramShader, m_arrowShader;
928 Slider *m_pointCountSlider, *m_parameterSlider, *m_parameter2Slider, *m_angleSlider;
929 TextBox *m_pointCountBox, *m_parameterBox, *m_parameter2Box, *m_angleBox;
930 nanogui::ref<nanogui::Texture> m_textures[2];
931 ComboBox *m_pointTypeBox;
932 ComboBox *m_warpTypeBox;
933 CheckBox *m_gridCheckBox;
934 CheckBox *m_brdfValueCheckBox;
936 int m_pointCount, m_lineCount;
937 bool m_drawHistogram;
938 std::unique_ptr<BSDF> m_brdf;
939 BSDFQueryRecord m_bRec;
940 std::pair<bool, std::string> m_testResult;
941 nanogui::ref<RenderPass> m_renderPass;
945 std::tuple<WarpType, float, float> parse_arguments(
int argc,
char **argv) {
946 WarpType tp = WarpTypeCount;
947 for (
int i = 0; i < WarpTypeCount; ++i) {
948 if (strcmp(kWarpTypeNames[i].c_str(), argv[1]) == 0)
951 if (tp >= WarpTypeCount)
952 throw std::runtime_error(
"Invalid warp type!");
954 float value = 0.f, value2 = 0.f;
956 value = std::stof(argv[2]);
958 value2 = std::stof(argv[3]);
960 return { tp, value, value2 };
964 int main(
int argc,
char **argv) {
977 float paramValue, param2Value;
978 std::unique_ptr<BSDF> bsdf;
979 auto bRec = BSDFQueryRecord(nori::Vector3f());
980 std::tie(warpType, paramValue, param2Value) = parse_arguments(argc, argv);
981 if (warpType == MicrofacetBRDF) {
982 float bsdfAngle = M_PI * 0.f;
984 std::tie(ptr, bRec) = WarpTest::create_microfacet_bsdf(
985 paramValue, param2Value, bsdfAngle);
989 std::string extra =
"";
991 extra = tfm::format(
", second parameter value = %f", param2Value);
992 std::cout << tfm::format(
993 "Testing warp %s, parameter value = %f%s",
994 kWarpTypeNames[
int(warpType)], paramValue, extra
996 WarpTest tester(warpType, paramValue, bsdf.get(), bRec);
997 auto res = tester.run();
1001 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()