Nori  24
warptest.cpp
1 /*
2  This file is part of Nori, a simple educational ray tracer
3 
4  Copyright (c) 2015 by Wenzel Jakob
5 
6  Nori is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License Version 3
8  as published by the Free Software Foundation.
9 
10  Nori is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
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>
38 
39 #include <nanovg_gl.h>
40 
41 #include <pcg32.h>
42 #include <hypothesis.h>
43 #include <tinyformat.h>
44 
45 #include <Eigen/Geometry>
46 
47 /* =======================================================================
48  * WARNING WARNING WARNING WARNING WARNING WARNING
49  * =======================================================================
50  * Remember to put on SAFETY GOGGLES before looking at this file. You
51  * are most certainly not expected to read or understand any of it.
52  * ======================================================================= */
53 
54 using namespace nanogui;
55 using namespace std;
56 
57 using nori::NoriException;
58 using nori::NoriObjectFactory;
59 using nori::Point2f;
60 using nori::Point2i;
61 using nori::Point3f;
62 using nori::Warp;
63 using nori::PropertyList;
64 using nori::BSDF;
65 using nori::BSDFQueryRecord;
66 using nori::Color3f;
67 
68 
69 enum PointType : int {
70  Independent = 0,
71  Grid,
72  Stratified
73 };
74 
75 enum WarpType : int {
76  Square = 0,
77  Disk,
78  UniformSphere,
79  UniformSphereCap,
80  UniformHemisphere,
81  CosineHemisphere,
82  Beckmann,
83  MicrofacetBRDF,
84  WarpTypeCount
85 };
86 
87 static const std::string kWarpTypeNames[WarpTypeCount] = {
88  "square", "disk", "uniform_sphere", "uniform_sphere_cap", "uniform_hemisphere",
89  "cosine_hemisphere", "beckmann", "microfacet_brdf"
90 };
91 
92 
93 struct WarpTest {
94  static const int kDefaultXres = 51;
95  static const int kDefaultYres = 51;
96 
97  WarpType warpType;
98  float parameterValue;
99  BSDF *bsdf;
100  BSDFQueryRecord bRec;
101  int xres, yres, res;
102 
103  // Observed and expected frequencies, initialized after calling run().
104  std::unique_ptr<double[]> obsFrequencies, expFrequencies;
105 
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_) {
111 
112  if (warpType != Square && warpType != Disk)
113  xres *= 2;
114  res = xres * yres;
115  }
116 
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));
123 
124  nori::MatrixXf points, values;
125  generatePoints(sampleCount, Independent, points, values);
126 
127  for (int i=0; i<sampleCount; ++i) {
128  if (values(0, i) == 0)
129  continue;
130  nori::Vector3f sample = points.col(i);
131  float x, y;
132 
133  if (warpType == Square) {
134  x = sample.x();
135  y = sample.y();
136  } else if (warpType == Disk) {
137  x = sample.x() * 0.5f + 0.5f;
138  y = sample.y() * 0.5f + 0.5f;
139  } else {
140  x = std::atan2(sample.y(), sample.x()) * INV_TWOPI;
141  if (x < 0)
142  x += 1;
143  y = sample.z() * 0.5f + 0.5f;
144  }
145 
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;
149  }
150 
151  auto integrand = [&](double y, double x) -> double {
152  if (warpType == Square) {
153  return Warp::squareToUniformSquarePdf(Point2f(x, y));
154  } else if (warpType == Disk) {
155  x = x * 2 - 1; y = y * 2 - 1;
156  return Warp::squareToUniformDiskPdf(Point2f(x, y));
157  } else {
158  x *= 2 * M_PI;
159  y = y * 2 - 1;
160 
161  double sinTheta = std::sqrt(1 - y * y);
162  double sinPhi = std::sin(x),
163  cosPhi = std::cos(x);
164 
165  nori::Vector3f v((float) (sinTheta * cosPhi),
166  (float) (sinTheta * sinPhi),
167  (float) y);
168 
169  if (warpType == UniformSphere)
171  else if (warpType == UniformSphereCap)
172  return Warp::squareToUniformSphereCapPdf(v, parameterValue);
173  else if (warpType == UniformHemisphere)
175  else if (warpType == CosineHemisphere)
177  else if (warpType == Beckmann)
178  return Warp::squareToBeckmannPdf(v, parameterValue);
179  else if (warpType == MicrofacetBRDF) {
180  BSDFQueryRecord br(bRec);
181  br.wo = v;
182  br.measure = nori::ESolidAngle;
183  return bsdf->pdf(br);
184  } else {
185  throw NoriException("Invalid warp type");
186  }
187  }
188  };
189 
190  double scale = sampleCount;
191  if (warpType == Square)
192  scale *= 1;
193  else if (warpType == Disk)
194  scale *= 4;
195  else
196  scale *= 4*M_PI;
197 
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!");
209  }
210  }
211 
212  /* Write the test input data to disk for debugging */
213  hypothesis::chi2_dump(yres, xres, obsFrequencies.get(), expFrequencies.get(), "chi2test.py");
214 
215  /* Perform the Chi^2 test */
216  const int minExpFrequency = 5;
217  const float significanceLevel = 0.01f;
218 
219  return hypothesis::chi2_test(yres*xres, obsFrequencies.get(),
220  expFrequencies.get(), sampleCount,
221  minExpFrequency, significanceLevel, 1);
222  }
223 
224 
225  std::pair<Point3f, float> warpPoint(const Point2f &sample) {
226  Point3f result;
227 
228  switch (warpType) {
229  case Square:
230  result << Warp::squareToUniformSquare(sample), 0; break;
231  case Disk:
232  result << Warp::squareToUniformDisk(sample), 0; break;
233  case UniformSphere:
234  result << Warp::squareToUniformSphere(sample); break;
235  case UniformSphereCap:
236  result << Warp::squareToUniformSphereCap(sample, parameterValue); break;
237  case UniformHemisphere:
238  result << Warp::squareToUniformHemisphere(sample); break;
239  case CosineHemisphere:
240  result << Warp::squareToCosineHemisphere(sample); break;
241  case Beckmann:
242  result << Warp::squareToBeckmann(sample, parameterValue); break;
243  case MicrofacetBRDF: {
244  BSDFQueryRecord br(bRec);
245  float value = bsdf->sample(br, sample).getLuminance();
246  return std::make_pair(
247  br.wo,
248  value == 0 ? 0.f : bsdf->eval(br)[0]
249  );
250  }
251  default:
252  throw std::runtime_error("Unsupported warp type.");
253  }
254 
255  return std::make_pair(result, 1.f);
256  }
257 
258 
259  void generatePoints(int &pointCount, PointType pointType,
260  nori::MatrixXf &positions, nori::MatrixXf &weights) {
261  /* Determine the number of points that should be sampled */
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;
266 
267  pcg32 rng;
268  positions.resize(3, pointCount);
269  weights.resize(1, pointCount);
270 
271  for (int i=0; i<pointCount; ++i) {
272  int y = i / sqrtVal, x = i % sqrtVal;
273  Point2f sample;
274 
275  switch (pointType) {
276  case Independent:
277  sample = Point2f(rng.nextFloat(), rng.nextFloat());
278  break;
279 
280  case Grid:
281  sample = Point2f((x + 0.5f) * invSqrtVal, (y + 0.5f) * invSqrtVal);
282  break;
283 
284  case Stratified:
285  sample = Point2f((x + rng.nextFloat()) * invSqrtVal,
286  (y + rng.nextFloat()) * invSqrtVal);
287  break;
288  }
289 
290  auto result = warpPoint(sample);
291  positions.col(i) = result.first;
292  weights(0, i) = result.second;
293  }
294  }
295 
296  static std::pair<BSDF *, BSDFQueryRecord>
297  create_microfacet_bsdf(float alpha, float kd, float bsdfAngle) {
298  PropertyList list;
299  list.setFloat("alpha", alpha);
300  list.setColor("kd", Color3f(kd));
301  auto * brdf = (BSDF *) NoriObjectFactory::createInstance("microfacet", list);
302 
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 };
308  }
309 };
310 
311 
312 struct Arcball {
313  using Quaternionf = Eigen::Quaternion<float, Eigen::DontAlign>;
314 
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) { }
320 
321  void setSize(nori::Vector2i size) { m_size = size; }
322 
323  const nori::Vector2i &size() const { return m_size; }
324 
325  void button(nori::Vector2i pos, bool pressed) {
326  m_active = pressed;
327  m_lastPos = pos;
328  if (!m_active)
329  m_quat = (m_incr * m_quat).normalized();
330  m_incr = Quaternionf::Identity();
331  }
332 
333  bool motion(nori::Vector2i pos) {
334  if (!m_active)
335  return false;
336 
337  /* Based on the rotation controller from AntTweakBar */
338  float invMinDim = 1.0f / m_size.minCoeff();
339  float w = (float) m_size.x(), h = (float) m_size.y();
340 
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;
345 
346  ox *= invMinDim; oy *= invMinDim;
347  tx *= invMinDim; ty *= invMinDim;
348 
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)),
354  ca = v0.dot(v1),
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();
361  }
362  return true;
363  }
364 
365  Eigen::Matrix4f matrix() const {
366  Eigen::Matrix4f result2 = Eigen::Matrix4f::Identity();
367  result2.block<3,3>(0, 0) = (m_incr * m_quat).toRotationMatrix();
368  return result2;
369  }
370 
371 
372 private:
374  bool m_active;
375 
377  nori::Vector2i m_lastPos;
378 
380  nori::Vector2i m_size;
381 
387  Quaternionf m_quat;
388 
390  Quaternionf m_incr;
391 
396  float m_speedFactor;
397 
398 public:
399  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
400 };
401 
402 
403 class WarpTestScreen : public Screen {
404 public:
405 
406  WarpTestScreen(): Screen(Vector2i(800, 600), "warptest: Sampling and Warping"), m_bRec(nori::Vector3f(), Point2f()) {
407  inc_ref();
408  m_drawHistogram = false;
409  initializeGUI();
410  }
411 
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;
421  }
422 
423  void refresh() {
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);
429 
430  if (warpType == MicrofacetBRDF) {
431  BSDF *ptr;
432  float bsdfAngle = M_PI * (m_angleSlider->value() - 0.5f);
433  std::tie(ptr, m_bRec) = WarpTest::create_microfacet_bsdf(
434  parameterValue, parameter2Value, bsdfAngle);
435  m_brdf.reset(ptr);
436  }
437 
438  /* Generate the point positions */
439  nori::MatrixXf positions, values;
440  try {
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);
445  refresh();
446  new MessageDialog(this, MessageDialog::Type::Warning, "Error", "An error occurred: " + std::string(e.what()));
447  return;
448  }
449 
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;
454 
455  if (!m_brdfValueCheckBox->checked() || warpType != MicrofacetBRDF)
456  value_scale = 0.f;
457 
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());
462  continue;
463  }
464  positions.col(i) =
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);
467  }
468  }
469 
470  /* Generate a color gradient */
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;
475 
476  /* Upload points to GPU */
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());
479 
480  /* Upload lines to GPU */
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;
487 
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);
499  }
500  }
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);
504  }
505  m_gridShader->set_buffer("position", VariableType::Float32, {(size_t) m_lineCount, 3}, positions.data());
506  }
507 
508  int ctr = 0;
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;
515  }
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());
521 
522  /* Update user interface */
523  std::string str;
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);
530  } else {
531  m_pointCountBox->set_units(" ");
532  str = tfm::format("%i", m_pointCount);
533  }
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);
546  }
547 
548  bool mouse_motion_event(const Vector2i &p, const Vector2i &rel,
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()));
552  return true;
553  }
554 
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);
559  return true;
560  }
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);
564  }
565  return true;
566  }
567 
568  void draw_contents() {
569  /* Set up a perspective camera matrix */
570  Matrix4f view, proj, model(1.f);
571  view = Matrix4f::look_at(Vector3f(0, 0, 4), Vector3f(0, 0, 0), Vector3f(0, 1, 0));
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;
576 
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();
582 
583  if (m_drawHistogram) {
584  /* Render the histograms */
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;
590 
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());
593 
594  auto ctx = m_nvg_context;
595  nvgBeginFrame(ctx, m_size[0], m_size[1], m_pixel_ratio);
596  nvgBeginPath(ctx);
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));
599  nvgFill(ctx);
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);
610  nvgBeginPath(ctx);
611  nvgRect(ctx, spacer, verticalOffset, histWidth, histHeight);
612  nvgRect(ctx, 2 * spacer + histWidth, verticalOffset, histWidth,
613  histHeight);
614  nvgStroke(ctx);
615  nvgFontSize(ctx, 20.0f);
616  nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
617 
618  float bounds[4];
619  nvgTextBoxBounds(ctx, 0, 0, width() - 2 * spacer,
620  m_testResult.second.c_str(), nullptr, bounds);
621  nvgTextBox(
622  ctx, spacer, verticalOffset + histHeight + spacer + (70 - bounds[3])/2,
623  width() - 2 * spacer, m_testResult.second.c_str(), nullptr);
624  nvgEndFrame(ctx);
625  } else {
626  /* Render the point set */
627  Matrix4f mvp = proj * view * model;
628  m_pointShader->set_uniform("mvp", mvp);
629  glPointSize(2);
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();
634 
635  bool drawGrid = m_gridCheckBox->checked();
636  if (drawGrid) {
637  m_gridShader->set_uniform("mvp", mvp);
638  m_gridShader->begin();
639  m_gridShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, m_lineCount);
640  m_gridShader->end();
641  }
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();
647  }
648  }
649  m_renderPass->end();
650  }
651 
652  void drawHistogram(const nanogui::Vector2i &pos_, const nanogui::Vector2i &size_, Texture *texture) {
653  Vector2f s = -(Vector2f(pos_) + Vector2f(0.25f, 0.25f)) / Vector2f(size_);
654  Vector2f e = Vector2f(size()) / Vector2f(size_) + s;
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();
662  }
663 
664  void runTest() {
665  // Prepare and run test, passing parameters from UI.
666  WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
667  float parameterValue = mapParameter(warpType, m_parameterSlider->value());
668 
669  WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
670  m_testResult = tester.run();
671 
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]));
678  }
679  minValue /= 2;
680  float texScale = 1/(maxValue - minValue);
681 
682  /* Upload histograms to GPU */
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());
694  }
695  m_drawHistogram = true;
696  m_window->set_visible(false);
697  }
698 
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());
703 
704  set_resize_callback([&](Vector2i size) {
705  m_arcball.setSize(nori::Vector2i(size.x(), size.y()));
706  });
707 
708  m_arcball.setSize(nori::Vector2i(m_size.x(), m_size.y()));
709 
710  new Label(m_window, "Input point set", "sans-bold");
711 
712  /* Create an empty panel with a horizontal layout */
713  Widget *panel = new Widget(m_window);
714  panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
715 
716  /* Add a slider and set defaults */
717  m_pointCountSlider = new Slider(panel);
718  m_pointCountSlider->set_fixed_width(55);
719  m_pointCountSlider->set_callback([&](float) { refresh(); });
720 
721  /* Add a textbox and set defaults */
722  m_pointCountBox = new TextBox(panel);
723  m_pointCountBox->set_fixed_size(Vector2i(80, 25));
724 
725  m_pointTypeBox = new ComboBox(m_window, { "Independent", "Grid", "Stratified" });
726  m_pointTypeBox->set_callback([&](int) { refresh(); });
727 
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(); });
732 
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(); });
749 
750  new Label(m_window, "BSDF parameters", "sans-bold");
751 
752  panel = new Widget(m_window);
753  panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
754 
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());
761 
762  m_brdfValueCheckBox = new CheckBox(m_window, "Visualize BRDF values");
763  m_brdfValueCheckBox->set_callback([&](bool) { refresh(); });
764 
765  new Label(m_window,
766  std::string(utf8(0x03C7).data()) +
767  std::string(utf8(0x00B2).data()) + " hypothesis test",
768  "sans-bold");
769 
770  Button *testBtn = new Button(m_window, "Run", FA_CHECK);
771  testBtn->set_background_color(Color(0, 255, 0, 25));
772  testBtn->set_callback([&]{
773  try {
774  runTest();
775  } catch (const NoriException &e) {
776  new MessageDialog(this, MessageDialog::Type::Warning, "Error", "An error occurred: " + std::string(e.what()));
777  }
778  });
779 
780  m_renderPass = new RenderPass({ this });
781  m_renderPass->set_clear_color(0, Color(0.f, 0.f, 0.f, 1.f));
782 
783  perform_layout();
784 
785  m_pointShader = new Shader(
786  m_renderPass,
787  "Point shader",
788 
789  /* Vertex shader */
790  R"(#version 330
791  uniform mat4 mvp;
792  in vec3 position;
793  in vec3 color;
794  out vec3 frag_color;
795  void main() {
796  gl_Position = mvp * vec4(position, 1.0);
797  if (isnan(position.r)) /* nan (missing value) */
798  frag_color = vec3(0.0);
799  else
800  frag_color = color;
801  })",
802 
803  /* Fragment shader */
804  R"(#version 330
805  in vec3 frag_color;
806  out vec4 out_color;
807  void main() {
808  if (frag_color == vec3(0.0))
809  discard;
810  out_color = vec4(frag_color, 1.0);
811  })"
812  );
813 
814  m_gridShader = new Shader(
815  m_renderPass,
816  "Grid shader",
817 
818  /* Vertex shader */
819  R"(#version 330
820  uniform mat4 mvp;
821  in vec3 position;
822  void main() {
823  gl_Position = mvp * vec4(position, 1.0);
824  })",
825 
826  /* Fragment shader */
827  R"(#version 330
828  out vec4 out_color;
829  void main() {
830  out_color = vec4(vec3(1.0), 0.4);
831  })", Shader::BlendMode::AlphaBlend
832  );
833 
834  m_arrowShader = new Shader(
835  m_renderPass,
836  "Arrow shader",
837 
838  /* Vertex shader */
839  R"(#version 330
840  uniform mat4 mvp;
841  in vec3 position;
842  void main() {
843  gl_Position = mvp * vec4(position, 1.0);
844  })",
845 
846  /* Fragment shader */
847  R"(#version 330
848  out vec4 out_color;
849  void main() {
850  out_color = vec4(vec3(1.0), 0.4);
851  })"
852  );
853 
854  m_histogramShader = new Shader(
855  m_renderPass,
856  "Histogram shader",
857 
858  /* Vertex shader */
859  R"(#version 330
860  uniform mat4 mvp;
861  in vec2 position;
862  out vec2 uv;
863  void main() {
864  gl_Position = mvp * vec4(position, 0.0, 1.0);
865  uv = position;
866  })",
867 
868  /* Fragment shader */
869  R"(#version 330
870  out vec4 out_color;
871  uniform sampler2D tex;
872  in vec2 uv;
873  /* http://paulbourke.net/texture_colour/colourspace/ */
874  vec3 colormap(float v, float vmin, float vmax) {
875  vec3 c = vec3(1.0);
876  if (v < vmin)
877  v = vmin;
878  if (v > vmax)
879  v = vmax;
880  float dv = vmax - vmin;
881 
882  if (v < (vmin + 0.25 * dv)) {
883  c.r = 0.0;
884  c.g = 4.0 * (v - vmin) / dv;
885  } else if (v < (vmin + 0.5 * dv)) {
886  c.r = 0.0;
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;
890  c.b = 0.0;
891  } else {
892  c.g = 1.0 + 4.0 * (vmin + 0.75 * dv - v) / dv;
893  c.b = 0.0;
894  }
895  return c;
896  }
897  void main() {
898  float value = texture(tex, uv).r;
899  out_color = vec4(colormap(value, 0.0, 1.0), 1.0);
900  })"
901  );
902 
903  /* Upload a single quad */
904  uint32_t indices[3 * 2] = {
905  0, 1, 2,
906  2, 3, 0
907  };
908  float positions[2 * 4] = {
909  0.f, 0.f,
910  1.f, 0.f,
911  1.f, 1.f,
912  0.f, 1.f
913  };
914  m_histogramShader->set_buffer("indices", VariableType::UInt32, {3 * 2}, indices);
915  m_histogramShader->set_buffer("position", VariableType::Float32, {4, 2}, positions);
916 
917  /* Set default and register slider callback */
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);
922 
923  refresh();
924  set_size(Vector2i(800, 600));
925  set_visible(true);
926  draw_all();
927  }
928 
929 private:
930  nanogui::ref<Shader> m_pointShader, m_gridShader, m_histogramShader, m_arrowShader;
931  Window *m_window;
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;
939  Arcball m_arcball;
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;
946 };
947 
948 
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)
953  tp = WarpType(i);
954  }
955  if (tp >= WarpTypeCount)
956  throw std::runtime_error("Invalid warp type!");
957 
958  float value = 0.f, value2 = 0.f;
959  if (argc > 2)
960  value = std::stof(argv[2]);
961  if (argc > 3)
962  value2 = std::stof(argv[3]);
963 
964  return { tp, value, value2 };
965 }
966 
967 
968 int main(int argc, char **argv) {
969  if (argc <= 1) {
970  // GUI mode
971  nanogui::init();
972  WarpTestScreen *screen = new WarpTestScreen();
973  nanogui::mainloop();
974  delete screen;
975  nanogui::shutdown();
976  return 0;
977  }
978 
979  // CLI mode
980  WarpType warpType;
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;
987  BSDF *ptr;
988  std::tie(ptr, bRec) = WarpTest::create_microfacet_bsdf(
989  paramValue, param2Value, bsdfAngle);
990  bsdf.reset(ptr);
991  }
992 
993  std::string extra = "";
994  if (param2Value > 0)
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
999  ) << std::endl;
1000  WarpTest tester(warpType, paramValue, bsdf.get(), bRec);
1001  auto res = tester.run();
1002  if (res.first)
1003  return 0;
1004 
1005  std::cout << tfm::format("warptest failed: %s", res.second) << std::endl;
1006  return 1;
1007 }
static NoriObject * createInstance(const std::string &name, const PropertyList &propList)
Construct an instance from the class of the given name.
Definition: object.h:158
Superclass of all texture.
Definition: texture.h:30
static float squareToUniformDiskPdf(const Point2f &p)
Probability density of squareToUniformDisk()
Definition: warp.cpp:53
static float squareToUniformHemispherePdf(const Vector3f &v)
Probability density of squareToUniformHemisphere()
Definition: warp.cpp:77
static float squareToUniformSphereCapPdf(const Vector3f &v, float cosThetaMax)
Probability density of squareToUniformSphereCap()
Definition: warp.cpp:61
static Point2f squareToUniformSquare(const Point2f &sample)
Dummy warping function: takes uniformly distributed points in a square and just returns them.
Definition: warp.cpp:41
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...
Definition: warp.cpp:81
static Vector3f squareToBeckmann(const Point2f &sample, float alpha)
Warp a uniformly distributed square sample to a Beckmann distribution * cosine for the given 'alpha' ...
Definition: warp.cpp:89
static Vector3f squareToUniformSphere(const Point2f &sample)
Uniformly sample a vector on the unit sphere with respect to solid angles.
Definition: warp.cpp:65
static float squareToCosineHemispherePdf(const Vector3f &v)
Probability density of squareToCosineHemisphere()
Definition: warp.cpp:85
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...
Definition: warp.cpp:73
static Point2f squareToUniformDisk(const Point2f &sample)
Uniformly sample a vector on a 2D disk with radius 1, centered around the origin.
Definition: warp.cpp:49
static float squareToBeckmannPdf(const Vector3f &m, float alpha)
Probability density of squareToBeckmann()
Definition: warp.cpp:93
static Vector3f squareToUniformSphereCap(const Point2f &sample, float cosThetaMax)
Uniformly sample a vector on a spherical cap around (0, 0, 1)
Definition: warp.cpp:57
static float squareToUniformSpherePdf(const Vector3f &v)
Probability density of squareToUniformSphere()
Definition: warp.cpp:69
static float squareToUniformSquarePdf(const Point2f &p)
Probability density of squareToUniformSquare()
Definition: warp.cpp:45