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:
parent
652c66a2fa
commit
3fc8b1c98f
10 changed files with 185 additions and 32 deletions
1
TODO
1
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 :
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
54
src/tests/Rasterizer_Test.cpp
Normal file
54
src/tests/Rasterizer_Test.cpp
Normal 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());
|
||||
}
|
10
src/tests/Vector3_Test.cpp
Normal file
10
src/tests/Vector3_Test.cpp
Normal 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);
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue