diff --git a/WickedEngine/Editor.cpp b/WickedEngine/Editor.cpp
index a7b870b8b..c97bdbb57 100644
--- a/WickedEngine/Editor.cpp
+++ b/WickedEngine/Editor.cpp
@@ -691,6 +691,7 @@ void EditorComponent::Load()
pointLightTex = *(Texture2D*)Content.add("images/pointlight.dds");
spotLightTex = *(Texture2D*)Content.add("images/spotlight.dds");
dirLightTex = *(Texture2D*)Content.add("images/directional_light.dds");
+ areaLightTex = *(Texture2D*)Content.add("images/arealight.dds");
}
void EditorComponent::Start()
{
@@ -1162,6 +1163,9 @@ void EditorComponent::Compose()
case Light::DIRECTIONAL:
wiImage::Draw(&dirLightTex, fx, GRAPHICSTHREAD_IMMEDIATE);
break;
+ default:
+ wiImage::Draw(&areaLightTex, fx, GRAPHICSTHREAD_IMMEDIATE);
+ break;
}
}
}
diff --git a/WickedEngine/Editor.h b/WickedEngine/Editor.h
index d6273b26c..0802b791a 100644
--- a/WickedEngine/Editor.h
+++ b/WickedEngine/Editor.h
@@ -29,7 +29,7 @@ class EditorComponent
: public TiledForwardRenderableComponent
{
private:
- wiGraphicsTypes::Texture2D pointLightTex, spotLightTex, dirLightTex;
+ wiGraphicsTypes::Texture2D pointLightTex, spotLightTex, dirLightTex, areaLightTex;
public:
MaterialWindow* materialWnd;
PostprocessWindow* postprocessWnd;
diff --git a/WickedEngine/LightWindow.cpp b/WickedEngine/LightWindow.cpp
index 7cd33ac74..7cd725fb3 100644
--- a/WickedEngine/LightWindow.cpp
+++ b/WickedEngine/LightWindow.cpp
@@ -10,7 +10,7 @@ LightWindow::LightWindow(wiGUI* gui) : GUI(gui), light(nullptr)
float screenH = (float)wiRenderer::GetDevice()->GetScreenHeight();
lightWindow = new wiWindow(GUI, "Light Window");
- lightWindow->SetSize(XMFLOAT2(400, 420));
+ lightWindow->SetSize(XMFLOAT2(400, 500));
//lightWindow->SetEnabled(false);
GUI->AddWidget(lightWindow);
@@ -44,6 +44,45 @@ LightWindow::LightWindow(wiGUI* gui) : GUI(gui), light(nullptr)
distanceSlider->SetTooltip("Adjust the maximum range the light can affect.");
lightWindow->AddWidget(distanceSlider);
+ radiusSlider = new wiSlider(0.01f, 100, 0, 100000, "Radius: ");
+ radiusSlider->SetSize(XMFLOAT2(100, 30));
+ radiusSlider->SetPos(XMFLOAT2(x, y += step));
+ radiusSlider->OnSlide([&](wiEventArgs args) {
+ if (light != nullptr)
+ {
+ light->radius = args.fValue;
+ }
+ });
+ radiusSlider->SetEnabled(false);
+ radiusSlider->SetTooltip("Adjust the radius of an area light.");
+ lightWindow->AddWidget(radiusSlider);
+
+ widthSlider = new wiSlider(1, 100, 0, 100000, "Width: ");
+ widthSlider->SetSize(XMFLOAT2(100, 30));
+ widthSlider->SetPos(XMFLOAT2(x, y += step));
+ widthSlider->OnSlide([&](wiEventArgs args) {
+ if (light != nullptr)
+ {
+ light->width = args.fValue;
+ }
+ });
+ widthSlider->SetEnabled(false);
+ widthSlider->SetTooltip("Adjust the width of an area light.");
+ lightWindow->AddWidget(widthSlider);
+
+ heightSlider = new wiSlider(1, 100, 0, 100000, "Height: ");
+ heightSlider->SetSize(XMFLOAT2(100, 30));
+ heightSlider->SetPos(XMFLOAT2(x, y += step));
+ heightSlider->OnSlide([&](wiEventArgs args) {
+ if (light != nullptr)
+ {
+ light->height = args.fValue;
+ }
+ });
+ heightSlider->SetEnabled(false);
+ heightSlider->SetTooltip("Adjust the height of an area light.");
+ lightWindow->AddWidget(heightSlider);
+
fovSlider = new wiSlider(0.1f, XM_PI - 0.01f, 0, 100000, "FOV: ");
fovSlider->SetSize(XMFLOAT2(100, 30));
fovSlider->SetPos(XMFLOAT2(x, y += step));
@@ -133,6 +172,7 @@ LightWindow::LightWindow(wiGUI* gui) : GUI(gui), light(nullptr)
if (light != nullptr && args.iValue >= 0)
{
light->type = (Light::LightType)args.iValue;
+ light->UpdateLight();
SetLightType(light->type); // for the gui changes to apply to the new type
}
});
@@ -140,6 +180,10 @@ LightWindow::LightWindow(wiGUI* gui) : GUI(gui), light(nullptr)
typeSelectorComboBox->AddItem("Directional");
typeSelectorComboBox->AddItem("Point");
typeSelectorComboBox->AddItem("Spot");
+ typeSelectorComboBox->AddItem("Sphere");
+ typeSelectorComboBox->AddItem("Disc");
+ typeSelectorComboBox->AddItem("Rectangle");
+ typeSelectorComboBox->AddItem("Tube");
typeSelectorComboBox->SetTooltip("Choose the light source type...");
lightWindow->AddWidget(typeSelectorComboBox);
@@ -156,6 +200,9 @@ LightWindow::~LightWindow()
SAFE_DELETE(lightWindow);
SAFE_DELETE(energySlider);
SAFE_DELETE(distanceSlider);
+ SAFE_DELETE(radiusSlider);
+ SAFE_DELETE(widthSlider);
+ SAFE_DELETE(heightSlider);
SAFE_DELETE(fovSlider);
SAFE_DELETE(biasSlider);
SAFE_DELETE(shadowCheckBox);
@@ -175,6 +222,9 @@ void LightWindow::SetLight(Light* light)
energySlider->SetEnabled(true);
energySlider->SetValue(light->enerDis.x);
distanceSlider->SetValue(light->enerDis.y);
+ radiusSlider->SetValue(light->radius);
+ widthSlider->SetValue(light->width);
+ heightSlider->SetValue(light->height);
fovSlider->SetValue(light->enerDis.z);
biasSlider->SetEnabled(true);
biasSlider->SetValue(light->shadowBias);
@@ -191,6 +241,9 @@ void LightWindow::SetLight(Light* light)
else
{
distanceSlider->SetEnabled(false);
+ radiusSlider->SetEnabled(false);
+ widthSlider->SetEnabled(false);
+ heightSlider->SetEnabled(false);
fovSlider->SetEnabled(false);
biasSlider->SetEnabled(false);
shadowCheckBox->SetEnabled(false);
@@ -210,14 +263,29 @@ void LightWindow::SetLightType(Light::LightType type)
}
else
{
- distanceSlider->SetEnabled(true);
- if (type == Light::SPOT)
+ if (type == Light::SPHERE || type == Light::DISC || type == Light::RECTANGLE || type == Light::TUBE)
{
- fovSlider->SetEnabled(true);
+ distanceSlider->SetEnabled(false);
+ radiusSlider->SetEnabled(true);
+ widthSlider->SetEnabled(true);
+ heightSlider->SetEnabled(true);
+ fovSlider->SetEnabled(false);
}
else
{
- fovSlider->SetEnabled(false);
+ distanceSlider->SetEnabled(true);
+ radiusSlider->SetEnabled(false);
+ widthSlider->SetEnabled(false);
+ heightSlider->SetEnabled(false);
+ if (type == Light::SPOT)
+ {
+ fovSlider->SetEnabled(true);
+ }
+ else
+ {
+ fovSlider->SetEnabled(false);
+ }
}
}
+
}
diff --git a/WickedEngine/LightWindow.h b/WickedEngine/LightWindow.h
index 0c72e7617..1b980ed35 100644
--- a/WickedEngine/LightWindow.h
+++ b/WickedEngine/LightWindow.h
@@ -28,6 +28,9 @@ public:
wiWindow* lightWindow;
wiSlider* energySlider;
wiSlider* distanceSlider;
+ wiSlider* radiusSlider;
+ wiSlider* widthSlider;
+ wiSlider* heightSlider;
wiSlider* fovSlider;
wiSlider* biasSlider;
wiCheckBox* shadowCheckBox;
diff --git a/WickedEngine/WickedEngineEditor.vcxproj b/WickedEngine/WickedEngineEditor.vcxproj
index 3a0f5c5da..757b08cbb 100644
--- a/WickedEngine/WickedEngineEditor.vcxproj
+++ b/WickedEngine/WickedEngineEditor.vcxproj
@@ -203,6 +203,7 @@
+
diff --git a/WickedEngine/WickedEngineEditor.vcxproj.filters b/WickedEngine/WickedEngineEditor.vcxproj.filters
index 5d65f298d..52c4c9c1e 100644
--- a/WickedEngine/WickedEngineEditor.vcxproj.filters
+++ b/WickedEngine/WickedEngineEditor.vcxproj.filters
@@ -233,6 +233,9 @@
images
+
+ images
+
diff --git a/WickedEngine/images/arealight.dds b/WickedEngine/images/arealight.dds
new file mode 100644
index 000000000..278bbd7ec
Binary files /dev/null and b/WickedEngine/images/arealight.dds differ
diff --git a/WickedEngine/lightCullingCS.hlsl b/WickedEngine/lightCullingCS.hlsl
index 863d2f819..54f9c96fc 100644
--- a/WickedEngine/lightCullingCS.hlsl
+++ b/WickedEngine/lightCullingCS.hlsl
@@ -247,9 +247,11 @@ void main(ComputeShaderInput IN)
}
break;
case 0/*DIRECTIONAL_LIGHT*/:
+ case 3/*SPHERE_LIGHT*/:
+ case 4/*DISC_LIGHT*/:
+ case 5/*RECTANGLE_LIGHT*/:
+ case 6/*TUBE_LIGHT*/:
{
- // Directional lights always get added to our light list.
- // (Hopefully there are not too many directional lights!)
t_AppendLight(i);
o_AppendLight(i);
}
diff --git a/WickedEngine/lightingHF.hlsli b/WickedEngine/lightingHF.hlsli
index cacfb5c6b..e20a06d65 100644
--- a/WickedEngine/lightingHF.hlsli
+++ b/WickedEngine/lightingHF.hlsli
@@ -71,6 +71,7 @@ inline float shadowCascade(float4 shadowPos, float2 ShTex, float shadowKernel, f
return retVal;
}
+
inline LightingResult DirectionalLight(in LightArrayType light, in float3 N, in float3 V, in float3 P, in float roughness, in float3 f0)
{
LightingResult result;
@@ -224,4 +225,444 @@ inline LightingResult SpotLight(in LightArrayType light, in float3 N, in float3
return result;
}
+
+
+
+// AREA LIGHTS
+
+// Based on the Frostbite presentation:
+// Moving Frostbite to Physically Based Rendering by Sebastien Lagarde, Charles de Rousiers, Siggraph 2014
+// http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf
+
+float cot(float x) { return cos(x) / sin(x); }
+float acot(float x) { return atan(1 / x); }
+
+// Return the closest point on the line (without limit)
+float3 ClosestPointOnLine(float3 a, float3 b, float3 c)
+{
+ float3 ab = b - a;
+ float t = dot(c - a, ab) / dot(ab, ab);
+ return a + t * ab;
+}
+// Return the closest point on the segment (with limit)
+float3 ClosestPointOnSegment(float3 a, float3 b, float3 c)
+{
+ float3 ab = b - a;
+ float t = dot(c - a, ab) / dot(ab, ab);
+ return a + saturate(t) * ab;
+}
+float RightPyramidSolidAngle(float dist, float halfWidth, float halfHeight)
+{
+ float a = halfWidth;
+ float b = halfHeight;
+ float h = dist;
+
+ return 4 * asin(a * b / sqrt((a * a + h * h) * (b * b + h * h)));
+}
+float RectangleSolidAngle(float3 worldPos,
+ float3 p0, float3 p1,
+ float3 p2, float3 p3)
+{
+ float3 v0 = p0 - worldPos;
+ float3 v1 = p1 - worldPos;
+ float3 v2 = p2 - worldPos;
+ float3 v3 = p3 - worldPos;
+
+ float3 n0 = normalize(cross(v0, v1));
+ float3 n1 = normalize(cross(v1, v2));
+ float3 n2 = normalize(cross(v2, v3));
+ float3 n3 = normalize(cross(v3, v0));
+
+
+ float g0 = acos(dot(-n0, n1));
+ float g1 = acos(dot(-n1, n2));
+ float g2 = acos(dot(-n2, n3));
+ float g3 = acos(dot(-n3, n0));
+
+ return g0 + g1 + g2 + g3 - 2 * PI;
+}
+
+
+// o : ray origin
+// d : ray direction
+// center : sphere center
+// radius : sphere radius
+// returns distance on the ray to the object if hit, 0 otherwise
+float Trace_sphere(float3 o, float3 d, float3 center, float radius)
+{
+ float3 rc = o - center;
+ float c = dot(rc, rc) - (radius*radius);
+ float b = dot(d, rc);
+ float dd = b*b - c;
+ float t = -b - sqrt(abs(dd));
+ float st = step(0.0, min(t, dd));
+ return lerp(-1.0, t, st);
+}
+// o : ray origin
+// d : ray direction
+// returns distance on the ray to the object if hit, 0 otherwise
+float Trace_plane(float3 o, float3 d, float3 planeOrigin, float3 planeNormal)
+{
+ return dot(planeNormal, (planeOrigin - o) / dot(planeNormal, d));
+}
+// o : ray origin
+// d : ray direction
+// A,B,C : traingle corners
+// returns distance on the ray to the object if hit, 0 otherwise
+float Trace_triangle(float3 o, float3 d, float3 A, float3 B, float3 C)
+{
+ float3 planeNormal = normalize(cross(B - A, C - B));
+ float t = Trace_plane(o, d, A, planeNormal);
+ float3 p = o + d*t;
+
+ float3 N1 = normalize(cross(B - A, p - B));
+ float3 N2 = normalize(cross(C - B, p - C));
+ float3 N3 = normalize(cross(A - C, p - A));
+
+ float d0 = dot(N1, N2);
+ float d1 = dot(N2, N3);
+
+ float threshold = 1.0f - 0.001f;
+ return (d0 > threshold && d1 > threshold) ? 1.0f : 0.0f;
+}
+// o : ray origin
+// d : ray direction
+// A,B,C,D : rectangle corners
+// returns distance on the ray to the object if hit, 0 otherwise
+float Trace_rectangle(float3 o, float3 d, float3 A, float3 B, float3 C, float3 D)
+{
+ return max(Trace_triangle(o, d, A, B, C), Trace_triangle(o, d, C, D, A));
+}
+// o : ray origin
+// d : ray direction
+// diskNormal : disk facing direction
+// returns distance on the ray to the object if hit, 0 otherwise
+float Trace_disk(float3 o, float3 d, float3 diskCenter, float diskRadius, float3 diskNormal)
+{
+ float t = Trace_plane(o, d, diskCenter, diskNormal);
+ float3 p = o + d*t;
+ float3 diff = p - diskCenter;
+ return dot(diff, diff) sqrt(sinSigmaSqr),
+ // cosTheta > -sqrt(sinSigmaSqr) and else it is 0
+ // The two outer case can be merge into a cosTheta * cosTheta > sinSigmaSqr
+ // and using saturate(cosTheta) instead.
+ if (cosTheta * cosTheta > sinSigmaSqr)
+ {
+ illuminance = PI * sinSigmaSqr * saturate(cosTheta);
+ }
+ else
+ {
+ float x = sqrt(1.0f / sinSigmaSqr - 1.0f); // For a disk this simplify to x = d / r
+ float y = -x * (cosTheta / sinTheta);
+ float sinThetaSqrtY = sinTheta * sqrt(1.0f - y * y);
+ illuminance = (cosTheta * acos(y) - x * sinThetaSqrtY) * sinSigmaSqr + atan(sinThetaSqrtY / x);
+ }
+
+ return max(illuminance, 0.0f);
+}
+
+inline float3 _GetLeft(LightArrayType light) { return light.directionWS; }
+inline float3 _GetUp(LightArrayType light) { return light.directionVS; }
+inline float3 _GetFront(LightArrayType light) { return light.positionVS; }
+inline float _GetRadius(LightArrayType light) { return light.texMulAdd.x; }
+inline float _GetWidth(LightArrayType light) { return light.texMulAdd.y; }
+inline float _GetHeight(LightArrayType light) { return light.texMulAdd.z; }
+
+inline LightingResult SphereLight(in LightArrayType light, in float3 N, in float3 V, in float3 P, in float roughness, in float3 f0)
+{
+ LightingResult result = (LightingResult)0;
+
+ float3 Lunormalized = light.positionWS - P;
+ float dist = length(Lunormalized);
+ float3 L = Lunormalized / dist;
+
+ float sqrDist = dot(Lunormalized, Lunormalized);
+
+ float cosTheta = clamp(dot(N, L), -0.999, 0.999); // Clamp to avoid edge case
+ // We need to prevent the object penetrating into the surface
+ // and we must avoid divide by 0, thus the 0.9999f
+ float sqrLightRadius = _GetRadius(light) * _GetRadius(light);
+ float sinSigmaSqr = min(sqrLightRadius / sqrDist, 0.9999f);
+ float fLight = illuminanceSphereOrDisk(cosTheta, sinSigmaSqr);
+
+
+ // We approximate L by the closest point on the reflection ray to the light source (representative point technique) to achieve a nice looking specular reflection
+ {
+ float3 r = reflect(V, N);
+ r = getSpecularDominantDirArea(N, r, roughness);
+
+ float3 centerToRay = dot(Lunormalized, r) * r - Lunormalized;
+ float3 closestPoint = Lunormalized + centerToRay * saturate(_GetRadius(light) / length(centerToRay));
+ L = normalize(closestPoint);
+ }
+
+ float3 lightColor = light.color.rgb*light.energy;
+
+ BRDF_MAKE(N, L, V);
+ result.specular = lightColor * BRDF_SPECULAR(roughness, f0) * fLight;
+ result.diffuse = lightColor * fLight / PI;
+
+ result.diffuse = max(0.0f, result.diffuse);
+ result.specular = max(0.0f, result.specular);
+
+ return result;
+}
+inline LightingResult DiscLight(in LightArrayType light, in float3 N, in float3 V, in float3 P, in float roughness, in float3 f0)
+{
+ LightingResult result = (LightingResult)0;
+
+ float3 Lunormalized = light.positionWS - P;
+ float dist = length(Lunormalized);
+ float3 L = Lunormalized / dist;
+
+ float sqrDist = dot(Lunormalized, Lunormalized);
+
+ float3 lightPlaneNormal = _GetFront(light);
+
+ float cosTheta = clamp(dot(N, L), -0.999, 0.999);
+ float sqrLightRadius = _GetRadius(light) * _GetRadius(light);
+ // Do not let the surface penetrate the light
+ float sinSigmaSqr = sqrLightRadius / (sqrLightRadius + max(sqrLightRadius, sqrDist));
+ // Multiply by saturate(dot(planeNormal , -L)) to better match ground truth.
+ float fLight = illuminanceSphereOrDisk(cosTheta, sinSigmaSqr)
+ * saturate(dot(lightPlaneNormal, -L));
+
+ // We approximate L by the closest point on the reflection ray to the light source (representative point technique) to achieve a nice looking specular reflection
+ {
+ float3 r = reflect(V, N);
+ r = getSpecularDominantDirArea(N, r, roughness);
+
+ float t = Trace_plane(P, r, light.positionWS, lightPlaneNormal);
+ float3 p = P + r*t;
+ float3 centerToRay = p - light.positionWS;
+ float3 closestPoint = Lunormalized + centerToRay * saturate(_GetRadius(light) / length(centerToRay));
+ L = normalize(closestPoint);
+ }
+
+ float3 lightColor = light.color.rgb*light.energy;
+
+ BRDF_MAKE(N, L, V);
+ result.specular = lightColor * BRDF_SPECULAR(roughness, f0) * fLight;
+ result.diffuse = lightColor * fLight / PI;
+
+ result.diffuse = max(0.0f, result.diffuse);
+ result.specular = max(0.0f, result.specular);
+
+ return result;
+}
+inline LightingResult RectangleLight(in LightArrayType light, in float3 N, in float3 V, in float3 P, in float roughness, in float3 f0)
+{
+ LightingResult result = (LightingResult)0;
+
+
+ float3 L = light.positionWS - P;
+ float dist = length(L);
+ L /= dist;
+
+
+ float3 lightPlaneNormal = _GetFront(light);
+ float3 lightLeft = _GetLeft(light);
+ float3 lightUp = _GetUp(light);
+ float lightWidth = _GetWidth(light);
+ float lightHeight = _GetHeight(light);
+ float3 worldPos = P;
+ float3 worldNormal = N;
+
+
+ float fLight = 0;
+ float halfWidth = lightWidth * 0.5;
+ float halfHeight = lightHeight * 0.5;
+ float3 p0 = light.positionWS + lightLeft * -halfWidth + lightUp * halfHeight;
+ float3 p1 = light.positionWS + lightLeft * -halfWidth + lightUp * -halfHeight;
+
+ float3 p2 = light.positionWS + lightLeft * halfWidth + lightUp * -halfHeight;
+ float3 p3 = light.positionWS + lightLeft * halfWidth + lightUp * halfHeight;
+ float solidAngle = RectangleSolidAngle(worldPos, p0, p1, p2, p3);
+
+ if (dot(worldPos - light.positionWS, lightPlaneNormal) > 0)
+ {
+ fLight = solidAngle * 0.2 * (
+ saturate(dot(normalize(p0 - worldPos), worldNormal)) +
+ saturate(dot(normalize(p1 - worldPos), worldNormal)) +
+ saturate(dot(normalize(p2 - worldPos), worldNormal)) +
+ saturate(dot(normalize(p3 - worldPos), worldNormal)) +
+ saturate(dot(normalize(light.positionWS - worldPos), worldNormal)));
+ }
+ fLight = max(0, fLight);
+
+
+ // We approximate L by the closest point on the reflection ray to the light source (representative point technique) to achieve a nice looking specular reflection
+ {
+ float3 r = reflect(-V, N);
+ r = getSpecularDominantDirArea(N, r, roughness);
+
+ float traced = Trace_rectangle(P, r, p0, p1, p2, p3);
+
+ [branch]
+ if (traced > 0)
+ {
+ // Trace succeeded so the light vector L is the reflection vector itself
+ L = r;
+ }
+ else
+ {
+ // The trace didn't succeed, so we need to find the closest point to the ray on the rectangle
+
+ // We find the intersection point on the plane of the rectangle
+ float3 tracedPlane = P + r * Trace_plane(P, r, light.positionWS, lightPlaneNormal);
+
+ // Then find the closest point along the edges of the rectangle (edge = segment)
+ float3 PC[4] = {
+ ClosestPointOnSegment(p0, p1, tracedPlane),
+ ClosestPointOnSegment(p1, p2, tracedPlane),
+ ClosestPointOnSegment(p2, p3, tracedPlane),
+ ClosestPointOnSegment(p3, p0, tracedPlane),
+ };
+ float dist[4] = {
+ distance(PC[0], tracedPlane),
+ distance(PC[1], tracedPlane),
+ distance(PC[2], tracedPlane),
+ distance(PC[3], tracedPlane),
+ };
+
+ float3 min = PC[0];
+ float minDist = dist[0];
+ [unroll]
+ for (uint iLoop = 1; iLoop < 4; iLoop++)
+ {
+ if (dist[iLoop] < minDist)
+ {
+ minDist = dist[iLoop];
+ min = PC[iLoop];
+ }
+ }
+
+ L = min - P;
+ }
+ L = normalize(L); // TODO: Is it necessary?
+ }
+
+ float3 lightColor = light.color.rgb*light.energy;
+
+ BRDF_MAKE(N, L, V);
+ result.specular = lightColor * BRDF_SPECULAR(roughness, f0) * fLight;
+ result.diffuse = lightColor * fLight / PI;
+
+ result.diffuse = max(0.0f, result.diffuse);
+ result.specular = max(0.0f, result.specular);
+
+ return result;
+}
+inline LightingResult TubeLight(in LightArrayType light, in float3 N, in float3 V, in float3 P, in float roughness, in float3 f0)
+{
+ LightingResult result = (LightingResult)0;
+
+ float3 Lunormalized = light.positionWS - P;
+ float dist = length(Lunormalized);
+ float3 L = Lunormalized / dist;
+
+ float sqrDist = dot(Lunormalized, Lunormalized);
+
+ float3 lightLeft = _GetLeft(light);
+ float lightWidth = _GetWidth(light);
+ float3 worldPos = P;
+ float3 worldNormal = N;
+
+
+ float3 P0 = light.positionWS - lightLeft*lightWidth*0.5f;
+ float3 P1 = light.positionWS + lightLeft*lightWidth*0.5f;
+
+ // The sphere is placed at the nearest point on the segment.
+ // The rectangular plane is define by the following orthonormal frame:
+ float3 forward = normalize(ClosestPointOnLine(P0, P1, worldPos) - worldPos);
+ float3 left = lightLeft;
+ float3 up = cross(lightLeft, forward);
+
+ float3 p0 = light.positionWS - left * (0.5 * lightWidth) + _GetRadius(light) * up;
+ float3 p1 = light.positionWS - left * (0.5 * lightWidth) - _GetRadius(light) * up;
+ float3 p2 = light.positionWS + left * (0.5 * lightWidth) - _GetRadius(light) * up;
+ float3 p3 = light.positionWS + left * (0.5 * lightWidth) + _GetRadius(light) * up;
+
+
+ float solidAngle = RectangleSolidAngle(worldPos, p0, p1, p2, p3);
+
+ float fLight = solidAngle * 0.2 * (
+ saturate(dot(normalize(p0 - worldPos), worldNormal)) +
+ saturate(dot(normalize(p1 - worldPos), worldNormal)) +
+ saturate(dot(normalize(p2 - worldPos), worldNormal)) +
+ saturate(dot(normalize(p3 - worldPos), worldNormal)) +
+ saturate(dot(normalize(light.positionWS - worldPos), worldNormal)));
+
+ // We then add the contribution of the sphere
+ float3 spherePosition = ClosestPointOnSegment(P0, P1, worldPos);
+ float3 sphereUnormL = spherePosition - worldPos;
+ float3 sphereL = normalize(sphereUnormL);
+ float sqrSphereDistance = dot(sphereUnormL, sphereUnormL);
+
+ float fLightSphere = PI * saturate(dot(sphereL, worldNormal)) *
+ ((_GetRadius(light) * _GetRadius(light)) / sqrSphereDistance);
+
+ fLight += fLightSphere;
+
+ fLight = max(0, fLight);
+
+
+ // We approximate L by the closest point on the reflection ray to the light source (representative point technique) to achieve a nice looking specular reflection
+ {
+ float3 r = reflect(V, N);
+ r = getSpecularDominantDirArea(N, r, roughness);
+
+ // First, the closest point to the ray on the segment
+ float3 L0 = P0 - P;
+ float3 L1 = P1 - P;
+ float3 Ld = L1 - L0;
+
+ float t = dot(r, L0) * dot(r, Ld) - dot(L0, Ld);
+ t /= dot(Ld, Ld) - sqr(dot(r, Ld));
+
+ L = (L0 + saturate(t) * Ld);
+
+ // Then I place a sphere on that point and calculate the lisght vector like for sphere light.
+ float3 centerToRay = dot(L, r) * r - L;
+ float3 closestPoint = L + centerToRay * saturate(_GetRadius(light) / length(centerToRay));
+ L = normalize(closestPoint);
+ }
+
+ float3 lightColor = light.color.rgb*light.energy;
+
+ BRDF_MAKE(N, L, V);
+ result.specular = lightColor * BRDF_SPECULAR(roughness, f0) * fLight;
+ result.diffuse = lightColor * fLight / PI;
+
+ result.diffuse = max(0.0f, result.diffuse);
+ result.specular = max(0.0f, result.specular);
+
+ return result;
+}
+
#endif // _LIGHTING_HF_
\ No newline at end of file
diff --git a/WickedEngine/objectHF.hlsli b/WickedEngine/objectHF.hlsli
index afb8b031a..967094d7d 100644
--- a/WickedEngine/objectHF.hlsli
+++ b/WickedEngine/objectHF.hlsli
@@ -154,6 +154,26 @@ inline void TiledLighting(in float2 pixel, in float3 N, in float3 V, in float3 P
result = SpotLight(light, N, V, P, roughness, f0);
}
break;
+ case 3/*SPHERE*/:
+ {
+ result = SphereLight(light, N, V, P, roughness, f0);
+ }
+ break;
+ case 4/*DISC*/:
+ {
+ result = DiscLight(light, N, V, P, roughness, f0);
+ }
+ break;
+ case 5/*RECTANGLE*/:
+ {
+ result = RectangleLight(light, N, V, P, roughness, f0);
+ }
+ break;
+ case 6/*TUBE*/:
+ {
+ result = TubeLight(light, N, V, P, roughness, f0);
+ }
+ break;
#ifndef DISABLE_DECALS
case 100/*DECAL*/:
{
@@ -186,7 +206,7 @@ inline void TiledLighting(in float2 pixel, in float3 N, in float3 V, in float3 P
////////////
#define OBJECT_PS_MAKE_COMMON \
- float3 N = input.nor; \
+ float3 N = normalize(input.nor); \
float3 P = input.pos3D; \
float3 V = g_xCamera_CamPos - P; \
float dist = length(V); \
diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp
index e4d29a9ab..02235d4d9 100644
--- a/WickedEngine/wiArchive.cpp
+++ b/WickedEngine/wiArchive.cpp
@@ -2,7 +2,7 @@
#include "wiHelper.h"
// this should always be only INCREMENTED and only if a new serialization is implemeted somewhere!
-uint64_t __archiveVersion = 5;
+uint64_t __archiveVersion = 6;
// this is the version number of which below the archive is not compatible with the current version
uint64_t __archiveVersionBarrier = 1;
diff --git a/WickedEngine/wiLoader.cpp b/WickedEngine/wiLoader.cpp
index 04266bcf8..89068e2e1 100644
--- a/WickedEngine/wiLoader.cpp
+++ b/WickedEngine/wiLoader.cpp
@@ -3926,6 +3926,9 @@ Light::Light():Transform() {
shadowMap_index = -1;
lightArray_index = 0;
shadowBias = 0.0001f;
+ radius = 1.0f;
+ width = 1.0f;
+ height = 1.0f;
}
Light::~Light() {
for (string x : lensFlareNames)
@@ -4068,6 +4071,13 @@ void Light::Serialize(wiArchive& archive)
lensFlareNames.push_back(rim);
}
}
+
+ if (archive.GetVersion() >= 6)
+ {
+ archive >> radius;
+ archive >> width;
+ archive >> height;
+ }
}
else
{
@@ -4082,6 +4092,13 @@ void Light::Serialize(wiArchive& archive)
{
archive << wiHelper::GetFileNameFromPath(x);
}
+
+ if (archive.GetVersion() >= 6)
+ {
+ archive << radius;
+ archive << width;
+ archive << height;
+ }
}
}
#pragma endregion
diff --git a/WickedEngine/wiLoader.h b/WickedEngine/wiLoader.h
index 8d74c862c..f91739334 100644
--- a/WickedEngine/wiLoader.h
+++ b/WickedEngine/wiLoader.h
@@ -749,10 +749,17 @@ struct Light : public Cullable , public Transform
float shadowBias;
+ // area light props:
+ float radius, width, height;
+
enum LightType{
DIRECTIONAL,
POINT,
SPOT,
+ SPHERE,
+ DISC,
+ RECTANGLE,
+ TUBE,
LIGHTTYPE_COUNT,
};
LightType type;
diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp
index ea89721f3..0e91029fe 100644
--- a/WickedEngine/wiRenderer.cpp
+++ b/WickedEngine/wiRenderer.cpp
@@ -1715,6 +1715,18 @@ void wiRenderer::UpdateRenderData(GRAPHICSTHREAD threadID)
lightArray[lightCounter].shadowKernel = 1.0f / SHADOWRES_CUBE;
}
break;
+ case Light::SPHERE:
+ case Light::DISC:
+ case Light::RECTANGLE:
+ case Light::TUBE:
+ {
+ XMMATRIX lightMat = XMLoadFloat4x4(&l->world);
+ XMStoreFloat3(&lightArray[lightCounter].directionWS, XMVector3TransformNormal(XMVectorSet(-1, 0, 0, 0), lightMat)); // left dir
+ XMStoreFloat3(&lightArray[lightCounter].directionVS, XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), lightMat)); // up dir
+ XMStoreFloat3(&lightArray[lightCounter].posVS, XMVector3TransformNormal(XMVectorSet(0, 0, -1, 0), lightMat)); // front dir
+ lightArray[lightCounter].texMulAdd = XMFLOAT4(l->radius, l->width, l->height, 0);
+ }
+ break;
default:
break;
}
diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp
index eaf1136c1..acc368113 100644
--- a/WickedEngine/wiVersion.cpp
+++ b/WickedEngine/wiVersion.cpp
@@ -7,7 +7,7 @@ namespace wiVersion
// minor features, major updates
const int minor = 9;
// minor bug fixes, alterations, refactors, updates
- const int revision = 53;
+ const int revision = 54;
long GetVersion()