* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef GRAPHICS_EFFECT_GE_SDF_SHADOW_SHADER_H
#define GRAPHICS_EFFECT_GE_SDF_SHADOW_SHADER_H
#include "effect/runtime_shader_builder.h"
#include "ge_filter_type_info.h"
#include "ge_shader.h"
#include "ge_shader_filter_params.h"
#include "ge_visual_effect_impl.h"
namespace OHOS {
namespace Rosen {
class GESDFShadowShader : public GEShader {
public:
GESDFShadowShader(const Drawing::GESDFShadowShaderParams& params);
GESDFShadowShader(const GESDFShadowShader&) = delete;
GESDFShadowShader operator=(const GESDFShadowShader&) = delete;
~GESDFShadowShader() override = default;
DECLARE_GEFILTER_TYPEFUNC(GESDFShadowShader, Drawing::GESDFShadowShaderParams);
const std::string GetDescription() const { return "GESDFShadowShader"; }
void MakeDrawingShader(const Drawing::Rect& rect, float progress) override {}
void MakeDrawingShader(Drawing::Canvas& canvas, const Drawing::Rect& rect, float progress) override;
void SetSDFShadowParams(const Drawing::GESDFShadowShaderParams& params)
{
params_ = params;
}
std::shared_ptr<Drawing::ShaderEffect> MakeSDFShadowShader(Drawing::Canvas& canvas, const Drawing::Rect& rect);
void OnDrawShader(Drawing::Canvas& canvas, const Drawing::Rect& rect) override;
private:
bool IsElevationMode() const { return params_.shadow.elevation > 0.0f; }
std::shared_ptr<Drawing::RuntimeEffect> GetSDFShadowEffect();
void UpdateRectForShadow(Drawing::Rect& rect);
void ComputeElevationParams();
std::shared_ptr<Drawing::RuntimeEffect> GetElevationShadowEffect();
void UpdateRectForElevationShadow(Drawing::Rect& rect);
std::shared_ptr<Drawing::ShaderEffect> MakeElevationShadowShader(Drawing::Canvas& canvas,
const Drawing::Rect& rect);
Drawing::GESDFShadowShaderParams params_;
float ambientBlurRadius_ = 0.0f;
float ambientOutset_ = 0.0f;
float spotBlurRadius_ = 0.0f;
Drawing::Color ambientColor_;
Drawing::Color spotColor_;
inline static const std::string shaderCode_ = R"(
uniform shader sdfShape;
uniform vec2 iResolution;
uniform vec4 shadowColor;
uniform vec2 shadowOffset;
uniform float shadowRadius;
uniform float isFilled;
// Input data:
// vec2 coord - coordinates used to calculate SDFMap
// vec2 shadowOffset - offset of the shadow
// float d - current SDF shape distance
// vec4 shadowColor - color of the shadow
// float shadowRadius - radius of the shadow
// bool isFilled - should SDFBody be filled with shadow
vec4 shadowEffect(vec2 coord, vec2 shadowOffset,
float d, vec4 shadowColor, float shadowRadius, bool isFilled)
{
vec4 color = vec4(0.0);
float alphaFilled = 1.0;
if (!isFilled && d < 0.0)
{
alphaFilled = smoothstep(-1.0, 0.0, d);
}
// Recalculate the distance if there is offset
if (any(notEqual(shadowOffset, vec2(0.0))))
{
d = sdfShape.eval(coord - shadowOffset).a;
}
if (d <= shadowRadius)
{
float alpha = 1.0 - smoothstep(-shadowRadius, shadowRadius, d);
color = shadowColor * alpha * alphaFilled;
}
return color;
}
half4 main(float2 fragCoord)
{
vec2 coord = fragCoord;
float d = sdfShape.eval(coord).a;
vec4 color = shadowEffect(coord, shadowOffset, d, shadowColor, shadowRadius, isFilled > 0.5);
return half4(color);
}
)";
inline static const std::string elevationShaderCode_ = R"(
uniform shader sdfShape;
uniform vec2 iResolution;
uniform vec4 ambientColor;
uniform float ambientBlurRadius;
uniform float ambientOutset;
uniform vec4 spotColor;
uniform float spotBlurRadius;
uniform float isFilled;
uniform vec2 shadowOffset;
// Skia GaussianColorFilter quartic approximation (SkRasterPipeline_opts.h:4957)
// Input: x = 1.0 at boundary (d=0), 0.0 at blurRadius (d=blurRadius)
// Approximates: exp(-(1-x)^2 * 4) - 0.018
float skiaGaussian(float x)
{
const float c4 = -2.26661229133605957031;
const float c3 = 2.89795351028442382812;
const float c2 = 0.21345567703247070312;
const float c1 = 0.15489584207534790039;
const float c0 = 0.00030726194381713867;
return c0 + x * (c1 + x * (c2 + x * (c3 + x * c4)));
}
// Spot shadow coverage: symmetric tessellation model (inset = outset)
// alpha = 0.5 - d / (2 * blurRadius), maps d in [-blurRadius, +blurRadius] -> alpha in [1, 0]
float computeSpotCoverage(float d, float blurRadius)
{
if (blurRadius < 0.001)
{
return 0.0;
}
float alpha = 0.5 - d / (2.0 * blurRadius);
alpha = clamp(alpha, 0.0, 1.0);
return max(skiaGaussian(alpha), 0.0);
}
// Ambient shadow coverage: asymmetric tessellation model (inset != outset)
// alpha = (outset - d) / ambientBlurRadius, maps d in [-inset, +outset] -> alpha in [1, 0]
// ambientBlurRadius = outset * recipAlpha = inset + outset
float computeAmbientCoverage(float d, float ambientBlurRadius, float ambientOutset)
{
if (ambientBlurRadius < 0.001)
{
return 0.0;
}
float alpha = (ambientOutset - d) / ambientBlurRadius;
alpha = clamp(alpha, 0.0, 1.0);
return max(skiaGaussian(alpha), 0.0);
}
vec4 elevationShadowEffect(float d, float d_original)
{
float alphaFilled = 1.0;
if (isFilled < 0.5)
{
alphaFilled = smoothstep(-1.0, 0.0, d_original);
}
float ambientCoverage = computeAmbientCoverage(d, ambientBlurRadius, ambientOutset);
float spotCoverage = computeSpotCoverage(d, spotBlurRadius);
// Premultiplied alpha colors
vec3 ambientPM = ambientColor.rgb * ambientColor.a * ambientCoverage;
vec3 spotPM = spotColor.rgb * spotColor.a * spotCoverage;
float ambientA = ambientColor.a * ambientCoverage;
float spotA = spotColor.a * spotCoverage;
// SrcOver: spot over ambient (matching Skia draw order)
vec3 colorPM = spotPM + ambientPM * (1.0 - spotA);
float alpha = spotA + ambientA * (1.0 - spotA);
return vec4(colorPM * alphaFilled, alpha * alphaFilled);
}
half4 main(float2 fragCoord)
{
// isFilled clipping: check original shape position
float d_original = sdfShape.eval(fragCoord).a;
// Shadow rendering: use offset position (matches shaderCode_ pattern)
float d = d_original;
if (any(notEqual(shadowOffset, vec2(0.0))))
{
d = sdfShape.eval(fragCoord - shadowOffset).a;
}
vec4 color = elevationShadowEffect(d, d_original);
return half4(color);
}
)";
};
}
}
#endif