From 0bdae65cbcd0f10c90273dec510ca6b5bd6e38b3 Mon Sep 17 00:00:00 2001 From: turanszkij Date: Mon, 23 Jan 2017 23:49:57 +0100 Subject: [PATCH] added area lights --- WickedEngine/Editor.cpp | 4 + WickedEngine/Editor.h | 2 +- WickedEngine/LightWindow.cpp | 78 +++- WickedEngine/LightWindow.h | 3 + WickedEngine/WickedEngineEditor.vcxproj | 1 + .../WickedEngineEditor.vcxproj.filters | 3 + WickedEngine/images/arealight.dds | Bin 0 -> 22000 bytes WickedEngine/lightCullingCS.hlsl | 6 +- WickedEngine/lightingHF.hlsli | 441 ++++++++++++++++++ WickedEngine/objectHF.hlsli | 22 +- WickedEngine/wiArchive.cpp | 2 +- WickedEngine/wiLoader.cpp | 17 + WickedEngine/wiLoader.h | 7 + WickedEngine/wiRenderer.cpp | 12 + WickedEngine/wiVersion.cpp | 2 +- 15 files changed, 589 insertions(+), 11 deletions(-) create mode 100644 WickedEngine/images/arealight.dds 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 0000000000000000000000000000000000000000..278bbd7ecbc3bb06e08494766038ddc749217b12 GIT binary patch literal 22000 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%0D{Wy1dT@}`=?gtNl_NrJs9xY5GU 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 literal 0 HcmV?d00001 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()