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.
- 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 :

View file

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

View file

@ -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:

View file

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

View file

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

View file

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

View file

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

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 \
LightingManager_Test.cpp \
GodRaysSampler_Test.cpp \
Interpolation_Test.cpp
Interpolation_Test.cpp \
Rasterizer_Test.cpp \
Vector3_Test.cpp
HEADERS += \
BaseTestCase.h