Nori  23
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()),
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(), "chitest.m");
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);
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()) {
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  return parameterValue;
417  }
418 
419  void refresh() {
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);
425 
426  if (warpType == MicrofacetBRDF) {
427  BSDF *ptr;
428  float bsdfAngle = M_PI * (m_angleSlider->value() - 0.5f);
429  std::tie(ptr, m_bRec) = WarpTest::create_microfacet_bsdf(
430  parameterValue, parameter2Value, bsdfAngle);
431  m_brdf.reset(ptr);
432  }
433 
434  /* Generate the point positions */
435  nori::MatrixXf positions, values;
436  try {
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);
441  refresh();
442  new MessageDialog(this, MessageDialog::Type::Warning, "Error", "An error occurred: " + std::string(e.what()));
443  return;
444  }
445 
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;
450 
451  if (!m_brdfValueCheckBox->checked() || warpType != MicrofacetBRDF)
452  value_scale = 0.f;
453 
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());
458  continue;
459  }
460  positions.col(i) =
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);
463  }
464  }
465 
466  /* Generate a color gradient */
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;
471 
472  /* Upload points to GPU */
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());
475 
476  /* Upload lines to GPU */
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;
483 
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);
495  }
496  }
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);
500  }
501  m_gridShader->set_buffer("position", VariableType::Float32, {(size_t) m_lineCount, 3}, positions.data());
502  }
503 
504  int ctr = 0;
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;
511  }
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());
517 
518  /* Update user interface */
519  std::string str;
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);
526  } else {
527  m_pointCountBox->set_units(" ");
528  str = tfm::format("%i", m_pointCount);
529  }
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);
542  }
543 
544  bool mouse_motion_event(const Vector2i &p, const Vector2i &rel,
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()));
548  return true;
549  }
550 
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);
555  return true;
556  }
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);
560  }
561  return true;
562  }
563 
564  void draw_contents() {
565  /* Set up a perspective camera matrix */
566  Matrix4f view, proj, model(1.f);
567  view = Matrix4f::look_at(Vector3f(0, 0, 4), Vector3f(0, 0, 0), Vector3f(0, 1, 0));
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;
572 
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();
578 
579  if (m_drawHistogram) {
580  /* Render the histograms */
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;
586 
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());
589 
590  auto ctx = m_nvg_context;
591  nvgBeginFrame(ctx, m_size[0], m_size[1], m_pixel_ratio);
592  nvgBeginPath(ctx);
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));
595  nvgFill(ctx);
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);
606  nvgBeginPath(ctx);
607  nvgRect(ctx, spacer, verticalOffset, histWidth, histHeight);
608  nvgRect(ctx, 2 * spacer + histWidth, verticalOffset, histWidth,
609  histHeight);
610  nvgStroke(ctx);
611  nvgFontSize(ctx, 20.0f);
612  nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
613 
614  float bounds[4];
615  nvgTextBoxBounds(ctx, 0, 0, width() - 2 * spacer,
616  m_testResult.second.c_str(), nullptr, bounds);
617  nvgTextBox(
618  ctx, spacer, verticalOffset + histHeight + spacer + (70 - bounds[3])/2,
619  width() - 2 * spacer, m_testResult.second.c_str(), nullptr);
620  nvgEndFrame(ctx);
621  } else {
622  /* Render the point set */
623  Matrix4f mvp = proj * view * model;
624  m_pointShader->set_uniform("mvp", mvp);
625  glPointSize(2);
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();
630 
631  bool drawGrid = m_gridCheckBox->checked();
632  if (drawGrid) {
633  m_gridShader->set_uniform("mvp", mvp);
634  m_gridShader->begin();
635  m_gridShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, m_lineCount);
636  m_gridShader->end();
637  }
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();
643  }
644  }
645  m_renderPass->end();
646  }
647 
648  void drawHistogram(const nanogui::Vector2i &pos_, const nanogui::Vector2i &size_, Texture *texture) {
649  Vector2f s = -(Vector2f(pos_) + Vector2f(0.25f, 0.25f)) / Vector2f(size_);
650  Vector2f e = Vector2f(size()) / Vector2f(size_) + s;
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();
658  }
659 
660  void runTest() {
661  // Prepare and run test, passing parameters from UI.
662  WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
663  float parameterValue = mapParameter(warpType, m_parameterSlider->value());
664 
665  WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
666  m_testResult = tester.run();
667 
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]));
674  }
675  minValue /= 2;
676  float texScale = 1/(maxValue - minValue);
677 
678  /* Upload histograms to GPU */
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());
690  }
691  m_drawHistogram = true;
692  m_window->set_visible(false);
693  }
694 
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());
699 
700  set_resize_callback([&](Vector2i size) {
701  m_arcball.setSize(nori::Vector2i(size.x(), size.y()));
702  });
703 
704  m_arcball.setSize(nori::Vector2i(m_size.x(), m_size.y()));
705 
706  new Label(m_window, "Input point set", "sans-bold");
707 
708  /* Create an empty panel with a horizontal layout */
709  Widget *panel = new Widget(m_window);
710  panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
711 
712  /* Add a slider and set defaults */
713  m_pointCountSlider = new Slider(panel);
714  m_pointCountSlider->set_fixed_width(55);
715  m_pointCountSlider->set_callback([&](float) { refresh(); });
716 
717  /* Add a textbox and set defaults */
718  m_pointCountBox = new TextBox(panel);
719  m_pointCountBox->set_fixed_size(Vector2i(80, 25));
720 
721  m_pointTypeBox = new ComboBox(m_window, { "Independent", "Grid", "Stratified" });
722  m_pointTypeBox->set_callback([&](int) { refresh(); });
723 
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(); });
728 
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(); });
745 
746  new Label(m_window, "BSDF parameters", "sans-bold");
747 
748  panel = new Widget(m_window);
749  panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
750 
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());
757 
758  m_brdfValueCheckBox = new CheckBox(m_window, "Visualize BRDF values");
759  m_brdfValueCheckBox->set_callback([&](bool) { refresh(); });
760 
761  new Label(m_window,
762  std::string(utf8(0x03C7).data()) +
763  std::string(utf8(0x00B2).data()) + " hypothesis test",
764  "sans-bold");
765 
766  Button *testBtn = new Button(m_window, "Run", FA_CHECK);
767  testBtn->set_background_color(Color(0, 255, 0, 25));
768  testBtn->set_callback([&]{
769  try {
770  runTest();
771  } catch (const NoriException &e) {
772  new MessageDialog(this, MessageDialog::Type::Warning, "Error", "An error occurred: " + std::string(e.what()));
773  }
774  });
775 
776  m_renderPass = new RenderPass({ this });
777  m_renderPass->set_clear_color(0, Color(0.f, 0.f, 0.f, 1.f));
778 
779  perform_layout();
780 
781  m_pointShader = new Shader(
782  m_renderPass,
783  "Point shader",
784 
785  /* Vertex shader */
786  R"(#version 330
787  uniform mat4 mvp;
788  in vec3 position;
789  in vec3 color;
790  out vec3 frag_color;
791  void main() {
792  gl_Position = mvp * vec4(position, 1.0);
793  if (isnan(position.r)) /* nan (missing value) */
794  frag_color = vec3(0.0);
795  else
796  frag_color = color;
797  })",
798 
799  /* Fragment shader */
800  R"(#version 330
801  in vec3 frag_color;
802  out vec4 out_color;
803  void main() {
804  if (frag_color == vec3(0.0))
805  discard;
806  out_color = vec4(frag_color, 1.0);
807  })"
808  );
809 
810  m_gridShader = new Shader(
811  m_renderPass,
812  "Grid shader",
813 
814  /* Vertex shader */
815  R"(#version 330
816  uniform mat4 mvp;
817  in vec3 position;
818  void main() {
819  gl_Position = mvp * vec4(position, 1.0);
820  })",
821 
822  /* Fragment shader */
823  R"(#version 330
824  out vec4 out_color;
825  void main() {
826  out_color = vec4(vec3(1.0), 0.4);
827  })", Shader::BlendMode::AlphaBlend
828  );
829 
830  m_arrowShader = new Shader(
831  m_renderPass,
832  "Arrow shader",
833 
834  /* Vertex shader */
835  R"(#version 330
836  uniform mat4 mvp;
837  in vec3 position;
838  void main() {
839  gl_Position = mvp * vec4(position, 1.0);
840  })",
841 
842  /* Fragment shader */
843  R"(#version 330
844  out vec4 out_color;
845  void main() {
846  out_color = vec4(vec3(1.0), 0.4);
847  })"
848  );
849 
850  m_histogramShader = new Shader(
851  m_renderPass,
852  "Histogram shader",
853 
854  /* Vertex shader */
855  R"(#version 330
856  uniform mat4 mvp;
857  in vec2 position;
858  out vec2 uv;
859  void main() {
860  gl_Position = mvp * vec4(position, 0.0, 1.0);
861  uv = position;
862  })",
863 
864  /* Fragment shader */
865  R"(#version 330
866  out vec4 out_color;
867  uniform sampler2D tex;
868  in vec2 uv;
869  /* http://paulbourke.net/texture_colour/colourspace/ */
870  vec3 colormap(float v, float vmin, float vmax) {
871  vec3 c = vec3(1.0);
872  if (v < vmin)
873  v = vmin;
874  if (v > vmax)
875  v = vmax;
876  float dv = vmax - vmin;
877 
878  if (v < (vmin + 0.25 * dv)) {
879  c.r = 0.0;
880  c.g = 4.0 * (v - vmin) / dv;
881  } else if (v < (vmin + 0.5 * dv)) {
882  c.r = 0.0;
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;
886  c.b = 0.0;
887  } else {
888  c.g = 1.0 + 4.0 * (vmin + 0.75 * dv - v) / dv;
889  c.b = 0.0;
890  }
891  return c;
892  }
893  void main() {
894  float value = texture(tex, uv).r;
895  out_color = vec4(colormap(value, 0.0, 1.0), 1.0);
896  })"
897  );
898 
899  /* Upload a single quad */
900  uint32_t indices[3 * 2] = {
901  0, 1, 2,
902  2, 3, 0
903  };
904  float positions[2 * 4] = {
905  0.f, 0.f,
906  1.f, 0.f,
907  1.f, 1.f,
908  0.f, 1.f
909  };
910  m_histogramShader->set_buffer("indices", VariableType::UInt32, {3 * 2}, indices);
911  m_histogramShader->set_buffer("position", VariableType::Float32, {4, 2}, positions);
912 
913  /* Set default and register slider callback */
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);
918 
919  refresh();
920  set_size(Vector2i(800, 600));
921  set_visible(true);
922  draw_all();
923  }
924 
925 private:
926  nanogui::ref<Shader> m_pointShader, m_gridShader, m_histogramShader, m_arrowShader;
927  Window *m_window;
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;
935  Arcball m_arcball;
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;
942 };
943 
944 
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)
949  tp = WarpType(i);
950  }
951  if (tp >= WarpTypeCount)
952  throw std::runtime_error("Invalid warp type!");
953 
954  float value = 0.f, value2 = 0.f;
955  if (argc > 2)
956  value = std::stof(argv[2]);
957  if (argc > 3)
958  value2 = std::stof(argv[3]);
959 
960  return { tp, value, value2 };
961 }
962 
963 
964 int main(int argc, char **argv) {
965  if (argc <= 1) {
966  // GUI mode
967  nanogui::init();
968  WarpTestScreen *screen = new WarpTestScreen();
969  nanogui::mainloop();
970  delete screen;
971  nanogui::shutdown();
972  return 0;
973  }
974 
975  // CLI mode
976  WarpType warpType;
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;
983  BSDF *ptr;
984  std::tie(ptr, bRec) = WarpTest::create_microfacet_bsdf(
985  paramValue, param2Value, bsdfAngle);
986  bsdf.reset(ptr);
987  }
988 
989  std::string extra = "";
990  if (param2Value > 0)
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
995  ) << std::endl;
996  WarpTest tester(warpType, paramValue, bsdf.get(), bRec);
997  auto res = tester.run();
998  if (res.first)
999  return 0;
1000 
1001  std::cout << tfm::format("warptest failed: %s", res.second) << std::endl;
1002  return 1;
1003 }
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