scattersim.cpp
上传用户:center1979
上传日期:2022-07-26
资源大小:50633k
文件大小:62k
源码类别:

OpenGL

开发平台:

Visual C++

  1. // scattersim.cpp
  2. //
  3. // Copyright (C) 2007, Chris Laurel <claurel@shatters.net>
  4. //
  5. // This program is free software; you can redistribute it and/or
  6. // modify it under the terms of the GNU General Public License
  7. // as published by the Free Software Foundation; either version 2
  8. // of the License, or (at your option) any later version.
  9. //
  10. #include <iostream>
  11. #include <fstream>
  12. #include <string>
  13. #include <cstdlib>
  14. #include <cmath>
  15. #include <algorithm>
  16. #include <map>
  17. #include <celutil/basictypes.h>
  18. #include <celmath/vecmath.h>
  19. #include <celmath/mathlib.h>
  20. #include <celmath/ray.h>
  21. #include <celmath/sphere.h>
  22. #include <celmath/intersect.h>
  23. #ifdef MACOSX
  24. #include "../../../macosx/png.h"
  25. #else
  26. #include "png.h"
  27. #endif // MACOSX
  28. using namespace std;
  29. // Extinction lookup table dimensions
  30. const unsigned int ExtinctionLUTHeightSteps = 256;
  31. const unsigned int ExtinctionLUTViewAngleSteps = 512;
  32. // Scattering lookup table dimensions
  33. const unsigned int ScatteringLUTHeightSteps = 64;
  34. const unsigned int ScatteringLUTViewAngleSteps = 64;
  35. const unsigned int ScatteringLUTLightAngleSteps = 64;
  36. // Values settable via the command line
  37. static unsigned int IntegrateScatterSteps = 20;
  38. static unsigned int IntegrateDepthSteps = 20;
  39. static unsigned int OutputImageWidth = 600;
  40. static unsigned int OutputImageHeight = 450;
  41. enum LUTUsageType
  42. {
  43.     NoLUT,
  44.     UseExtinctionLUT,
  45.     UseScatteringLUT
  46. };
  47. static LUTUsageType LUTUsage = NoLUT;
  48. static bool UseFisheyeCameras = false;
  49. static double CameraExposure = 0.0;
  50. typedef map<string, double> ParameterSet;
  51. struct Color
  52. {
  53.     Color() : r(0.0f), g(0.0f), b(0.0f) {}
  54.     Color(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {}
  55.     Color exposure(float e) const
  56.     {
  57. #if 0
  58.         float brightness = max(r, max(g, b));
  59.         float f = 1.0f - (float) exp(-e * brightness);
  60.         return Color(r * f, g * f, b * f);
  61. #endif
  62.         return Color((float) (1.0 - exp(-e * r)),
  63.                      (float) (1.0 - exp(-e * g)),
  64.                      (float) (1.0 - exp(-e * b)));
  65.     }
  66.     float r, g, b;
  67. };
  68. Color operator*(const Color& color, double d)
  69. {
  70.     return Color((float) (color.r * d),
  71.                  (float) (color.g * d),
  72.                  (float) (color.b * d));
  73. }
  74. Color operator+(const Color& a, const Color& b)
  75. {
  76.     return Color(a.r + b.r, a.g + b.g, a.b + b.b);
  77. }
  78. Color operator*(const Color& a, const Color& b)
  79. {
  80.     return Color(a.r * b.r, a.g * b.g, a.b * b.b);
  81. }
  82. Color operator*(const Color& a, const Vec3d& v)
  83. {
  84.     return Color((float) (a.r * v.x), (float) (a.g * v.y), (float) (a.b * v.z));
  85. }
  86. static uint8 floatToByte(float f)
  87. {
  88.     if (f <= 0.0f)
  89.         return 0;
  90.     else if (f >= 1.0f)
  91.         return 255;
  92.     else
  93.         return (uint8) (f * 255.99f);
  94. }
  95. class Camera
  96. {
  97. public:
  98.     enum CameraType
  99.     {
  100.         Planar,
  101.         Spherical,
  102.     };
  103.     Camera() :
  104.         fov(PI / 2.0),
  105.         front(1.0),
  106.         transform(Mat4d::identity()),
  107.         type(Planar)
  108.     {
  109.     }
  110.     Ray3d getViewRay(double viewportX, double viewportY) const
  111.     {
  112.         Vec3d viewDir;
  113.         if (type == Planar)
  114.         {
  115.             double viewPlaneHeight = tan(fov / 2.0) * 2 * front;
  116.             viewDir.x = viewportX * viewPlaneHeight;
  117.             viewDir.y = viewportY * viewPlaneHeight;
  118.             viewDir.z = front;
  119.             viewDir.normalize();
  120.         }
  121.         else
  122.         {
  123.             double phi = -viewportY * fov / 2.0 + PI / 2.0;
  124.             double theta = viewportX * fov / 2.0 + PI / 2.0; 
  125.             viewDir.x = sin(phi) * cos(theta);
  126.             viewDir.y = cos(phi);
  127.             viewDir.z = sin(phi) * sin(theta);
  128.             viewDir.normalize();
  129.         }
  130.         Ray3d viewRay(Point3d(0.0, 0.0, 0.0), viewDir);
  131.         return viewRay * transform;
  132.     }
  133.     double fov;
  134.     double front;
  135.     Mat4d transform;
  136.     CameraType type;
  137. };
  138. class Viewport
  139. {
  140. public:
  141.     Viewport(unsigned int _x, unsigned int _y,
  142.              unsigned int _width, unsigned int _height) :
  143.         x(_x), y(_y), width(_width), height(_height)
  144.     {
  145.     }
  146.     unsigned int x;
  147.     unsigned int y;
  148.     unsigned int width;
  149.     unsigned int height;
  150. };
  151. class Light
  152. {
  153. public:
  154.     Vec3d direction;
  155.     Color color;
  156. };
  157. struct OpticalDepths
  158. {
  159.     double rayleigh;
  160.     double mie;
  161.     double absorption;
  162. };
  163. OpticalDepths sumOpticalDepths(OpticalDepths a, OpticalDepths b)
  164. {
  165.     OpticalDepths depths;
  166.     depths.rayleigh   = a.rayleigh + b.rayleigh;
  167.     depths.mie        = a.mie + b.mie;
  168.     depths.absorption = a.absorption + b.absorption;
  169.     return depths;
  170. }
  171. typedef double (*MiePhaseFunction)(double cosTheta, double asymmetry);
  172. double phaseHenyeyGreenstein_CS(double, double);
  173. double phaseHenyeyGreenstein(double, double);
  174. double phaseSchlick(double, double);
  175. class Atmosphere
  176. {
  177. public:
  178.     Atmosphere() :
  179.         miePhaseFunction(phaseHenyeyGreenstein_CS)
  180.     {
  181.     }
  182.     double calcShellHeight()
  183.     {
  184.         double maxScaleHeight = max(rayleighScaleHeight, max(mieScaleHeight, absorbScaleHeight));
  185.         return -log(0.002) * maxScaleHeight;
  186.     }
  187.     double miePhase(double cosAngle) const
  188.     {
  189.         return miePhaseFunction(cosAngle, mieAsymmetry);
  190.     }
  191.     double rayleighDensity(double h) const;
  192.     double mieDensity(double h) const;
  193.     double absorbDensity(double h) const;
  194.     Vec3d computeExtinction(const OpticalDepths&) const;
  195.     double rayleighScaleHeight;
  196.     double mieScaleHeight;
  197.     double absorbScaleHeight;
  198.     Vec3d rayleighCoeff;
  199.     Vec3d absorbCoeff;
  200.     double mieCoeff;
  201.     double mieAsymmetry;
  202.     MiePhaseFunction miePhaseFunction;
  203. };
  204. class LUT2
  205. {
  206. public:
  207.     LUT2(unsigned int w, unsigned int h);
  208.     
  209.     Vec3d getValue(unsigned int x, unsigned int y) const;
  210.     void setValue(unsigned int x, unsigned int y, const Vec3d&);
  211.     Vec3d lookup(double x, double y) const;
  212.     unsigned int getWidth() const { return width; }
  213.     unsigned int getHeight() const { return height; }
  214. private:
  215.     unsigned int width;
  216.     unsigned int height;
  217.     float* values;
  218. };
  219. class LUT3
  220. {
  221. public:
  222.     LUT3(unsigned int w, unsigned int h, unsigned int d);
  223.     
  224.     Vec4d getValue(unsigned int x, unsigned int y, unsigned int z) const;
  225.     void setValue(unsigned int x, unsigned int y, unsigned int z, const Vec4d&);
  226.     Vec4d lookup(double x, double y, double z) const;
  227.     unsigned int getWidth() const { return width; }
  228.     unsigned int getHeight() const { return height; }
  229.     unsigned int getDepth() const { return depth; }
  230. private:
  231.     unsigned int width;
  232.     unsigned int height;
  233.     unsigned int depth;
  234.     float* values;
  235. };
  236. class Scene
  237. {
  238. public:
  239.     Scene() {};
  240.     
  241.     void setParameters(ParameterSet& params);
  242.     Color raytrace(const Ray3d& ray) const;
  243.     Color raytrace_LUT(const Ray3d& ray) const;
  244.     Color background;
  245.     Light light;
  246.     Sphered planet;
  247.     Color planetColor;
  248.     Color planetColor2;
  249.     Atmosphere atmosphere;
  250.     double atmosphereShellHeight;
  251.     double sunAngularDiameter;
  252.     LUT2* extinctionLUT;
  253.     LUT3* scatteringLUT;
  254. };
  255. class RGBImage
  256. {
  257. public:
  258.     RGBImage(int w, int h) :
  259.         width(w),
  260.         height(h),
  261.         pixels(NULL)
  262.     {
  263.         pixels = new uint8[width * height * 3];
  264.     }
  265.     ~RGBImage()
  266.     {
  267.         delete[] pixels;
  268.     }
  269.     void clearRect(const Color& color,
  270.                    unsigned int x,
  271.                    unsigned int y,
  272.                    unsigned int w,
  273.                    unsigned int h)
  274.     {
  275.         
  276.         uint8 r = floatToByte(color.r);
  277.         uint8 g = floatToByte(color.g);
  278.         uint8 b = floatToByte(color.b);
  279.         for (unsigned int i = y; i < y + h; i++)
  280.         {
  281.             for (unsigned int j = x; j < x + w; j++)
  282.             {
  283.                 pixels[(i * width + j) * 3    ] = r;
  284.                 pixels[(i * width + j) * 3 + 1] = g;
  285.                 pixels[(i * width + j) * 3 + 2] = b;
  286.             }
  287.         }
  288.     }
  289.     void clear(const Color& color)
  290.     {
  291.         clearRect(color, 0, 0, width, height);
  292.     }
  293.     void setPixel(unsigned int x, unsigned int y, const Color& color)
  294.     {
  295.         if (x >= 0 && x < width && y >= 0 && y < height)
  296.         {
  297.             unsigned int pix = (x + y * width) * 3;
  298.             pixels[pix + 0] = floatToByte(color.r);
  299.             pixels[pix + 1] = floatToByte(color.g);
  300.             pixels[pix + 2] = floatToByte(color.b);
  301.         }
  302.     }
  303.     unsigned int width;
  304.     unsigned int height;
  305.     unsigned char* pixels;
  306. };
  307. void usage()
  308. {
  309.     cerr << "Usage: scattersim [options] <config file>n";
  310.     cerr << "   --lut (or -l)              : accelerate calculation by using a lookup tablen";
  311.     cerr << "   --fisheye (or -f)          : use wide angle cameras on surfacen";
  312.     cerr << "   --exposure <value> (or -e) : set exposure for HDRn";
  313.     cerr << "   --width <value> (or -w)    : set width of output imagen";
  314.     cerr << "   --height <value> (or -h)   : set height of output imagen";
  315.     cerr << "   --image <filename> (or -i) : set filename of output imagen";
  316.     cerr << "           (default is out.png)n";
  317.     cerr << "   --depthsteps <value> (or -d)n";
  318.     cerr << "           set the number of integration steps for depthn";
  319.     cerr << "   --scattersteps <value> (or -s)n";
  320.     cerr << "           set the number of integration steps for scatteringn";
  321. }
  322. static void PNGWriteData(png_structp png_ptr, png_bytep data, png_size_t length)
  323. {
  324.     FILE* fp = (FILE*) png_get_io_ptr(png_ptr);
  325.     fwrite((void*) data, 1, length, fp);
  326. }
  327. bool WritePNG(const string& filename, const RGBImage& image)
  328.               
  329. {
  330.     int rowStride = image.width * 3;
  331.     FILE* out;
  332.     out = fopen(filename.c_str(), "wb");
  333.     if (out == NULL)
  334.     {
  335.         cerr << "Can't open screen capture file " << filename << "n";
  336.         return false;
  337.     }
  338.     png_bytep* row_pointers = new png_bytep[image.height];
  339.     for (unsigned int i = 0; i < image.height; i++)
  340.         row_pointers[i] = (png_bytep) &image.pixels[rowStride * (image.height - i - 1)];
  341.     png_structp png_ptr;
  342.     png_infop info_ptr;
  343.     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
  344.                                       NULL, NULL, NULL);
  345.     if (png_ptr == NULL)
  346.     {
  347.         cerr << "Screen capture: error allocating png_ptrn";
  348.         fclose(out);
  349.         delete[] row_pointers;
  350.         return false;
  351.     }
  352.     info_ptr = png_create_info_struct(png_ptr);
  353.     if (info_ptr == NULL)
  354.     {
  355.         cerr << "Screen capture: error allocating info_ptrn";
  356.         fclose(out);
  357.         delete[] row_pointers;
  358.         png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
  359.         return false;
  360.     }
  361.     if (setjmp(png_jmpbuf(png_ptr)))
  362.     {
  363.         cerr << "Error writing PNG file " << filename << endl;
  364.         fclose(out);
  365.         delete[] row_pointers;
  366.         png_destroy_write_struct(&png_ptr, &info_ptr);
  367.         return false;
  368.     }
  369.     // png_init_io(png_ptr, out);
  370.     png_set_write_fn(png_ptr, (void*) out, PNGWriteData, NULL);
  371.     png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
  372.     png_set_IHDR(png_ptr, info_ptr,
  373.                  image.width, image.height,
  374.                  8,
  375.                  PNG_COLOR_TYPE_RGB,
  376.                  PNG_INTERLACE_NONE,
  377.                  PNG_COMPRESSION_TYPE_DEFAULT,
  378.                  PNG_FILTER_TYPE_DEFAULT);
  379.     png_write_info(png_ptr, info_ptr);
  380.     png_write_image(png_ptr, row_pointers);
  381.     png_write_end(png_ptr, info_ptr);
  382.     // Clean up everything . . .
  383.     png_destroy_write_struct(&png_ptr, &info_ptr);
  384.     delete[] row_pointers;
  385.     fclose(out);
  386.     return true;
  387. }
  388. float mapColor(double c)
  389. {
  390.     //return (float) ((5.0 + log(max(0.0, c))) * 1.0);
  391.     return (float) c;
  392. }
  393. void DumpLUT(const LUT3& lut, const string& filename)
  394. {
  395.     unsigned int xtiles = 8;
  396.     unsigned int ytiles = lut.getDepth() / xtiles;
  397.     unsigned int tileWidth = lut.getHeight();
  398.     unsigned int tileHeight = lut.getWidth();
  399.     double scale = 1.0 / 1.0;
  400.     RGBImage img(xtiles * tileWidth, xtiles * tileHeight);
  401.     for (unsigned int i = 0; i < ytiles; i++)
  402.     {
  403.         for (unsigned int j = 0; j < xtiles; j++)
  404.         {
  405.             unsigned int z = j + xtiles * i;
  406.             for (unsigned int k = 0; k < tileWidth; k++)
  407.             {
  408.                 unsigned int y = k;
  409.                 for (unsigned int l = 0; l < tileHeight; l++)
  410.                 {
  411.                     unsigned int x = l;
  412.                     Vec4d v = lut.getValue(x, y, z);
  413.                     Color c(mapColor(v.x * 0.000617f * 4.0 * PI),
  414.                             mapColor(v.y * 0.00109f * 4.0 * PI),
  415.                             mapColor(v.z * 0.00195f * 4.0 * PI));
  416.                     img.setPixel(j * tileWidth + k, i * tileHeight + l, c);
  417.                 }
  418.             }
  419.         }
  420.     }
  421.     for (unsigned int blah = 0; blah < img.width; blah++)
  422.         img.setPixel(blah, 0, Color(1.0f, 0.0f, 0.0f));
  423.     WritePNG(filename, img);
  424. }
  425. void DumpLUT(const LUT2& lut, const string& filename)
  426. {
  427.     RGBImage img(lut.getHeight(), lut.getWidth());
  428.     for (unsigned int i = 0; i < lut.getWidth(); i++)
  429.     {
  430.         for (unsigned int j = 0; j < lut.getHeight(); j++)
  431.         {
  432.             Vec3d v = lut.getValue(i, j);
  433.             Color c(mapColor(v.x), mapColor(v.y), mapColor(v.z));
  434.             img.setPixel(j, i, c);
  435.         }
  436.     }
  437.     for (unsigned int blah = 0; blah < img.width; blah++)
  438.         img.setPixel(blah, 0, Color(1.0f, 0.0f, 0.0f));
  439.     WritePNG(filename, img);
  440. }
  441. template<class T> T lerp(double t, T v0, T v1)
  442. {
  443.     return (1 - t) * v0 + t * v1;
  444. }
  445. template<class T> T bilerp(double t, double u, T v00, T v01, T v10, T v11)
  446. {
  447.     return lerp(u, lerp(t, v00, v01), lerp(t, v10, v11));
  448. }
  449. template<class T> T trilerp(double t, double u, double v,
  450.                             T v000, T v001, T v010, T v011,
  451.                             T v100, T v101, T v110, T v111)
  452. {
  453.     return lerp(v,
  454.                 bilerp(t, u, v000, v001, v010, v011),
  455.                 bilerp(t, u, v100, v101, v110, v111));
  456. }
  457. LUT2::LUT2(unsigned int _width, unsigned int _height) :
  458.     width(_width),
  459.     height(_height),
  460.     values(NULL)
  461. {
  462.     values = new float[width * height * 3];
  463. }
  464. Vec3d
  465. LUT2::getValue(unsigned int x, unsigned int y) const
  466. {
  467.     unsigned int n = 3 * (x + y * width);
  468.     return Vec3d(values[n], values[n + 1], values[n + 2]);
  469. }
  470. void
  471. LUT2::setValue(unsigned int x, unsigned int y, const Vec3d& v)
  472. {
  473.     unsigned int n = 3 * (x + y * width);
  474.     values[n]     = (float) v.x;
  475.     values[n + 1] = (float) v.y;
  476.     values[n + 2] = (float) v.z;
  477. }
  478. Vec3d
  479. LUT2::lookup(double x, double y) const
  480. {
  481.     x = max(0.0, min(x, 0.999999));
  482.     y = max(0.0, min(y, 0.999999));
  483.     unsigned int fx = (unsigned int) (x * (width - 1));
  484.     unsigned int fy = (unsigned int) (y * (height - 1));
  485.     double t = x * (width - 1) - fx;
  486.     double u = y * (height - 1) - fy;
  487.     return bilerp(t, u,
  488.                   getValue(fx, fy),
  489.                   getValue(fx + 1, fy),
  490.                   getValue(fx, fy + 1),
  491.                   getValue(fx + 1, fy + 1));
  492. }
  493. LUT3::LUT3(unsigned int _width, unsigned int _height, unsigned int _depth) :
  494.     width(_width),
  495.     height(_height),
  496.     depth(_depth),
  497.     values(NULL)
  498. {
  499.     values = new float[width * height * depth * 4];
  500. }
  501. Vec4d
  502. LUT3::getValue(unsigned int x, unsigned int y, unsigned int z) const
  503. {
  504.     unsigned int n = 4 * (x + (y + z * height) * width);
  505.     return Vec4d(values[n], values[n + 1], values[n + 2], values[n + 3]);
  506. }
  507. void
  508. LUT3::setValue(unsigned int x, unsigned int y, unsigned int z, const Vec4d& v)
  509. {
  510.     unsigned int n = 4 * (x + (y + z * height) * width);
  511.     values[n]     = (float) v.x;
  512.     values[n + 1] = (float) v.y;
  513.     values[n + 2] = (float) v.z;
  514.     values[n + 3] = (float) v.w;
  515. }
  516. Vec4d
  517. LUT3::lookup(double x, double y, double z) const
  518. {
  519.     x = max(0.0, min(x, 0.999999));
  520.     y = max(0.0, min(y, 0.999999));
  521.     z = max(0.0, min(z, 0.999999));
  522.     unsigned int fx = (unsigned int) (x * (width - 1));
  523.     unsigned int fy = (unsigned int) (y * (height - 1));
  524.     unsigned int fz = (unsigned int) (z * (depth - 1));
  525.     double t = x * (width - 1) - fx;
  526.     double u = y * (height - 1) - fy;
  527.     double v = z * (depth - 1) - fz;
  528.     return trilerp(t, u, v,
  529.                    getValue(fx,     fy,     fz),
  530.                    getValue(fx + 1, fy,     fz),
  531.                    getValue(fx,     fy + 1, fz),
  532.                    getValue(fx + 1, fy + 1, fz),
  533.                    getValue(fx,     fy,     fz + 1),
  534.                    getValue(fx + 1, fy,     fz + 1),
  535.                    getValue(fx,     fy + 1, fz + 1),
  536.                    getValue(fx + 1, fy + 1, fz + 1));
  537. }
  538. template<class T> bool raySphereIntersect(const Ray3<T>& ray,
  539.                                           const Sphere<T>& sphere,
  540.                                           T& dist0,
  541.                                           T& dist1)
  542. {
  543.     Vector3<T> diff = ray.origin - sphere.center;
  544.     T s = (T) 1.0 / square(sphere.radius);
  545.     T a = ray.direction * ray.direction * s;
  546.     T b = ray.direction * diff * s;
  547.     T c = diff * diff * s - (T) 1.0;
  548.     T disc = b * b - a * c;
  549.     if (disc < 0.0)
  550.         return false;
  551.     disc = (T) sqrt(disc);
  552.     T sol0 = (-b + disc) / a;
  553.     T sol1 = (-b - disc) / a;
  554.     if (sol0 <= 0.0 && sol1 <= 0.0)
  555.     {
  556.         return false;
  557.     }
  558.     else if (sol0 < sol1)
  559.     {
  560.         dist0 = max(0.0, sol0);
  561.         dist1 = sol1;
  562.         return true;
  563.     }
  564.     else if (sol1 < sol0)
  565.     {
  566.         dist0 = max(0.0, sol1);
  567.         dist1 = sol0;
  568.         return true;
  569.     }
  570.     else
  571.     {
  572.         return false;
  573.     }
  574. }
  575. template<class T> bool raySphereIntersect2(const Ray3<T>& ray,
  576.                                            const Sphere<T>& sphere,
  577.                                            T& dist0,
  578.                                            T& dist1)
  579. {
  580.     Vector3<T> diff = ray.origin - sphere.center;
  581.     T s = (T) 1.0 / square(sphere.radius);
  582.     T a = ray.direction * ray.direction * s;
  583.     T b = ray.direction * diff * s;
  584.     T c = diff * diff * s - (T) 1.0;
  585.     T disc = b * b - a * c;
  586.     if (disc < 0.0)
  587.         return false;
  588.     disc = (T) sqrt(disc);
  589.     T sol0 = (-b + disc) / a;
  590.     T sol1 = (-b - disc) / a;
  591.     if (sol0 < sol1)
  592.     {
  593.         dist0 = sol0;
  594.         dist1 = sol1;
  595.         return true;
  596.     }
  597.     else if (sol0 > sol1)
  598.     {
  599.         dist0 = sol1;
  600.         dist1 = sol0;
  601.         return true;
  602.     }
  603.     else
  604.     {
  605.         // One solution to quadratic indicates grazing intersection; treat
  606.         // as no intersection.
  607.         return false;
  608.     }
  609. }
  610. double phaseRayleigh(double cosTheta)
  611. {
  612.     return 0.75 * (1.0 + cosTheta * cosTheta);
  613. }
  614. double phaseHenyeyGreenstein(double cosTheta, double g)
  615. {
  616.     return (1.0 - g * g) / pow(1.0 + g * g - 2.0 * g * cosTheta, 1.5);
  617. }
  618. double mu2g(double mu)
  619. {
  620.     // mu = <cosTheta>
  621.     // This routine invertes the simple relation of the Cornette-Shanks
  622.     // improved Henyey-Greenstein(HG) phase function:
  623.     // mu = <cosTheta>= 3*g *(g^2 + 4)/(5*(2+g^2))
  624.     double mu2 = mu * mu; 
  625.     double x = 0.5555555556 * mu + 0.17146776 * mu * mu2 + sqrt(max(2.3703704 - 1.3374486 * mu2 + 0.57155921 * mu2 * mu2, 0.0));
  626.     double y = pow(x, 0.33333333333);
  627.     return 0.55555555556 * mu - (1.33333333333 - 0.30864198 * mu2) / y + y;
  628. }
  629. double phaseHenyeyGreenstein_CS(double cosTheta, double g)
  630.     // improved HG - phase function -> Rayleigh phase function for 
  631.     // g -> 0, -> HG-phase function for g -> 1.
  632.     double g2 = g * g;
  633.     return 1.5 * (1.0 - g2) * (1.0 + cosTheta * cosTheta) /((2.0 + g2) * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5));
  634. }
  635. // Convert the asymmetry paramter for the Henyey-Greenstein function to
  636. // the approximate equivalent for the Schlick phase function. From Blasi,
  637. // Saec, and Schlick: 1993, "A rendering algorithm for discrete volume
  638. // density objects"
  639. double schlick_g2k(double g)
  640. {
  641.     return 1.55 * g - 0.55 * g * g * g;
  642. }
  643. // The Schlick phase function is a less computationally expensive than the
  644. // Henyey-Greenstein function, but produces similar results. May be more
  645. // appropriate for a GPU implementation.
  646. double phaseSchlick(double cosTheta, double k)
  647. {
  648.     return (1 - k * k) / square(1 - k * cosTheta);
  649. }
  650. /*
  651.  * Theory:
  652.  * Atmospheres are assumed to be composed of three different populations of
  653.  * particles: Rayleigh scattering, Mie scattering, and absorbing. The density
  654.  * of each population decreases exponentially with height above the planet
  655.  * surface to a degree determined by a scale height:
  656.  *
  657.  *     density(height) = e^(-height/scaleHeight)
  658.  *
  659.  * Rayleigh scattering is wavelength dependent, with a fixed phase function.
  660.  *
  661.  * Mie scattering is wavelength independent, with a phase function determined
  662.  * by a single parameter g (the asymmetry parameter)
  663.  *
  664.  * Absorption is wavelength dependent
  665.  *
  666.  * The light source is assumed to be at an effectively infinite distance from
  667.  * the planet. This means that for any view ray, the light angle will be
  668.  * constant, and the phase function can thus be pulled out of the inscattering
  669.  * integral to simplify the calculation.
  670.  */
  671. /*** Pure ray marching integration functions; no use of lookup tables ***/
  672. OpticalDepths integrateOpticalDepth(const Scene& scene,
  673.                                     const Point3d& atmStart,
  674.                                     const Point3d& atmEnd)
  675. {
  676.     unsigned int nSteps = IntegrateDepthSteps;
  677.     OpticalDepths depth;
  678.     depth.rayleigh   = 0.0;
  679.     depth.mie        = 0.0;
  680.     depth.absorption = 0.0;
  681.     Vec3d dir = atmEnd - atmStart;
  682.     double length = dir.length();
  683.     double stepDist = length / (double) nSteps;
  684.     if (length == 0.0)
  685.         return depth;
  686.     dir = dir * (1.0 / length);
  687.     Point3d samplePoint = atmStart + (0.5 * stepDist * dir);
  688.     for (unsigned int i = 0; i < nSteps; i++)
  689.     {
  690.         double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
  691.         // Optical depth due to two phenomena:
  692.         //   Outscattering by Rayleigh and Mie scattering particles
  693.         //   Absorption by absorbing particles
  694.         depth.rayleigh   += scene.atmosphere.rayleighDensity(h) * stepDist;
  695.         depth.mie        += scene.atmosphere.mieDensity(h)      * stepDist;
  696.         depth.absorption += scene.atmosphere.absorbDensity(h)   * stepDist;
  697.         
  698.         samplePoint += stepDist * dir;
  699.     }
  700.     return depth;
  701. }
  702. double
  703. Atmosphere::rayleighDensity(double h) const
  704. {
  705.     return exp(min(1.0, -h / rayleighScaleHeight));
  706. }
  707. double
  708. Atmosphere::mieDensity(double h) const
  709. {
  710.     return exp(min(1.0, -h / mieScaleHeight));
  711. }
  712. double
  713. Atmosphere::absorbDensity(double h) const
  714. {
  715.     return exp(min(1.0, -h / absorbScaleHeight));
  716. }
  717. Vec3d
  718. Atmosphere::computeExtinction(const OpticalDepths& depth) const
  719. {
  720.     Vec3d extinction;
  721.     extinction.x = exp(-depth.rayleigh   * rayleighCoeff.x -
  722.                         depth.mie        * mieCoeff -
  723.                         depth.absorption * absorbCoeff.x);
  724.     extinction.y = exp(-depth.rayleigh   * rayleighCoeff.y -
  725.                         depth.mie        * mieCoeff -
  726.                         depth.absorption * absorbCoeff.y);
  727.     extinction.z = exp(-depth.rayleigh   * rayleighCoeff.z -
  728.                         depth.mie        * mieCoeff -
  729.                         depth.absorption * absorbCoeff.z);
  730.     return extinction;
  731. }
  732. Vec3d integrateInscattering(const Scene& scene,
  733.                             const Point3d& atmStart,
  734.                             const Point3d& atmEnd)
  735. {
  736.     const unsigned int nSteps = IntegrateScatterSteps;
  737.     Vec3d dir = atmEnd - atmStart;
  738.     Point3d origin = Point3d(0.0, 0.0, 0.0) + (atmStart - scene.planet.center);
  739.     double stepDist = dir.length() / (double) nSteps;
  740.     dir.normalize();
  741.     // Start at the midpoint of the first interval
  742.     Point3d samplePoint = origin + 0.5 * stepDist * dir;
  743.     Vec3d rayleighScatter(0.0, 0.0, 0.0);
  744.     Vec3d mieScatter(0.0, 0.0, 0.0);
  745.     Vec3d lightDir = -scene.light.direction;
  746.     Sphered shell = Sphered(Point3d(0.0, 0.0, 0.0),
  747.                             scene.planet.radius + scene.atmosphereShellHeight);
  748.     for (unsigned int i = 0; i < nSteps; i++)
  749.     {
  750.         Ray3d sunRay(samplePoint, lightDir);
  751.         double sunDist = 0.0;
  752.         testIntersection(sunRay, shell, sunDist);
  753.         // Compute the optical depth along path from sample point to the sun
  754.         OpticalDepths sunDepth = integrateOpticalDepth(scene, samplePoint, sunRay.point(sunDist));
  755.         // Compute the optical depth along the path from the sample point to the eye
  756.         OpticalDepths eyeDepth = integrateOpticalDepth(scene, samplePoint, atmStart);
  757.         // Sum the optical depths to get the depth on the complete path from sun
  758.         // to sample point to eye.
  759.         OpticalDepths totalDepth = sumOpticalDepths(sunDepth, eyeDepth);
  760.         totalDepth.rayleigh *= 4.0 * PI;
  761.         totalDepth.mie      *= 4.0 * PI;
  762.         Vec3d extinction = scene.atmosphere.computeExtinction(totalDepth);
  763.         double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
  764.         // Add the inscattered light from Rayleigh and Mie scattering particles
  765.         rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
  766.         mieScatter +=      scene.atmosphere.mieDensity(h)      * stepDist * extinction;
  767.         
  768.         samplePoint += stepDist * dir;
  769.     }
  770.     double cosSunAngle = lightDir * dir;
  771.     double miePhase = scene.atmosphere.miePhase(cosSunAngle);
  772.     const Vec3d& rayleigh = scene.atmosphere.rayleighCoeff;
  773.     return phaseRayleigh(cosSunAngle) * Vec3d(rayleighScatter.x * rayleigh.x,
  774.                                               rayleighScatter.y * rayleigh.y,
  775.                                               rayleighScatter.z * rayleigh.z) +
  776.         miePhase * mieScatter * scene.atmosphere.mieCoeff;
  777. }
  778. Vec4d integrateInscatteringFactors(const Scene& scene,
  779.                                    const Point3d& atmStart,
  780.                                    const Point3d& atmEnd,
  781.                                    const Vec3d& lightDir)
  782. {
  783.     const unsigned int nSteps = IntegrateScatterSteps;
  784.     Vec3d dir = atmEnd - atmStart;
  785.     Point3d origin = Point3d(0.0, 0.0, 0.0) + (atmStart - scene.planet.center);
  786.     double stepDist = dir.length() / (double) nSteps;
  787.     dir.normalize();
  788.     // Start at the midpoint of the first interval
  789.     Point3d samplePoint = origin + 0.5 * stepDist * dir;
  790.     Vec3d rayleighScatter(0.0, 0.0, 0.0);
  791.     Vec3d mieScatter(0.0, 0.0, 0.0);
  792.     Sphered shell = Sphered(Point3d(0.0, 0.0, 0.0),
  793.                             scene.planet.radius + scene.atmosphereShellHeight);
  794.     for (unsigned int i = 0; i < nSteps; i++)
  795.     {
  796.         Ray3d sunRay(samplePoint, lightDir);
  797.         double sunDist = 0.0;
  798.         testIntersection(sunRay, shell, sunDist);
  799.         // Compute the optical depth along path from sample point to the sun
  800.         OpticalDepths sunDepth = integrateOpticalDepth(scene, samplePoint, sunRay.point(sunDist));
  801.         // Compute the optical depth along the path from the sample point to the eye
  802.         OpticalDepths eyeDepth = integrateOpticalDepth(scene, samplePoint, atmStart);
  803.         // Sum the optical depths to get the depth on the complete path from sun
  804.         // to sample point to eye.
  805.         OpticalDepths totalDepth = sumOpticalDepths(sunDepth, eyeDepth);
  806.         totalDepth.rayleigh *= 4.0 * PI;
  807.         totalDepth.mie      *= 4.0 * PI;
  808.         Vec3d extinction = scene.atmosphere.computeExtinction(totalDepth);
  809.         double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
  810.         // Add the inscattered light from Rayleigh and Mie scattering particles
  811.         rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
  812.         mieScatter +=      scene.atmosphere.mieDensity(h)      * stepDist * extinction;
  813.         
  814.         samplePoint += stepDist * dir;
  815.     }
  816.     return Vec4d(rayleighScatter.x,
  817.                  rayleighScatter.y,
  818.                  rayleighScatter.z,
  819.                  0.0);
  820. }
  821. /**** Lookup table acceleration of scattering ****/
  822. // Pack a signed value in [-1, 1] into [0, 1]
  823. double packSNorm(double sn)
  824. {
  825.     return (sn + 1.0) * 0.5;
  826. }
  827. // Expand an unsigned value in [0, 1] into [-1, 1]
  828. double unpackSNorm(double un)
  829. {
  830.     return un * 2.0 - 1.0;
  831. }
  832. LUT2*
  833. buildExtinctionLUT(const Scene& scene)
  834. {
  835.     LUT2* lut = new LUT2(ExtinctionLUTHeightSteps,
  836.                          ExtinctionLUTViewAngleSteps);
  837.     Sphered planet = Sphered(scene.planet.radius);
  838.     Sphered shell = Sphered(scene.planet.radius + scene.atmosphereShellHeight);
  839.     for (unsigned int i = 0; i < ExtinctionLUTHeightSteps; i++)
  840.     {
  841.         double h = (double) i / (double) (ExtinctionLUTHeightSteps - 1) *
  842.             scene.atmosphereShellHeight * 0.9999;
  843.         Point3d atmStart = Point3d(0.0, 0.0, 0.0) +
  844.             Vec3d(1.0, 0.0, 0.0) * (h + scene.planet.radius);
  845.         for (unsigned int j = 0; j < ExtinctionLUTViewAngleSteps; j++)
  846.         {
  847.             double cosAngle = (double) j / (ExtinctionLUTViewAngleSteps - 1) * 2.0 - 1.0;
  848.             double sinAngle = sqrt(1.0 - min(1.0, cosAngle * cosAngle));
  849.             Vec3d viewDir(cosAngle, sinAngle, 0.0);
  850.             
  851.             Ray3d ray(atmStart, viewDir);
  852.             double dist = 0.0;
  853.             if (!testIntersection(ray, shell, dist))
  854.                 dist = 0.0;
  855.             OpticalDepths depth = integrateOpticalDepth(scene, atmStart,
  856.                                                         ray.point(dist));
  857.             depth.rayleigh *= 4.0 * PI;
  858.             depth.mie      *= 4.0 * PI;
  859.             Vec3d ext = scene.atmosphere.computeExtinction(depth);
  860.             ext.x = max(ext.x, 1.0e-18);
  861.             ext.y = max(ext.y, 1.0e-18);
  862.             ext.z = max(ext.z, 1.0e-18);
  863.             lut->setValue(i, j, ext);
  864.         }
  865.     }
  866.     return lut;
  867. }
  868. Vec3d
  869. lookupExtinction(const Scene& scene,
  870.                  const Point3d& atmStart,
  871.                  const Point3d& atmEnd)
  872. {
  873.     Vec3d viewDir = atmEnd - atmStart;
  874.     Vec3d toCenter = atmStart - Point3d(0.0, 0.0, 0.0);
  875.     viewDir.normalize();
  876.     toCenter.normalize();
  877.     double h = (atmStart.distanceFromOrigin() - scene.planet.radius) /
  878.         scene.atmosphereShellHeight;
  879.     double cosViewAngle = viewDir * toCenter;
  880.     return scene.extinctionLUT->lookup(h, packSNorm(cosViewAngle));
  881. }
  882. LUT2*
  883. buildOpticalDepthLUT(const Scene& scene)
  884. {
  885.     LUT2* lut = new LUT2(ExtinctionLUTHeightSteps,
  886.                          ExtinctionLUTViewAngleSteps);
  887.     Sphered planet = Sphered(scene.planet.radius);
  888.     Sphered shell = Sphered(scene.planet.radius + scene.atmosphereShellHeight);
  889.     for (unsigned int i = 0; i < ExtinctionLUTHeightSteps; i++)
  890.     {
  891.         double h = (double) i / (double) (ExtinctionLUTHeightSteps - 1) *
  892.             scene.atmosphereShellHeight;
  893.         Point3d atmStart = Point3d(0.0, 0.0, 0.0) +
  894.             Vec3d(1.0, 0.0, 0.0) * (h + scene.planet.radius);
  895.         for (unsigned int j = 0; j < ExtinctionLUTViewAngleSteps; j++)
  896.         {
  897.             double cosAngle = (double) j / (ExtinctionLUTViewAngleSteps - 1) * 2.0 - 1.0;
  898.             double sinAngle = sqrt(1.0 - min(1.0, cosAngle * cosAngle));
  899.             Vec3d dir(cosAngle, sinAngle, 0.0);
  900.             
  901.             Ray3d ray(atmStart, dir);
  902.             double dist = 0.0;
  903.             if (!testIntersection(ray, shell, dist))
  904.                 dist = 0.0;
  905.             OpticalDepths depth = integrateOpticalDepth(scene, atmStart,
  906.                                                         ray.point(dist));
  907.             depth.rayleigh *= 4.0 * PI;
  908.             depth.mie      *= 4.0 * PI;
  909.             lut->setValue(i, j, Vec3d(depth.rayleigh, depth.mie, depth.absorption));
  910.         }
  911.     }
  912.     return lut;
  913. }
  914. OpticalDepths
  915. lookupOpticalDepth(const Scene& scene,
  916.                    const Point3d& atmStart,
  917.                    const Point3d& atmEnd)
  918. {
  919.     Vec3d dir = atmEnd - atmStart;
  920.     Vec3d toCenter = atmStart - Point3d(0.0, 0.0, 0.0);
  921.     dir.normalize();
  922.     toCenter.normalize();
  923.     double h = (atmStart.distanceFromOrigin() - scene.planet.radius) /
  924.         scene.atmosphereShellHeight;
  925.     double cosViewAngle = dir * toCenter;
  926.     Vec3d v = scene.extinctionLUT->lookup(h, (cosViewAngle + 1.0) * 0.5);
  927.     OpticalDepths depth;
  928.     depth.rayleigh = v.x;
  929.     depth.mie = v.y;
  930.     depth.absorption = v.z;
  931.     return depth;
  932. }
  933. Vec3d integrateInscattering_LUT(const Scene& scene,
  934.                                 const Point3d& atmStart,
  935.                                 const Point3d& atmEnd,
  936.                                 const Point3d& eyePt,
  937.                                 bool hitPlanet)
  938. {
  939.     const unsigned int nSteps = IntegrateScatterSteps;
  940.     double shellHeight = scene.planet.radius + scene.atmosphereShellHeight;
  941.     Sphered shell = Sphered(shellHeight);
  942.     bool eyeInsideAtmosphere = eyePt.distanceFromOrigin() < shellHeight;
  943.     Vec3d lightDir = -scene.light.direction;
  944.     Point3d origin = eyeInsideAtmosphere ? eyePt : atmStart;
  945.     Vec3d viewDir = atmEnd - origin;
  946.     double stepDist = viewDir.length() / (double) nSteps;
  947.     viewDir.normalize();
  948.     // Start at the midpoint of the first interval
  949.     Point3d samplePoint = origin + 0.5 * stepDist * viewDir;
  950.     Vec3d rayleighScatter(0.0, 0.0, 0.0);
  951.     Vec3d mieScatter(0.0, 0.0, 0.0);
  952.     for (unsigned int i = 0; i < nSteps; i++)
  953.     {
  954.         Ray3d sunRay(samplePoint, lightDir);
  955.         double sunDist = 0.0;
  956.         testIntersection(sunRay, shell, sunDist);
  957.         Vec3d sunExt = lookupExtinction(scene, samplePoint, sunRay.point(sunDist));
  958.         Vec3d eyeExt;
  959.         if (!eyeInsideAtmosphere)
  960.         {
  961.             eyeExt = lookupExtinction(scene, samplePoint, atmStart);
  962.         }
  963.         else
  964.         {
  965.             // Eye is inside the atmosphere, so we need to subtract
  966.             // extinction from the part of the light path not traveled.
  967.             // Do this carefully! We want to avoid doing arithmetic with
  968.             // intervals that pass through the planet, since they tend to
  969.             // have values extremely close to zero.
  970.             Vec3d subExt;
  971.             if (hitPlanet)
  972.             {
  973.                 eyeExt = lookupExtinction(scene, samplePoint, atmStart);
  974.                 subExt = lookupExtinction(scene, eyePt, atmStart);
  975.             }
  976.             else
  977.             {
  978.                 eyeExt = lookupExtinction(scene, eyePt, atmEnd);
  979.                 subExt = lookupExtinction(scene, samplePoint, atmEnd);
  980.             }
  981.             // Subtract the extinction from the untraversed portion of the
  982.             // light path.
  983.             eyeExt = Vec3d(eyeExt.x / subExt.x,
  984.                            eyeExt.y / subExt.y,
  985.                            eyeExt.z / subExt.z);
  986.         }
  987.         // Compute the extinction along the entire light path from sun to sample point
  988.         // to eye.
  989.         Vec3d extinction(sunExt.x * eyeExt.x, sunExt.y * eyeExt.y, sunExt.z * eyeExt.z);
  990.         double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
  991.         // Add the inscattered light from Rayleigh and Mie scattering particles
  992.         rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
  993.         mieScatter      += scene.atmosphere.mieDensity(h)      * stepDist * extinction;
  994.         
  995.         samplePoint += stepDist * viewDir;
  996.     }
  997.     double cosSunAngle = lightDir * viewDir;
  998.     double miePhase = scene.atmosphere.miePhase(cosSunAngle);
  999.     const Vec3d& rayleigh = scene.atmosphere.rayleighCoeff;
  1000.     return phaseRayleigh(cosSunAngle) * Vec3d(rayleighScatter.x * rayleigh.x,
  1001.                                               rayleighScatter.y * rayleigh.y,
  1002.                                               rayleighScatter.z * rayleigh.z) +
  1003.         miePhase * mieScatter * scene.atmosphere.mieCoeff;
  1004. }
  1005. // Used for building LUT; start point is assumed to be within
  1006. // atmosphere.
  1007. Vec4d integrateInscatteringFactors_LUT(const Scene& scene,
  1008.                                        const Point3d& atmStart,
  1009.                                        const Point3d& atmEnd,
  1010.                                        const Vec3d& lightDir,
  1011.                                        bool planetHit)
  1012. {
  1013.     const unsigned int nSteps = IntegrateScatterSteps;
  1014.     double shellHeight = scene.planet.radius + scene.atmosphereShellHeight;
  1015.     Sphered shell = Sphered(shellHeight);
  1016.     Point3d origin = atmStart;
  1017.     Vec3d viewDir = atmEnd - origin;
  1018.     double stepDist = viewDir.length() / (double) nSteps;
  1019.     viewDir.normalize();
  1020.     // Start at the midpoint of the first interval
  1021.     Point3d samplePoint = origin + 0.5 * stepDist * viewDir;
  1022.     Vec3d rayleighScatter(0.0, 0.0, 0.0);
  1023.     Vec3d mieScatter(0.0, 0.0, 0.0);
  1024.     for (unsigned int i = 0; i < nSteps; i++)
  1025.     {
  1026.         Ray3d sunRay(samplePoint, lightDir);
  1027.         double sunDist = 0.0;
  1028.         testIntersection(sunRay, shell, sunDist);
  1029.         Vec3d sunExt = lookupExtinction(scene, samplePoint, sunRay.point(sunDist));
  1030.         Vec3d eyeExt;
  1031.         Vec3d subExt;
  1032.         if (planetHit)
  1033.         {
  1034.             eyeExt = lookupExtinction(scene, samplePoint, atmEnd);
  1035.             subExt = lookupExtinction(scene, atmEnd, atmStart);
  1036.         }
  1037.         else
  1038.         {
  1039.             eyeExt = lookupExtinction(scene, atmStart, atmEnd);
  1040.             subExt = lookupExtinction(scene, samplePoint, atmEnd);
  1041.         }
  1042.         // Subtract the extinction from the untraversed portion of the
  1043.         // light path.
  1044.         eyeExt = Vec3d(eyeExt.x / subExt.x,
  1045.                        eyeExt.y / subExt.y,
  1046.                        eyeExt.z / subExt.z);
  1047.         //Vec3d eyeExt = lookupExtinction(scene, samplePoint, atmStart);
  1048.         // Compute the extinction along the entire light path from sun to sample point
  1049.         // to eye.
  1050.         Vec3d extinction(sunExt.x * eyeExt.x, sunExt.y * eyeExt.y, sunExt.z * eyeExt.z);
  1051.         double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
  1052.         // Add the inscattered light from Rayleigh and Mie scattering particles
  1053.         rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
  1054.         mieScatter      += scene.atmosphere.mieDensity(h) * stepDist * extinction;
  1055.         
  1056.         samplePoint += stepDist * viewDir;
  1057.     }
  1058.     return Vec4d(rayleighScatter.x,
  1059.                  rayleighScatter.y,
  1060.                  rayleighScatter.z,
  1061.                  0.0);
  1062. }
  1063. LUT3*
  1064. buildScatteringLUT(const Scene& scene)
  1065. {
  1066.     LUT3* lut = new LUT3(ScatteringLUTHeightSteps,
  1067.                          ScatteringLUTViewAngleSteps,
  1068.                          ScatteringLUTLightAngleSteps);
  1069.     Sphered shell = Sphered(scene.planet.radius + scene.atmosphereShellHeight);
  1070.     for (unsigned int i = 0; i < ScatteringLUTHeightSteps; i++)
  1071.     {
  1072.         double h = (double) i / (double) (ScatteringLUTHeightSteps - 1) *
  1073.             scene.atmosphereShellHeight * 0.9999;
  1074.         Point3d atmStart = Point3d(0.0, 0.0, 0.0) +
  1075.             Vec3d(1.0, 0.0, 0.0) * (h + scene.planet.radius);
  1076.         for (unsigned int j = 0; j < ScatteringLUTViewAngleSteps; j++)
  1077.         {
  1078.             double cosAngle = unpackSNorm((double) j / (ScatteringLUTViewAngleSteps - 1));
  1079.             double sinAngle = sqrt(1.0 - min(1.0, cosAngle * cosAngle));
  1080.             Vec3d viewDir(cosAngle, sinAngle, 0.0);
  1081.             Ray3d viewRay(atmStart, viewDir);
  1082.             double dist = 0.0;
  1083.             if (!testIntersection(viewRay, shell, dist))
  1084.                 dist = 0.0;
  1085.             Point3d atmEnd = viewRay.point(dist);
  1086.             for (unsigned int k = 0; k < ScatteringLUTLightAngleSteps; k++)
  1087.             {
  1088.                 double cosLightAngle = unpackSNorm((double) k / (ScatteringLUTLightAngleSteps - 1));
  1089.                 double sinLightAngle = sqrt(1.0 - min(1.0, cosLightAngle * cosLightAngle));
  1090.                 Vec3d lightDir(cosLightAngle, sinLightAngle, 0.0);
  1091. #if 0                
  1092.                 Vec4d inscatter = integrateInscatteringFactors_LUT(scene,
  1093.                                                                    atmStart,
  1094.                                                                    atmEnd,
  1095.                                                                    lightDir,
  1096.                                                                    true);
  1097. #else
  1098.                 Vec4d inscatter = integrateInscatteringFactors(scene,
  1099.                                                                atmStart,
  1100.                                                                atmEnd,
  1101.                                                                lightDir);
  1102. #endif
  1103.                 lut->setValue(i, j, k, inscatter);
  1104.             }
  1105.         }
  1106.     }
  1107.     return lut;
  1108. }
  1109. Vec3d
  1110. lookupScattering(const Scene& scene,
  1111.                  const Point3d& atmStart,
  1112.                  const Point3d& atmEnd,
  1113.                  const Vec3d& lightDir)
  1114. {
  1115.     Vec3d viewDir = atmEnd - atmStart;
  1116.     Vec3d toCenter = atmStart - Point3d(0.0, 0.0, 0.0);
  1117.     viewDir.normalize();
  1118.     toCenter.normalize();
  1119.     double h = (atmStart.distanceFromOrigin() - scene.planet.radius) /
  1120.         scene.atmosphereShellHeight;
  1121.     double cosViewAngle = viewDir * toCenter;
  1122.     double cosLightAngle = lightDir * toCenter;
  1123.     Vec4d v = scene.scatteringLUT->lookup(h,
  1124.                                           packSNorm(cosViewAngle),
  1125.                                           packSNorm(cosLightAngle));
  1126.     return Vec3d(v.x, v.y, v.z);
  1127. }
  1128. Color getPlanetColor(const Scene& scene, const Point3d& p)
  1129. {
  1130.     Vec3d n = p - Point3d(0.0, 0.0, 0.0);
  1131.     n.normalize();
  1132.     // Give the planet a checkerboard texture
  1133.     double phi = atan2(n.z, n.x);
  1134.     double theta = asin(n.y);
  1135.     int tx = (int) (8 + 8 * phi / PI);
  1136.     int ty = (int) (8 + 8 * theta / PI);
  1137.     return ((tx ^ ty) & 0x1) ? scene.planetColor : scene.planetColor2;
  1138. }
  1139. Color Scene::raytrace(const Ray3d& ray) const
  1140. {
  1141.     double dist = 0.0;
  1142.     double atmEnter = 0.0;
  1143.     double atmExit = 0.0;
  1144.     double shellRadius = planet.radius + atmosphereShellHeight;
  1145.     Color color = background;
  1146.     if (ray.direction * -light.direction > cos(sunAngularDiameter / 2.0))
  1147.         color = light.color;
  1148.     if (raySphereIntersect(ray,
  1149.                            Sphered(planet.center, shellRadius),
  1150.                            atmEnter,
  1151.                            atmExit))
  1152.     {
  1153.         Color baseColor = color;
  1154.         Point3d atmStart = ray.origin + atmEnter * ray.direction;
  1155.         Point3d atmEnd = ray.origin + atmExit * ray.direction;
  1156.         if (testIntersection(ray, planet, dist))
  1157.         {
  1158.             Point3d intersectPoint = ray.point(dist);
  1159.             Vec3d normal = intersectPoint - planet.center;
  1160.             normal.normalize();
  1161.             Vec3d lightDir = -light.direction;
  1162.             double diffuse = max(0.0, normal * lightDir);
  1163.             
  1164.             Point3d surfacePt = Point3d(0.0, 0.0, 0.0) + (intersectPoint - planet.center);
  1165.             Color planetColor = getPlanetColor(*this, surfacePt);
  1166.             Sphered shell = Sphered(shellRadius);
  1167.             // Compute ray from surface point to edge of the atmosphere in the direction
  1168.             // of the sun.
  1169.             Ray3d sunRay(surfacePt, lightDir);
  1170.             double sunDist = 0.0;
  1171.             testIntersection(sunRay, shell, sunDist);
  1172.             // Compute color of sunlight filtered by the atmosphere; consider extinction
  1173.             // along both the sun-to-surface and surface-to-eye paths.
  1174.             OpticalDepths sunDepth = integrateOpticalDepth(*this, surfacePt, sunRay.point(sunDist));
  1175.             OpticalDepths eyeDepth = integrateOpticalDepth(*this, atmStart, surfacePt);
  1176.             OpticalDepths totalDepth = sumOpticalDepths(sunDepth, eyeDepth);
  1177.             totalDepth.rayleigh *= 4.0 * PI;
  1178.             totalDepth.mie      *= 4.0 * PI;
  1179.             Vec3d extinction = atmosphere.computeExtinction(totalDepth);
  1180.             // Reflected color of planet surface is:
  1181.             //   surface color * sun color * atmospheric extinction
  1182.             baseColor = (planetColor * extinction) * light.color * diffuse;
  1183.             
  1184.             atmEnd = ray.origin + dist * ray.direction;
  1185.         }
  1186.         Vec3d inscatter = integrateInscattering(*this, atmStart, atmEnd) * 4.0 * PI;
  1187.         return Color((float) inscatter.x, (float) inscatter.y, (float) inscatter.z) +
  1188.             baseColor;
  1189.     }
  1190.     else
  1191.     {
  1192.         return color;
  1193.     }
  1194. }
  1195. Color
  1196. Scene::raytrace_LUT(const Ray3d& ray) const
  1197. {
  1198.     double atmEnter = 0.0;
  1199.     double atmExit = 0.0;
  1200.     double  shellRadius = planet.radius + atmosphereShellHeight;
  1201.     Sphered shell(shellRadius);
  1202.     Point3d eyePt = Point3d(0.0, 0.0, 0.0) + (ray.origin - planet.center);
  1203.     Color color = background;
  1204.     if (ray.direction * -light.direction > cos(sunAngularDiameter / 2.0))
  1205.         color = light.color;
  1206.     // Transform ray to model space
  1207.     Ray3d mray(eyePt, ray.direction);
  1208.     bool hit = raySphereIntersect2(mray, shell, atmEnter, atmExit);
  1209.     if (hit && atmExit > 0.0)
  1210.     {
  1211.         Color baseColor = color;
  1212.         bool eyeInsideAtmosphere = atmEnter < 0.0;
  1213.         Point3d atmStart = mray.origin + atmEnter * mray.direction;
  1214.         Point3d atmEnd = mray.origin + atmExit * mray.direction;
  1215.         double planetEnter = 0.0;
  1216.         double planetExit = 0.0;
  1217.         hit = raySphereIntersect2(mray,
  1218.                                  Sphered(planet.radius),
  1219.                                  planetEnter,
  1220.                                  planetExit);
  1221.         if (hit && planetEnter > 0.0)
  1222.         {
  1223.             Point3d surfacePt = mray.point(planetEnter);
  1224.             // Lambert lighting
  1225.             Vec3d normal = surfacePt - Point3d(0, 0, 0);
  1226.             normal.normalize();
  1227.             Vec3d lightDir = -light.direction;
  1228.             double diffuse = max(0.0, normal * lightDir);
  1229.             Color planetColor = getPlanetColor(*this, surfacePt);
  1230.             // Compute ray from surface point to edge of the atmosphere in the direction
  1231.             // of the sun.
  1232.             Ray3d sunRay(surfacePt, lightDir);
  1233.             double sunDist = 0.0;
  1234.             testIntersection(sunRay, shell, sunDist);
  1235.             
  1236.             // Compute color of sunlight filtered by the atmosphere; consider extinction
  1237.             // along both the sun-to-surface and surface-to-eye paths.
  1238.             Vec3d sunExt = lookupExtinction(*this, surfacePt, sunRay.point(sunDist));
  1239.             Vec3d eyeExt = lookupExtinction(*this, surfacePt, atmStart);
  1240.             if (eyeInsideAtmosphere)
  1241.             {
  1242.                 Vec3d oppExt = lookupExtinction(*this, eyePt, atmStart);
  1243.                 eyeExt = Vec3d(eyeExt.x / oppExt.x,
  1244.                                eyeExt.y / oppExt.y,
  1245.                                eyeExt.z / oppExt.z);
  1246.             }
  1247.             Vec3d extinction(sunExt.x * eyeExt.x,
  1248.                              sunExt.y * eyeExt.y,
  1249.                              sunExt.z * eyeExt.z);
  1250.             // Reflected color of planet surface is:
  1251.             //   surface color * sun color * atmospheric extinction
  1252.             baseColor = (planetColor * extinction) * light.color * diffuse;
  1253.             atmEnd = mray.point(planetEnter);
  1254.         }
  1255.         Vec3d inscatter;
  1256.         if (LUTUsage == UseExtinctionLUT)
  1257.         {
  1258.             bool hitPlanet = hit && planetEnter > 0.0;
  1259.             inscatter = integrateInscattering_LUT(*this, atmStart, atmEnd, eyePt, hitPlanet) * 4.0 * PI;
  1260.             //if (!hit)
  1261.             //inscatter = Vec3d(0.0, 0.0, 0.0);
  1262.         }
  1263.         else if (LUTUsage == UseScatteringLUT)
  1264.         {
  1265.             Vec3d rayleighScatter;
  1266.             if (eyeInsideAtmosphere)
  1267.             {
  1268. #if 1
  1269.                 if (!hit || planetEnter < 0.0)
  1270.                 {
  1271.                     rayleighScatter = 
  1272.                         lookupScattering(*this, eyePt, atmEnd, -light.direction);
  1273.                 }
  1274.                 else
  1275.                 {
  1276.                     atmEnd = atmStart;
  1277.                     atmStart = mray.point(planetEnter);
  1278.                     //cout << atmEnter << ", " << planetEnter << ", " << atmExit << "n";
  1279.                     rayleighScatter = 
  1280.                         lookupScattering(*this, atmStart, atmEnd, -light.direction) -
  1281.                         lookupScattering(*this, eyePt, atmEnd, -light.direction);
  1282.                     //cout << rayleighScatter.y << endl;
  1283.                     //cout << (atmStart - atmEnd).length() - (eyePt - atmEnd).length()
  1284.                     //<< ", " << rayleighScatter.y
  1285.                     //<< endl;
  1286.                     //rayleighScatter = Vec3d(0.0, 0.0, 0.0);
  1287.                 }
  1288. #else
  1289.                 //rayleighScatter = lookupScattering(*this, atmEnd, atmStart, -light.direction);
  1290. #endif
  1291.             }
  1292.             else
  1293.             {
  1294.                 rayleighScatter = lookupScattering(*this, atmEnd, atmStart, -light.direction);
  1295.             }
  1296.             const Vec3d& rayleigh = atmosphere.rayleighCoeff;
  1297.             double cosSunAngle = mray.direction * -light.direction;
  1298.             inscatter = phaseRayleigh(cosSunAngle) *
  1299.                 Vec3d(rayleighScatter.x * rayleigh.x,
  1300.                       rayleighScatter.y * rayleigh.y,
  1301.                       rayleighScatter.z * rayleigh.z);
  1302.             inscatter = inscatter * 4.0 * PI;
  1303.         }
  1304.         return Color((float) inscatter.x, (float) inscatter.y, (float) inscatter.z) +
  1305.             baseColor;
  1306.     }
  1307.     else
  1308.     {
  1309.         return color;
  1310.     }
  1311. }
  1312. void render(const Scene& scene,
  1313.             const Camera& camera,
  1314.             const Viewport& viewport,
  1315.             RGBImage& image)
  1316. {
  1317.     double aspectRatio = (double) image.width / (double) image.height;
  1318.     if (viewport.x >= image.width || viewport.y >= image.height)
  1319.         return;
  1320.     unsigned int right = min(image.width, viewport.x + viewport.width);
  1321.     unsigned int bottom = min(image.height, viewport.y + viewport.height);
  1322.     cout << "Rendering " << viewport.width << "x" << viewport.height << " view" << endl;
  1323.     for (unsigned int i = viewport.y; i < bottom; i++)
  1324.     {
  1325.         unsigned int row = i - viewport.y;
  1326.         if (row % 50 == 49)
  1327.             cout << row + 1 << endl;
  1328.         else if (row % 10 == 0)
  1329.             cout << ".";
  1330.         for (unsigned int j = viewport.x; j < right; j++)
  1331.         {
  1332.             double viewportX = ((double) (j - viewport.x) / (double) (viewport.width - 1) - 0.5) * aspectRatio;
  1333.             double viewportY ((double) (i - viewport.y) / (double) (viewport.height - 1) - 0.5);
  1334.             Ray3d viewRay = camera.getViewRay(viewportX, viewportY);
  1335.             Color color;
  1336.             if (LUTUsage != NoLUT)
  1337.                 color = scene.raytrace_LUT(viewRay);
  1338.             else
  1339.                 color = scene.raytrace(viewRay);
  1340.             if (CameraExposure != 0.0)
  1341.                 color = color.exposure((float) CameraExposure);
  1342.             image.setPixel(j, i, color);
  1343.         }
  1344.     }
  1345.     cout << endl << "Complete" << endl;
  1346. }
  1347. Vec3d computeRayleighCoeffs(Vec3d wavelengths)
  1348. {
  1349.     return Vec3d(pow(wavelengths.x, -4.0),
  1350.                  pow(wavelengths.y, -4.0),
  1351.                  pow(wavelengths.z, -4.0));
  1352. }
  1353. void Scene::setParameters(ParameterSet& params)
  1354. {
  1355.     atmosphere.rayleighScaleHeight = params["RayleighScaleHeight"];
  1356.     atmosphere.rayleighCoeff.x     = params["RayleighRed"];
  1357.     atmosphere.rayleighCoeff.y     = params["RayleighGreen"];
  1358.     atmosphere.rayleighCoeff.z     = params["RayleighBlue"];
  1359.     atmosphere.mieScaleHeight      = params["MieScaleHeight"];
  1360.     atmosphere.mieCoeff            = params["Mie"];
  1361.     double phaseFunc = params["MiePhaseFunction"];
  1362.     if (phaseFunc == 0)
  1363.     {
  1364.         double mu                      = params["MieAsymmetry"];
  1365. atmosphere.mieAsymmetry        = mu2g(mu);
  1366.         atmosphere.miePhaseFunction    = phaseHenyeyGreenstein_CS;
  1367.     }
  1368.     else if (phaseFunc == 1)
  1369.     {
  1370.         atmosphere.mieAsymmetry        = params["MieAsymmetry"];
  1371.         atmosphere.miePhaseFunction    = phaseHenyeyGreenstein;
  1372.     }
  1373.     else if (phaseFunc == 2)
  1374.     {
  1375.         double k                       = params["MieAsymmetry"];
  1376.         atmosphere.mieAsymmetry        = schlick_g2k(k);
  1377.         atmosphere.miePhaseFunction    = phaseSchlick;
  1378.     }
  1379.     
  1380.     atmosphere.absorbScaleHeight   = params["AbsorbScaleHeight"];
  1381.     atmosphere.absorbCoeff.x       = params["AbsorbRed"];
  1382.     atmosphere.absorbCoeff.y       = params["AbsorbGreen"];
  1383.     atmosphere.absorbCoeff.z       = params["AbsorbBlue"];
  1384.     atmosphereShellHeight          = atmosphere.calcShellHeight();
  1385.     sunAngularDiameter             = degToRad(params["SunAngularDiameter"]);
  1386.     planet.radius                  = params["Radius"];
  1387.     planet.center                  = Point3d(0.0, 0.0, 0.0);
  1388.     planetColor.r                  = (float) params["SurfaceRed"];
  1389.     planetColor.g                  = (float) params["SurfaceGreen"];
  1390.     planetColor.b                  = (float) params["SurfaceBlue"];
  1391.     planetColor2 = planetColor + Color(0.15f, 0.15f, 0.15f);
  1392. }
  1393. void setSceneDefaults(ParameterSet& params)
  1394. {
  1395.     params["RayleighScaleHeight"] = 79.94;
  1396.     params["RayleighRed"]         = 0.0;
  1397.     params["RayleighGreen"]       = 0.0;
  1398.     params["RayleighBlue"]        = 0.0;
  1399.     
  1400.     params["MieScaleHeight"]      = 1.2;
  1401.     params["Mie"]                 = 0.0;
  1402.     params["MieAsymmetry"]        = 0.0;
  1403.     params["AbsorbScaleHeight"]   = 7.994;
  1404.     params["AbsorbRed"]           = 0.0;
  1405.     params["AbsorbGreen"]         = 0.0;
  1406.     params["AbsorbBlue"]          = 0.0;
  1407.     params["Radius"]              = 6378.0;
  1408.     params["SurfaceRed"]          = 0.2;
  1409.     params["SurfaceGreen"]        = 0.3;
  1410.     params["SurfaceBlue"]         = 1.0;
  1411.     params["SunAngularDiameter"]  = 0.5; // degrees
  1412.     params["MiePhaseFunction"]       = 0;
  1413. }
  1414. bool LoadParameterSet(ParameterSet& params, const string& filename)
  1415. {
  1416.     ifstream in(filename.c_str());
  1417.     if (!in.good())
  1418.     {
  1419.         cerr << "Error opening config file " << filename << endl;
  1420.         return false;
  1421.     }
  1422.     while (in.good())
  1423.     {
  1424.         string name;
  1425.         in >> name;
  1426.         if (name == "MiePhaseFunction")
  1427.         {
  1428.             string strValue;
  1429.             in >> strValue;
  1430.             if (strValue == "HenyeyGreenstein_CS")
  1431.             {
  1432.                 params[name] = 0;
  1433.             }
  1434.             else if (strValue == "HenyeyGreenstein")
  1435.             {
  1436.                 params[name] = 1;
  1437.             }
  1438.             else if (strValue == "Schlick")
  1439.             {
  1440.                 params[name] = 2;
  1441.             }
  1442.         }
  1443.         else
  1444.         {
  1445.             double numValue;
  1446.             in >> numValue;
  1447.             if (in.good())
  1448.             {
  1449.                 params[name] = numValue;
  1450.             }
  1451.         }
  1452.     }
  1453.     if (in.bad())
  1454.     {
  1455.         cerr << "Error in scene config file " << filename << endl;
  1456.         return false;
  1457.     }
  1458.     else
  1459.     {
  1460.         return true;
  1461.     }
  1462. }
  1463. template<class T> static Matrix4<T> 
  1464. lookAt(Point3<T> from, Point3<T> to, Vector3<T> up)
  1465. {
  1466.     Vector3<T> n = to - from;
  1467.     n.normalize();
  1468.     Vector3<T> v = n ^ up;
  1469.     v.normalize();
  1470.     Vector3<T> u = v ^ n;
  1471.     Matrix4<T> rot(Vector4<T>(v.x, v.y, v.z, 0.0),
  1472.                    Vector4<T>(u.x, u.y, u.z, 0.0),
  1473.                    -Vector4<T>(n.x, n.y, n.z, 0.0),
  1474.                    Vector4<T>(0.0, 0.0, 0.0, 1.0));
  1475.     return Matrix4<T>::translation(from) * rot;
  1476. }
  1477. Camera lookAt(Point3d cameraPos,
  1478.               Point3d targetPos,
  1479.               Vec3d up,
  1480.               double fov)
  1481. {
  1482.     Camera camera;
  1483.     camera.fov = degToRad(fov);
  1484.     camera.front = 1.0;
  1485.     camera.transform = lookAt(cameraPos, targetPos, up);
  1486.     return camera;
  1487. }
  1488. string configFilename;
  1489. string outputImageName("out.png");
  1490. bool parseCommandLine(int argc, char* argv[])
  1491. {
  1492.     int i = 1;
  1493.     int fileCount = 0;
  1494.     while (i < argc)
  1495.     {
  1496.         if (argv[i][0] == '-')
  1497.         {
  1498.             if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--lut"))
  1499.             {
  1500.                 LUTUsage = UseExtinctionLUT;
  1501.             }
  1502.             else if (!strcmp(argv[i], "-L") || !strcmp(argv[i], "--LUT"))
  1503.             {
  1504.                 LUTUsage = UseScatteringLUT;
  1505.             }
  1506.             else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--fisheye"))
  1507.             {
  1508.                 UseFisheyeCameras = true;
  1509.             }
  1510.             else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--exposure"))
  1511.             {
  1512.                 if (i == argc - 1)
  1513.                 {
  1514.                     return false;
  1515.                 }
  1516.                 else
  1517.                 {
  1518.                     if (sscanf(argv[i + 1], " %lf", &CameraExposure) != 1)
  1519.                         return false;
  1520.                     i++;
  1521.                 }
  1522.             }
  1523.             else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--scattersteps"))
  1524.             {
  1525.                 if (i == argc - 1)
  1526.                 {
  1527.                     return false;
  1528.                 }
  1529.                 else
  1530.                 {
  1531.                     if (sscanf(argv[i + 1], " %u", &IntegrateScatterSteps) != 1)
  1532.                         return false;
  1533.                     i++;
  1534.                 }
  1535.             }
  1536.             else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--depthsteps"))
  1537.             {
  1538.                 if (i == argc - 1)
  1539.                 {
  1540.                     return false;
  1541.                 }
  1542.                 else
  1543.                 {
  1544.                     if (sscanf(argv[i + 1], " %u", &IntegrateDepthSteps) != 1)
  1545.                         return false;
  1546.                     i++;
  1547.                 }
  1548.             }
  1549.             else if (!strcmp(argv[i], "-w") || !strcmp(argv[i], "--width"))
  1550.             {
  1551.                 if (i == argc - 1)
  1552.                 {
  1553.                     return false;
  1554.                 }
  1555.                 else
  1556.                 {
  1557.                     if (sscanf(argv[i + 1], " %u", &OutputImageWidth) != 1)
  1558.                         return false;
  1559.                     i++;
  1560.                 }
  1561.             }
  1562.             else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--height"))
  1563.             {
  1564.                 if (i == argc - 1)
  1565.                 {
  1566.                     return false;
  1567.                 }
  1568.                 else
  1569.                 {
  1570.                     if (sscanf(argv[i + 1], " %u", &OutputImageHeight) != 1)
  1571.                         return false;
  1572.                     i++;
  1573.                 }
  1574.             }
  1575.             else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--image"))
  1576.             {
  1577.                 if (i == argc - 1)
  1578.                 {
  1579.                     return false;
  1580.                 }
  1581.                 else
  1582.                 {
  1583.                     outputImageName = string(argv[i + 1]);
  1584.                     i++;
  1585.                 }
  1586.             }
  1587.             else
  1588.             {
  1589.                 return false;
  1590.             }
  1591.             i++;
  1592.         }
  1593.         else
  1594.         {
  1595.             if (fileCount == 0)
  1596.             {
  1597.                 // input filename first
  1598.                 configFilename = string(argv[i]);
  1599.                 fileCount++;
  1600.             }
  1601.             else
  1602.             {
  1603.                 // more than one filenames on the command line is an error
  1604.                 return false;
  1605.             }
  1606.             i++;
  1607.         }
  1608.     }
  1609.     return true;
  1610. }
  1611. int main(int argc, char* argv[])
  1612. {
  1613.     bool commandLineOK = parseCommandLine(argc, argv);
  1614.     if (!commandLineOK || configFilename.empty())
  1615.     {
  1616.         usage();
  1617.         exit(1);
  1618.     }
  1619.     ParameterSet sceneParams;
  1620.     setSceneDefaults(sceneParams);
  1621.     if (!LoadParameterSet(sceneParams, configFilename))
  1622.     {
  1623.         exit(1);
  1624.     }
  1625.     Scene scene;
  1626.     scene.light.color = Color(1, 1, 1);
  1627.     scene.light.direction = Vec3d(0.0, 0.0, 1.0);
  1628.     scene.light.direction.normalize();
  1629. #if 0
  1630.     // N0 = 2.668e25  // m^-3
  1631.     scene.planet = Sphered(Point3d(0.0, 0.0, 0.0), planetRadius);
  1632.     scene.atmosphere.rayleighScaleHeight = 79.94;
  1633.     scene.atmosphere.mieScaleHeight = 1.2;
  1634.     scene.atmosphere.rayleighCoeff =
  1635.         computeRayleighCoeffs(Vec3d(0.600, 0.520, 0.450)) * 0.000008;
  1636.     scene.atmosphereShellHeight = scene.atmosphere.calcShellHeight();
  1637.     scene.atmosphere.rayleighCoeff =
  1638.         computeRayleighCoeffs(Vec3d(0.600, 0.520, 0.450)) * 0.000008;
  1639. #endif
  1640.     scene.setParameters(sceneParams);
  1641.     cout << "atmosphere height: " << scene.atmosphereShellHeight << "n";
  1642.     cout << "attenuation coeffs: " <<
  1643.         scene.atmosphere.rayleighCoeff.x * 4 * PI << ", " <<
  1644.         scene.atmosphere.rayleighCoeff.y * 4 * PI << ", " <<
  1645.         scene.atmosphere.rayleighCoeff.z * 4 * PI << endl;
  1646.     if (LUTUsage != NoLUT)
  1647.     {
  1648.         cout << "Building extinction LUT...n";
  1649.         scene.extinctionLUT = buildExtinctionLUT(scene);
  1650.         cout << "Complete!n";
  1651.         DumpLUT(*scene.extinctionLUT, "extlut.png");
  1652.     }
  1653.     if (LUTUsage == UseScatteringLUT)
  1654.     {
  1655.         cout << "Building scattering LUT...n";
  1656.         scene.scatteringLUT = buildScatteringLUT(scene);
  1657.         cout << "Complete!n";
  1658.         DumpLUT(*scene.scatteringLUT, "lut.png");
  1659.     }
  1660.     double planetRadius = scene.planet.radius;
  1661.     double cameraFarDist = planetRadius * 3;
  1662.     double cameraCloseDist = planetRadius * 1.2;
  1663.     Camera cameraLowPhase;
  1664.     cameraLowPhase.fov = degToRad(45.0);
  1665.     cameraLowPhase.front = 1.0;
  1666.     cameraLowPhase.transform =
  1667.         Mat4d::translation(Point3d(0.0, 0.0, -cameraFarDist)) *
  1668.         Mat4d::yrotation(degToRad(20.0));
  1669.     Camera cameraHighPhase;
  1670.     cameraHighPhase.fov = degToRad(45.0);
  1671.     cameraHighPhase.front = 1.0;
  1672.     cameraHighPhase.transform = 
  1673.         Mat4d::translation(Point3d(0.0, 0.0, -cameraFarDist)) *
  1674.         Mat4d::yrotation(degToRad(160.0));
  1675.     Camera cameraClose;
  1676.     cameraClose.fov = degToRad(45.0);
  1677.     cameraClose.front = 1.0;
  1678.     cameraClose.transform = 
  1679.         Mat4d::xrotation(degToRad(55.0)) *
  1680.         Mat4d::translation(Point3d(0.0, 0.0, -cameraCloseDist)) *
  1681.         Mat4d::yrotation(degToRad(50.0));
  1682.     Camera cameraSurface;
  1683.     cameraSurface.fov = degToRad(45.0);
  1684.     cameraSurface.front = 1.0;
  1685.     cameraSurface.transform = 
  1686.         Mat4d::xrotation(degToRad(85.0)) *
  1687.         Mat4d::translation(Point3d(0.0, 0.0, -planetRadius * 1.0002)) *
  1688.         Mat4d::yrotation(degToRad(20.0));
  1689.     double aspectRatio = (double) OutputImageWidth / (double) OutputImageHeight;
  1690.     // Make the horizontal FOV of the fisheye cameras 180 degrees
  1691.     double fisheyeFOV = degToRad(max(180.0, 180.0 / aspectRatio));
  1692.     Camera cameraFisheyeMidday;
  1693.     cameraFisheyeMidday.fov = fisheyeFOV;
  1694.     cameraFisheyeMidday.type = Camera::Spherical;
  1695.     cameraFisheyeMidday.transform = 
  1696.         Mat4d::xrotation(degToRad(85.0)) *
  1697.         Mat4d::translation(Point3d(0.0, 0.0, -planetRadius * 1.0002)) *
  1698.         Mat4d::yrotation(degToRad(20.0));
  1699.     Camera cameraFisheyeSunset;
  1700.     cameraFisheyeSunset.fov = degToRad(180.0);
  1701.     cameraFisheyeSunset.type = Camera::Spherical;
  1702.     cameraFisheyeSunset.transform = 
  1703.         Mat4d::zrotation(degToRad(-5.0)) *
  1704.         Mat4d::yrotation(degToRad(90.0)) *
  1705.         Mat4d::xrotation(degToRad(85.0)) *
  1706.         Mat4d::translation(Point3d(0.0, 0.0, -planetRadius * 1.0002)) *
  1707.         Mat4d::yrotation(degToRad(80.0));
  1708.     RGBImage image(OutputImageWidth, OutputImageHeight);
  1709.     Viewport topleft (0, 0, image.width / 2, image.height / 2);
  1710.     Viewport topright(image.width / 2, 0, image.width / 2, image.height / 2);
  1711.     Viewport botleft (0, image.height / 2, image.width / 2, image.height / 2);
  1712.     Viewport botright(image.width / 2, image.height / 2, image.width / 2, image.height / 2);
  1713.     Viewport tophalf (0, 0, image.width, image.height / 2);
  1714.     Viewport bothalf (0, image.height / 2, image.width, image.height / 2);
  1715.     image.clear(Color(0.1f, 0.1f, 1.0f));
  1716.     if (UseFisheyeCameras)
  1717.     {
  1718.         render(scene, cameraFisheyeMidday, tophalf, image);
  1719.         render(scene, cameraFisheyeSunset, bothalf, image);
  1720.     }
  1721.     else
  1722.     {
  1723.         render(scene, cameraLowPhase, topleft, image);
  1724.         render(scene, cameraHighPhase, topright, image);
  1725.         render(scene, cameraClose, botleft, image);
  1726.         render(scene, cameraSurface, botright, image);
  1727.     }
  1728.     WritePNG(outputImageName, image);
  1729.     exit(0);
  1730. }