From f0e00c5dc7c8d09bfd8ab3bb1045f45967d70be4 Mon Sep 17 00:00:00 2001
From: Ian Curtis <i.curtis@gmail.com>
Date: Sun, 2 Apr 2017 21:03:59 +0000
Subject: [PATCH] Rewrite the spot light code, and implement the missing fog
 logic. (HarryTuttle)

---
 Src/Graphics/New3D/Model.h          |   4 +-
 Src/Graphics/New3D/New3D.cpp        |  76 ++++-----
 Src/Graphics/New3D/R3DScrollFog.cpp | 102 ++++++++----
 Src/Graphics/New3D/R3DScrollFog.h   |  30 ++--
 Src/Graphics/New3D/R3DShader.cpp    | 243 +++++++++++++++-------------
 Src/Graphics/New3D/R3DShader.h      |  27 ++--
 6 files changed, 277 insertions(+), 205 deletions(-)

diff --git a/Src/Graphics/New3D/Model.h b/Src/Graphics/New3D/Model.h
index 540b009..bbb692c 100644
--- a/Src/Graphics/New3D/Model.h
+++ b/Src/Graphics/New3D/Model.h
@@ -137,11 +137,13 @@ struct Viewport
 	float	spotEllipse[4];			// spotlight ellipse (see RenderViewport())
 	float	spotRange[2];			// Z range
 	float	spotColor[3];			// color
-	float	fogParams[5];			// fog parameters (...)
+	float	fogParams[7];			// fog parameters (...)
 	float	scrollFog;				// a transparency value that determines if fog is blended over the bottom 2D layer
 	int		x, y;					// viewport coordinates (scaled and in OpenGL format)
 	int		width, height;			// viewport dimensions (scaled for display surface size)
 	int		priority;
+	float	spotFogColor[3];			// spotlight color on fog
+	float	scrollAtt;
 };
 
 enum class Clip { INSIDE, OUTSIDE, INTERCEPT, NOT_SET };
diff --git a/Src/Graphics/New3D/New3D.cpp b/Src/Graphics/New3D/New3D.cpp
index 4ca5129..af1b86c 100644
--- a/Src/Graphics/New3D/New3D.cpp
+++ b/Src/Graphics/New3D/New3D.cpp
@@ -97,15 +97,24 @@ void CNew3D::UploadTextures(unsigned level, unsigned x, unsigned y, unsigned wid
 
 void CNew3D::DrawScrollFog()
 {
+
+	// the logic for this is still not quite right
+	// the sroll fog value seems to be set with multiple viewports.. currently unknown which one to use
+
 	for (int i = 0; i < 4; i++) {
 
 		for (auto &n : m_nodes) {
 
 			if (n.viewport.scrollFog > 0 && n.viewport.priority == i) {	
 
-				float *rgb = n.viewport.fogParams;
+				float rgba[4]		= {n.viewport.fogParams[0], n.viewport.fogParams[1], n.viewport.fogParams[2], n.viewport.scrollFog};
+				float attenuation	= n.viewport.scrollAtt; // Seems to pass the wrong values!
+				float ambient		= n.viewport.fogParams[6];
+				float *spotRGB		= n.viewport.spotFogColor;
+				float *spotEllipse	= n.viewport.spotEllipse;
+
 				glViewport(0, 0, m_totalXRes, m_totalYRes);		// fill the whole viewport
-				m_r3dScrollFog.DrawScrollFog(rgb[0], rgb[1], rgb[2], n.viewport.scrollFog);
+				m_r3dScrollFog.DrawScrollFog(rgba, attenuation, ambient, spotRGB, spotEllipse);
 				return;
 			}
 
@@ -752,27 +761,36 @@ void CNew3D::RenderViewport(UINT32 addr)
 
 		// Spotlight
 		int spotColorIdx = (vpnode[0x20] >> 11) & 7;									// spotlight color index
-		vp->spotEllipse[0] = (float)((vpnode[0x1E] >> 3) & 0x1FFF);						// spotlight X position (fractional component?)
-		vp->spotEllipse[1] = (float)((vpnode[0x1D] >> 3) & 0x1FFF);						// spotlight Y
-		vp->spotEllipse[2] = (float)((vpnode[0x1E] >> 16) & 0xFFFF);					// spotlight X size (16-bit? May have fractional component below bit 16)
+		int spotFogColorIdx = (vpnode[0x20] >> 8) & 7;									// spotlight on fog color index
+		vp->spotEllipse[0] = (float)(INT16)(vpnode[0x1E] & 0xFFFF) / 8.0f;				// spotlight X position (13.3 fixed point)
+		vp->spotEllipse[1] = (float)(INT16)(vpnode[0x1D] & 0xFFFF) / 8.0f;				// spotlight Y
+		vp->spotEllipse[2] = (float)((vpnode[0x1E] >> 16) & 0xFFFF);					// spotlight X size (16-bit)
 		vp->spotEllipse[3] = (float)((vpnode[0x1D] >> 16) & 0xFFFF);					// spotlight Y size
 
 		vp->spotRange[0] = 1.0f / (*(float *)&vpnode[0x21]);							// spotlight start
 		vp->spotRange[1] = *(float *)&vpnode[0x1F];										// spotlight extent
 
-		if (vp->spotRange[1] == 0) {													// if light extent = 0 light is effectively disabled
-			spotColorIdx = 0;															
-		}
+		// Star Wars Trilogy needs this
+		vp->spotRange[0] = std::min(vp->spotRange[0], std::numeric_limits<float>::max());
+		vp->spotRange[0] = std::max(vp->spotRange[0], std::numeric_limits<float>::lowest());
 
 		vp->spotColor[0] = color[spotColorIdx][0];										// spotlight color
 		vp->spotColor[1] = color[spotColorIdx][1];
 		vp->spotColor[2] = color[spotColorIdx][2];
 
-		// Spotlight is applied on a per pixel basis, must scale its position and size to screen
-		vp->spotEllipse[1] = 384.0f - vp->spotEllipse[1];
-		vp->spotRange[1] += vp->spotRange[0];	// limit
-		vp->spotEllipse[2] = 496.0f / sqrt(vp->spotEllipse[2]);	// spotlight appears to be specified in terms of physical resolution (unconfirmed)
-		vp->spotEllipse[3] = 384.0f / sqrt(vp->spotEllipse[3]);
+		vp->spotFogColor[0] = color[spotFogColorIdx][0];								// spotlight color on fog
+		vp->spotFogColor[1] = color[spotFogColorIdx][1];
+		vp->spotFogColor[2] = color[spotFogColorIdx][2];
+
+		// spotlight is specified in terms of physical resolution
+		vp->spotEllipse[1] = 384.0f - vp->spotEllipse[1];								// flip Y position
+
+		// Avoid division by zero
+		vp->spotEllipse[2] = std::max(1.0f, vp->spotEllipse[2]);
+		vp->spotEllipse[3] = std::max(1.0f, vp->spotEllipse[3]);
+
+		vp->spotEllipse[2] = std::roundf(2047.0f / vp->spotEllipse[2]);
+		vp->spotEllipse[3] = std::roundf(2047.0f / vp->spotEllipse[3]);
 
 		// Scale the spotlight to the OpenGL viewport
 		vp->spotEllipse[0] = vp->spotEllipse[0] * m_xRatio + m_xOffs;
@@ -787,30 +805,16 @@ void CNew3D::RenderViewport(UINT32 addr)
 		vp->fogParams[3] = std::abs(*(float *)&vpnode[0x23]);						// fog density	- ocean hunter uses negative values, but looks the same
 		vp->fogParams[4] = (float)(INT16)(vpnode[0x25] & 0xFFFF)*(1.0f / 255.0f);	// fog start
 
+		// Avoid Infinite and NaN values for Star Wars Trilogy
+		if (std::isinf(vp->fogParams[3]) || std::isnan(vp->fogParams[3])) {
+			for (int i = 0; i < 7; i++) vp->fogParams[i] = 0.0f;
+		}
+
+		vp->fogParams[5] = (float)((vpnode[0x24] >> 16) & 0xFF) * (1.0f / 255.0f);	// fog attenuation
+		vp->fogParams[6] = (float)((vpnode[0x25] >> 16) & 0xFF) * (1.0f / 255.0f);	// fog ambient
+
 		vp->scrollFog = (float)(vpnode[0x20] & 0xFF) * (1.0f / 255.0f);				// scroll fog
-
-		// Unknown light/fog parameters
-		float scrollAtt = (float)(vpnode[0x24] & 0xFF) * (1.0f / 255.0f);			// scroll attenuation
-
-		{
-			//test fog paramaters
-			float lightFogColour[3];
-			int fogColourIdx;
-
-			fogColourIdx = (vpnode[0x20] >> 8) & 7;
-
-			lightFogColour[0] = color[fogColourIdx][0];
-			lightFogColour[1] = color[fogColourIdx][1];
-			lightFogColour[2] = color[fogColourIdx][2];
-
-			float fogAttenuation = ((vpnode[0x24] >> 16) & 0xFF) / 255.f;
-			float fogAmbient	 = ((vpnode[0x25] >> 16) & 0xFF) / 255.f;
-			int debug = 0;
-		}
-		
-		if (std::isinf(vp->fogParams[3]) || std::isnan(vp->fogParams[3]) || std::isinf(vp->fogParams[4]) || std::isnan(vp->fogParams[4])) {	// Star Wars Trilogy
-			vp->fogParams[3] = vp->fogParams[4] = 0.0f;
-		}
+		vp->scrollAtt = (float)(vpnode[0x24] & 0xFF) * (1.0f / 255.0f);				// scroll attenuation
 
 		// Clear texture offsets before proceeding
 		m_nodeAttribs.Reset();
diff --git a/Src/Graphics/New3D/R3DScrollFog.cpp b/Src/Graphics/New3D/R3DScrollFog.cpp
index 9519a13..77400a1 100644
--- a/Src/Graphics/New3D/R3DScrollFog.cpp
+++ b/Src/Graphics/New3D/R3DScrollFog.cpp
@@ -12,17 +12,49 @@ static const char *vertexShaderFog =
 	"{\n"
 	    "gl_Position = mvp * gl_Vertex;\n"
 	"}\n";
-
-static const char *fragmentShaderFog =
-
-	"uniform vec4 fogColour;\n"
-
-	"void main()\n"
-	"{\n"
-	    "gl_FragColor = fogColour;\n"
-	"}\n";
-
-
+
+static const char *fragmentShaderFog =
+
+	"uniform float	fogAttenuation;\n"
+	"uniform float	fogAmbient;\n"
+	"uniform vec4	fogColour;\n"
+	"uniform vec3	spotFogColor;\n"
+	"uniform vec4	spotEllipse;\n"
+
+	// Spotlight on fog
+	"float	ellipse;\n"
+	"vec2	position, size;\n"
+	"vec3	lSpotFogColor;\n"
+
+	// Scroll fog
+	"float	lfogAttenuation;\n"
+	"vec3	lFogColor;\n"
+	"vec4	scrollFog;\n"
+
+	"void main()\n"
+	"{\n"
+		// Scroll fog base color
+		"lFogColor = fogColour.rgb * fogAmbient;\n"
+
+		// Spotlight on fog (area) 
+		"position = spotEllipse.xy;\n"
+		"size = spotEllipse.zw;\n"
+		"ellipse = length((gl_FragCoord.xy - position) / size);\n"
+		"ellipse = pow(ellipse, 2.0);\n"			// decay rate = square of distance from center
+		"ellipse = 1.0 - ellipse;\n"				// invert
+		"ellipse = max(0.0, ellipse);\n"			// clamp
+
+		// Spotlight on fog (color)
+		"lSpotFogColor = mix(spotFogColor * ellipse * fogColour.rgb, vec3(0.0), fogAttenuation);\n"
+
+		// Scroll fog density
+		"scrollFog = vec4(lFogColor + lSpotFogColor, fogColour.a);\n"
+
+		// Final Color
+		"gl_FragColor = scrollFog;\n"
+	"}\n";
+
+
 R3DScrollFog::R3DScrollFog()
 {
 	//default coordinates are NDC -1,1 etc
@@ -44,13 +76,13 @@ R3DScrollFog::R3DScrollFog()
 
 R3DScrollFog::~R3DScrollFog()
 {
-	DeallocResources();
-}
-
-void R3DScrollFog::DrawScrollFog(float r, float g, float b, float a)
-{
-	//=======
-	Mat4 mvp;
+	DeallocResources();
+}
+
+void R3DScrollFog::DrawScrollFog(float rgba[4], float attenuation, float ambient, float *spotRGB, float *spotEllipse)
+{
+	//=======
+	Mat4 mvp;
 	//=======
 
 	// yeah this would have been much easier with immediate mode and fixed function ..  >_<
@@ -60,13 +92,17 @@ void R3DScrollFog::DrawScrollFog(float r, float g, float b, float a)
 	glDisable			(GL_DEPTH_TEST);	// disable depth testing
 	glEnable			(GL_BLEND);
 	glBlendFunc			(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-	m_vbo.Bind			(true);
-	glUseProgram		(m_shaderProgram);
-	glUniform4f			(m_locFogColour, r, g, b, a);
-	glUniformMatrix4fv	(m_locMVP, 1, GL_FALSE, mvp);
-
-	glEnableClientState	(GL_VERTEX_ARRAY);
+
+	m_vbo.Bind			(true);
+	glUseProgram		(m_shaderProgram);
+	glUniform4f			(m_locFogColour, rgba[0], rgba[1], rgba[2], rgba[3]);
+	glUniform1f			(m_locFogAttenuation, attenuation);
+	glUniform1f			(m_locFogAmbient, ambient);
+	glUniform3f			(m_locSpotFogColor, spotRGB[0], spotRGB[1], spotRGB[2]);
+	glUniform4f			(m_locSpotEllipse, spotEllipse[0], spotEllipse[1], spotEllipse[2], spotEllipse[3]);
+	glUniformMatrix4fv	(m_locMVP, 1, GL_FALSE, mvp);
+
+	glEnableClientState	(GL_VERTEX_ARRAY);
 	glVertexPointer		(3, GL_FLOAT, sizeof(SFVertex), 0);
 	glDrawArrays		(GL_TRIANGLES, 0, 6);
 	glDisableClientState(GL_VERTEX_ARRAY);
@@ -81,12 +117,16 @@ void R3DScrollFog::DrawScrollFog(float r, float g, float b, float a)
 void R3DScrollFog::AllocResources()
 {
 	bool success = LoadShaderProgram(&m_shaderProgram, &m_vertexShader, &m_fragmentShader, std::string(), std::string(), vertexShaderFog, fragmentShaderFog);
-
-	m_locMVP		= glGetUniformLocation(m_shaderProgram, "mvp");
-	m_locFogColour	= glGetUniformLocation(m_shaderProgram, "fogColour");
-
-	m_vbo.Create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(SFTriangle) * (2), m_triangles);
-}
+
+	m_locMVP			= glGetUniformLocation(m_shaderProgram, "mvp");
+	m_locFogColour		= glGetUniformLocation(m_shaderProgram, "fogColour");
+	m_locFogAttenuation	= glGetUniformLocation(m_shaderProgram, "fogAttenuation");
+	m_locFogAmbient		= glGetUniformLocation(m_shaderProgram, "fogAmbient");
+	m_locSpotFogColor	= glGetUniformLocation(m_shaderProgram, "spotFogColor");
+	m_locSpotEllipse	= glGetUniformLocation(m_shaderProgram, "spotEllipse");
+
+	m_vbo.Create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(SFTriangle) * (2), m_triangles);
+}
 
 void R3DScrollFog::DeallocResources()
 {
diff --git a/Src/Graphics/New3D/R3DScrollFog.h b/Src/Graphics/New3D/R3DScrollFog.h
index 893607f..c44214d 100644
--- a/Src/Graphics/New3D/R3DScrollFog.h
+++ b/Src/Graphics/New3D/R3DScrollFog.h
@@ -9,13 +9,13 @@ class R3DScrollFog
 {
 public:
 
-	R3DScrollFog();
-	~R3DScrollFog();
-
-	void DrawScrollFog(float r, float g, float b, float a);
-
-private:
-
+	R3DScrollFog();
+	~R3DScrollFog();
+
+	void DrawScrollFog(float rbga[4], float attenuation, float ambient, float *spotRGB, float *spotEllipse);
+
+private:
+
 	void AllocResources();
 	void DeallocResources();
 
@@ -42,12 +42,16 @@ private:
 	GLuint m_shaderProgram;
 	GLuint m_vertexShader;
 	GLuint m_fragmentShader;
-
-	GLuint m_locFogColour;
-	GLuint m_locMVP;
-
-	VBO m_vbo;
-};
+
+	GLuint m_locFogColour;
+	GLuint m_locMVP;
+	GLuint m_locFogAttenuation;
+	GLuint m_locFogAmbient;
+	GLuint m_locSpotFogColor;
+	GLuint m_locSpotEllipse;
+
+	VBO m_vbo;
+};
 
 }
 
diff --git a/Src/Graphics/New3D/R3DShader.cpp b/Src/Graphics/New3D/R3DShader.cpp
index 1600c34..ae8afbb 100644
--- a/Src/Graphics/New3D/R3DShader.cpp
+++ b/Src/Graphics/New3D/R3DShader.cpp
@@ -2,30 +2,29 @@
 #include "Graphics/Shader.h"
 
 namespace New3D {
-
-static const char *vertexShaderBasic =
-
+
+static const char *vertexShaderBasic =
+
 // uniforms
 "uniform float	fogIntensity;\n"
 "uniform float	fogDensity;\n"
-"uniform float	fogStart;\n"
-
-//outputs to fragment shader
-"varying float	fsFogFactor;\n"
-"varying float	fsSpecularTerm;\n"		// specular light term (additive)
-"varying vec3	fsViewVertex;\n"
-"varying vec3	fsViewNormal;\n"		// per vertex normal vector
+"uniform float	fogStart;\n"
+
+//outputs to fragment shader
+"varying float	fsFogFactor;\n"
+"varying vec3	fsViewVertex;\n"
+"varying vec3	fsViewNormal;\n"		// per vertex normal vector
 "varying vec4   fsColor;\n"
 
 "void main(void)\n"
-"{\n"
-	"fsViewVertex	= vec3(gl_ModelViewMatrix * gl_Vertex);\n"
-	"fsViewNormal	= normalize(gl_NormalMatrix  *gl_Normal);\n"
+"{\n"
+	"fsViewVertex	= vec3(gl_ModelViewMatrix * gl_Vertex);\n"
+	"fsViewNormal	= normalize(gl_NormalMatrix  *gl_Normal);\n"
 	"float z		= length(fsViewVertex);\n"
-	"fsFogFactor	= fogIntensity * clamp(fogStart + z * fogDensity, 0.0, 1.0);\n"
-
-	"fsColor    	= gl_Color;\n"
-	"gl_TexCoord[0]	= gl_MultiTexCoord0;\n"
+	"fsFogFactor	= fogIntensity * clamp(fogStart + z * fogDensity, 0.0, 1.0);\n"
+
+	"fsColor    	= gl_Color;\n"
+	"gl_TexCoord[0]	= gl_MultiTexCoord0;\n"
 	"gl_Position	= gl_ModelViewProjectionMatrix * gl_Vertex;\n"
 "}\n";
 
@@ -41,40 +40,45 @@ static const char *fragmentShaderBasic =
 "uniform int	alphaTest;\n"
 "uniform int	textureAlpha;\n"
 "uniform vec3	fogColour;\n"
-"uniform vec4	spotEllipse;\n"			// spotlight ellipse position: .x=X position (screen coordinates), .y=Y position, .z=half-width, .w=half-height)
-"uniform vec2	spotRange;\n"			// spotlight Z range: .x=start (viewspace coordinates), .y=limit
-"uniform vec3	spotColor;\n"			// spotlight RGB color
-"uniform vec3	lighting[2];\n"			// lighting state (lighting[0] = sun direction, lighting[1].x,y = diffuse, ambient intensities from 0-1.0)
-"uniform int	lightEnable;\n"			// lighting enabled (1.0) or luminous (0.0), drawn at full intensity
-"uniform float	specularCoefficient;\n"	// specular coefficient
-"uniform float	shininess;\n"			// specular shininess
-
-//interpolated inputs from vertex shader
-"varying float	fsFogFactor;\n"
-"varying float	fsSpecularTerm;\n"		// specular light term (additive)
-"varying vec3	fsViewVertex;\n"
-"varying vec3	fsViewNormal;\n"		// per vertex normal vector
+"uniform vec4	spotEllipse;\n"			// spotlight ellipse position: .x=X position (screen coordinates), .y=Y position, .z=half-width, .w=half-height)
+"uniform vec2	spotRange;\n"			// spotlight Z range: .x=start (viewspace coordinates), .y=limit
+"uniform vec3	spotColor;\n"			// spotlight RGB color
+"uniform vec3	spotFogColor;\n"		// spotlight RGB color on fog
+"uniform vec3	lighting[2];\n"			// lighting state (lighting[0] = sun direction, lighting[1].x,y = diffuse, ambient intensities from 0-1.0)
+"uniform int	lightEnable;\n"			// lighting enabled (1.0) or luminous (0.0), drawn at full intensity
+"uniform float	specularCoefficient;\n"	// specular coefficient
+"uniform float	shininess;\n"			// specular shininess
+"uniform float	fogAttenuation;\n"
+"uniform float	fogAmbient;\n"
+
+//interpolated inputs from vertex shader
+"varying float	fsFogFactor;\n"
+"varying vec3	fsViewVertex;\n"
+"varying vec3	fsViewNormal;\n"		// per vertex normal vector
 "varying vec4   fsColor;\n"
 
 "void main()\n"
 "{\n"
-  "vec4 tex1Data;\n"
-  "vec4 colData;\n"
-  "vec4 finalData;\n"
-
-  "bool discardFragment = false;\n"
-
-  "tex1Data = vec4(1.0, 1.0, 1.0, 1.0);\n"
-
-  "if(textureEnabled==1) {\n"
-
-    "tex1Data = texture2D( tex1, gl_TexCoord[0].st);\n"
-
-    "if (microTexture==1) {\n"
-	  "vec2 scale    = baseTexSize/256.0;\n"
-      "vec4 tex2Data = texture2D( tex2, gl_TexCoord[0].st * scale * microTextureScale);\n"
-
-      "tex1Data = (tex1Data+tex2Data)/2.0;\n"
+  "vec4 tex1Data;\n"
+  "vec4 colData;\n"
+  "vec4 finalData;\n"
+  "vec4 fogData;\n"
+
+  "bool discardFragment = false;\n"
+
+  "fogData = vec4(fogColour.rgb * fogAmbient, fsFogFactor);\n"
+
+  "tex1Data = vec4(1.0, 1.0, 1.0, 1.0);\n"
+
+  "if(textureEnabled==1) {\n"
+
+    "tex1Data = texture2D( tex1, gl_TexCoord[0].st);\n"
+
+    "if (microTexture==1) {\n"
+      "vec2 scale    = baseTexSize/256.0;\n"
+      "vec4 tex2Data = texture2D( tex2, gl_TexCoord[0].st * scale * microTextureScale);\n"
+
+      "tex1Data = (tex1Data+tex2Data)/2.0;\n"
     "}\n"
 
     "if (alphaTest==1) {\n"         // does it make any sense to do this later?
@@ -95,14 +99,20 @@ static const char *fragmentShaderBasic =
     "discardFragment = true;\n"
   "}\n"
 
-  "if (discardFragment) {\n"
-    "discard;\n"
-  "}\n"
-   
-  "if (lightEnable==1) {\n"
-    "vec3   lightIntensity;\n"
-    "vec3   sunVector;\n"     // sun lighting vector (as reflecting away from vertex)
-    "float   sunFactor;\n"    // sun light projection along vertex normal (0.0 to 1.0)
+  "if (discardFragment) {\n"
+    "discard;\n"
+  "}\n"
+
+  "float ellipse;\n"
+  "ellipse = length((gl_FragCoord.xy - spotEllipse.xy) / spotEllipse.zw);\n"
+  "ellipse = pow(ellipse, 2.0);\n"  // decay rate = square of distance from center
+  "ellipse = 1.0 - ellipse;\n"      // invert
+  "ellipse = max(0.0, ellipse);\n"  // clamp
+
+  "if (lightEnable==1) {\n"
+    "vec3   lightIntensity;\n"
+    "vec3   sunVector;\n"     // sun lighting vector (as reflecting away from vertex)
+    "float  sunFactor;\n"     // sun light projection along vertex normal (0.0 to 1.0)
 
     // Real3D -> OpenGL view space convention (TO-DO: do this outside of shader)
     "sunVector = lighting[0] * vec3(1.0, -1.0, -1.0);\n"
@@ -112,40 +122,43 @@ static const char *fragmentShaderBasic =
 
     // Total light intensity: sum of all components 
     "lightIntensity = vec3(sunFactor*lighting[1].x + lighting[1].y);\n"   // ambient + diffuse
-
-    "lightIntensity = clamp(lightIntensity,0.0,1.0);\n"
-
-    "vec2   ellipse;\n"
-    "float   insideSpot;\n"
-
-    // Compute spotlight and apply lighting
-    "ellipse   = (gl_FragCoord.xy - spotEllipse.xy) / spotEllipse.zw;\n"
-    "insideSpot = dot(ellipse, ellipse);\n"
-
-    "if ((insideSpot <= 1.0) && (-fsViewVertex.z >= spotRange.x)) {\n"
-      "lightIntensity.rgb += (1.0 - insideSpot)*spotColor;\n"
-    "}\n"
-
-    "finalData.rgb *= lightIntensity;\n"
-
-    "if (sunFactor > 0.0 && specularCoefficient > 0.0) {\n"
-
-	  "float nDotL = max(dot(fsViewNormal,sunVector),0.0);\n"
-
-      "finalData.rgb += vec3(specularCoefficient * pow(nDotL,shininess));\n"
-
-	  //"vec3 v = normalize(-fsViewVertex);\n"
-	  //"vec3 h = normalize(sunVector + v);\n"   // halfway vector
-	  //"float NdotHV = max(dot(fsViewNormal,h),0.0);\n"
-	  //"finalData.rgb += vec3(specularCoefficient * pow(NdotHV,shininess));\n"
-    "}\n"
-  "}\n"
-
-
-  "finalData.rgb = mix(finalData.rgb, fogColour, fsFogFactor);\n"
-
-  "gl_FragColor = finalData;\n"
-"}\n";
+
+    "lightIntensity = clamp(lightIntensity,0.0,1.0);\n"
+
+    // Compute spotlight and apply lighting
+    "float enable, range, d;\n"
+    "float inv_r = 1.0 / spotEllipse.z;\n" // slope of decay function
+
+    "d = spotRange.x + spotRange.y + fsViewVertex.z;\n"
+    "enable = step(spotRange.x + min(spotRange.y, 0.0), -fsViewVertex.z);\n"
+
+    // inverse-linear falloff
+    // Reference: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
+    // y = 1 / (d/r + 1)^2
+    "range = 1.0 / pow(min(0.0, d * inv_r) - 1.0, 2.0);\n"
+    "range = clamp(range, 0.0, 1.0);\n"
+    "range *= enable;\n"
+
+    "float lobeEffect = range * ellipse;\n"
+
+    "lightIntensity.rgb += spotColor*lobeEffect;\n"
+
+    "finalData.rgb *= lightIntensity;\n"
+
+    "if (sunFactor > 0.0 && specularCoefficient > 0.0) {\n"
+      "float nDotL = max(dot(fsViewNormal,sunVector),0.0);\n"
+      "finalData.rgb += vec3(specularCoefficient * pow(nDotL,shininess));\n"
+    "}\n"
+  "}\n"
+
+  // Spotlight on fog
+  "vec3 lSpotFogColor = spotFogColor * ellipse * fogColour.rgb;\n"
+
+  // Fog & spotlight applied
+  "finalData.rgb = mix(finalData.rgb, lSpotFogColor * fogAttenuation + fogData.rgb, fogData.a);\n"
+
+  "gl_FragColor = finalData;\n"
+"}\n";
 
 R3DShader::R3DShader()
 {
@@ -211,20 +224,23 @@ bool R3DShader::LoadShader(const char* vertexShader, const char* fragmentShader)
 	m_locBaseTexSize	= glGetUniformLocation(m_shaderProgram, "baseTexSize");
 
 	m_locFogIntensity	= glGetUniformLocation(m_shaderProgram, "fogIntensity");
-	m_locFogDensity		= glGetUniformLocation(m_shaderProgram, "fogDensity");
-	m_locFogStart		= glGetUniformLocation(m_shaderProgram, "fogStart");
-	m_locFogColour		= glGetUniformLocation(m_shaderProgram, "fogColour");
-
-	m_locLighting		= glGetUniformLocation(m_shaderProgram, "lighting");
-	m_locLightEnable	= glGetUniformLocation(m_shaderProgram, "lightEnable");
+	m_locFogDensity		= glGetUniformLocation(m_shaderProgram, "fogDensity");
+	m_locFogStart		= glGetUniformLocation(m_shaderProgram, "fogStart");
+	m_locFogColour		= glGetUniformLocation(m_shaderProgram, "fogColour");
+	m_locFogAttenuation	= glGetUniformLocation(m_shaderProgram, "fogAttenuation");
+	m_locFogAmbient		= glGetUniformLocation(m_shaderProgram, "fogAmbient");
+
+	m_locLighting		= glGetUniformLocation(m_shaderProgram, "lighting");
+	m_locLightEnable	= glGetUniformLocation(m_shaderProgram, "lightEnable");
 	m_locShininess		= glGetUniformLocation(m_shaderProgram, "shininess");
 	m_locSpecCoefficient= glGetUniformLocation(m_shaderProgram, "specularCoefficient");
-	m_locSpotEllipse	= glGetUniformLocation(m_shaderProgram, "spotEllipse");
-	m_locSpotRange		= glGetUniformLocation(m_shaderProgram, "spotRange");
-	m_locSpotColor		= glGetUniformLocation(m_shaderProgram, "spotColor");
-
-	return success;
-}
+	m_locSpotEllipse	= glGetUniformLocation(m_shaderProgram, "spotEllipse");
+	m_locSpotRange		= glGetUniformLocation(m_shaderProgram, "spotRange");
+	m_locSpotColor		= glGetUniformLocation(m_shaderProgram, "spotColor");
+	m_locSpotFogColor	= glGetUniformLocation(m_shaderProgram, "spotFogColor");
+
+	return success;
+}
 
 void R3DShader::SetShader(bool enable)
 {
@@ -331,17 +347,20 @@ void R3DShader::SetMeshUniforms(const Mesh* m)
 void R3DShader::SetViewportUniforms(const Viewport *vp)
 {	
 	//didn't bother caching these, they don't get frequently called anyway
-	glUniform1f	(m_locFogDensity, vp->fogParams[3]);
-	glUniform1f	(m_locFogStart, vp->fogParams[4]);
-	glUniform3fv(m_locFogColour, 1, vp->fogParams);
-
-	glUniform3fv(m_locLighting, 2, vp->lightingParams);
-	glUniform4fv(m_locSpotEllipse, 1, vp->spotEllipse);
-	glUniform2fv(m_locSpotRange, 1, vp->spotRange);
-	glUniform3fv(m_locSpotColor, 1, vp->spotColor);
-}
-
-void R3DShader::SetModelStates(const Model* model)
+	glUniform1f	(m_locFogDensity, vp->fogParams[3]);
+	glUniform1f	(m_locFogStart, vp->fogParams[4]);
+	glUniform3fv(m_locFogColour, 1, vp->fogParams);
+	glUniform1f	(m_locFogAttenuation, vp->fogParams[5]);
+	glUniform1f	(m_locFogAmbient, vp->fogParams[6]);
+
+	glUniform3fv(m_locLighting, 2, vp->lightingParams);
+	glUniform4fv(m_locSpotEllipse, 1, vp->spotEllipse);
+	glUniform2fv(m_locSpotRange, 1, vp->spotRange);
+	glUniform3fv(m_locSpotColor, 1, vp->spotColor);
+	glUniform3fv(m_locSpotFogColor, 1, vp->spotFogColor);
+}
+
+void R3DShader::SetModelStates(const Model* model)
 {
 	//==========
 	MatDet test;
diff --git a/Src/Graphics/New3D/R3DShader.h b/Src/Graphics/New3D/R3DShader.h
index 867a29c..bad4636 100644
--- a/Src/Graphics/New3D/R3DShader.h
+++ b/Src/Graphics/New3D/R3DShader.h
@@ -59,20 +59,23 @@ private:
 
 	// viewport uniform locations
 	GLint m_locFogIntensity;
-	GLint m_locFogDensity;
-	GLint m_locFogStart;
-	GLint m_locFogColour;
-
-	// lighting
-	GLint m_locLighting;
+	GLint m_locFogDensity;
+	GLint m_locFogStart;
+	GLint m_locFogColour;
+	GLint m_locFogAttenuation;
+	GLint m_locFogAmbient;
+
+	// lighting
+	GLint m_locLighting;
 	GLint m_locLightEnable;
 	GLint m_locShininess;
 	GLint m_locSpecCoefficient;
-	GLint m_locSpotEllipse;
-	GLint m_locSpotRange;
-	GLint m_locSpotColor;
-};
-
-} // New3D
+	GLint m_locSpotEllipse;
+	GLint m_locSpotRange;
+	GLint m_locSpotColor;
+	GLint m_locSpotFogColor;
+};
+
+} // New3D
 
 #endif
\ No newline at end of file