Bläddra i källkod

Merge pull request #11232 from WestLangley/dev-rect_area

RectAreaLight: cleanup and refactoring
Mr.doob 8 år sedan
förälder
incheckning
1558bf0ec0

+ 74 - 341
src/renderers/shaders/ShaderChunk/bsdfs.glsl

@@ -1,27 +1,28 @@
 float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {
 
-		if( decayExponent > 0.0 ) {
+	if( decayExponent > 0.0 ) {
 
 #if defined ( PHYSICALLY_CORRECT_LIGHTS )
 
-			// based upon Frostbite 3 Moving to Physically-based Rendering
-			// page 32, equation 26: E[window1]
-			// http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr_v2.pdf
-			// this is intended to be used on spot and point lights who are represented as luminous intensity
-			// but who must be converted to luminous irradiance for surface lighting calculation
-			float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );
-			float maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );
-			return distanceFalloff * maxDistanceCutoffFactor;
+		// based upon Frostbite 3 Moving to Physically-based Rendering
+		// page 32, equation 26: E[window1]
+		// http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr_v2.pdf
+		// this is intended to be used on spot and point lights who are represented as luminous intensity
+		// but who must be converted to luminous irradiance for surface lighting calculation
+		float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );
+		float maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );
+		return distanceFalloff * maxDistanceCutoffFactor;
 
 #else
 
-			return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );
+		return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );
 
 #endif
 
-		}
+	}
+
+	return 1.0;
 
-		return 1.0;
 }
 
 vec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {
@@ -30,11 +31,10 @@ vec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {
 
 } // validated
 
-
 vec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {
 
 	// Original approximation by Christophe Schlick '94
-	//;float fresnel = pow( 1.0 - dotLH, 5.0 );
+	// float fresnel = pow( 1.0 - dotLH, 5.0 );
 
 	// Optimized variant (presented by Epic at SIGGRAPH '13)
 	float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );
@@ -43,7 +43,6 @@ vec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {
 
 } // validated
 
-
 // Microfacet Models for Refraction through Rough Surfaces - equation (34)
 // http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
 // alpha is "roughness squared" in Disney’s reparameterization
@@ -60,7 +59,8 @@ float G_GGX_Smith( const in float alpha, const in float dotNL, const in float do
 
 } // validated
 
-// from page 12, listing 2 of http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr_v2.pdf
+// Moving Frostbite to Physically Based Rendering 2.0 - page 12, listing 2
+// http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr_v2.pdf
 float G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {
 
 	float a2 = pow2( alpha );
@@ -72,8 +72,6 @@ float G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const i
 	return 0.5 / max( gv + gl, EPSILON );
 }
 
-
-
 // Microfacet Models for Refraction through Rough Surfaces - equation (33)
 // http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
 // alpha is "roughness squared" in Disney’s reparameterization
@@ -87,7 +85,6 @@ float D_GGX( const in float alpha, const in float dotNH ) {
 
 }
 
-
 // GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
 vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {
 
@@ -110,29 +107,21 @@ vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in Geometric
 
 } // validated
 
-//
-// Rect Area Light BRDF Approximations
-//
+// Rect Area Light
 
 // Area light computation code adapted from:
-// http://blog.selfshadow.com/sandbox/ltc.html
-//
-// Based on paper:
 // Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
 // By: Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
+// https://drive.google.com/file/d/0BzvWIdpUpRx_d09ndGVjNVJzZjA/view
 // https://eheitzresearch.wordpress.com/415-2/
+// http://blog.selfshadow.com/sandbox/ltc.html
 
-vec2 ltcTextureCoords( const in GeometricContext geometry, const in float roughness ) {
+vec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {
 
 	const float LUT_SIZE  = 64.0;
-	const float LUT_SCALE = (LUT_SIZE - 1.0)/LUT_SIZE;
-	const float LUT_BIAS  = 0.5/LUT_SIZE;
-
-	vec3 N = geometry.normal;
-	vec3 V = geometry.viewDir;
-	vec3 P = geometry.position;
+	const float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;
+	const float LUT_BIAS  = 0.5 / LUT_SIZE;
 
-	// view angle on surface determines which LTC BRDF values we use
 	float theta = acos( dot( N, V ) );
 
 	// Parameterization of texture:
@@ -151,340 +140,84 @@ vec2 ltcTextureCoords( const in GeometricContext geometry, const in float roughn
 
 }
 
-void clipQuadToHorizon( inout vec3 L[5], out int n ) {
-
-	// detect clipping config
-	int config = 0;
-	if ( L[0].z > 0.0 ) config += 1;
-	if ( L[1].z > 0.0 ) config += 2;
-	if ( L[2].z > 0.0 ) config += 4;
-	if ( L[3].z > 0.0 ) config += 8;
-
-	// clip
-	n = 0;
-
-	if ( config == 0 ) {
-
-		// clip all
-
-	} else if ( config == 1 ) {
-
-		// V1 clip V2 V3 V4
-		n = 3;
-		L[1] = -L[1].z * L[0] + L[0].z * L[1];
-		L[2] = -L[3].z * L[0] + L[0].z * L[3];
-
-	} else if ( config == 2 ) {
-
-		// V2 clip V1 V3 V4
-		n = 3;
-		L[0] = -L[0].z * L[1] + L[1].z * L[0];
-		L[2] = -L[2].z * L[1] + L[1].z * L[2];
-
-	} else if ( config == 3 ) {
-
-		// V1 V2 clip V3 V4
-		n = 4;
-		L[2] = -L[2].z * L[1] + L[1].z * L[2];
-		L[3] = -L[3].z * L[0] + L[0].z * L[3];
-
-	} else if ( config == 4 ) {
-
-		// V3 clip V1 V2 V4
-		n = 3;
-		L[0] = -L[3].z * L[2] + L[2].z * L[3];
-		L[1] = -L[1].z * L[2] + L[2].z * L[1];
-
-	} else if ( config == 5 ) {
-
-		// V1 V3 clip V2 V4) impossible
-		n = 0;
-
-	} else if ( config == 6 ) {
-
-		// V2 V3 clip V1 V4
-		n = 4;
-		L[0] = -L[0].z * L[1] + L[1].z * L[0];
-		L[3] = -L[3].z * L[2] + L[2].z * L[3];
-
-	} else if ( config == 7 ) {
-
-		// V1 V2 V3 clip V4
-		n = 5;
-		L[4] = -L[3].z * L[0] + L[0].z * L[3];
-		L[3] = -L[3].z * L[2] + L[2].z * L[3];
-
-	} else if ( config == 8 ) {
-
-		// V4 clip V1 V2 V3
-		n = 3;
-		L[0] = -L[0].z * L[3] + L[3].z * L[0];
-		L[1] = -L[2].z * L[3] + L[3].z * L[2];
-		L[2] =  L[3];
-
-	} else if ( config == 9 ) {
-
-		// V1 V4 clip V2 V3
-		n = 4;
-		L[1] = -L[1].z * L[0] + L[0].z * L[1];
-		L[2] = -L[2].z * L[3] + L[3].z * L[2];
-
-	} else if ( config == 10 ) {
-
-		// V2 V4 clip V1 V3) impossible
-		n = 0;
-
-	} else if ( config == 11 ) {
-
-		// V1 V2 V4 clip V3
-		n = 5;
-		L[4] = L[3];
-		L[3] = -L[2].z * L[3] + L[3].z * L[2];
-		L[2] = -L[2].z * L[1] + L[1].z * L[2];
-
-	} else if ( config == 12 ) {
-
-		// V3 V4 clip V1 V2
-		n = 4;
-		L[1] = -L[1].z * L[2] + L[2].z * L[1];
-		L[0] = -L[0].z * L[3] + L[3].z * L[0];
-
-	} else if ( config == 13 ) {
-
-		// V1 V3 V4 clip V2
-		n = 5;
-		L[4] = L[3];
-		L[3] = L[2];
-		L[2] = -L[1].z * L[2] + L[2].z * L[1];
-		L[1] = -L[1].z * L[0] + L[0].z * L[1];
-
-	} else if ( config == 14 ) {
-
-		// V2 V3 V4 clip V1
-		n = 5;
-		L[4] = -L[0].z * L[3] + L[3].z * L[0];
-		L[0] = -L[0].z * L[1] + L[1].z * L[0];
-
-	} else if ( config == 15 ) {
-
-		// V1 V2 V3 V4
-		n = 4;
-
-	}
-
-	if ( n == 3 )
-		L[3] = L[0];
-	if ( n == 4 )
-		L[4] = L[0];
-
-}
-
-// Equation (11) of "Real-Time Polygonal-Light Shading with Linearly Transformed Cosines"
-float integrateLtcBrdfOverRectEdge( vec3 v1, vec3 v2 ) {
+// Real-Time Area Lighting: a Journey from Research to Production
+// By: Stephen Hill & Eric Heitz
+// http://advances.realtimerendering.com/s2016/s2016_ltc_rnd.pdf
+// An approximation for the form factor of a clipped rectangle.
+float LTC_ClippedSphereFormFactor( const in vec3 f ) {
 
-	float cosTheta = dot( v1, v2 );
-	float theta = acos( cosTheta );
-	float res = cross( v1, v2 ).z * ( ( theta > 0.001 ) ? theta / sin( theta ) : 1.0 );
+	float l = length( f );
 
-	return res;
+	return max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );
 
 }
 
-void initRectPoints( const in vec3 pos, const in vec3 halfWidth, const in vec3 halfHeight, out vec3 rectPoints[4] ) {
+// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
+// also Real-Time Area Lighting: a Journey from Research to Production
+// http://advances.realtimerendering.com/s2016/s2016_ltc_rnd.pdf
+// Normalization by 2*PI is incorporated in this function itself.
+// theta/sin(theta) is approximated by rational polynomial
+vec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {
 
-	rectPoints[0] = pos - halfWidth - halfHeight;
-	rectPoints[1] = pos + halfWidth - halfHeight;
-	rectPoints[2] = pos + halfWidth + halfHeight;
-	rectPoints[3] = pos - halfWidth + halfHeight;
+	float x = dot( v1, v2 );
 
-}
+	float y = abs( x );
+	float a = 0.86267 + (0.49788 + 0.01436 * y ) * y;
+	float b = 3.45068 + (4.18814 + y) * y;
+	float v = a / b;
 
-vec3 integrateLtcBrdfOverRect( const in GeometricContext geometry, const in mat3 brdfMat, const in vec3 rectPoints[4] ) {
+	float theta_sintheta = (x > 0.0) ? v : 0.5 * inversesqrt( 1.0 - x * x ) - v;
 
-	vec3 N = geometry.normal;
-	vec3 V = geometry.viewDir;
-	vec3 P = geometry.position;
-
-	// construct orthonormal basis around N
-	vec3 T1, T2;
-	T1 = normalize(V - N * dot( V, N ));
-	// TODO (abelnation): I had to negate this cross product to get proper light.  Curious why sample code worked without negation
-	T2 = - cross( N, T1 );
-
-	// rotate area light in (T1, T2, N) basis
-	mat3 brdfWrtSurface = brdfMat * transpose( mat3( T1, T2, N ) );
-
-	// transformed rect relative to surface point
-	vec3 clippedRect[5];
-	clippedRect[0] = brdfWrtSurface * ( rectPoints[0] - P );
-	clippedRect[1] = brdfWrtSurface * ( rectPoints[1] - P );
-	clippedRect[2] = brdfWrtSurface * ( rectPoints[2] - P );
-	clippedRect[3] = brdfWrtSurface * ( rectPoints[3] - P );
-
-	// clip light rect to horizon, resulting in at most 5 points
-	// we do this because we are integrating the BRDF over the hemisphere centered on the surface points normal
-	int n;
-	clipQuadToHorizon(clippedRect, n);
-
-	// light is completely below horizon
-	if ( n == 0 )
-		return vec3( 0, 0, 0 );
-
-	// project clipped rect onto sphere
-	clippedRect[0] = normalize( clippedRect[0] );
-	clippedRect[1] = normalize( clippedRect[1] );
-	clippedRect[2] = normalize( clippedRect[2] );
-	clippedRect[3] = normalize( clippedRect[3] );
-	clippedRect[4] = normalize( clippedRect[4] );
-
-	// integrate
-	// simplified integration only needs to be evaluated for each edge in the polygon
-	float sum = 0.0;
-	sum += integrateLtcBrdfOverRectEdge( clippedRect[0], clippedRect[1] );
-	sum += integrateLtcBrdfOverRectEdge( clippedRect[1], clippedRect[2] );
-	sum += integrateLtcBrdfOverRectEdge( clippedRect[2], clippedRect[3] );
-	if (n >= 4)
-		sum += integrateLtcBrdfOverRectEdge( clippedRect[3], clippedRect[4] );
-	if (n == 5)
-		sum += integrateLtcBrdfOverRectEdge( clippedRect[4], clippedRect[0] );
-
-	// TODO (abelnation): two-sided area light
-	// sum = twoSided ? abs(sum) : max(0.0, sum);
-	sum = max( 0.0, sum );
-	// sum = abs( sum );
-
-	vec3 Lo_i = vec3( sum, sum, sum );
-
-	return Lo_i/(2.0 * PI);
+	return cross( v1, v2 ) * theta_sintheta;
 
 }
 
-/* From http://advances.realtimerendering.com/s2016/s2016_ltc_rnd.pdf
-Basically its an approximation for the form factor of a clipped rectangle.
-*/
+vec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {
 
-float ClippedSphereFormFactor( const in vec3 f ) {
-	float l = length(f);
-	return max((l*l + f.z)/(l+1.0), 0.0);
-}
+	// bail if point is on back side of plane of light
+	// assumes ccw winding order of light vertices
+	vec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];
+	vec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];
+	vec3 lightNormal = cross( v1, v2 );
 
-/* From http://advances.realtimerendering.com/s2016/s2016_ltc_rnd.pdf
- Normalization by 2*PI is incorporated in this function itself.
- This Normalization can be seen in Eq 11 of
- https://drive.google.com/file/d/0BzvWIdpUpRx_d09ndGVjNVJzZjA/view
- theta/sin(theta)	is approximated by rational polynomial
- */
-
-vec3 EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {
-
-	float x = dot(v1, v2);
-
-	float y = abs(x);
-	float a = 0.86267 + (0.49788 + 0.01436*y)*y;
-	float b = 3.45068 + (4.18814 + y)*y;
-	float v = a/b;
-
-	float theta_sintheta = (x > 0.0) ? v : 0.5*inversesqrt(1.0 - x*x) - v;
-
-	return cross(v1, v2)*theta_sintheta;
-}
-
-vec3 integrateLtcBrdfOverRectOptimized( const in GeometricContext geometry, const in mat3 brdfMat, const in vec3 rectPoints[4] ) {
-
-	vec3 N = geometry.normal;
-	vec3 V = geometry.viewDir;
-	vec3 P = geometry.position;
+	if( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );
 
 	// construct orthonormal basis around N
 	vec3 T1, T2;
-	T1 = normalize(V - N * dot( V, N ));
-	// TODO (abelnation): I had to negate this cross product to get proper light.  Curious why sample code worked without negation
-	T2 = - cross( N, T1 );
-
-	// rotate area light in (T1, T2, N) basis
-	mat3 brdfWrtSurface = brdfMat * transpose( mat3( T1, T2, N ) );
-
-	// transformed rect relative to surface point
-	vec3 clippedRect[4];
-	clippedRect[0] = brdfWrtSurface * ( rectPoints[0] - P );
-	clippedRect[1] = brdfWrtSurface * ( rectPoints[1] - P );
-	clippedRect[2] = brdfWrtSurface * ( rectPoints[2] - P );
-	clippedRect[3] = brdfWrtSurface * ( rectPoints[3] - P );
-
-	// Find light normal
-	vec3 v1 = clippedRect[1] - clippedRect[0];
-	vec3 v2 = clippedRect[3] - clippedRect[0];
-	vec3 lightNormal = cross(v1, v2);
-
-	bool bSameSide = dot(lightNormal, clippedRect[0]) > 0.0;
-	if( !bSameSide )
-		return vec3(0.0);
-
-	// project clipped rect onto sphere
-	clippedRect[0] = normalize( clippedRect[0] );
-	clippedRect[1] = normalize( clippedRect[1] );
-	clippedRect[2] = normalize( clippedRect[2] );
-	clippedRect[3] = normalize( clippedRect[3] );
-
-	// Accumulate vector form factor
-	vec3 edgeVectorFormFactor = vec3(0.0);
-	edgeVectorFormFactor += EdgeVectorFormFactor( clippedRect[0], clippedRect[1] );
-	edgeVectorFormFactor += EdgeVectorFormFactor( clippedRect[1], clippedRect[2] );
-	edgeVectorFormFactor += EdgeVectorFormFactor( clippedRect[2], clippedRect[3] );
-	edgeVectorFormFactor += EdgeVectorFormFactor( clippedRect[3], clippedRect[0] );
-
-	vec3 Lo_i = vec3( ClippedSphereFormFactor( edgeVectorFormFactor ) );
-
-	return Lo_i;
-
-}
-
-vec3 Rect_Area_Light_Specular_Reflectance(
-		const in GeometricContext geometry,
-		const in vec3 lightPos, const in vec3 lightHalfWidth, const in vec3 lightHalfHeight,
-		const in float roughness,
-		const in sampler2D ltcMat, const in sampler2D ltcMag ) {
-
-	vec3 rectPoints[4];
-	initRectPoints( lightPos, lightHalfWidth, lightHalfHeight, rectPoints );
+	T1 = normalize( V - N * dot( V, N ) );
+	T2 = - cross( N, T1 ); // negated from paper; possibly due to a different assumed handedness of world coordinate system
 
-	vec2 uv = ltcTextureCoords( geometry, roughness );
+	// compute transform
+	mat3 mat = mInv * transpose( mat3( T1, T2, N ) );
 
-	vec4 brdfLtcApproxParams, t;
+	// transform rect
+	vec3 coords[ 4 ];
+	coords[ 0 ] = mat * ( rectCoords[ 0 ] - P );
+	coords[ 1 ] = mat * ( rectCoords[ 1 ] - P );
+	coords[ 2 ] = mat * ( rectCoords[ 2 ] - P );
+	coords[ 3 ] = mat * ( rectCoords[ 3 ] - P );
 
-	brdfLtcApproxParams = texture2D( ltcMat, uv );
-	t = texture2D( ltcMat, uv );
+	// project rect onto sphere
+	coords[ 0 ] = normalize( coords[ 0 ] );
+	coords[ 1 ] = normalize( coords[ 1 ] );
+	coords[ 2 ] = normalize( coords[ 2 ] );
+	coords[ 3 ] = normalize( coords[ 3 ] );
 
-	float brdfLtcScalar = texture2D( ltcMag, uv ).a;
+	// calculate vector form factor
+	vec3 vectorFormFactor = vec3( 0.0 );
+	vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );
+	vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );
+	vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );
+	vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );
 
-	// inv(M) matrix referenced by equation (6) in paper
-	mat3 brdfLtcApproxMat = mat3(
-		vec3(   1,   0, t.y ),
-		vec3(   0, t.z,   0 ),
-		vec3( t.w,   0, t.x )
-	);
+	// adjust for horizon clipping
+	vec3 result = vec3( LTC_ClippedSphereFormFactor( vectorFormFactor ) );
 
-	vec3 specularReflectance = integrateLtcBrdfOverRectOptimized( geometry, brdfLtcApproxMat, rectPoints );
-	specularReflectance *= brdfLtcScalar;
-
-	return specularReflectance;
+	return result;
 
 }
 
-vec3 Rect_Area_Light_Diffuse_Reflectance(
-		const in GeometricContext geometry,
-		const in vec3 lightPos, const in vec3 lightHalfWidth, const in vec3 lightHalfHeight ) {
-
-	vec3 rectPoints[4];
-	initRectPoints( lightPos, lightHalfWidth, lightHalfHeight, rectPoints );
-
-	mat3 diffuseBrdfMat = mat3(1);
-	vec3 diffuseReflectance = integrateLtcBrdfOverRectOptimized( geometry, diffuseBrdfMat, rectPoints );
-
-	return diffuseReflectance;
-
-}
-// End RectArea BRDF
+// End Rect Area Light
 
 // ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
 vec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {

+ 30 - 16
src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl

@@ -17,29 +17,42 @@ struct BlinnPhongMaterial {
 };
 
 #if NUM_RECT_AREA_LIGHTS > 0
-	void RE_Direct_RectArea_BlinnPhong( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
 
-		vec3 matDiffColor = material.diffuseColor;
-		vec3 matSpecColor = material.specularColor;
-		vec3 lightColor   = rectAreaLight.color;
+	void RE_Direct_RectArea_BlinnPhong( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
 
+		vec3 normal = geometry.normal;
+		vec3 viewDir = geometry.viewDir;
+		vec3 position = geometry.position;
+		vec3 lightPos = rectAreaLight.position;
+		vec3 halfWidth = rectAreaLight.halfWidth;
+		vec3 halfHeight = rectAreaLight.halfHeight;
+		vec3 lightColor = rectAreaLight.color;
 		float roughness = BlinnExponentToGGXRoughness( material.specularShininess );
 
-		// Evaluate Lighting Equation
-		vec3 spec = Rect_Area_Light_Specular_Reflectance(
-				geometry,
-				rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight,
-				roughness,
-				ltcMat, ltcMag );
-		vec3 diff = Rect_Area_Light_Diffuse_Reflectance(
-				geometry,
-				rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight );
+		vec3 rectCoords[ 4 ];
+		rectCoords[ 0 ] = lightPos - halfWidth - halfHeight; // counterclockwise
+		rectCoords[ 1 ] = lightPos + halfWidth - halfHeight;
+		rectCoords[ 2 ] = lightPos + halfWidth + halfHeight;
+		rectCoords[ 3 ] = lightPos - halfWidth + halfHeight;
+
+		vec2 uv = LTC_Uv( normal, viewDir, roughness );
+
+		float norm = texture2D( ltcMag, uv ).a;
+
+		vec4 t = texture2D( ltcMat, uv );
 
-		// division by 2 * PI is moved inside the above calls. This is essential as Eq (11) is having this normalization.
-		reflectedLight.directSpecular += lightColor * matSpecColor * spec;
-		reflectedLight.directDiffuse  += lightColor * matDiffColor * diff;
+		mat3 mInv = mat3(
+			vec3(   1,   0, t.y ),
+			vec3(   0, t.z,   0 ),
+			vec3( t.w,   0, t.x )
+		);
+
+		reflectedLight.directSpecular += lightColor * material.specularColor * norm * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords ); // no fresnel
+
+		reflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1 ), rectCoords );
 
 	}
+
 #endif
 
 void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
@@ -62,6 +75,7 @@ void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in Geometri
 	#endif
 
 	reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
+
 	reflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;
 
 }

+ 30 - 14
src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl

@@ -22,27 +22,42 @@ float clearCoatDHRApprox( const in float roughness, const in float dotNL ) {
 }
 
 #if NUM_RECT_AREA_LIGHTS > 0
+
 	void RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {
 
-		vec3 matDiffColor = material.diffuseColor;
-		vec3 matSpecColor = material.specularColor;
-		vec3 lightColor   = rectAreaLight.color;
+		vec3 normal = geometry.normal;
+		vec3 viewDir = geometry.viewDir;
+		vec3 position = geometry.position;
+		vec3 lightPos = rectAreaLight.position;
+		vec3 halfWidth = rectAreaLight.halfWidth;
+		vec3 halfHeight = rectAreaLight.halfHeight;
+		vec3 lightColor = rectAreaLight.color;
 		float roughness = material.specularRoughness;
 
-		vec3 spec = Rect_Area_Light_Specular_Reflectance(
-				geometry,
-				rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight,
-				roughness,
-				ltcMat, ltcMag );
-		vec3 diff = Rect_Area_Light_Diffuse_Reflectance(
-				geometry,
-				rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight );
+		vec3 rectCoords[ 4 ];
+		rectCoords[ 0 ] = lightPos - halfWidth - halfHeight; // counterclockwise
+		rectCoords[ 1 ] = lightPos + halfWidth - halfHeight;
+		rectCoords[ 2 ] = lightPos + halfWidth + halfHeight;
+		rectCoords[ 3 ] = lightPos - halfWidth + halfHeight;
+
+		vec2 uv = LTC_Uv( normal, viewDir, roughness );
+
+		float norm = texture2D( ltcMag, uv ).a;
+
+		vec4 t = texture2D( ltcMat, uv );
 
-		// division by 2 * PI is inside the above calls. This is essential as Eq (11) is having this normalization.
-		reflectedLight.directSpecular += lightColor * matSpecColor * spec;
-		reflectedLight.directDiffuse  += lightColor * matDiffColor * diff;
+		mat3 mInv = mat3(
+			vec3(   1,   0, t.y ),
+			vec3(   0, t.z,   0 ),
+			vec3( t.w,   0, t.x )
+		);
+
+		reflectedLight.directSpecular += lightColor * material.specularColor * norm * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords ); // no fresnel
+
+		reflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1 ), rectCoords );
 
 	}
+
 #endif
 
 void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {
@@ -64,6 +79,7 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC
 	#endif
 
 	reflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );
+
 	reflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
 
 	#ifndef STANDARD