Deobfuscating / deconstructing Andrew Kensler's postcard pathtracer. Based off Fabien Sanglard's work.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

561 lines
20 KiB

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <math.h>
  4. #include <string>
  5. #include <sstream>
  6. #include <ctime>
  7. #include <vector>
  8. #include <Urho3D/Urho3DAll.h>
  9. using namespace Urho3D;
  10. // ======================== =====================>>>
  11. // ======================== original pathtracer code
  12. float randomVal() { return (float) rand() / RAND_MAX; }
  13. // Box CSG equation. Returns minimum signed distance from space
  14. // carved by start vector and opposite corner vector end.
  15. class BoxTester {
  16. public:
  17. Vector3 start;
  18. Vector3 end;
  19. BoxTester(Vector3 b, Vector3 e) : start(b), end(e) {}
  20. float Test(Vector3& position)
  21. {
  22. Vector3 toLowerLeft = position - start;
  23. Vector3 toUpperRight = end - position;
  24. return -std::min( // <- minimum distance out of all, i.e. to closest wall of the box
  25. std::min( // <- minimum distance on x- OR y-axis
  26. std::min(toLowerLeft.x_, toUpperRight.x_), // <- minimum distance on x-axis
  27. std::min(toLowerLeft.y_, toUpperRight.y_) // <- minimum distance on y-axis
  28. ),
  29. std::min(toLowerLeft.z_, toUpperRight.z_) // <- minimum distance on z-axis
  30. );
  31. }
  32. };
  33. // Lower room (the room itself)
  34. BoxTester box_room(Vector3(-30, -.5, -30), Vector3(30, 18, 30));
  35. // Upper room (The space where the ceiling planks are)
  36. BoxTester box_roof(Vector3(-25, 17, -25), Vector3(25, 20, 25));
  37. // Ceiling "planks", or one of them. Actual tested position will be modulo'd along an axis.
  38. BoxTester box_planks(Vector3(1.5, 18.5, -25), Vector3(6.5, 20, 25));
  39. #define HIT_NONE 0
  40. #define HIT_LETTER 1
  41. #define HIT_WALL 2
  42. #define HIT_SUN 3
  43. class LetterLine {
  44. public:
  45. const Vector2 start;
  46. const Vector2 end;
  47. const Vector2 direction;
  48. const Vector2 normal;
  49. const float length;
  50. LetterLine(Vector2& b, Vector2& e) : start(b), end(e),
  51. direction(e - b),
  52. normal(direction.Normalized()),
  53. length(direction.Length())
  54. {}
  55. };
  56. std::vector<int> pixar_points = {
  57. -13, 0, -13, 8, // P stem
  58. -13, 4, -11, 4, // P top bar
  59. -13, 8, -11, 8, // P mid bar
  60. -7, 0, -5, 0, // I bottom bar
  61. -6, 0, -6, 8, // I stem
  62. -7, 8, -5, 8, // I top bar
  63. -3, 0, 1, 8, // X slash
  64. -3, 8, 1, 0, // X backslash
  65. 3, 0, 5, 8, // A slash
  66. 5, 8, 7, 0, // A backslash
  67. 4, 4, 6, 4, // A bar
  68. 9, 0, 9, 8, // R stem
  69. 9, 4, 11, 4, // R mid bar
  70. 9, 8, 11, 8, // R top bar
  71. 10, 4, 13, 0 // R leg
  72. };
  73. std::vector<LetterLine> letterlines = {};
  74. // Two curves (for P and R in PixaR) with hard-coded locations.
  75. // curve centers are between right ends of the bars.
  76. std::vector<Vector2> curve_centers = {
  77. Vector2(-11, 6),
  78. Vector2( 11, 6)
  79. };
  80. Vector3 camera_position = Vector3(-22, 5, 25);
  81. Vector3 lookat_position = Vector3(-3, 4, 0);
  82. Vector3 light_direction = Vector3(.6, .6, 1);
  83. void SetupPathtracer() {
  84. for (int i = 0; i+3 < pixar_points.size(); i += 4) { // letters.Length()
  85. //Vector3 begin = Vector3(letters.At(i) - 79, letters.At(i+1) - 79);
  86. //Vector3 end = Vector3(letters.At(i+2) - 79, letters.At(i+3) - 79);
  87. Vector2 b = Vector2(pixar_points[i ], pixar_points[i+1]);
  88. Vector2 e = Vector2(pixar_points[i+2], pixar_points[i+3]);
  89. LetterLine* l = new LetterLine(b, e);
  90. letterlines.push_back(*l);
  91. }
  92. }
  93. // Sample the world using Signed Distance Fields.
  94. float QueryDatabase(Vector3 position, int &hitType) {
  95. float distance = 1e9;
  96. Vector2 pos_in_z0(position.x_, position.y_); // Flattened position (z=0)
  97. for (LetterLine line : letterlines) {
  98. // factor to scale line.normal by to project position onto line
  99. // i.e. with this factor alone (line.start + line.normal * scale_factor)
  100. // gives us the closest point on an infinite line
  101. float scale_factor = Vector2(pos_in_z0 - line.start).DotProduct(line.normal);
  102. // But now we clamp the scaling within 0.0f <= scale_factor <= line length
  103. scale_factor = std::min(std::max(scale_factor, 0.0f), line.length);
  104. Vector2 letter_distance = pos_in_z0 - (line.start + line.normal * scale_factor);
  105. distance = std::min(distance, letter_distance.Length()); // compare distance.
  106. }
  107. for (Vector2 c : curve_centers) {
  108. Vector2 center_to_pos = pos_in_z0 - c;
  109. if(center_to_pos.x_ > 0) {
  110. float comparison_dist = std::abs(center_to_pos.Length() - 2.0f);
  111. distance = std::min(distance, comparison_dist);
  112. }
  113. }
  114. distance = powf(powf(distance, 8) + powf(position.z_, 8), .125) - .5;
  115. hitType = HIT_LETTER;
  116. Vector3 plank_test_pos(position);
  117. plank_test_pos.x_ = fmodf(fabsf(plank_test_pos.x_), 8.0f);
  118. float roomDist = std::min(// min(A,B) = Union with Constructive solid geometry
  119. //-min carves an empty space
  120. -std::min(
  121. box_room.Test(position), // Lower room
  122. box_roof.Test(position) // Upper room
  123. ),
  124. box_planks.Test(plank_test_pos) // Ceiling "planks" spaced 8 units apart.
  125. );
  126. if (roomDist < distance) distance = roomDist, hitType = HIT_WALL;
  127. float sun = 19.9 - position.y_ ; // Everything above 19.9 is light source.
  128. if (sun < distance)distance = sun, hitType = HIT_SUN;
  129. return distance;
  130. }
  131. // Perform signed sphere marching
  132. // Returns hitType 0, 1, 2, or 3 and update hit position/normal
  133. int RayMarching(Vector3 origin, Vector3 direction, Vector3 &hitPos, Vector3 &hitNorm) {
  134. int hitType = HIT_NONE;
  135. int noHitCount = 0;
  136. float d; // distance from closest object in world.
  137. // Signed distance marching
  138. for (float total_d=0; total_d < 100; total_d += d) {
  139. hitPos = origin + direction * total_d;
  140. d = QueryDatabase(hitPos, hitType);
  141. // negative distance will mean we're inside the object we hit, I guess
  142. // and we'll return right away so no worries there
  143. if (d < .01 || noHitCount > 99) {
  144. int unusedReturnRef = 0;
  145. hitNorm = Vector3(QueryDatabase(hitPos + Vector3(.01, 0, 0), unusedReturnRef) - d,
  146. QueryDatabase(hitPos + Vector3(0, .01, 0), unusedReturnRef) - d,
  147. QueryDatabase(hitPos + Vector3(0, 0, .01), unusedReturnRef) - d).Normalized();
  148. return hitType;
  149. }
  150. ++noHitCount;
  151. }
  152. return 0;
  153. }
  154. Vector3 Trace(Vector3 origin, Vector3 direction) {
  155. Vector3 sampledPosition(1, 1, 1);
  156. Vector3 normal(1, 1, 1);
  157. Vector3 color(0, 0, 0);
  158. Vector3 attenuation(1, 1, 1);
  159. Vector3 lightDirection(light_direction); // Directional light
  160. lightDirection.Normalize();
  161. for (int bounceCount = 3; bounceCount--;) {
  162. int hitType = RayMarching(origin, direction, sampledPosition, normal);
  163. if (hitType == HIT_NONE) break; // No hit. This is over, return color.
  164. if (hitType == HIT_LETTER) { // Specular bounce on a letter. No color acc.
  165. direction = direction + normal * ( normal.DotProduct(direction) * -2);
  166. origin = sampledPosition + direction * 0.1;
  167. attenuation = attenuation * 0.2; // Attenuation via distance traveled.
  168. }
  169. if (hitType == HIT_WALL) { // Wall hit uses color yellow?
  170. float incidence = normal.DotProduct(lightDirection);
  171. float p = 6.283185 * randomVal();
  172. float c = randomVal();
  173. float s = sqrtf(1 - c);
  174. direction = Vector3(normal.y_,
  175. normal.z_,
  176. normal.x_) * (cosf(p) * s)
  177. + Vector3(normal.z_,
  178. normal.x_,
  179. normal.y_) * (sinf(p) * s)
  180. + normal * sqrtf(c);
  181. origin = sampledPosition + direction * .1;
  182. attenuation = attenuation * 0.2;
  183. if (incidence > 0 &&
  184. RayMarching(sampledPosition + normal * .1,
  185. lightDirection,
  186. sampledPosition,
  187. normal) == HIT_SUN)
  188. color = color + attenuation * Vector3(500, 400, 100) * incidence;
  189. }
  190. if (hitType == HIT_SUN) { //
  191. color = color + attenuation * Vector3(50, 80, 100); break; // Sun Color
  192. }
  193. }
  194. return color;
  195. }
  196. // <<<===================== ========================
  197. // ======================== ========================
  198. /** (Used a Urho3d Sample project as a base)
  199. * Using the convenient Application API we don't have
  200. * to worry about initializing the engine or writing a main.
  201. */
  202. class MyApp : public Application
  203. {
  204. public:
  205. int framecount_;
  206. float time_;
  207. std::time_t timestart_; // when the scene started rendering
  208. std::time_t render_time_; // how long the last render took
  209. String render_log_; // The log message detailing the last render
  210. bool rendering_;
  211. static const int render_width_ = 960;
  212. static const int render_height_ = 540;
  213. static const int render_scale_ = 3;
  214. int w_;
  215. int h_;
  216. int y_;
  217. int samplesCount_;
  218. SharedPtr<Text> text_;
  219. SharedPtr<LineEdit> inputRenderscale_;
  220. SharedPtr<LineEdit> inputPnorm_;
  221. SharedPtr<Scene> scene_;
  222. SharedPtr<Node> boxNode_;
  223. SharedPtr<Texture2D> texture_;
  224. SharedPtr<Image> image_;
  225. SharedPtr<Sprite> sprite_;
  226. SharedPtr<Node> cameraNode_;
  227. /**
  228. * This happens before the engine has been initialized so it's usually
  229. * minimal code setting defaults for whatever instance variables you have.
  230. * You can also do this in the Setup method.
  231. */
  232. MyApp(Context * context) : Application(context), framecount_(0), time_(0),
  233. rendering_(false), samplesCount_(2)
  234. {
  235. w_ = render_width_/render_scale_;
  236. h_ = render_height_/render_scale_;
  237. }
  238. /**
  239. * Setup() is called before the engine has been initialized. Thusly, we can
  240. * setup the engine parameters before anything else of engine importance
  241. * gets initialized (such as windows, search paths, resolution, etc.)
  242. */
  243. virtual void Setup()
  244. {
  245. // These parameters should be self-explanatory.
  246. // See http://urho3d.github.io/documentation/1.7/_main_loop.html for a more complete list.
  247. engineParameters_[EP_FULL_SCREEN] = false;
  248. engineParameters_[EP_WINDOW_WIDTH] = 1280;
  249. engineParameters_[EP_WINDOW_HEIGHT] = 800;
  250. GetSubsystem<Input>()->SetMouseVisible(true);
  251. // All 'EP_' constants are defined in ${URHO3D_INSTALL}/include/Urho3D/Engine/EngineDefs.h file
  252. }
  253. /**
  254. * Start() is called after the engine has been initialized. This is where you
  255. * set up your actual content, such as scenes, controls and what not.
  256. * Basically, anything that needs the engine initialized and ready goes here.
  257. */
  258. virtual void Start()
  259. {
  260. ResourceCache* cache = GetSubsystem<ResourceCache>();
  261. // Let's use the default style that comes with Urho3D.
  262. XMLFile* xmlFile = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
  263. GetSubsystem<UI>()->GetRoot()->SetDefaultStyle(xmlFile);
  264. // Let's create some text to display.
  265. text_=new Text(context_);
  266. // Text will be updated later in the E_UPDATE handler.
  267. text_->SetText("aek path tracer, heavily modified by rf");
  268. text_->SetFont("Fonts/Anonymous Pro.ttf", 16);
  269. text_->SetColor(Color(1.0, 0.8, 0.7));
  270. text_->SetHorizontalAlignment(HA_CENTER);
  271. text_->SetVerticalAlignment(VA_TOP);
  272. GetSubsystem<UI>()->GetRoot()->AddChild(text_);
  273. // Add a button, which will start the rendering process.
  274. Button* button=new Button(context_);
  275. // Note, must be part of the UI system before SetSize calls!
  276. GetSubsystem<UI>()->GetRoot()->AddChild(button);
  277. button->SetStyle("Button");
  278. button->SetSize(120, 32);
  279. button->SetPosition(16, 116);
  280. button->SetInternal(true);
  281. auto* buttonText = button->CreateChild<Text>();
  282. buttonText->SetFont("Fonts/Anonymous Pro.ttf", 12);
  283. buttonText->SetAlignment(HA_CENTER, VA_CENTER);
  284. buttonText->SetText("Render");
  285. buttonText->SetInternal(true);
  286. // Subscribe to button release (following a 'press') events
  287. SubscribeToEvent(button, E_RELEASED, URHO3D_HANDLER(MyApp, HandleButtonPressed));
  288. // Let's setup a scene to render.
  289. scene_=new Scene(context_);
  290. // // Let the scene have an Octree component!
  291. // scene_->CreateComponent<Octree>();
  292. // Adding DebugRenderer, even though it's unused so far...
  293. scene_->CreateComponent<DebugRenderer>();
  294. // We need a camera from which the viewport can render.
  295. cameraNode_=scene_->CreateChild("Camera");
  296. Camera* camera=cameraNode_->CreateComponent<Camera>();
  297. camera->SetFarClip(2000);
  298. // Create our drawing surface. An image for the data, a texture and a sprite.
  299. // Sizes get initialized when starting render
  300. image_=new Image(context_);
  301. texture_=new Texture2D(context_);
  302. texture_->SetFilterMode(TextureFilterMode::FILTER_NEAREST);
  303. texture_->SetNumLevels(1);
  304. texture_->SetData(image_);
  305. sprite_=GetSubsystem<UI>()->GetRoot()->CreateChild<Sprite>();
  306. sprite_->SetPosition(150, 150);
  307. sprite_->SetTexture(texture_);
  308. GetSubsystem<UI>()->GetRoot()->AddChild(sprite_);
  309. // Now we setup the viewport.
  310. Renderer* renderer=GetSubsystem<Renderer>();
  311. SharedPtr<Viewport> viewport(new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>()));
  312. renderer->SetViewport(0, viewport);
  313. // We subscribe to the events we'd like to handle.
  314. SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(MyApp, HandleUpdate));
  315. }
  316. /**
  317. * Good place to get rid of any system resources that requires the
  318. * engine still initialized. You could do the rest in the destructor,
  319. * but there's no need, this method will get called when the engine stops,
  320. * for whatever reason (short of a segfault).
  321. */
  322. virtual void Stop()
  323. {
  324. }
  325. /**
  326. * You can get these events from when ever the user interacts with the UI.
  327. */
  328. void HandleButtonPressed(StringHash eventType, VariantMap& eventData)
  329. {
  330. rendering_ = true;
  331. y_ = 0;
  332. timestart_ = std::time(nullptr);
  333. samplesCount_ = 2;
  334. URHO3D_LOGINFO("New width: " + String(w_));
  335. URHO3D_LOGINFO("New height: " + String(h_));
  336. // only resize if necessary, 0s
  337. if(image_->GetWidth() != w_ || image_->GetHeight() != h_)
  338. {
  339. image_->SetSize(w_, h_, 4);
  340. image_=image_->ConvertToRGBA();
  341. }
  342. URHO3D_LOGINFO("image_ Width: " + String(image_->GetWidth()));
  343. URHO3D_LOGINFO("image_ Height: " + String(image_->GetHeight()));
  344. // only resize if necessary, 0s
  345. if(texture_->GetWidth() != w_ || texture_->GetHeight() != h_)
  346. {
  347. texture_->SetSize(w_, h_, Graphics::GetRGBAFormat(), TEXTURE_STATIC);
  348. URHO3D_LOGINFO("texture_ Width: " + String(texture_->GetWidth()));
  349. URHO3D_LOGINFO("texture_ Height: " + String(texture_->GetHeight()));
  350. sprite_->SetTexture(texture_);
  351. }
  352. // only resize if necessary, 0s
  353. if(sprite_->GetWidth() != texture_->GetWidth() || sprite_->GetHeight() != texture_->GetHeight())
  354. {
  355. sprite_->SetSize(texture_->GetWidth(), texture_->GetHeight());
  356. URHO3D_LOGINFO("sprite_ Width: " + String(sprite_->GetWidth()));
  357. URHO3D_LOGINFO("sprite_ Height: " + String(sprite_->GetHeight()));
  358. }
  359. SetupPathtracer();
  360. }
  361. /**
  362. * Your non-rendering logic should be handled here.
  363. * This could be moving objects, checking collisions and reaction, etc.
  364. */
  365. void HandleUpdate(StringHash eventType,VariantMap& eventData)
  366. {
  367. if(rendering_) {
  368. // Advance to rendering next line, and handle reaching the bottom of our rendering
  369. if(++y_ > h_) {
  370. render_time_ = std::time(nullptr) - timestart_;
  371. render_log_ = "Drawing done: " + String(samplesCount_) + " samples/pixel in " + String(render_time_) + "s";
  372. URHO3D_LOGINFO(render_log_);
  373. // Naming scheme, e.g. pixar_960x540_64.png
  374. image_->SavePNG("pixar_"+String(w_)+"x"+String(h_)+"_"+String(samplesCount_) + ".png");
  375. // Prepare to start from the top again...
  376. y_ = 0;
  377. // We will continue rendering, this time with twice the samples per pixel.
  378. samplesCount_ *= 2;
  379. // For now, repeat until we hit 512, i.e. render 2, 4, 8, 16, 32, 64, 128, 256 and then exit
  380. if(samplesCount_ >= 512) {
  381. engine_->Exit();
  382. }
  383. timestart_ = std::time(nullptr);
  384. }
  385. // ======================== =====================>>>
  386. // ======================== original pathtracer code
  387. // int w = 960, h = 540, samplesCount = 8;
  388. Vector3 position(camera_position);
  389. Vector3 goal = lookat_position - position;
  390. goal.Normalize();
  391. // this vector is actually to the right of goal... flipped signs of x and z for left.
  392. // Up is down. Hence the subtractions instead of additions for trace directions
  393. // Note to self: Yes, it was rendering upside-down, but only because originally it
  394. // wrote values for y = h .. 0 and x = w .. 0, so from bottom right to top left, sequentially
  395. Vector3 left = Vector3(-goal.z_, 0, goal.x_).Normalized() / w_;
  396. // Cross-product to get the up vector
  397. Vector3 up = goal.CrossProduct(left);
  398. // this used to be the argument to Trace
  399. Vector3 trace_dir{};
  400. for (int x = 0; x < w_; x++) {
  401. Vector3 color;
  402. for (int p = samplesCount_; p--;) {
  403. // this was the second Trace argument as both lines in one:
  404. // !(goal + left * (x - w_ / 2 + randomVal()) + up * (y_ - h_ / 2 + randomVal()))
  405. trace_dir = goal + left * (x - w_ / 2 + randomVal()) + up * (y_ - h_ / 2 + randomVal());
  406. trace_dir.Normalize();
  407. color = color + Trace(position, trace_dir);
  408. }
  409. // Reinhard tone mapping
  410. color = color / samplesCount_ + Vector3(1.0f, 1.0f, 1.0f) * 14. / 241.0;
  411. Vector3 o = color + Vector3(1.0, 1.0, 1.0);
  412. color = color / o;
  413. // <<<===================== ========================
  414. // ======================== ========================
  415. Color c = Color(color.x_, color.y_, color.z_, 1.0f);
  416. image_->SetPixelInt(x, y_, c.ToUInt());
  417. }
  418. if(y_ % (h_/6) == 0)
  419. {
  420. texture_->SetData(image_);
  421. }
  422. }
  423. float timeStep = eventData[Update::P_TIMESTEP].GetFloat();
  424. framecount_++;
  425. time_ += timeStep;
  426. if(time_ >= 1)
  427. {
  428. String show_text = "Rendering " + String(rendering_ ? "in progress: " : "settings: ") \
  429. + String(samplesCount_) + " samples at " + String(w_) + "x" + String(h_) + "\n";
  430. if(samplesCount_ > 2)
  431. show_text += render_log_ + "\n";
  432. show_text += String(framecount_) + " frames in " + String(time_) + "s = " + String(framecount_/time_) + " fps";
  433. text_->SetText(show_text);
  434. framecount_ = 0;
  435. time_ = 0;
  436. }
  437. }
  438. };
  439. /**
  440. * This macro is expanded to (roughly, depending on OS) this:
  441. *
  442. * > int RunApplication()
  443. * > {
  444. * > Urho3D::SharedPtr<Urho3D::Context> context(new Urho3D::Context());
  445. * > Urho3D::SharedPtr<className> application(new className(context));
  446. * > return application->Run();
  447. * > }
  448. * >
  449. * > int main(int argc, char** argv)
  450. * > {
  451. * > Urho3D::ParseArguments(argc, argv);
  452. * > return function;
  453. * > }
  454. */
  455. URHO3D_DEFINE_APPLICATION_MAIN(MyApp)