// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
namespace Stride.Rendering.Materials
{
    /// <summary>
    /// Performs subsurface scattering using shadow maps.
    /// </summary>
    class MaterialSurfaceSubsurfaceScatteringShading :
        IMaterialSubsurfaceScatteringScatteringProfile,
        IMaterialSurfaceShading,    // Required for the "PrepareForLightingAndShading()", "ComputeDirectLightContribution()" and "AfterLightingAndShading()" functions. Already includes "MaterialPixelStream.sdsl".
        MaterialPixelShadingStream,  // Required for "streams.shadingColorAlpha".
        ShadowStream,    // Required for "streams.thicknessWS".
        Math
    {
        cbuffer PerMaterial
        {
            stage float Translucency;
            stage float ScatteringWidth;
        }
        
        compose IMaterialSubsurfaceScatteringScatteringProfile scatteringProfileFunction;

        stream float scatteringStrength;    // TODO: Do we need the stage keyword here?
               
        float3 CalculateTransmittance(float thickness,
                                      float translucency,    // This parameter allows to control the transmittance effect. Its range should be [0..1]. Higher values translate to a stronger effect.
                                      float sssWidth,   // This parameter should be the same as the one for the post-process.
                                      float3 meshNormalWS,
                                      float3 lightDirectionWS,
                                      float3 attenuatedLightColor,
                                      float3 surfaceAlbedo)
        {
            // Calculate the scale of the effect:
            const float scale = 8.25 * (1.0 - translucency) / sssWidth;
            
            // Armed with the thickness, we can now calculate the color by means of the
            // precalculated transmittance profile.
            // (It can be precomputed into a texture, for maximum performance):
            const float d = scale * thickness;
            const float dd = -d * d;
           
            float3 profile = scatteringProfileFunction.Compute(dd);
                             
            // Using the profile, we finally approximate the transmitted lighting from the back of the object:
            return profile * saturate(0.3 + dot(lightDirectionWS, -meshNormalWS)) * attenuatedLightColor * surfaceAlbedo; 
        }
        
        override void PrepareForLightingAndShading()
        {
            scatteringProfileFunction.Prepare();
            streams.scatteringStrength = Translucency * streams.matScatteringStrength;
        }

        override float3 ComputeDirectLightContribution()
        {
            float3 scatteredLighting = CalculateTransmittance(streams.thicknessWS,
                                                              streams.scatteringStrength,
                                                              ScatteringWidth,
                                                              streams.meshNormalWS,
                                                              streams.lightDirectionWS,
                                                              streams.lightColor * streams.lightAttenuation,
                                                              streams.matDiffuseVisible);
                                                              
            return scatteredLighting;
            //return scatteredLighting / PI;    // TODO: Divide by Pi?
        }
        
        override void AfterLightingAndShading()
        {
            // Store the scattering strength in the alpha channel, so the post-process can sample it:
            streams.shadingColorAlpha = streams.scatteringStrength;    // TODO: Is this the best way to write to the alpha channel of the render target?
        }
    };
}