Added automatic tessellation near camera frustum culling

This allows the camera nearer the ground
and fixes holes in lower quality renders
This commit is contained in:
Michaël Lemaire 2015-10-08 19:20:44 +02:00
parent 652c66a2fa
commit 3fc8b1c98f
10 changed files with 185 additions and 32 deletions

1
TODO
View file

@ -5,7 +5,6 @@ Technlology Preview 2 :
- Add clouds to OpenGL with 3d textures. - Add clouds to OpenGL with 3d textures.
- Refactor medium traversal to unify clouds, atmosphere and god rays. - Refactor medium traversal to unify clouds, atmosphere and god rays.
- Fix potential holes in land rendering (OpenGL and software). - 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 - Fix sun size not being consistent between opengl and software
Technology Preview 3 : Technology Preview 3 :

View file

@ -76,3 +76,8 @@ VectorSpherical Vector3::toSpherical() const
return result; 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);
}

View file

@ -61,6 +61,11 @@ public:
double dotProduct(const Vector3 &other) const; double dotProduct(const Vector3 &other) const;
Vector3 crossProduct(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; VectorSpherical toSpherical() const;
public: public:

View file

@ -191,8 +191,8 @@ void Scenery::getWater(WaterDefinition* water)
void Scenery::keepCameraAboveGround(CameraDefinition* camera) void Scenery::keepCameraAboveGround(CameraDefinition* camera)
{ {
Vector3 camera_location = camera->getLocation(); Vector3 camera_location = camera->getLocation();
double terrain_height = terrain->getInterpolatedHeight(camera_location.x, camera_location.z, true, true) + 2.0; double terrain_height = terrain->getInterpolatedHeight(camera_location.x, camera_location.z, true, true) + 1.0;
double water_height = 1.5; double water_height = 0.5;
if (camera_location.y < water_height || camera_location.y < terrain_height) if (camera_location.y < water_height || camera_location.y < terrain_height)
{ {
double diff = ((water_height > terrain_height) ? water_height : terrain_height) - camera_location.y; double diff = ((water_height > terrain_height) ? water_height : terrain_height) - camera_location.y;

View file

@ -21,14 +21,18 @@
void startRender(SoftwareCanvasRenderer *renderer, const char *outputpath); 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; std::ostringstream stream;
stream << "pic_test_" << name << "_"; stream << "pic_test_" << name;
stream.width(4); if (iteration >= 0)
stream.fill('0'); {
stream << iteration; stream << "_";
stream.width(4);
stream.fill('0');
stream << iteration;
}
stream << ".png"; stream << ".png";
startRender(renderer, stream.str().data()); 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() void runTestSuite()
{ {
testGroundShadowQuality(); testGroundShadowQuality();
testRasterizationQuality(); testRasterizationQuality();
testCloudQuality(); testCloudQuality();
testGodRays(); testGodRays();
testNearFrustum();
} }

View file

@ -38,6 +38,8 @@ Rasterizer::Rasterizer(SoftwareRenderer* renderer, RenderProgress *progress, int
this->color = new Color(color); this->color = new Color(color);
interrupted = false; interrupted = false;
triangle_count = 0;
auto_cut_limit = 0.01;
setQuality(0.5); 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; ScanPoint point1, point2, point3;
double limit_width = (double)(canvas->getWidth() - 1); 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 dpixel2 = pixel2.sub(canvas_offset);
Vector3 dpixel3 = pixel3.sub(canvas_offset); Vector3 dpixel3 = pixel3.sub(canvas_offset);
/* Filter if outside screen */ double limit_near = renderer->render_camera->getPerspective().znear;
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)) 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.x = dpixel1.x;
point1.pixel.y = dpixel1.y; point1.pixel.y = dpixel1.y;
point1.pixel.z = dpixel1.z; point1.pixel.z = dpixel1.z;
@ -98,7 +117,7 @@ void Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pix
point3.location.z = location3.z; point3.location.z = location3.z;
point3.client = client_id; point3.client = client_id;
/* Prepare scanlines */ // Prepare scanlines
// TODO Don't create scanlines for each triangles (one by thread is more appropriate) // TODO Don't create scanlines for each triangles (one by thread is more appropriate)
RenderScanlines scanlines; RenderScanlines scanlines;
int width = canvas->getWidth(); int width = canvas->getWidth();
@ -107,17 +126,20 @@ void Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pix
scanlines.up = new ScanPoint[width]; scanlines.up = new ScanPoint[width];
scanlines.down = new ScanPoint[width]; scanlines.down = new ScanPoint[width];
/* Render edges in scanlines */ // Render edges in scanlines
pushScanLineEdge(canvas, &scanlines, &point1, &point2); pushScanLineEdge(canvas, &scanlines, &point1, &point2);
pushScanLineEdge(canvas, &scanlines, &point2, &point3); pushScanLineEdge(canvas, &scanlines, &point2, &point3);
pushScanLineEdge(canvas, &scanlines, &point3, &point1); pushScanLineEdge(canvas, &scanlines, &point3, &point1);
/* Commit scanlines to area */ // Commit scanlines to area
renderScanLines(canvas, &scanlines); renderScanLines(canvas, &scanlines);
/* Free scalines */ // Free scalines
delete[] scanlines.up; delete[] scanlines.up;
delete[] scanlines.down; delete[] scanlines.down;
triangle_count++;
return false;
} }
void Rasterizer::pushTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) 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); p2 = getRenderer()->projectPoint(v2);
p3 = getRenderer()->projectPoint(v3); p3 = getRenderer()->projectPoint(v3);
pushProjectedTriangle(canvas, p1, p2, p3, v1, v2, v3); if (pushProjectedTriangle(canvas, p1, p2, p3, v1, v2, v3))
} {
// Cutting needed
void Rasterizer::pushQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4) Vector3 vm1 = v1.midPointTo(v2);
{ Vector3 vm2 = v2.midPointTo(v3);
pushTriangle(canvas, v2, v3, v1); Vector3 vm3 = v3.midPointTo(v1);
pushTriangle(canvas, v4, v1, v3); 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) 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); p2 = getRenderer()->projectPoint(v2);
p3 = getRenderer()->projectPoint(v3); 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) 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)

View file

@ -19,6 +19,7 @@ public:
virtual ~Rasterizer(); virtual ~Rasterizer();
inline SoftwareRenderer *getRenderer() const {return renderer;} inline SoftwareRenderer *getRenderer() const {return renderer;}
inline int getTriangleCount() const {return triangle_count;}
virtual Color shadeFragment(const CanvasFragment &fragment) const = 0; virtual Color shadeFragment(const CanvasFragment &fragment) const = 0;
virtual void interrupt(); virtual void interrupt();
@ -28,6 +29,16 @@ public:
*/ */
virtual void setQuality(double factor); 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. * 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. * Abstract method to effectively do the rasterization on a canvas.
*/ */
virtual void rasterizeToCanvas(CanvasPortion* canvas) = 0; 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);
void pushTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3); 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 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); 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; Color* color;
SoftwareRenderer *renderer; SoftwareRenderer *renderer;
RenderProgress *progress; RenderProgress *progress;
@ -57,6 +69,9 @@ private:
void pushScanPoint(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point); void pushScanPoint(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point);
void pushScanLineEdge(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point1, ScanPoint *point2); void pushScanLineEdge(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point1, ScanPoint *point2);
void renderScanLines(CanvasPortion *canvas, RenderScanlines *scanlines); void renderScanLines(CanvasPortion *canvas, RenderScanlines *scanlines);
int triangle_count;
double auto_cut_limit;
}; };
} }

View file

@ -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());
}

View file

@ -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);
}

View file

@ -31,7 +31,9 @@ SOURCES += main.cpp \
IntNode_Test.cpp \ IntNode_Test.cpp \
LightingManager_Test.cpp \ LightingManager_Test.cpp \
GodRaysSampler_Test.cpp \ GodRaysSampler_Test.cpp \
Interpolation_Test.cpp Interpolation_Test.cpp \
Rasterizer_Test.cpp \
Vector3_Test.cpp
HEADERS += \ HEADERS += \
BaseTestCase.h BaseTestCase.h