From 82e91559e01c4af8f992524686b089663d3d94cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Tue, 21 Feb 2023 23:16:24 +0100 Subject: [PATCH] Sphere and capsule area lights --- Editor/LightWindow.cpp | 40 +++++ Editor/LightWindow.h | 2 + WickedEngine/shaders/ShaderInterop_Renderer.h | 18 ++- WickedEngine/shaders/brdf.hlsli | 30 ++-- WickedEngine/shaders/ddgi_raytraceCS.hlsl | 23 ++- WickedEngine/shaders/globals.hlsli | 9 ++ WickedEngine/shaders/lightCullingCS.hlsl | 2 +- WickedEngine/shaders/lightingHF.hlsli | 67 +++++++- WickedEngine/shaders/raytraceCS.hlsl | 11 +- WickedEngine/shaders/renderlightmapPS.hlsl | 18 ++- WickedEngine/shaders/screenspaceshadowCS.hlsl | 22 ++- WickedEngine/shaders/shadowHF.hlsli | 2 +- WickedEngine/shaders/surfel_raytraceCS.hlsl | 23 ++- WickedEngine/shaders/vPointLightVS.hlsl | 7 +- WickedEngine/wiEnums.h | 1 + WickedEngine/wiMath.h | 10 ++ WickedEngine/wiRenderer.cpp | 153 +++++++++++++++++- WickedEngine/wiScene.cpp | 3 + WickedEngine/wiScene.h | 2 +- WickedEngine/wiScene_Components.h | 2 + WickedEngine/wiScene_Serializers.cpp | 10 ++ 21 files changed, 406 insertions(+), 49 deletions(-) diff --git a/Editor/LightWindow.cpp b/Editor/LightWindow.cpp index d839a3e74..c1dbbc94e 100644 --- a/Editor/LightWindow.cpp +++ b/Editor/LightWindow.cpp @@ -82,6 +82,34 @@ void LightWindow::Create(EditorComponent* _editor) rangeSlider.SetTooltip("Adjust the maximum range the light can affect."); AddWidget(&rangeSlider); + radiusSlider.Create(0, 10, 0, 100000, "Radius: "); + radiusSlider.SetSize(XMFLOAT2(wid, hei)); + radiusSlider.SetPos(XMFLOAT2(x, y += step)); + radiusSlider.OnSlide([&](wi::gui::EventArgs args) { + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); + if (light != nullptr) + { + light->radius = args.fValue; + } + }); + radiusSlider.SetEnabled(false); + radiusSlider.SetTooltip("[Experimental] Adjust the radius of the light source.\nFor directional light, this will only affect ray traced shadow softness."); + AddWidget(&radiusSlider); + + lengthSlider.Create(0, 10, 0, 100000, "Length: "); + lengthSlider.SetSize(XMFLOAT2(wid, hei)); + lengthSlider.SetPos(XMFLOAT2(x, y += step)); + lengthSlider.OnSlide([&](wi::gui::EventArgs args) { + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); + if (light != nullptr) + { + light->length = args.fValue; + } + }); + lengthSlider.SetEnabled(false); + lengthSlider.SetTooltip("[Experimental] Adjust the length of the light source.\nWith this you can make capsule light out of a point light."); + AddWidget(&lengthSlider); + outerConeAngleSlider.Create(0.1f, XM_PIDIV2 - 0.01f, 0, 100000, "Outer Cone Angle: "); outerConeAngleSlider.SetSize(XMFLOAT2(wid, hei)); outerConeAngleSlider.SetPos(XMFLOAT2(x, y += step)); @@ -287,6 +315,8 @@ void LightWindow::SetEntity(Entity entity) intensitySlider.SetEnabled(true); intensitySlider.SetValue(light->intensity); rangeSlider.SetValue(light->range); + radiusSlider.SetValue(light->radius); + lengthSlider.SetValue(light->length); outerConeAngleSlider.SetValue(light->outerConeAngle); innerConeAngleSlider.SetValue(light->innerConeAngle); shadowCheckBox.SetEnabled(true); @@ -323,6 +353,8 @@ void LightWindow::SetEntity(Entity entity) else { rangeSlider.SetEnabled(false); + radiusSlider.SetEnabled(false); + lengthSlider.SetEnabled(false); outerConeAngleSlider.SetEnabled(false); innerConeAngleSlider.SetEnabled(false); shadowCheckBox.SetEnabled(false); @@ -349,6 +381,8 @@ void LightWindow::SetLightType(LightComponent::LightType type) rangeSlider.SetEnabled(false); outerConeAngleSlider.SetEnabled(false); innerConeAngleSlider.SetEnabled(false); + radiusSlider.SetRange(0, 1); + lengthSlider.SetEnabled(false); } else { @@ -357,13 +391,17 @@ void LightWindow::SetLightType(LightComponent::LightType type) { outerConeAngleSlider.SetEnabled(true); innerConeAngleSlider.SetEnabled(true); + lengthSlider.SetEnabled(false); } else { outerConeAngleSlider.SetEnabled(false); innerConeAngleSlider.SetEnabled(false); + lengthSlider.SetEnabled(true); } + radiusSlider.SetRange(0, 10); } + radiusSlider.SetEnabled(true); RefreshCascades(); } @@ -476,6 +514,8 @@ void LightWindow::ResizeLayout() add(rangeSlider); add(outerConeAngleSlider); add(innerConeAngleSlider); + add(radiusSlider); + add(lengthSlider); add_right(shadowCheckBox); add_right(haloCheckBox); add_right(volumetricsCheckBox); diff --git a/Editor/LightWindow.h b/Editor/LightWindow.h index 4d0607c01..43c369e73 100644 --- a/Editor/LightWindow.h +++ b/Editor/LightWindow.h @@ -16,6 +16,8 @@ public: wi::gui::Slider intensitySlider; wi::gui::Slider rangeSlider; + wi::gui::Slider radiusSlider; + wi::gui::Slider lengthSlider; wi::gui::Slider outerConeAngleSlider; wi::gui::Slider innerConeAngleSlider; wi::gui::CheckBox shadowCheckBox; diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h index fb8e5578b..e64c48ba6 100644 --- a/WickedEngine/shaders/ShaderInterop_Renderer.h +++ b/WickedEngine/shaders/ShaderInterop_Renderer.h @@ -625,7 +625,7 @@ struct ShaderEntity uint layerMask; uint indices; uint remap; - uint userdata; + uint radius16_length16; float4 shadowAtlasMulAdd; @@ -643,6 +643,14 @@ struct ShaderEntity { return f16tof32(type8_flags8_range16 >> 16u); } + inline float GetRadius() + { + return f16tof32(radius16_length16); + } + inline float GetLength() + { + return f16tof32(radius16_length16 >> 16u); + } inline float3 GetDirection() { return normalize(float3( @@ -719,6 +727,14 @@ struct ShaderEntity { type8_flags8_range16 |= XMConvertFloatToHalf(value) << 16u; } + inline void SetRadius(float value) + { + radius16_length16 |= XMConvertFloatToHalf(value); + } + inline void SetLength(float value) + { + radius16_length16 |= XMConvertFloatToHalf(value) << 16u; + } inline void SetColor(float4 value) { color.x |= XMConvertFloatToHalf(value.x); diff --git a/WickedEngine/shaders/brdf.hlsli b/WickedEngine/shaders/brdf.hlsli index 05f6e4713..2ca800a2c 100644 --- a/WickedEngine/shaders/brdf.hlsli +++ b/WickedEngine/shaders/brdf.hlsli @@ -154,41 +154,41 @@ struct SurfaceToLight // These are the functions that will be used by shaders: -float3 BRDF_GetSpecular(in Surface surface, in SurfaceToLight surfaceToLight) +float3 BRDF_GetSpecular(in Surface surface, in SurfaceToLight surface_to_light) { #ifdef ANISOTROPIC - float D = D_GGX_Anisotropic(surface.at, surface.ab, surfaceToLight.TdotH, surfaceToLight.BdotH, surfaceToLight.NdotH); + float D = D_GGX_Anisotropic(surface.at, surface.ab, surface_to_light.TdotH, surface_to_light.BdotH, surface_to_light.NdotH); float Vis = V_SmithGGXCorrelated_Anisotropic(surface.at, surface.ab, surface.TdotV, surface.BdotV, - surfaceToLight.TdotL, surfaceToLight.BdotL, surface.NdotV, surfaceToLight.NdotL); + surface_to_light.TdotL, surface_to_light.BdotL, surface.NdotV, surface_to_light.NdotL); #else - float D = D_GGX(surface.roughnessBRDF, surfaceToLight.NdotH, surfaceToLight.H); - float Vis = V_SmithGGXCorrelated(surface.roughnessBRDF, surface.NdotV, surfaceToLight.NdotL); + float D = D_GGX(surface.roughnessBRDF, surface_to_light.NdotH, surface_to_light.H); + float Vis = V_SmithGGXCorrelated(surface.roughnessBRDF, surface.NdotV, surface_to_light.NdotL); #endif // ANISOTROPIC - float3 specular = D * Vis * surfaceToLight.F; + float3 specular = D * Vis * surface_to_light.F; #ifdef SHEEN specular *= surface.sheen.albedoScaling; - D = D_Charlie(surface.sheen.roughnessBRDF, surfaceToLight.NdotH); - Vis = V_Neubelt(surface.NdotV, surfaceToLight.NdotL); + D = D_Charlie(surface.sheen.roughnessBRDF, surface_to_light.NdotH); + Vis = V_Neubelt(surface.NdotV, surface_to_light.NdotL); specular += D * Vis * surface.sheen.color; #endif // SHEEN #ifdef CLEARCOAT specular *= 1 - surface.clearcoat.F; - float NdotH = saturate(dot(surface.clearcoat.N, surfaceToLight.H)); - D = D_GGX(surface.clearcoat.roughnessBRDF, NdotH, surfaceToLight.H); - Vis = V_Kelemen(surfaceToLight.LdotH); + float NdotH = saturate(dot(surface.clearcoat.N, surface_to_light.H)); + D = D_GGX(surface.clearcoat.roughnessBRDF, NdotH, surface_to_light.H); + Vis = V_Kelemen(surface_to_light.LdotH); specular += D * Vis * surface.clearcoat.F; #endif // CLEARCOAT - return specular * surfaceToLight.NdotL; + return specular * surface_to_light.NdotL; } -float3 BRDF_GetDiffuse(in Surface surface, in SurfaceToLight surfaceToLight) +float3 BRDF_GetDiffuse(in Surface surface, in SurfaceToLight surface_to_light) { // Note: subsurface scattering will remove Fresnel (F), because otherwise // there would be artifact on backside where diffuse wraps - float3 diffuse = (1 - lerp(surfaceToLight.F, 0, saturate(surface.sss.a))); + float3 diffuse = (1 - lerp(surface_to_light.F, 0, saturate(surface.sss.a))); #ifdef SHEEN diffuse *= surface.sheen.albedoScaling; @@ -198,7 +198,7 @@ float3 BRDF_GetDiffuse(in Surface surface, in SurfaceToLight surfaceToLight) diffuse *= 1 - surface.clearcoat.F; #endif // CLEARCOAT - return diffuse * surfaceToLight.NdotL_sss; + return diffuse * surface_to_light.NdotL_sss; } #endif // WI_BRDF_HF diff --git a/WickedEngine/shaders/ddgi_raytraceCS.hlsl b/WickedEngine/shaders/ddgi_raytraceCS.hlsl index 5b789989e..a39ffa9a4 100644 --- a/WickedEngine/shaders/ddgi_raytraceCS.hlsl +++ b/WickedEngine/shaders/ddgi_raytraceCS.hlsl @@ -231,13 +231,26 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn RayDesc newRay; newRay.Origin = surface.P; -#if 1 - newRay.Direction = normalize(lerp(L, sample_hemisphere_cos(L, rng), 0.025f)); -#else - newRay.Direction = L; -#endif newRay.TMin = 0.001; newRay.TMax = dist; + + newRay.Direction = L; + if (light.GetRadius() > 0) + { + switch (light.GetType()) + { + default: + case ENTITY_TYPE_DIRECTIONALLIGHT: + newRay.Direction += sample_hemisphere_cos(L, rng) * light.GetRadius(); + break; + case ENTITY_TYPE_POINTLIGHT: + case ENTITY_TYPE_SPOTLIGHT: + newRay.Direction = normalize(light.position + sample_hemisphere_cos(L, rng) * light.GetRadius() - surface.P); + break; + } + } + newRay.Direction += max3(surface.sss); + #ifdef RTAPI q.TraceRayInline( scene_acceleration_structure, // RaytracingAccelerationStructure AccelerationStructure diff --git a/WickedEngine/shaders/globals.hlsli b/WickedEngine/shaders/globals.hlsli index a7d63992b..d9ced3d40 100644 --- a/WickedEngine/shaders/globals.hlsli +++ b/WickedEngine/shaders/globals.hlsli @@ -1039,6 +1039,15 @@ inline float dither(in float2 pixel) } +inline float sphere_surface_area(in float radius) +{ + return 4 * PI * radius * radius; +} +inline float sphere_volume(in float radius) +{ + return 4.0 / 3.0 * PI * radius * radius * radius; +} + float plane_point_distance(float3 planeOrigin, float3 planeNormal, float3 P) { diff --git a/WickedEngine/shaders/lightCullingCS.hlsl b/WickedEngine/shaders/lightCullingCS.hlsl index 857fe3600..e975e423b 100644 --- a/WickedEngine/shaders/lightCullingCS.hlsl +++ b/WickedEngine/shaders/lightCullingCS.hlsl @@ -240,7 +240,7 @@ void main(uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : case ENTITY_TYPE_POINTLIGHT: { float3 positionVS = mul(GetCamera().view, float4(entity.position, 1)).xyz; - Sphere sphere = { positionVS.xyz, entity.GetRange() }; + Sphere sphere = { positionVS.xyz, entity.GetRange() + entity.GetLength() }; if (SphereInsideFrustum(sphere, GroupFrustum, nearClipVS, maxDepthVS)) { AppendEntity_Transparent(i); diff --git a/WickedEngine/shaders/lightingHF.hlsli b/WickedEngine/shaders/lightingHF.hlsli index af2d91fb3..091cb8ee9 100644 --- a/WickedEngine/shaders/lightingHF.hlsli +++ b/WickedEngine/shaders/lightingHF.hlsli @@ -155,6 +155,20 @@ inline float attenuation_pointlight(in float dist, in float dist2, in float rang inline void light_point(in ShaderEntity light, in Surface surface, inout Lighting lighting, in float shadow_mask = 1) { float3 L = light.position - surface.P; + +#ifndef DISABLE_AREA_LIGHTS + if (light.GetLength() > 0) + { + // Diffuse representative point on line: + const float3 line_point = closest_point_on_segment( + light.position - light.GetDirection() * light.GetLength() * 0.5, + light.position + light.GetDirection() * light.GetLength() * 0.5, + surface.P + ); + L = line_point - surface.P; + } +#endif // DISABLE_AREA_LIGHTS + const float dist2 = dot(L, L); const float range = light.GetRange(); const float range2 = range * range; @@ -162,7 +176,6 @@ inline void light_point(in ShaderEntity light, in Surface surface, inout Lightin [branch] if (dist2 < range2) { - const float3 Lunnormalized = L; const float dist = sqrt(dist2); L /= dist; @@ -182,7 +195,7 @@ inline void light_point(in ShaderEntity light, in Surface surface, inout Lightin if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0) #endif // SHADOW_MASK_ENABLED { - shadow *= shadow_cube(light, Lunnormalized); + shadow *= shadow_cube(light, light.position - surface.P); } } @@ -193,7 +206,42 @@ inline void light_point(in ShaderEntity light, in Surface surface, inout Lightin light_color *= attenuation_pointlight(dist, dist2, range, range2); lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); + +#ifndef DISABLE_AREA_LIGHTS + if (light.GetLength() > 0) + { + // Specular representative point on line: + float3 P0 = light.position - light.GetDirection() * light.GetLength() * 0.5; + float3 P1 = light.position + light.GetDirection() * light.GetLength() * 0.5; + float3 L0 = P0 - surface.P; + float3 L1 = P1 - surface.P; + float3 Ld = L1 - L0; + float RdotLd = dot(surface.R, Ld); + float t = dot(surface.R, L0) * RdotLd - dot(L0, Ld); + t /= dot(Ld, Ld) - RdotLd * RdotLd; + L = (L0 + saturate(t) * Ld); + } + else + { + L = light.position - surface.P; + } + if(light.GetRadius() > 0) + { + // Specular representative point on sphere: + float3 centerToRay = mad(dot(L, surface.R), surface.R, -L); + L = mad(centerToRay, saturate(light.GetRadius() / length(centerToRay)), L); + // Energy conservation for radius: + light_color /= max(1, sphere_volume(light.GetRadius())); + } + if (light.GetLength() > 0 || light.GetRadius() > 0) + { + L = normalize(L); + surface_to_light.create(surface, L); // recompute all surface-light vectors + } +#endif // DISABLE_AREA_LIGHTS + lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); + } } } @@ -260,6 +308,21 @@ inline void light_spot(in ShaderEntity light, in Surface surface, inout Lighting light_color *= attenuation_spotlight(dist, dist2, range, range2, spot_factor, light.GetAngleScale(), light.GetAngleOffset()); lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); + +#ifndef DISABLE_AREA_LIGHTS + if (light.GetRadius() > 0) + { + // Specular representative point on sphere: + L = light.position - surface.P; + float3 centerToRay = mad(dot(L, surface.R), surface.R, -L); + L = mad(centerToRay, saturate(light.GetRadius() / length(centerToRay)), L); + L = normalize(L); + surface_to_light.create(surface, L); // recompute all surface-light vectors + // Energy conservation for radius: + light_color /= max(1, sphere_volume(light.GetRadius())); + } +#endif // DISABLE_AREA_LIGHTS + lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); } } diff --git a/WickedEngine/shaders/raytraceCS.hlsl b/WickedEngine/shaders/raytraceCS.hlsl index 559f0ffe2..d6789e738 100644 --- a/WickedEngine/shaders/raytraceCS.hlsl +++ b/WickedEngine/shaders/raytraceCS.hlsl @@ -213,6 +213,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) dist = FLT_MAX; L = light.GetDirection().xyz; + L += sample_hemisphere_cos(L, rng) * light.GetRadius(); surfaceToLight.create(surface, L); @@ -231,6 +232,8 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) break; case ENTITY_TYPE_POINTLIGHT: { + light.position += sample_hemisphere_cos(normalize(light.position - surface.P), rng) * light.GetRadius(); + light.position += light.GetDirection() * (rng.next_float() - 0.5) * light.GetLength(); L = light.position - surface.P; const float dist2 = dot(L, L); const float range = light.GetRange(); @@ -255,6 +258,8 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) break; case ENTITY_TYPE_SPOTLIGHT: { + float3 Loriginal = normalize(light.position - surface.P); + light.position += sample_hemisphere_cos(normalize(light.position - surface.P), rng) * light.GetRadius(); L = light.position - surface.P; const float dist2 = dot(L, L); const float range = light.GetRange(); @@ -271,7 +276,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) [branch] if (any(surfaceToLight.NdotL_sss)) { - const float spot_factor = dot(L, light.GetDirection()); + const float spot_factor = dot(Loriginal, light.GetDirection()); const float spot_cutoff = light.GetConeAngleCos(); [branch] @@ -292,9 +297,9 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) RayDesc newRay; newRay.Origin = surface.P; - newRay.Direction = normalize(lerp(L, sample_hemisphere_cos(L, rng), 0.025 + max3(surface.sss))); newRay.TMin = 0.001; newRay.TMax = dist; + newRay.Direction = L + max3(surface.sss); #ifdef RTAPI @@ -338,8 +343,8 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) if (any(shadow)) { lightColor *= shadow; - lighting.direct.specular = lightColor * BRDF_GetSpecular(surface, surfaceToLight); lighting.direct.diffuse = lightColor * BRDF_GetDiffuse(surface, surfaceToLight); + lighting.direct.specular = lightColor * BRDF_GetSpecular(surface, surfaceToLight); result += light_count * mad(surface.albedo / PI * (1 - surface.transmission), lighting.direct.diffuse, lighting.direct.specular) * surface.opacity; } } diff --git a/WickedEngine/shaders/renderlightmapPS.hlsl b/WickedEngine/shaders/renderlightmapPS.hlsl index 14691c6d6..79b49fe19 100644 --- a/WickedEngine/shaders/renderlightmapPS.hlsl +++ b/WickedEngine/shaders/renderlightmapPS.hlsl @@ -148,10 +148,26 @@ float4 main(Input input) : SV_TARGET RayDesc newRay; newRay.Origin = surface.P; - newRay.Direction = normalize(lerp(L, sample_hemisphere_cos(L, rng), 0.025f)); newRay.TMin = 0.001; newRay.TMax = dist; + newRay.Direction = L; + if (light.GetRadius() > 0) + { + switch (light.GetType()) + { + default: + case ENTITY_TYPE_DIRECTIONALLIGHT: + newRay.Direction += sample_hemisphere_cos(L, rng) * light.GetRadius(); + break; + case ENTITY_TYPE_POINTLIGHT: + case ENTITY_TYPE_SPOTLIGHT: + newRay.Direction = normalize(light.position + sample_hemisphere_cos(L, rng) * light.GetRadius() - surface.P); + break; + } + } + newRay.Direction += max3(surface.sss); + #ifdef RTAPI uint flags = RAY_FLAG_CULL_FRONT_FACING_TRIANGLES; if (bounce > ANYTHIT_CUTOFF_AFTER_BOUNCE_COUNT) diff --git a/WickedEngine/shaders/screenspaceshadowCS.hlsl b/WickedEngine/shaders/screenspaceshadowCS.hlsl index c1346a3db..58b5704e4 100644 --- a/WickedEngine/shaders/screenspaceshadowCS.hlsl +++ b/WickedEngine/shaders/screenspaceshadowCS.hlsl @@ -143,6 +143,10 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint3 GTid : break; case ENTITY_TYPE_POINTLIGHT: { + if (light.GetLength() > 0) + { + light.position = light.position + light.GetDirection() * (blue_noise(DTid.xy).g - 0.5) * light.GetLength(); + } L = light.position - surface.P; const float dist2 = dot(L, L); const float range = light.GetRange(); @@ -199,7 +203,23 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint3 GTid : uint seed = 0; float shadow = 0; - ray.Direction = normalize(lerp(L, mul(hemispherepoint_cos(bluenoise.x, bluenoise.y), get_tangentspace(L)), 0.025 + max3(surface.sss))); + ray.Direction = L; + if (light.GetRadius() > 0) + { + switch (light.GetType()) + { + default: + case ENTITY_TYPE_DIRECTIONALLIGHT: + ray.Direction += mul(hemispherepoint_cos(bluenoise.x, bluenoise.y), get_tangentspace(L)) * light.GetRadius(); + break; + case ENTITY_TYPE_POINTLIGHT: + case ENTITY_TYPE_SPOTLIGHT: + ray.Direction = normalize(light.position + mul(hemispherepoint_cos(bluenoise.x, bluenoise.y), get_tangentspace(L)) * light.GetRadius() - P); + break; + } + } + ray.Direction += max3(surface.sss); + #ifdef RTAPI RayQuery< RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES | diff --git a/WickedEngine/shaders/shadowHF.hlsli b/WickedEngine/shaders/shadowHF.hlsli index 55f01fb46..e6dda45db 100644 --- a/WickedEngine/shaders/shadowHF.hlsli +++ b/WickedEngine/shaders/shadowHF.hlsli @@ -64,7 +64,7 @@ inline float3 shadow_2D(in ShaderEntity light, in float3 shadow_pos, in float2 s inline float3 shadow_cube(in ShaderEntity light, in float3 Lunnormalized) { - const float remapped_distance = light.GetCubemapDepthRemapNear() + light.GetCubemapDepthRemapFar() / (max(max(abs(Lunnormalized.x), abs(Lunnormalized.y)), abs(Lunnormalized.z)) * 0.989); // little bias to avoid artifact + const float remapped_distance = light.GetCubemapDepthRemapNear() + light.GetCubemapDepthRemapFar() / (max(max(abs(Lunnormalized.x), abs(Lunnormalized.y)), abs(Lunnormalized.z))); const float3 uv_slice = cubemap_to_uv(-Lunnormalized); float2 shadow_uv = uv_slice.xy; shadow_border_shrink(light, shadow_uv); diff --git a/WickedEngine/shaders/surfel_raytraceCS.hlsl b/WickedEngine/shaders/surfel_raytraceCS.hlsl index 1c6e13b67..f7eb10895 100644 --- a/WickedEngine/shaders/surfel_raytraceCS.hlsl +++ b/WickedEngine/shaders/surfel_raytraceCS.hlsl @@ -225,13 +225,26 @@ void main(uint3 DTid : SV_DispatchThreadID) RayDesc newRay; newRay.Origin = surface.P; -#if 1 - newRay.Direction = normalize(lerp(L, sample_hemisphere_cos(L, rng), 0.025f)); -#else - newRay.Direction = L; -#endif newRay.TMin = 0.001; newRay.TMax = dist; + + newRay.Direction = L; + if (light.GetRadius() > 0) + { + switch (light.GetType()) + { + default: + case ENTITY_TYPE_DIRECTIONALLIGHT: + newRay.Direction += sample_hemisphere_cos(L, rng) * light.GetRadius(); + break; + case ENTITY_TYPE_POINTLIGHT: + case ENTITY_TYPE_SPOTLIGHT: + newRay.Direction = normalize(light.position + sample_hemisphere_cos(L, rng) * light.GetRadius() - surface.P); + break; + } + } + newRay.Direction += max3(surface.sss); + #ifdef RTAPI q.TraceRayInline( scene_acceleration_structure, // RaytracingAccelerationStructure AccelerationStructure diff --git a/WickedEngine/shaders/vPointLightVS.hlsl b/WickedEngine/shaders/vPointLightVS.hlsl index ba4a9261c..0c9a72fa1 100644 --- a/WickedEngine/shaders/vPointLightVS.hlsl +++ b/WickedEngine/shaders/vPointLightVS.hlsl @@ -2,14 +2,11 @@ #include "circle.hlsli" #include "lightingHF.hlsli" -VertexToPixel main(uint vID : SV_VertexID) +VertexToPixel main(float4 pos : POSITION) { VertexToPixel Out = (VertexToPixel)0; - float4 pos = CIRCLE[vID]; - Out.col = float4(xLightColor.rgb, 1) * saturate(1 - dot(pos.xyz, pos.xyz)); - - pos = mul(xLightWorld, pos); + Out.col = float4(xLightColor.rgb, 1); Out.pos = mul(GetCamera().view_projection, pos); return Out; diff --git a/WickedEngine/wiEnums.h b/WickedEngine/wiEnums.h index 14ab39a45..7261e590c 100644 --- a/WickedEngine/wiEnums.h +++ b/WickedEngine/wiEnums.h @@ -412,6 +412,7 @@ namespace wi::enums ILTYPE_OBJECT_DEBUG, ILTYPE_RENDERLIGHTMAP, ILTYPE_VERTEXCOLOR, + ILTYPE_POSITION, ILTYPE_COUNT }; // rasterizer states diff --git a/WickedEngine/wiMath.h b/WickedEngine/wiMath.h index ff952f413..eb25de78d 100644 --- a/WickedEngine/wiMath.h +++ b/WickedEngine/wiMath.h @@ -30,6 +30,7 @@ using namespace DirectX::PackedVector; namespace wi::math { static constexpr XMFLOAT4X4 IDENTITY_MATRIX = XMFLOAT4X4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + static constexpr float PI = XM_PI; constexpr float saturate(float x) { return std::min(std::max(x, 0.0f), 1.0f); } @@ -221,6 +222,15 @@ namespace wi::math // a, b, c: trangle side lengths float TriangleArea(float a, float b, float c); + constexpr float SphereSurfaceArea(float radius) + { + return 4 * PI * radius * radius; + } + constexpr float SphereVolume(float radius) + { + return 4.0f / 3.0f * PI * radius * radius * radius; + } + XMFLOAT3 GetCubicHermiteSplinePos( const XMFLOAT3& startPos, const XMFLOAT3& endPos, diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index cafbbde2c..f27f02b14 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -28,6 +28,8 @@ #include "shaders/ShaderInterop_DDGI.h" #include "shaders/ShaderInterop_VXGI.h" #include "shaders/ShaderInterop_FSR2.h" +#include "shaders/uvsphere.hlsli" +#include "shaders/cone.hlsli" #include #include @@ -129,6 +131,9 @@ wi::vector paintrads; wi::SpinLock deferredMIPGenLock; wi::vector> deferredMIPGens; +static const uint32_t vertexCount_uvsphere = arraysize(UVSPHERE); +static const uint32_t vertexCount_cone = arraysize(CONE); + bool volumetric_clouds_precomputed = false; Texture texture_shapeNoise; @@ -779,6 +784,11 @@ void LoadShaders() LoadShader(ShaderStage::VS, shaders[VSTYPE_RENDERLIGHTMAP], "renderlightmapVS.cso"); }); + inputLayouts[ILTYPE_POSITION].elements = + { + { "POSITION", 0, Format::R32G32B32A32_FLOAT, 0, InputLayout::APPEND_ALIGNED_ELEMENT, InputClassification::PER_VERTEX_DATA }, + }; + wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::VS, shaders[VSTYPE_OBJECT_COMMON_TESSELLATION], "objectVS_common_tessellation.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::VS, shaders[VSTYPE_OBJECT_PREPASS_TESSELLATION], "objectVS_prepass_tessellation.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::VS, shaders[VSTYPE_OBJECT_PREPASS_ALPHATEST_TESSELLATION], "objectVS_prepass_alphatest_tessellation.cso"); }); @@ -1287,6 +1297,7 @@ void LoadShaders() desc.bs = &blendStates[BSTYPE_ADDITIVE]; desc.vs = &shaders[VSTYPE_LIGHTVISUALIZER_POINTLIGHT]; desc.rs = &rasterizers[RSTYPE_FRONT]; + desc.il = &inputLayouts[ILTYPE_POSITION]; break; case LightComponent::SPOT: desc.bs = &blendStates[BSTYPE_ADDITIVE]; @@ -3837,6 +3848,9 @@ void UpdateRenderData( shaderentity.SetType(light.GetType()); shaderentity.position = light.position; shaderentity.SetRange(light.GetRange()); + shaderentity.SetRadius(light.radius); + shaderentity.SetLength(light.length); + shaderentity.SetDirection(light.direction); shaderentity.SetColor(float4(light.color.x * light.intensity, light.color.y * light.intensity, light.color.z * light.intensity, 1)); // mark as no shadow by default: @@ -3857,7 +3871,6 @@ void UpdateRenderData( { case LightComponent::DIRECTIONAL: { - shaderentity.SetDirection(light.direction); shaderentity.SetShadowCascadeCount((uint)light.cascade_distances.size()); if (shadow && !light.cascade_distances.empty()) @@ -3899,7 +3912,6 @@ void UpdateRenderData( shaderentity.SetConeAngleCos(outerConeAngleCos); shaderentity.SetAngleScale(lightAngleScale); shaderentity.SetAngleOffset(lightAngleOffset); - shaderentity.SetDirection(light.direction); if (shadow) { @@ -4796,23 +4808,148 @@ void DrawLightVisualizers( if (light.GetType() == type && light.IsVisualizerEnabled()) { - VolumeLightCB lcb; lcb.xLightColor = XMFLOAT4(light.color.x, light.color.y, light.color.z, 1); lcb.xLightEnerdis = XMFLOAT4(light.intensity, light.GetRange(), light.outerConeAngle, light.intensity); if (type == LightComponent::POINT) { + const float sphere_volume = std::max(1.0f, wi::math::SphereVolume(light.radius)); + lcb.xLightColor.x *= light.intensity / sphere_volume; + lcb.xLightColor.y *= light.intensity / sphere_volume; + lcb.xLightColor.z *= light.intensity / sphere_volume; lcb.xLightEnerdis.w = light.GetRange() * 0.025f; // scale XMStoreFloat4x4(&lcb.xLightWorld, XMMatrixScaling(lcb.xLightEnerdis.w, lcb.xLightEnerdis.w, lcb.xLightEnerdis.w)* camrot* XMMatrixTranslationFromVector(XMLoadFloat3(&light.position)) ); - device->BindDynamicConstantBuffer(lcb, CB_GETBINDSLOT(VolumeLightCB), cmd); - device->Draw(108, 0, cmd); // circle + uint32_t vertexCount = vertexCount_uvsphere; + uint32_t segmentCount_cylinder = 32; + if (light.length > 0) + { + vertexCount += segmentCount_cylinder * 2 * 3; + } + GraphicsDevice::GPUAllocation allocation = device->AllocateGPU(vertexCount * sizeof(float4), cmd); + float4* dst = (float4*)allocation.data; + float rad = std::max(0.025f, light.radius); + if (light.length > 0) + { + // Capsule from two half spheres and an uncapped cylinder: + XMMATRIX M = + XMMatrixScaling(rad, rad, rad) * + XMMatrixTranslation(-light.length * 0.5f, 0, 0) * + XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation)) * + XMMatrixTranslation(light.position.x, light.position.y, light.position.z) + ; + for (uint32_t i = 0; i < vertexCount_uvsphere; i += 3) + { + if (UVSPHERE[i].x <= 0.01f && UVSPHERE[i + 1].x <= 0.01f && UVSPHERE[i + 2].x <= 0.01f) + { + XMVECTOR pos = XMLoadFloat4(&UVSPHERE[i]); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMLoadFloat4(&UVSPHERE[i + 1]); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMLoadFloat4(&UVSPHERE[i + 2]); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + } + } + M = + XMMatrixScaling(rad, rad, rad) * + XMMatrixTranslation(light.length * 0.5f, 0, 0) * + XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation)) * + XMMatrixTranslation(light.position.x, light.position.y, light.position.z) + ; + for (uint32_t i = 0; i < vertexCount_uvsphere; i += 3) + { + if (UVSPHERE[i].x >= -0.01f && UVSPHERE[i + 1].x >= -0.01f && UVSPHERE[i + 2].x >= -0.01f) + { + XMVECTOR pos = XMLoadFloat4(&UVSPHERE[i]); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMLoadFloat4(&UVSPHERE[i + 1]); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMLoadFloat4(&UVSPHERE[i + 2]); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + } + } + M = + XMMatrixScaling(light.length * 0.5f, rad, rad) * + XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation)) * + XMMatrixTranslation(light.position.x, light.position.y, light.position.z) + ; + for (uint32_t i = 0; i < segmentCount_cylinder; ++i) + { + float t1 = float(i) / segmentCount_cylinder * XM_2PI; + float t2 = float(i + 1) / segmentCount_cylinder * XM_2PI; + XMVECTOR pos = XMVectorSet(-1, std::sin(t1), std::cos(t1), 1); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMVectorSet(1, std::sin(t2), std::cos(t2), 1); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMVectorSet(1, std::sin(t1), std::cos(t1), 1); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMVectorSet(-1, std::sin(t1), std::cos(t1), 1); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMVectorSet(-1, std::sin(t2), std::cos(t2), 1); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + pos = XMVectorSet(1, std::sin(t2), std::cos(t2), 1); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + } + } + else + { + // Sphere: + XMMATRIX M = + XMMatrixScaling(rad, rad, rad) * + XMMatrixTranslation(-light.length * 0.5f, 0, 0) * + XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation)) * + XMMatrixTranslation(light.position.x, light.position.y, light.position.z) + ; + for (uint32_t i = 0; i < vertexCount_uvsphere; ++i) + { + XMVECTOR pos = XMLoadFloat4(&UVSPHERE[i]); + pos = XMVector3Transform(pos, M); + XMStoreFloat4(dst, pos); + dst++; + } + } + const GPUBuffer* vbs[] = { + &allocation.buffer, + }; + const uint32_t strides[] = { + sizeof(float4), + }; + const uint64_t offsets[] = { + allocation.offset, + }; + device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, offsets, cmd); + + device->Draw(vertexCount, 0, cmd); } else if (type == LightComponent::SPOT) { @@ -4828,7 +4965,7 @@ void DrawLightVisualizers( device->BindDynamicConstantBuffer(lcb, CB_GETBINDSLOT(VolumeLightCB), cmd); - device->Draw(192, 0, cmd); // cone + device->Draw(vertexCount_cone, 0, cmd); } float coneS = (float)(light.outerConeAngle * 2 / XM_PIDIV4); @@ -4841,7 +4978,7 @@ void DrawLightVisualizers( device->BindDynamicConstantBuffer(lcb, CB_GETBINDSLOT(VolumeLightCB), cmd); - device->Draw(192, 0, cmd); // cone + device->Draw(vertexCount_cone, 0, cmd); } } } @@ -6275,7 +6412,7 @@ void DrawDebugWorld( device->BindResource(&scene.envmapArray, 0, cmd, scene.envmapArray.GetDesc().mip_levels + probe.textureIndex); } - device->Draw(2880, 0, cmd); // uv-sphere + device->Draw(vertexCount_uvsphere, 0, cmd); } diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index a2093744e..3e6cd280d 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -4007,6 +4007,7 @@ namespace wi::scene { default: case LightComponent::DIRECTIONAL: + XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W))); aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); locker.lock(); if (args.jobIndex < weather.most_important_light_index) @@ -4022,9 +4023,11 @@ namespace wi::scene locker.unlock(); break; case LightComponent::SPOT: + XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W))); aabb.createFromHalfWidth(light.position, XMFLOAT3(light.GetRange(), light.GetRange(), light.GetRange())); break; case LightComponent::POINT: + XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(1, 0, 0, 0), W))); aabb.createFromHalfWidth(light.position, XMFLOAT3(light.GetRange(), light.GetRange(), light.GetRange())); break; } diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 5531a7fc9..bd26409a5 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -35,7 +35,7 @@ namespace wi::scene wi::ecs::ComponentManager& rigidbodies = componentLibrary.Register("wi::scene::Scene::rigidbodies", 1); // version = 1 wi::ecs::ComponentManager& softbodies = componentLibrary.Register("wi::scene::Scene::softbodies"); wi::ecs::ComponentManager& armatures = componentLibrary.Register("wi::scene::Scene::armatures"); - wi::ecs::ComponentManager& lights = componentLibrary.Register("wi::scene::Scene::lights", 1); // version = 1 + wi::ecs::ComponentManager& lights = componentLibrary.Register("wi::scene::Scene::lights", 2); // version = 2 wi::ecs::ComponentManager& cameras = componentLibrary.Register("wi::scene::Scene::cameras"); wi::ecs::ComponentManager& probes = componentLibrary.Register("wi::scene::Scene::probes"); wi::ecs::ComponentManager& forces = componentLibrary.Register("wi::scene::Scene::forces", 1); // version = 1 diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index c35af2b38..737ea12b8 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -852,6 +852,8 @@ namespace wi::scene float range = 10.0f; float outerConeAngle = XM_PIDIV4; float innerConeAngle = 0; // default value is 0, means only outer cone angle is used + float radius = 0.025f; + float length = 0; wi::vector cascade_distances = { 8,80,800 }; wi::vector lensFlareNames; diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index 882fa2a48..6ada1aa04 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -825,6 +825,11 @@ namespace wi::scene { archive >> cascade_distances; } + if (seri.GetVersion() >= 2) + { + archive >> radius; + archive >> length; + } wi::jobsystem::Execute(seri.ctx, [&](wi::jobsystem::JobArgs args) { lensFlareRimTextures.resize(lensFlareNames.size()); @@ -882,6 +887,11 @@ namespace wi::scene { archive << cascade_distances; } + if (seri.GetVersion() >= 2) + { + archive << radius; + archive << length; + } } } void CameraComponent::Serialize(wi::Archive& archive, EntitySerializer& seri)