From 3fc8b1c98f6ded9206fe95228550cf7c1e10e818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Thu, 8 Oct 2015 19:20:44 +0200 Subject: [PATCH] Added automatic tessellation near camera frustum culling This allows the camera nearer the ground and fixes holes in lower quality renders --- TODO | 1 - src/basics/Vector3.cpp | 5 ++ src/basics/Vector3.h | 5 ++ src/definition/Scenery.cpp | 4 +- src/interface/commandline/tests.cpp | 30 ++++++++--- src/render/software/Rasterizer.cpp | 79 ++++++++++++++++++++++------- src/render/software/Rasterizer.h | 25 +++++++-- src/tests/Rasterizer_Test.cpp | 54 ++++++++++++++++++++ src/tests/Vector3_Test.cpp | 10 ++++ src/tests/tests.pro | 4 +- 10 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 src/tests/Rasterizer_Test.cpp create mode 100644 src/tests/Vector3_Test.cpp diff --git a/TODO b/TODO index c29fc65..c88912f 100644 --- a/TODO +++ b/TODO @@ -5,7 +5,6 @@ Technlology Preview 2 : - Add clouds to OpenGL with 3d textures. - Refactor medium traversal to unify clouds, atmosphere and god rays. - Fix potential holes in land rendering (OpenGL and software). -- Fix polygon culling near the camera in low-res renders (automatic tessellation ?). - Fix sun size not being consistent between opengl and software Technology Preview 3 : diff --git a/src/basics/Vector3.cpp b/src/basics/Vector3.cpp index c7b413f..bcd81d7 100644 --- a/src/basics/Vector3.cpp +++ b/src/basics/Vector3.cpp @@ -76,3 +76,8 @@ VectorSpherical Vector3::toSpherical() const return result; } + +Vector3 Vector3::midPointTo(const Vector3 &other) const +{ + return Vector3((other.x + x) * 0.5, (other.y + y) * 0.5, (other.z + z) * 0.5); +} diff --git a/src/basics/Vector3.h b/src/basics/Vector3.h index 778f69d..2c8a40c 100644 --- a/src/basics/Vector3.h +++ b/src/basics/Vector3.h @@ -61,6 +61,11 @@ public: double dotProduct(const Vector3 &other) const; Vector3 crossProduct(const Vector3 &other) const; + /** + * Get the mid-point of the segment between *this* point and *other*. + */ + Vector3 midPointTo(const Vector3 &other) const; + VectorSpherical toSpherical() const; public: diff --git a/src/definition/Scenery.cpp b/src/definition/Scenery.cpp index 001acd6..7637309 100644 --- a/src/definition/Scenery.cpp +++ b/src/definition/Scenery.cpp @@ -191,8 +191,8 @@ void Scenery::getWater(WaterDefinition* water) void Scenery::keepCameraAboveGround(CameraDefinition* camera) { Vector3 camera_location = camera->getLocation(); - double terrain_height = terrain->getInterpolatedHeight(camera_location.x, camera_location.z, true, true) + 2.0; - double water_height = 1.5; + double terrain_height = terrain->getInterpolatedHeight(camera_location.x, camera_location.z, true, true) + 1.0; + double water_height = 0.5; if (camera_location.y < water_height || camera_location.y < terrain_height) { double diff = ((water_height > terrain_height) ? water_height : terrain_height) - camera_location.y; diff --git a/src/interface/commandline/tests.cpp b/src/interface/commandline/tests.cpp index 6260484..867531f 100644 --- a/src/interface/commandline/tests.cpp +++ b/src/interface/commandline/tests.cpp @@ -21,14 +21,18 @@ void startRender(SoftwareCanvasRenderer *renderer, const char *outputpath); -static void startTestRender(SoftwareCanvasRenderer *renderer, const std::string &name, int iteration) +static void startTestRender(SoftwareCanvasRenderer *renderer, const std::string &name, int iteration=-1) { std::ostringstream stream; - stream << "pic_test_" << name << "_"; - stream.width(4); - stream.fill('0'); - stream << iteration; + stream << "pic_test_" << name; + if (iteration >= 0) + { + stream << "_"; + stream.width(4); + stream.fill('0'); + stream << iteration; + } stream << ".png"; startRender(renderer, stream.str().data()); @@ -192,11 +196,25 @@ static void testGodRays() } } +static void testNearFrustum() +{ + Scenery scenery; + scenery.autoPreset(3); + scenery.getCamera()->setLocation(Vector3(0.0, 0.0, 0.0)); + scenery.getCamera()->setTarget(Vector3(1.0, 0.0, 1.0)); + scenery.keepCameraAboveGround(scenery.getCamera()); + + SoftwareCanvasRenderer renderer(&scenery); + renderer.setSize(400, 300); + renderer.setQuality(0.1); + startTestRender(&renderer, "near_frustum"); +} + void runTestSuite() { testGroundShadowQuality(); testRasterizationQuality(); testCloudQuality(); testGodRays(); + testNearFrustum(); } - diff --git a/src/render/software/Rasterizer.cpp b/src/render/software/Rasterizer.cpp index 8fc42f1..950592e 100644 --- a/src/render/software/Rasterizer.cpp +++ b/src/render/software/Rasterizer.cpp @@ -38,6 +38,8 @@ Rasterizer::Rasterizer(SoftwareRenderer* renderer, RenderProgress *progress, int this->color = new Color(color); interrupted = false; + triangle_count = 0; + auto_cut_limit = 0.01; setQuality(0.5); } @@ -56,7 +58,17 @@ void Rasterizer::setQuality(double) { } -void Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3) +void Rasterizer::setAutoCutLimit(double limit) +{ + this->auto_cut_limit = limit; +} + +void Rasterizer::resetTriangleCount() +{ + triangle_count = 0; +} + +bool Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3) { ScanPoint point1, point2, point3; double limit_width = (double)(canvas->getWidth() - 1); @@ -67,13 +79,20 @@ void Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pix Vector3 dpixel2 = pixel2.sub(canvas_offset); Vector3 dpixel3 = pixel3.sub(canvas_offset); - /* Filter if outside screen */ - if (dpixel1.z < 1.0 || dpixel2.z < 1.0 || dpixel3.z < 1.0 || (dpixel1.x < 0.0 && dpixel2.x < 0.0 && dpixel3.x < 0.0) || (dpixel1.y < 0.0 && dpixel2.y < 0.0 && dpixel3.y < 0.0) || (dpixel1.x > limit_width && dpixel2.x > limit_width && dpixel3.x > limit_width) || (dpixel1.y > limit_height && dpixel2.y > limit_height && dpixel3.y > limit_height)) + double limit_near = renderer->render_camera->getPerspective().znear; + if ((dpixel1.z < limit_near && dpixel2.z < limit_near && dpixel3.z < limit_near) || (dpixel1.x < 0.0 && dpixel2.x < 0.0 && dpixel3.x < 0.0) || (dpixel1.y < 0.0 && dpixel2.y < 0.0 && dpixel3.y < 0.0) || (dpixel1.x > limit_width && dpixel2.x > limit_width && dpixel3.x > limit_width) || (dpixel1.y > limit_height && dpixel2.y > limit_height && dpixel3.y > limit_height)) { - return; + // Fully outside screen + return false; + } + else if (dpixel1.z < limit_near || dpixel2.z < limit_near || dpixel3.z < limit_near) + { + // Intersects the near frustum plane, needs cutting + // ... except if the triangle is already small + return location1.sub(location2).getNorm() > auto_cut_limit && location2.sub(location3).getNorm() > auto_cut_limit && location3.sub(location1).getNorm() > auto_cut_limit; } - /* Prepare vertices */ + // Prepare vertices point1.pixel.x = dpixel1.x; point1.pixel.y = dpixel1.y; point1.pixel.z = dpixel1.z; @@ -98,7 +117,7 @@ void Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pix point3.location.z = location3.z; point3.client = client_id; - /* Prepare scanlines */ + // Prepare scanlines // TODO Don't create scanlines for each triangles (one by thread is more appropriate) RenderScanlines scanlines; int width = canvas->getWidth(); @@ -107,17 +126,20 @@ void Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pix scanlines.up = new ScanPoint[width]; scanlines.down = new ScanPoint[width]; - /* Render edges in scanlines */ + // Render edges in scanlines pushScanLineEdge(canvas, &scanlines, &point1, &point2); pushScanLineEdge(canvas, &scanlines, &point2, &point3); pushScanLineEdge(canvas, &scanlines, &point3, &point1); - /* Commit scanlines to area */ + // Commit scanlines to area renderScanLines(canvas, &scanlines); - /* Free scalines */ + // Free scalines delete[] scanlines.up; delete[] scanlines.down; + + triangle_count++; + return false; } void Rasterizer::pushTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) @@ -128,13 +150,17 @@ void Rasterizer::pushTriangle(CanvasPortion *canvas, const Vector3 &v1, const Ve p2 = getRenderer()->projectPoint(v2); p3 = getRenderer()->projectPoint(v3); - pushProjectedTriangle(canvas, p1, p2, p3, v1, v2, v3); -} - -void Rasterizer::pushQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4) -{ - pushTriangle(canvas, v2, v3, v1); - pushTriangle(canvas, v4, v1, v3); + if (pushProjectedTriangle(canvas, p1, p2, p3, v1, v2, v3)) + { + // Cutting needed + Vector3 vm1 = v1.midPointTo(v2); + Vector3 vm2 = v2.midPointTo(v3); + Vector3 vm3 = v3.midPointTo(v1); + pushTriangle(canvas, v1, vm1, vm3); + pushTriangle(canvas, v2, vm1, vm2); + pushTriangle(canvas, v3, vm3, vm2); + pushTriangle(canvas, vm1, vm2, vm3); + } } void Rasterizer::pushDisplacedTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3) @@ -145,7 +171,26 @@ void Rasterizer::pushDisplacedTriangle(CanvasPortion *canvas, const Vector3 &v1, p2 = getRenderer()->projectPoint(v2); p3 = getRenderer()->projectPoint(v3); - pushProjectedTriangle(canvas, p1, p2, p3, ov1, ov2, ov3); + if (pushProjectedTriangle(canvas, p1, p2, p3, ov1, ov2, ov3)) + { + // Cutting needed + Vector3 vm1 = v1.midPointTo(v2); + Vector3 vm2 = v2.midPointTo(v3); + Vector3 vm3 = v3.midPointTo(v1); + Vector3 ovm1 = ov1.midPointTo(ov2); + Vector3 ovm2 = ov2.midPointTo(ov3); + Vector3 ovm3 = ov3.midPointTo(ov1); + pushDisplacedTriangle(canvas, v1, vm1, vm3, ov1, ovm1, ovm3); + pushDisplacedTriangle(canvas, v2, vm1, vm2, ov2, ovm1, ovm2); + pushDisplacedTriangle(canvas, v3, vm3, vm2, ov3, ovm3, ovm2); + pushDisplacedTriangle(canvas, vm1, vm2, vm3, ovm1, ovm2, ovm3); + } +} + +void Rasterizer::pushQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4) +{ + pushTriangle(canvas, v2, v3, v1); + pushTriangle(canvas, v4, v1, v3); } void Rasterizer::pushDisplacedQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, const Vector3 &ov4) diff --git a/src/render/software/Rasterizer.h b/src/render/software/Rasterizer.h index 4a3b4d3..b9360f6 100644 --- a/src/render/software/Rasterizer.h +++ b/src/render/software/Rasterizer.h @@ -19,6 +19,7 @@ public: virtual ~Rasterizer(); inline SoftwareRenderer *getRenderer() const {return renderer;} + inline int getTriangleCount() const {return triangle_count;} virtual Color shadeFragment(const CanvasFragment &fragment) const = 0; virtual void interrupt(); @@ -28,6 +29,16 @@ public: */ virtual void setQuality(double factor); + /** + * Set the edge length under which to stop auto-cutting triangles near the camera. + */ + void setAutoCutLimit(double limit); + + /** + * Reset the internal triangle counter to 0. + */ + void resetTriangleCount(); + /** * Abstract method to prepare for the rasterization process, and return the estimated progress count. */ @@ -35,16 +46,17 @@ public: /** * Abstract method to effectively do the rasterization on a canvas. */ - virtual void rasterizeToCanvas(CanvasPortion* canvas) = 0; - -protected: - void pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3); + virtual void rasterizeToCanvas(CanvasPortion *canvas) = 0; void pushTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3); - void pushQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4); void pushDisplacedTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3); + + void pushQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4); void pushDisplacedQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, const Vector3 &ov4); +protected: + bool pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3); + Color* color; SoftwareRenderer *renderer; RenderProgress *progress; @@ -57,6 +69,9 @@ private: void pushScanPoint(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point); void pushScanLineEdge(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point1, ScanPoint *point2); void renderScanLines(CanvasPortion *canvas, RenderScanlines *scanlines); + + int triangle_count; + double auto_cut_limit; }; } diff --git a/src/tests/Rasterizer_Test.cpp b/src/tests/Rasterizer_Test.cpp new file mode 100644 index 0000000..66dd886 --- /dev/null +++ b/src/tests/Rasterizer_Test.cpp @@ -0,0 +1,54 @@ +#include "BaseTestCase.h" +#include "Rasterizer.h" + +#include "SoftwareRenderer.h" +#include "Scenery.h" +#include "Vector3.h" +#include "CameraDefinition.h" +#include "Color.h" +#include "CanvasPortion.h" + +class FakeRasterizer: public Rasterizer +{ +public: + FakeRasterizer(SoftwareRenderer *renderer): Rasterizer(renderer, NULL, 0, COLOR_WHITE) + { + } + virtual Color shadeFragment(const CanvasFragment &) const override + { + return COLOR_RED; + } + virtual int prepareRasterization() override + { + return 0; + } + virtual void rasterizeToCanvas(CanvasPortion *) override + { + } +}; + +TEST(Rasterizer, autoSplitNearFrustum) +{ + Scenery scenery; + scenery.getCamera()->setLocation(Vector3(0.0, 5.0, 0.0)); + scenery.getCamera()->setTarget(Vector3(0.0, 5.0, 1.0)); + SoftwareRenderer renderer(&scenery); + + FakeRasterizer rast(&renderer); + CanvasPortion portion; + portion.setSize(300, 300); + portion.preparePixels(); + + rast.pushTriangle(&portion, Vector3(0.0, 0.0, 8.0), Vector3(0.0, 0.0, 10.0), Vector3(2.0, 0.0, 8.0)); + EXPECT_EQ(1, rast.getTriangleCount()); + + rast.resetTriangleCount(); + rast.setAutoCutLimit(15.0); + rast.pushTriangle(&portion, Vector3(0.0, 0.0, 0.0), Vector3(-10.0, 0.0, 10.0), Vector3(10.0, 0.0, 10.0)); + EXPECT_EQ(0, rast.getTriangleCount()); + + rast.resetTriangleCount(); + rast.setAutoCutLimit(9.0); + rast.pushTriangle(&portion, Vector3(0.0, 0.0, 0.0), Vector3(-10.0, 0.0, 10.0), Vector3(10.0, 0.0, 10.0)); + EXPECT_EQ(3, rast.getTriangleCount()); +} diff --git a/src/tests/Vector3_Test.cpp b/src/tests/Vector3_Test.cpp new file mode 100644 index 0000000..a03d157 --- /dev/null +++ b/src/tests/Vector3_Test.cpp @@ -0,0 +1,10 @@ +#include "BaseTestCase.h" +#include "Vector3.h" + +TEST(Vector3, midPointTo) +{ + Vector3 v1(1.0, 2.0, 8.0); + Vector3 v2(4.0, 2.5, -1.0); + Vector3 vm = v1.midPointTo(v2); + EXPECT_VECTOR3_COORDS(vm, 2.5, 2.25, 3.5); +} diff --git a/src/tests/tests.pro b/src/tests/tests.pro index 85b15ad..5aea89b 100644 --- a/src/tests/tests.pro +++ b/src/tests/tests.pro @@ -31,7 +31,9 @@ SOURCES += main.cpp \ IntNode_Test.cpp \ LightingManager_Test.cpp \ GodRaysSampler_Test.cpp \ - Interpolation_Test.cpp + Interpolation_Test.cpp \ + Rasterizer_Test.cpp \ + Vector3_Test.cpp HEADERS += \ BaseTestCase.h