diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 77d7133b67112ec6498ebcf79d3c6432952d38c3..de73ddb77a1f5563a7f253624f2999801b173759 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -17,6 +17,7 @@
 
 #define ZCLIP_PLANE 4.0f // Used for the actual game drawing
 #define NZCLIP_PLANE 0.9f // Seems to be only used for the HUD and screen textures
+#define FAR_ZCLIP_DEFAULT 32768.0f
 
 // The width/height/depth of the palette lookup table used by palette rendering.
 // Changing this also requires changing the shader code!
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index a5fdc00a47c17e6b5cbeba5d074554a66672cde7..4588e724bd2f8b3e83f636d8303fc5e498cef01e 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -41,7 +41,7 @@ EXPORT void HWRAPI(SetTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(DeleteTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(ReadScreenTexture) (int tex, UINT8 *dst_data);
-EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip);
+EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip, float farclip);
 EXPORT void HWRAPI(ClearMipMapCache) (void);
 
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value);
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 708b3cdf4459941b5f48425d712d592e96378ae7..78b4d057c3ab3197e46517e0c42085a864b76155 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -102,6 +102,15 @@ boolean gl_shadersavailable = false;
 // Whether the internal state is set to palette rendering or not.
 static boolean gl_palette_rendering_state = false;
 
+// values for the far clipping plane
+static float clipping_distances[] = {1024.0f, 2048.0f, 4096.0f, 6144.0f, 8192.0f, 12288.0f, 16384.0f};
+// values for bsp culling
+// slightly higher than the far clipping plane to compensate for impreciseness
+static INT32 bsp_culling_distances[] = {(1024+512)*FRACUNIT, (2048+512)*FRACUNIT, (4096+512)*FRACUNIT,
+	(6144+512)*FRACUNIT, (8192+512)*FRACUNIT, (12288+512)*FRACUNIT, (16384+512)*FRACUNIT};
+
+static INT32 current_bsp_culling_distance = 0;
+
 // --------------------------------------------------------------------------
 //                                              STUFF FOR THE PROJECTION CODE
 // --------------------------------------------------------------------------
@@ -2110,6 +2119,70 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	px2 = bspcoord[checkcoord[boxpos][2]];
 	py2 = bspcoord[checkcoord[boxpos][3]];
 
+	if (current_bsp_culling_distance)
+	{
+		//fixed_t midx = (px1 >> 1) + (px2 >> 1);
+		//fixed_t midy = (py1 >> 1) + (py2 >> 1);
+		//fixed_t mindist = min(min(R_PointToDist(px1, py1), R_PointToDist(px2, py2)), R_PointToDist(midx, midy));
+
+		//fixed_t mindist = ClosestPointOnLineDistance(px1, py1, px2, py2);
+
+		//fixed_t mindist1 = ClosestPointOnLineDistance(bspcoord[BOXLEFT], bspcoord[BOXTOP], bspcoord[BOXRIGHT], bspcoord[BOXTOP]); // top line
+		//fixed_t mindist2 = ClosestPointOnLineDistance(bspcoord[BOXLEFT], bspcoord[BOXTOP], bspcoord[BOXLEFT], bspcoord[BOXBOTTOM]); // left line
+		//fixed_t mindist3 = ClosestPointOnLineDistance(bspcoord[BOXLEFT], bspcoord[BOXBOTTOM], bspcoord[BOXRIGHT], bspcoord[BOXBOTTOM]); // bottom line
+		//fixed_t mindist4 = ClosestPointOnLineDistance(bspcoord[BOXRIGHT], bspcoord[BOXTOP], bspcoord[BOXRIGHT], bspcoord[BOXBOTTOM]); // right line
+		// this one seems too lax.. maybe closestpointonlinedistance is glitchy and returns points that are not on the line segment?
+		// could try building an if-else structure that determines what point or line is closest
+		// 1  | 2  | 3
+		//--------------
+		// 4  |node| 5
+		//--------------
+		// 6  | 7  | 8
+		// y
+		// ^
+		// |
+		// -----> x
+		// inside node: always inside draw distance. the above boxpos thing might have returned true already?
+		// 1. check top left corner     2. check top line     3. check top right corner
+		// 4. check left line                                 5. check right line
+		// 6. check bottom left corner  7. check bottom line  8. check bottom right corner
+		// one if statement will split the space in two for one coordinate
+		// for example:
+		// x < BOXLEFT   || BOXLEFT ||   !(x < BOXLEFT)   <-- (same as x >= BOXLEFT)
+		fixed_t mindist;// = min(min(mindist1, mindist2), min(mindist3, mindist4));
+
+		// new thing
+		// calculate distance to axis aligned bounding box.
+		if (viewx < bspcoord[BOXLEFT]) // 1,4,6
+		{
+			if (viewy > bspcoord[BOXTOP]) // 1
+				mindist = R_PointToDist(bspcoord[BOXLEFT], bspcoord[BOXTOP]);
+			else if (viewy < bspcoord[BOXBOTTOM]) // 6
+				mindist = R_PointToDist(bspcoord[BOXLEFT], bspcoord[BOXBOTTOM]);
+			else // 4
+				mindist = bspcoord[BOXLEFT] - viewx;
+		}
+		else if (viewx > bspcoord[BOXRIGHT]) // 3,5,8
+		{
+			if (viewy > bspcoord[BOXTOP]) // 3
+				mindist = R_PointToDist(bspcoord[BOXRIGHT], bspcoord[BOXTOP]);
+			else if (viewy < bspcoord[BOXBOTTOM]) // 8
+				mindist = R_PointToDist(bspcoord[BOXRIGHT], bspcoord[BOXBOTTOM]);
+			else // 5
+				mindist = viewx - bspcoord[BOXRIGHT];
+		}
+		else // 2,node,7
+		{
+			if (viewy > bspcoord[BOXTOP]) // 2
+				mindist = viewy - bspcoord[BOXTOP];
+			else if (viewy < bspcoord[BOXBOTTOM]) // 7
+				mindist = bspcoord[BOXBOTTOM] - viewy;
+			else // node
+				mindist = 0;
+		}
+		if (mindist > current_bsp_culling_distance) return false;
+	}
+
 	angle1 = R_PointToAngle64(px1, py1);
 	angle2 = R_PointToAngle64(px2, py2);
 	return gld_clipper_SafeCheckRange(angle2, angle1);
@@ -4319,7 +4392,7 @@ static void HWR_AddSprites(sector_t *sec)
 {
 	mobj_t *thing;
 	precipmobj_t *precipthing;
-	fixed_t limit_dist, hoop_limit_dist;
+	fixed_t limit_dist, hoop_limit_dist, precip_limit_dist;
 
 	// BSP is traversed by subsector.
 	// A sector might have been split into several
@@ -4331,13 +4404,28 @@ static void HWR_AddSprites(sector_t *sec)
 	// Well, now it will be done.
 	sec->validcount = validcount;
 
+	if (current_bsp_culling_distance)
+	{
+		// Use the smaller setting
+		if (cv_drawdist.value)
+			limit_dist = min((fixed_t)current_bsp_culling_distance, (fixed_t)(cv_drawdist.value) << FRACBITS);
+		else
+			limit_dist = (fixed_t)current_bsp_culling_distance;
+		precip_limit_dist = min((fixed_t)current_bsp_culling_distance, (fixed_t)(cv_drawdist_precip.value) << FRACBITS);
+		hoop_limit_dist = min((fixed_t)current_bsp_culling_distance, (fixed_t)(cv_drawdist_nights.value) << FRACBITS);
+	}
+	else
+	{
+		limit_dist = (fixed_t)(cv_drawdist.value) << FRACBITS;
+		precip_limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS;
+		hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
+	}
+
 	// sprite lighting
 	sectorlight = sec->lightlevel & 0xff;
 
 	// Handle all things in sector.
-	// If a limit exists, handle things a tiny bit different.
-	limit_dist = (fixed_t)(cv_drawdist.value) << FRACBITS;
-	hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
+	// If a limit exists, handle things a tiny bit different
 	for (thing = sec->thinglist; thing; thing = thing->snext)
 	{
 		if (R_ThingWithinDist(thing, limit_dist, hoop_limit_dist))
@@ -4352,11 +4440,11 @@ static void HWR_AddSprites(sector_t *sec)
 	}
 
 	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
-	if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS))
+	if (precip_limit_dist)
 	{
 		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
 		{
-			if (R_PrecipThingVisible(precipthing, limit_dist))
+			if (R_PrecipThingVisible(precipthing, precip_limit_dist))
 				HWR_ProjectPrecipitationSprite(precipthing);
 		}
 	}
@@ -5321,7 +5409,7 @@ static inline void HWR_ClearView(void)
 	                 (INT32)viewwindowy,
 	                 (INT32)(viewwindowx + viewwidth),
 	                 (INT32)(viewwindowy + viewheight),
-	                 ZCLIP_PLANE);
+	                 ZCLIP_PLANE, FAR_ZCLIP_DEFAULT);
 	HWD.pfnClearBuffer(false, true, 0);
 
 	//disable clip window - set to full size
@@ -5399,6 +5487,18 @@ static void HWR_SetupView(player_t *player, INT32 viewnumber, float fpov, boolea
 	else
 		R_SetupFrame(player);
 
+	current_bsp_culling_distance = 0;
+
+	if (!skybox && cv_glrenderdistance.value)
+	{
+		HWD.pfnGClipRect((INT32)viewwindowx,
+	                 (INT32)viewwindowy,
+	                 (INT32)(viewwindowx + viewwidth),
+	                 (INT32)(viewwindowy + viewheight),
+	                 ZCLIP_PLANE, clipping_distances[cv_glrenderdistance.value - 1]);
+		current_bsp_culling_distance = bsp_culling_distances[cv_glrenderdistance.value - 1];
+	}
+
 	gl_viewx = FixedToFloat(viewx);
 	gl_viewy = FixedToFloat(viewy);
 	gl_viewz = FixedToFloat(viewz);
@@ -5494,6 +5594,8 @@ static void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
 
+	current_bsp_culling_distance = 0;
+
 	if (cv_glbatching.value)
 		HWR_RenderBatches();
 
@@ -5531,7 +5633,7 @@ static void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 
 	// added by Hurdler for correct splitscreen
 	// moved here by hurdler so it works with the new near clipping plane
-	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
+	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE, FAR_ZCLIP_DEFAULT);
 }
 
 // ==========================================================================
@@ -5612,6 +5714,8 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
 
+	current_bsp_culling_distance = 0;
+
 	PS_STOP_TIMING(ps_bsptime);
 
 	if (cv_glbatching.value)
@@ -5661,7 +5765,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	// added by Hurdler for correct splitscreen
 	// moved here by hurdler so it works with the new near clipping plane
-	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
+	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE, FAR_ZCLIP_DEFAULT);
 }
 
 // Returns whether palette rendering is "actually enabled."
@@ -5756,6 +5860,9 @@ static CV_PossibleValue_t glfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSA
 	{0, NULL}};
 CV_PossibleValue_t glanisotropicmode_cons_t[] = {{1, "MIN"}, {16, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t glsecbright_cons_t[] = {{0, "MIN"}, {255, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t glrenderdistance_cons_t[] = {
+	{0, "Max"}, {1, "1024"}, {2, "2048"}, {3, "4096"}, {4, "6144"}, {5, "8192"},
+	{6, "12288"}, {7, "16384"}, {0, NULL}};
 
 consvar_t cv_glshaders = CVAR_INIT ("gr_shaders", "On", "Enables additional visual effects in OpenGL", CV_SAVE|CV_CALL, glshaders_cons_t, CV_glshaders_OnChange);
 
@@ -5785,6 +5892,8 @@ consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", NULL, 0, CV_OnOff,
 
 consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", "Whetever to send polygons in batches to the GPU or not", 0, CV_OnOff, NULL);
 
+consvar_t cv_glrenderdistance = CVAR_INIT("gr_renderdistance", "Max", "Changes how far level geometry is rendered", CV_SAVE, glrenderdistance_cons_t, NULL);
+
 static CV_PossibleValue_t glpalettedepth_cons_t[] = {{16, "16 bits"}, {24, "24 bits"}, {0, NULL}};
 
 consvar_t cv_glpaletterendering = CVAR_INIT ("gr_paletterendering", "On", "Emulate Software's coloring graphics (requires shaders)", CV_SAVE|CV_CALL, CV_OnOff, CV_glpaletterendering_OnChange);
@@ -5869,6 +5978,8 @@ void HWR_AddCommands(void)
 	CV_RegisterVar(&cv_glsolvetjoin);
 
 	CV_RegisterVar(&cv_glbatching);
+	CV_RegisterVar(&cv_glrenderdistance);
+
 
 	CV_RegisterVar(&cv_glpaletterendering);
 	CV_RegisterVar(&cv_glpalettedepth);
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 22ea3f24c73fb37b514a4e3ae07c13510c0b73f0..093cbe9693ed4c5addc0f492a9a5474a2597af01 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -96,6 +96,7 @@ extern consvar_t cv_glslopecontrast;
 extern consvar_t cv_glbatching;
 extern consvar_t cv_glpaletterendering;
 extern consvar_t cv_glpalettedepth;
+extern consvar_t cv_glrenderdistance;
 
 extern consvar_t cv_glwireframe;
 
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index db9fa17884e00779d8df9c37c3469707f798fbe7..bfdcb3e8e59c608829e6fd9dcf10107709d4e896 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -57,7 +57,9 @@ static GLuint NOTEXTURE_NUM = 0;
 #define      N_PI_DEMI               (M_PIl/2.0f) //(1.5707963268f)
 
 #define      ASPECT_RATIO            (1.0f)  //(320.0f/200.0f)
-#define      FAR_CLIPPING_PLANE      32768.0f // Draw further! Tails 01-21-2001
+//#define      FAR_CLIPPING_PLANE      32768.0f // Draw further! Tails 01-21-2001
+//#define      FAR_CLIPPING_PLANE      (4096.0f - 512.0f)
+static float FAR_CLIPPING_PLANE = 32768.0f;
 static float NEAR_CLIPPING_PLANE =   NZCLIP_PLANE;
 
 // **************************************************************************
@@ -1206,12 +1208,13 @@ EXPORT void HWRAPI(ReadScreenTexture) (int tex, UINT8 *dst_data)
 // -----------------+
 // GClipRect        : Defines the 2D hardware clipping window
 // -----------------+
-EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip)
+EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip, float farclip)
 {
 	// GL_DBG_Printf ("GClipRect(%d, %d, %d, %d)\n", minx, miny, maxx, maxy);
 
 	pglViewport(minx, screen_height-maxy, maxx-minx, maxy-miny);
 	NEAR_CLIPPING_PLANE = nearclip;
+	FAR_CLIPPING_PLANE = farclip;
 
 	//pglScissor(minx, screen_height-maxy, maxx-minx, maxy-miny);
 	pglMatrixMode(GL_PROJECTION);
diff --git a/src/m_menu.c b/src/m_menu.c
index 60a52052983a866ee28c9cee687f7796c86116c5..6a1a3ade9c4db441dcbac2490552925612c133fd 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1415,12 +1415,14 @@ static menuitem_t OP_OpenGLOptionsMenu[] =
 	{IT_STRING|IT_CVAR,         NULL, "Bit depth",           NULL, &cv_scr_depth,           124},
 	{IT_STRING|IT_CVAR,         NULL, "Texture filter",      NULL, &cv_glfiltermode,        134},
 	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",         NULL, &cv_glanisotropicmode,   144},
+	{IT_STRING|IT_CVAR,         NULL, "Render distance",     NULL, &cv_glrenderdistance,    154},
 #ifdef ALAM_LIGHTING
-	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",         NULL, &OP_OpenGLLightingDef,   154},
+	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",         NULL, &OP_OpenGLLightingDef,   164},
 #endif
 #if defined (_WINDOWS) && (!(defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)))
-	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",          NULL, &cv_fullscreen,          164},
+	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",          NULL, &cv_fullscreen,          174},
 #endif
+
 };
 
 #ifdef ALAM_LIGHTING
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index c78b43ec4097cafad8420ceb7497e2883220a67e..0445338792c2cf0201db34123fe53315e6145434 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -189,7 +189,7 @@ void OglSdlFinishUpdate(boolean waitvbl)
 	HWR_DrawScreenFinalTexture(sdlw, sdlh);
 	SDL_GL_SwapWindow(window);
 
-	GClipRect(0, 0, realwidth, realheight, NZCLIP_PLANE);
+	GClipRect(0, 0, realwidth, realheight, NZCLIP_PLANE, FAR_ZCLIP_DEFAULT);
 
 	// Sryder:	We need to draw the final screen texture again into the other buffer in the original position so that
 	//			effects that want to take the old screen can do so after this