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()