// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "../Common.ush" // Gaussian function with a=1/(c*sqrt(2*PI)) so it represents the probability density function of a normally distributed random variable with expected value mu = b for some variance and integrale = 1; float GaussianFunction(float x, float Mean, float Variance) { // To avoid numerical precision issue, clamp the variance (stddev =0.001) Variance = max(0.000001f, Variance); float StandardDeviation = sqrt(Variance); float FactorA = 1 / (StandardDeviation * sqrt(2 * PI)); float Bell = exp(-abs((x - Mean) * (x - Mean)) / (2 * Variance)); return FactorA * Bell; } float GaussianFunction(float2 P, float2 Mean, float Variance) { // To avoid numerical precision issue, clamp the variance (stddev =0.001) Variance = max(0.000001f, Variance); float StandardDeviation = sqrt(Variance); float FactorA = 1 / (StandardDeviation * sqrt(2 * PI)); float Bell = exp(-((P.x - Mean.x) * (P.x - Mean.x)) / (2 * Variance) - ((P.y - Mean.y) * (P.y - Mean.y)) / (2 * Variance)); return FactorA * Bell; } #if OPAQUE_ROUGH_REFRACTION_PS #include "/Engine/Private/Substrate/Substrate.ush" Texture2D SeparatedOpaqueRoughRefractionSceneColor; float2 BlurDirection; float BlurScale; void OpaqueRoughRefractionPS( float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0) { float2 PixelPos = SVPos.xy; float2 UvBuffer = PixelPos * View.BufferSizeAndInvSize.zw; // First read unblurred layers at the bottom of the top layer OutColor = SeparatedOpaqueRoughRefractionSceneColor[uint2(PixelPos)]; #if PERMUTATION_ENABLE_BLUR && SUBSTRATE_OPAQUE_ROUGH_REFRACTION_ENABLED FSubstrateOpaqueRoughRefractionData ORRData = SubstrateUnpackOpaqueRoughRefractionData(Substrate.OpaqueRoughRefractionTexture[PixelPos]); ORRData.VarianceCm *= BlurScale; const float DeviceZ = LookupDeviceZ(UvBuffer); const float SceneDepthCm = ConvertFromDeviceZ(DeviceZ); const float2 PixelRadiusCm2 = SceneDepthCm * GetTanHalfFieldOfView() * View.ViewSizeAndInvSize.zw; const float PixelRadiusCm = min(PixelRadiusCm2.x, PixelRadiusCm2.y); const float StandardDeviationCm = sqrt(ORRData.VarianceCm); const float StandardDeviationPixelSpace = StandardDeviationCm / PixelRadiusCm; // Be careful to do that conversion on linear standard deviation, not variance const float VariancePixelSpace = StandardDeviationPixelSpace * StandardDeviationPixelSpace; const float PerceptualBlurAcceptanceFactor = 2.5f; const bool OpaqueRoughRefractionEnabled = ORRData.OpaqueRoughRefractionEnabled;// && (PixelRadiusCm < PerceptualBlurAcceptanceFactor* ORRData.VarianceCm); // We are now going to blur the bottom layer luminance according to the top layer variance resulting from the roughness. // For this we have: measure GGX variance for a perpendicaular view, eta=1.33 and varying roughness. At runtim that variance is approximated by a Gaussian. if (OpaqueRoughRefractionEnabled) { // Proceed blurring the bottom layers if needed according to the blur footprint. float3 WeightedLuminance = 0.0f; float AccumulatedWeights = 0.0f; // A area of radius StandardDeviation*3 around the mean of a guassian normal distribution (=mu) encompass 99% of the energy. const float RadiusAroundCenterMu = 3.0f; const float BlurStep = 0.5f; // Randomly offset the blur center position around according to step size to leverage TAA and smooth the blur. const float BlurRandomness = InterleavedGradientNoise(PixelPos, float(View.StateFrameIndexMod8)) - 0.5; // In [-0.5, 0.5] const float RandAngle = BlurRandomness * 2.0f * PI; float CosA, SinA; sincos(RandAngle, SinA, CosA); const float2 BlurOrigin = PixelPos + BlurStep * StandardDeviationPixelSpace * float2(CosA, SinA); const float4 UvViewRectMinMax = float4(float2(View.ViewRectMinAndSize.xy) + 0.5f, float2(View.ViewRectMinAndSize.xy + View.BufferSizeAndInvSize.xy) - 0.5f) * View.BufferSizeAndInvSize.zwzw; for (float u = -RadiusAroundCenterMu; u <= RadiusAroundCenterMu; u += BlurStep) // steps of 0.5 means 13 taps { const float OffsetPixelSpace = u * StandardDeviationPixelSpace; const float2 SampleUV = (BlurOrigin + BlurDirection * OffsetPixelSpace) * View.BufferSizeAndInvSize.zw; const uint2 SamplePixelPos = uint2(SampleUV * View.BufferSizeAndInvSize.xy); float Weight = GaussianFunction(OffsetPixelSpace, 0.0f, VariancePixelSpace); // Do not accept rough refraction if the SampleUV is out of the view rect const bool bSampleIsWithinViewRect = all(SampleUV >= UvViewRectMinMax.xy) && all(SampleUV <= UvViewRectMinMax.zw); Weight *= bSampleIsWithinViewRect ? 1.0f : 0.0f; // Do not accept rough refraction if the sampled material does not do rough refraction. Solved well by depth difference below so disabled. //FSubstrateOpaqueRoughRefractionData ORRSampleData = SubstrateUnpackOpaqueRoughRefractionData(Substrate.OpaqueRoughRefractionTexture[SamplePixelPos]); //Weight *= ORRSampleData.OpaqueRoughRefractionEnabled ? 1.0f : 0.0f; // Do not accept rough refraction if the depth difference is too high. Solves a lot of problems such as: // - materials/sky being further away leaking, // - two materials hacing rough refraction touching on screen with a too large depth difference. // Using the sample UV with bilinear interpolation for such an exclusion mechanism is not perfect but does work well in most cases. // The true solution would be to only take the average of the 4 neightboor samples according to bilinear weights modulated by the exclusion filter itself. const float DepthDifferenceThresholdCm = 5.0f; const float SampleSceneDepthCm = ConvertFromDeviceZ(LookupDeviceZ(SampleUV)); Weight *= abs(SampleSceneDepthCm - SceneDepthCm) < DepthDifferenceThresholdCm ? 1.0f : 0.0f; // Do not accepth rough refraction if the normal difference is too high: // => this could prove complex to make work in many cases with high frequency normalmapped surfaces. A geometry normal would be better to use. WeightedLuminance += SeparatedOpaqueRoughRefractionSceneColor.SampleLevel(View.SharedBilinearClampedSampler, SampleUV, 0).rgb * Weight; AccumulatedWeights += Weight; } const float bIsBlurValid = AccumulatedWeights > 0.0; const float3 BlurredSceneColor = bIsBlurValid ? WeightedLuminance / AccumulatedWeights : 0.0f; // Combine blurred layers below with unblurred according to coverage OutColor = float4(lerp(OutColor.rgb, BlurredSceneColor, bIsBlurValid ? ORRData.Coverage : 0.0), OutColor.a); //OutColor *= float4(0, 1, 0, 1); } #endif } #endif #ifdef SUBSTRATE_RND_SHADERS ////////////////////////////////////////////////////////////////////////// // RnD shaders only used when enabled locally ////////////////////////////////////////////////////////////////////////// #include "/Engine/Private/MiniFontCommon.ush" #include "/Engine/Private/ShaderPrint.ush" #include "/Engine/Private/ColorMap.ush" #include "/Engine/Private/BRDF.ush" #include "/Engine/Private/MonteCarlo.ush" #include "/Engine/Private/PathTracing/Material/PathTracingFresnel.ush" #include "/Engine/Private/PathTracing/Material/PathTracingGlossy.ush" #include "/Engine/Private/Substrate/SubstrateStatisticalOperators.ush" #define MODE_REFLECTION 0 // Updated from http://jcgt.org/published/0007/03/04/ bool Slabs(float3 P0, float3 P1, float3 RayOrigin, float3 InvRaydir, out float OutTMin, out float OutTMax) { float3 T0 = (P0 - RayOrigin) * InvRaydir; float3 T1 = (P1 - RayOrigin) * InvRaydir; float3 TMin = min(T0, T1); float3 TMax = max(T0, T1); float MaxTMin = max(max(TMin.x, TMin.y), TMin.z); float MinTMax = min(min(TMax.x, TMax.y), TMax.z); OutTMin = MaxTMin; OutTMax = MinTMax; return MaxTMin <= MinTMax; } float GetEta12() { const float AirIOR = 1.0f; const float WaterIOR = 1.33f; const float EtaIn = WaterIOR / AirIOR; // We always consider entering from air to water return EtaIn; } #define WOLRD_NORMAL float3(0, 0, 1) // returns true the direction is valid for the current mode (refraction or reflection) bool GetGGXDirection(float2 RandomUV, float Roughness, float3 V, inout float3 L, inout float Value, float Eta12) { float2 Alpha; GetGGXBasis(Roughness, WOLRD_NORMAL, Alpha); float4 H = ImportanceSampleVisibleGGX(RandomUV, Alpha, V, false); Value = 0; L = 0; float F = 0; const float RandSample = MODE_REFLECTION ? -0.01f : 1.0f; const bool bIsRefraction = SampleRefraction(-V, H.xyz, Eta12, RandSample, L, F); const bool bTotalInternalReflection = F == 1.0f; if (bIsRefraction && !bTotalInternalReflection) { Value = GGXEvalRefraction(L, V, H.xyz, Alpha, Eta12).x; return MODE_REFLECTION == 0; } // Reflection Value = GGXEvalReflection(L, V, H.xyz, Alpha, false).x; return MODE_REFLECTION == 1; } struct FLobeStat { float Count; float2 Mean; float2 M2; float2 Variance; float pad; }; // See Welford's algorithm https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance void LobStatUpdate(inout FLobeStat LobeStat, in float2 InPos) { LobeStat.Count += 1; float2 Delta = InPos - LobeStat.Mean; LobeStat.Mean += Delta / LobeStat.Count; float2 Delta2 = InPos - LobeStat.Mean; LobeStat.M2 += Delta * Delta2; } void LobStatFinalize(inout FLobeStat LobeStat) { if (LobeStat.Count < 2) { // nan } else { // Mean is already valid LobeStat.Variance = LobeStat.M2 / LobeStat.Count; //LobeStat.SampleVariance = LobeStat.M2 / (LobeStat.Count - 1); } } #if defined(EVALUATE_ROUGH_REFRACTION_LOBE_CS) || defined(VISUALIZE_ROUGH_REFRACTION_PS) struct FUI { float IncidentAngle; float SlabThickness; float Roughness; float Eta12; // Derived float a2; float3 V; }; FShaderPrintContext GetUIContext(bool bActive) { return InitShaderPrintContext(bActive, uint2(100, 100)); } FUI GetUI(inout FShaderPrintContext Context) { FUI UI; UI.IncidentAngle = AddSlider(Context, TEXT("IncidentAngle"), 0.0f, GetDefaultFontColor(), 0.0f, 0.5f * PI); Newline(Context); Newline(Context); UI.Roughness = AddSlider(Context, TEXT("Roughness"), 0.5f, GetDefaultFontColor(), 0.0f, 1.0f); Newline(Context); Newline(Context); UI.Eta12 = AddSlider(Context, TEXT("Eta12"), 1.3f, GetDefaultFontColor(), 0.5f, 1.5f); Newline(Context); Newline(Context); UI.SlabThickness = AddSlider(Context, TEXT("Slab Thickness"), 5.0f, GetDefaultFontColor(), 0.0f, 50.0f); Newline(Context); UI.a2 = Pow4(UI.Roughness); UI.V = float3(sin(UI.IncidentAngle), 0, cos(UI.IncidentAngle)); return UI; } void DrawCircleXY(inout FShaderPrintContext Context, float3 Center, float2 Radius, float4 ColorAlpha) { const float TStep = 0.1f; for (float t = 0; t <= 1.0; t += TStep) { float S0, C0; float S1, C1; sincos(t * 2 * PI, S0, C0); sincos((t + TStep) * 2 * PI, S1, C1); float3 P0 = Center + float3(C0, S0, 0.0) * float3(Radius, 0.0); float3 P1 = Center + float3(C1, S1, 0.0) * float3(Radius, 0.0); AddLineWS(Context, P0, P1, ColorAlpha); } } #endif #ifdef EVALUATE_ROUGH_REFRACTION_LOBE_CS uint TraceSqrtSampleCount; RWTexture2D SampleCountTextureUAV; RWStructuredBuffer LobStatisticsBufferUAV; [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void EvaluateRoughRefractionLobeCS(uint3 ThreadId : SV_DispatchThreadID) { if (all(ThreadId == 0)) { FShaderPrintContext Context = GetUIContext(true); FUI UI = GetUI(Context); FLobeStat LobeState = (FLobeStat)0; float Count = 0; float2 Mean = 0; float2 Variance = 0; for (float u = 0.5f; u < float(TraceSqrtSampleCount); u++) { for (float v = 0.5f; v < float(TraceSqrtSampleCount); v++) { float2 RandomUV = float2(u, v) / float(TraceSqrtSampleCount); float3 L = 0; float Value = 0; if (GetGGXDirection(RandomUV, UI.Roughness, UI.V, L, Value, UI.Eta12) && Value > 0.0f) { float2 SampleUV = L.xy * 0.5 + 0.5; SampleCountTextureUAV[uint2(SampleUV * 64.0f)] += 1; const float3 RayOnPlane = L.xyz / abs(L.z); LobStatUpdate(LobeState, RayOnPlane.xy); Count++; Mean += RayOnPlane.xy; } } } /* Mean = Mean / Count; Count = 0; for (float u = 0.5f; u < float(TraceSqrtSampleCount); u++) { for (float v = 0.5f; v < float(TraceSqrtSampleCount); v++) { float2 RandomUV = float2(u, v) / float(TraceSqrtSampleCount); float3 L = 0; float3 Value = 0; if (GetGGXDirection(RandomUV, UI.Roughness, UI.V, L, Value, UI.Eta12) && Value > 0.0f) { float2 SampleUV = L.xy * 0.5 + 0.5; SampleCountTextureUAV[uint2(SampleUV * 64.0f)] += 1; LobStatUpdate(LobeState, L.xy); Count++; Variance += (L.xy - Mean) * (L.xy - Mean); } } } Variance = Variance / Count; LobeState.Mean = Mean; LobeState.Variance = Variance;*/ LobStatFinalize(LobeState); LobStatisticsBufferUAV[0] = LobeState; } } #endif // EVALUATE_ROUGH_REFRACTION_LOBE_CS #ifdef EVALUATE_ROUGH_REFRACTION_DATA_CS uint TraceSqrtSampleCount; RWBuffer RoughRefracDataUAV; // Shader used to export variance based on roughness for curve fitting [numthreads(THREADGROUP_SIZE, 1, 1)] void RoughRefracDataCS(uint3 ThreadId : SV_DispatchThreadID) { const float Roughness = float(ThreadId.x) / float(THREADGROUP_SIZE); FLobeStat LobeState = (FLobeStat)0; for (float u = 0.5f; u < float(TraceSqrtSampleCount); u++) { for (float v = 0.5f; v < float(TraceSqrtSampleCount); v++) { float2 RandomUV = float2(u, v) / float(TraceSqrtSampleCount); float3 V = float3(0, 0, 1); // Only considering perpendicular view for now float Eta12 = 1.33; // Only considering air to water for now float3 L = 0; float Value = 0; if (GetGGXDirection(RandomUV, Roughness, V, L, Value, Eta12) && Value > 0.0f) { // Project onto the plane at distance 1 to consider the blur resulting at a distance of 1. const float3 RayOnPlane = L.xyz / abs(L.z); LobStatUpdate(LobeState, RayOnPlane.xy); } } } LobStatFinalize(LobeState); RoughRefracDataUAV[ThreadId.x] = float2(Roughness, dot(LobeState.Variance, float2(0.5, 0.5))); // Roughness => Mean of both X and Y variances } #endif // EVALUATE_ROUGH_REFRACTION_DATA_CS #ifdef VISUALIZE_ROUGH_REFRACTION_PS float TraceDomainSize; uint SlabInterfaceLineCount; Texture2D SampleCountTexture; StructuredBuffer LobStatisticsBuffer; FSubstrateLobeStatistic SubstrateGetRefractedLobe2(FSubstrateLobeStatistic WiLobe, float3 InterfaceFDG, float InterfaceRoughness, float InterfaceEta12) { FSubstrateLobeStatistic WoLobe; WoLobe.E = WiLobe.E * (1.0f - InterfaceFDG); // The lobe is on the oposite side of the surface, and the direction account for change of medium IOR. WoLobe.Mu.xy = -WiLobe.Mu.xy * InterfaceEta12; WoLobe.Mu.z = -sign(WiLobe.Mu.z) * sqrt(1.0 - dot(WoLobe.Mu.xy, WoLobe.Mu.xy)); // derive z from vector projected xy // Equation 10, we have also noticed variance explosion at grazing angle so we make the inner product be 1 for now. const float OmegaIDotN = 1.0f; //WiLobe.Mu.z; const float OmegaTDotN = 1.0f; //WoLobe.Mu.z; //const float S = 0.5f * (1.0f + InterfaceEta12 * (abs(OmegaIDotN) / abs(OmegaTDotN))); // equation 10 from paper const float S = 0.5f * abs((OmegaTDotN * InterfaceEta12 - OmegaIDotN) / (OmegaTDotN * InterfaceEta12)); // equation from paper source code WoLobe.Sigma = (WiLobe.Sigma / InterfaceEta12) + SubstrateLobeRoughnessToVariance(InterfaceRoughness * S); return WoLobe; } void VisualizeRoughRefractionPS( float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0) { OutColor = float4(0.0, 0.0f, 0.0, 0.5f); float2 PixelPos = SVPos.xy; float2 UvBuffer = PixelPos * View.BufferSizeAndInvSize.zw; float3 WorldDir = GetScreenWorldDir(SVPos); float3 CamWorldPos = DFDemote(PrimaryView.WorldCameraOrigin); FLobeStat LobeState = LobStatisticsBuffer[0]; const float CountScale = 0.01f; FShaderPrintContext Context = GetUIContext(all(uint2(PixelPos.xy) == uint2(10, 10))); FUI UI = GetUI(Context); float3 SlabEdgeX = float3(60.0f, 0.0f, 0.0f); float3 SlabEdgeY = float3(0.0f, 60.0f, 0.0f); float3 SlabEdgeZ = float3(0.0f, 0.0f, UI.SlabThickness); float3 SlabCenter = CamWorldPos + View.ViewForward * 35.0f; float3 RefractionP= SlabCenter + 0.5 * SlabEdgeZ; const float SlabTopZ = SlabCenter.z + 0.5 * UI.SlabThickness; const float SlabBottomZ = SlabCenter.z - 0.5 * UI.SlabThickness; const float VisProjectedRayRadius = 15.0f; AddLineWS(Context, RefractionP, RefractionP + WOLRD_NORMAL * 25.0, float4(0, 0, 1, 1)); AddLineWS(Context, RefractionP, RefractionP + UI.V * 25.0, float4(0, 1, 1, 1)); float SlabTMin = -1.0f; float SlabTMax = -1.0f; float3 SlabP0 = SlabCenter - 0.5 * (SlabEdgeX + SlabEdgeY + SlabEdgeZ); float3 SlabP1 = SlabCenter + 0.5 * (SlabEdgeX + SlabEdgeY + SlabEdgeZ); if(Slabs(SlabP0, SlabP1, CamWorldPos, 1.0f / WorldDir, SlabTMin, SlabTMax)) { float3 P = CamWorldPos + WorldDir * SlabTMin; if (P.z > (SlabBottomZ + 0.01)) { P = CamWorldPos + WorldDir * SlabTMax; } if (P.z < (SlabBottomZ+0.01)) { const float2 UV = 0.5 + 0.5 * ((P - RefractionP) / (VisProjectedRayRadius)).xy; const int2 Coord = (UV * 64.0f + 0.5f); if (all(Coord >= 0) && all(Coord < 64)) { if (Coord.y < 32) { const float SampleCount = float(SampleCountTexture[Coord].x) * CountScale; OutColor = float4(GetHSVDebugColor(SampleCount), 0.0); } else { float Weight = GaussianFunction((P.xy - RefractionP.xy) / VisProjectedRayRadius, LobeState.Mean.xy, LobeState.Variance.x); OutColor = float4(GetHSVDebugColor(Weight), 0.0); } } } } #if 1 // Draw projected distribution as a curve float3 PrevP = -1.0f; float PrevValue = -1.0f; for (float u = 0.0; u < 1.0; u += 0.005f) { float3 P = lerp(SlabP0, SlabP1, u); P.y = 0.5 * (SlabP0.y + SlabP1.y); P.z = SlabBottomZ; const float2 UV = 0.5 + 0.5 * ((P - RefractionP) / (VisProjectedRayRadius)).xy; const int2 Coord = (UV * 64.0f + 0.5f); if (all(Coord >= 0) && all(Coord < 64)) { const float SampleCount = float(SampleCountTexture[Coord].x) * CountScale; const float Value = float(SampleCount); if (PrevValue != -1.0f) { AddLineWS(Context, PrevP + float3(0, 0, PrevValue), P + float3(0, 0, Value), float4(1, 1, 0, 0.5)); } PrevP = P; PrevValue = Value; } } // Draw gaussian curve mapping to recovered variance PrevP = -1.0f; PrevValue = -1.0f; for (float u = 0.0; u < 1.0; u += 0.005f) { float3 P = lerp(SlabP0, SlabP1, u); P.y = 0.5 * (SlabP0.y + SlabP1.y); P.z = SlabBottomZ; float Weight = GaussianFunction((P.x - RefractionP.x) / VisProjectedRayRadius, LobeState.Mean.x, LobeState.Variance.x); const float Value = Weight; if (PrevValue != -1.0f) { AddLineWS(Context, PrevP + float3(0, 0, PrevValue), P + float3(0, 0, Value), float4(0, 1, 0, 0.5)); } PrevP = P; PrevValue = Value; } #endif #if 0 const float SphereRadius = 10.0f; float2 Sol = RayIntersectSphere(CamWorldPos, WorldDir, float4(RefractionP, SphereRadius)); if (Sol.x > 0.0f && Sol.y > 0.0f) { const float3 P = CamWorldPos + WorldDir * Sol.x; const float3 OmegaOut = P - RefractionP; const float OmegaOutLen = length(OmegaOut); const float3 OmegaOutNorm = OmegaOut / OmegaOutLen; const float NoH = dot(normalize(OmegaOutNorm + UI.V), WOLRD_NORMAL); float D = D_GGX(UI.a2, NoH); OutColor = float4(D, D, 0.0, 0.0); } #endif // Draw the top and bottom interfaces float3 SlabOrigin = SlabCenter - 0.5 * (SlabEdgeX + SlabEdgeY + SlabEdgeZ); for (uint i = 0; i <= SlabInterfaceLineCount; i++) { const float Offset = (float(i) / float(SlabInterfaceLineCount)); AddLineWS(Context, SlabOrigin + SlabEdgeX * Offset, SlabOrigin + SlabEdgeX * Offset + SlabEdgeY, float4(0.2, 0.2, 0.2, 1.0)); AddLineWS(Context, SlabOrigin + SlabEdgeY * Offset, SlabOrigin + SlabEdgeY * Offset + SlabEdgeX, float4(0.2, 0.2, 0.2, 1.0)); AddLineWS(Context, SlabOrigin + SlabEdgeX * Offset + SlabEdgeZ, SlabOrigin + SlabEdgeX * Offset + SlabEdgeY + SlabEdgeZ, float4(0.2, 0.2, 0.2, 1.0)); AddLineWS(Context, SlabOrigin + SlabEdgeY * Offset + SlabEdgeZ, SlabOrigin + SlabEdgeY * Offset + SlabEdgeX + SlabEdgeZ, float4(0.2, 0.2, 0.2, 1.0)); } // Draw the rays for (float u = 0.5f; u < TraceDomainSize; u++) { for (float v = 0.5f; v < TraceDomainSize; v++) { float2 RandomUV = float2(u, v) / TraceDomainSize; float3 L = 0; float Value = 0; if (GetGGXDirection(RandomUV, UI.Roughness, UI.V, L, Value, UI.Eta12)) { const float3 RayOnPlane = L.xyz / abs(L.z); if(Value > 0.0f) AddLineWS(Context, RefractionP, RefractionP + RayOnPlane * VisProjectedRayRadius, float4(0, Value, 0, 0.3)); //if(Value > 0.0f) AddLineWS(Context, RefractionP + RayOnPlane * VisProjectedRayRadius, float3((RefractionP + L * VisProjectedRayRadius).xy, SlabCenter.z - 0.5* UI.SlabThickness), float4(0, 1, 0, 0.3)); } else { const float3 RayOnPlane = L.xyz / abs(L.z); if (Value > 0.0f) AddLineWS(Context, RefractionP, RefractionP + RayOnPlane * VisProjectedRayRadius, float4(Value, 0, 0, 0.3)); } } } #if 0 // Draw the mean / variance estimated by Belcour. // However, those data cannot be interpreted as-is because they have been linearized for a add operation of variance to lead to a angular distribution of two convolution (w.r.t. 2 roughnesses). #if MODE_REFLECTION FSubstrateLobeStatistic PaperLobe = SubstrateGetReflectedLobe(SubstrateGetDiracLobe(UI.V), 1.0, UI.Roughness); #else FSubstrateLobeStatistic PaperLobe = SubstrateGetRefractedLobe2(SubstrateGetDiracLobe(UI.V), 1.0, UI.Roughness, UI.Eta12); #endif DrawCircleXY(Context, float3(SlabCenter.xy + PaperLobe.Mu.xy * VisProjectedRayRadius, SlabBottomZ), PaperLobe.Sigma * VisProjectedRayRadius, float4(1, 0, 1, 1)); Newline(Context); Newline(Context); Print(Context, TEXT("Belcour variance = ")); Print(Context, PaperLobe.Sigma, FontWhite, 5, 5); #endif // Our mean / variance DrawCircleXY(Context, float3(SlabCenter.xy + LobeState.Mean * VisProjectedRayRadius, SlabBottomZ + 0.05), LobeState.Variance * VisProjectedRayRadius, float4(1, 1, 0, 1)); Newline(Context); Newline(Context); Print(Context, TEXT("New variance X = ")); Print(Context, LobeState.Variance.x, FontWhite, 5, 5); Newline(Context); Print(Context, TEXT("New variance Y = ")); Print(Context, LobeState.Variance.y, FontWhite, 5, 5); } #endif // VISUALIZE_ROUGH_REFRACTION_PS #endif // SUBSTRATE_RND_SHADERS