您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
282 行
12 KiB
282 行
12 KiB
using Unity.UIWidgets.material;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.UIWidgets.uiOld{
|
|
static class ShadowUtils {
|
|
public const bool kUseFastShadow = true;
|
|
|
|
const float kAmbientHeightFactor = 1.0f / 128.0f;
|
|
const float kAmbientGeomFactor = 64.0f;
|
|
|
|
const float kBlurSigmaScale = 0.57735f;
|
|
|
|
const float kMaxAmbientRadius = 300 * kAmbientHeightFactor * kAmbientGeomFactor;
|
|
|
|
const bool debugShadow = false;
|
|
|
|
static float divideAndPin(float numer, float denom, float min, float max) {
|
|
return (numer / denom).clamp(min, max);
|
|
}
|
|
|
|
static float ambientBlurRadius(float height) {
|
|
return Mathf.Min(height * kAmbientHeightFactor * kAmbientGeomFactor, kMaxAmbientRadius);
|
|
}
|
|
|
|
static float ambientRecipAlpha(float height) {
|
|
return 1.0f + Mathf.Max(height * kAmbientHeightFactor, 0.0f);
|
|
}
|
|
|
|
static float spotBlurRadius(float occluderZ, float lightZ, float lightRadius) {
|
|
return lightRadius * divideAndPin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f);
|
|
}
|
|
|
|
static void getSpotParams(float occluderZ, float lightX, float lightY, float lightZ,
|
|
float lightRadius,
|
|
ref float blurRadius, ref float scale, ref Vector2 translate) {
|
|
float zRatio = divideAndPin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f);
|
|
blurRadius = lightRadius * zRatio;
|
|
scale = divideAndPin(lightZ, lightZ - occluderZ, 1.0f, 1.95f);
|
|
translate = new Vector2(-zRatio * lightX, -zRatio * lightY);
|
|
}
|
|
|
|
static float convertRadiusToSigma(float radius) {
|
|
return radius > 0 ? kBlurSigmaScale * radius + 0.5f : 0.0f;
|
|
}
|
|
|
|
public static void computeTonalColors(uiColor inAmbientColor, uiColor inSpotColor,
|
|
ref uiColor? outAmbientColor, ref uiColor? outSpotColor) {
|
|
outAmbientColor = uiColor.fromARGB(inAmbientColor.alpha, 0, 0, 0);
|
|
outSpotColor = inSpotColor;
|
|
}
|
|
|
|
static readonly Matrix3 _toHomogeneous = Matrix3.I();
|
|
|
|
static bool getSpotShadowTransform(Vector3 lightPos, float lightRadius, Matrix3 ctm,
|
|
Vector3 zPlaneParams, Rect pathBounds, Matrix3 shadowTransform, ref float radius) {
|
|
float heightFunc(float x, float y) {
|
|
return zPlaneParams.x * x + zPlaneParams.y * y + zPlaneParams.z;
|
|
}
|
|
|
|
float occluderHeight = heightFunc(pathBounds.center.dx, pathBounds.center.dy);
|
|
|
|
if (!ctm.hasPerspective()) {
|
|
float scale = 0.0f;
|
|
Vector2 translate = new Vector2();
|
|
getSpotParams(occluderHeight, lightPos.x, lightPos.y, lightPos.z, lightRadius, ref radius, ref scale,
|
|
ref translate);
|
|
shadowTransform.setScaleTranslate(scale, scale, translate.x, translate.y);
|
|
shadowTransform.preConcat(ctm);
|
|
}
|
|
else {
|
|
if (pathBounds.width.valueNearlyZero() || pathBounds.height.valueNearlyZero()) {
|
|
return false;
|
|
}
|
|
|
|
Offset[] pts = ctm.mapRectToQuad(pathBounds);
|
|
if (!MathUtils.isConvexPolygon(pts, 4)) {
|
|
return false;
|
|
}
|
|
|
|
Vector3[] pts3D = new Vector3[4];
|
|
float z = heightFunc(pathBounds.left, pathBounds.top);
|
|
pts3D[0] = new Vector3(pts[0].dx, pts[0].dy, z);
|
|
z = heightFunc(pathBounds.right, pathBounds.top);
|
|
pts3D[1] = new Vector3(pts[1].dx, pts[1].dy, z);
|
|
z = heightFunc(pathBounds.right, pathBounds.bottom);
|
|
pts3D[2] = new Vector3(pts[2].dx, pts[2].dy, z);
|
|
z = heightFunc(pathBounds.left, pathBounds.bottom);
|
|
pts3D[3] = new Vector3(pts[3].dx, pts[3].dy, z);
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
float dz = lightPos.z - pts3D[i].z;
|
|
if (dz.valueNearlyZero()) {
|
|
return false;
|
|
}
|
|
|
|
float zRatio = pts3D[i].z / dz;
|
|
pts3D[i].x -= (lightPos.x - pts3D[i].x) * zRatio;
|
|
pts3D[i].y -= (lightPos.y - pts3D[i].y) * zRatio;
|
|
pts3D[i].z = 1f;
|
|
}
|
|
|
|
Vector3 h0 = Vector3.Cross(Vector3.Cross(pts3D[1], pts3D[0]), Vector3.Cross(pts3D[2], pts3D[3]));
|
|
Vector3 h1 = Vector3.Cross(Vector3.Cross(pts3D[0], pts3D[3]), Vector3.Cross(pts3D[1], pts3D[2]));
|
|
Vector3 h2 = Vector3.Cross(Vector3.Cross(pts3D[0], pts3D[2]), Vector3.Cross(pts3D[1], pts3D[3]));
|
|
|
|
if (h2.z.valueNearlyZero()) {
|
|
return false;
|
|
}
|
|
|
|
Vector3 v = pts3D[3] - pts3D[0];
|
|
Vector3 w = h0 - pts3D[0];
|
|
float perpDot = v.x * w.y - v.y * w.x;
|
|
if (perpDot > 0) {
|
|
h0 = -h0;
|
|
}
|
|
|
|
v = pts3D[1] - pts3D[0];
|
|
perpDot = v.x * w.y - v.y * w.x;
|
|
if (perpDot < 0) {
|
|
h1 = -h1;
|
|
}
|
|
|
|
shadowTransform.setAll(h0.x / h2.z, h1.x / h2.z, h2.x / h2.z,
|
|
h0.y / h2.z, h1.y / h2.z, h2.y / h2.z,
|
|
h0.z / h2.z, h1.z / h2.z, 1);
|
|
|
|
float xScale = 2.0f / (pathBounds.right - pathBounds.left);
|
|
float yScale = 2.0f / (pathBounds.bottom - pathBounds.top);
|
|
|
|
_toHomogeneous.setAll(xScale, 0, -xScale * pathBounds.left - 1,
|
|
0, yScale, -yScale * pathBounds.top - 1,
|
|
0, 0, 1);
|
|
|
|
shadowTransform.preConcat(_toHomogeneous);
|
|
|
|
radius = spotBlurRadius(occluderHeight, lightPos.z, lightRadius);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static readonly Path _devSpacePath = new Path();
|
|
public static void drawShadow(Canvas canvas, Path path, Vector3 zPlaneParams, Vector3 devLightPos,
|
|
float lightRadius, uiColor ambientColor, uiColor spotColor, int flags) {
|
|
#pragma warning disable CS0162
|
|
if (kUseFastShadow) {
|
|
drawShadowFast(canvas, path, zPlaneParams, devLightPos, lightRadius, ambientColor, spotColor, flags);
|
|
}
|
|
else {
|
|
drawShadowFull(canvas, path, zPlaneParams, devLightPos, lightRadius, ambientColor, spotColor, flags);
|
|
}
|
|
#pragma warning restore CS0162
|
|
}
|
|
|
|
//cached variables
|
|
static readonly Paint _shadowPaint = new Paint();
|
|
static readonly Matrix3 _shadowMatrix = Matrix3.I();
|
|
|
|
static void drawShadowFull(Canvas canvas, Path path, Vector3 zPlaneParams, Vector3 devLightPos,
|
|
float lightRadius, uiColor ambientColor, uiColor spotColor, int flags) {
|
|
Matrix3 viewMatrix = canvas.getTotalMatrix();
|
|
|
|
//ambient light
|
|
_devSpacePath.resetAll();
|
|
_devSpacePath.addPath(path, viewMatrix);
|
|
float devSpaceOutset = ambientBlurRadius(zPlaneParams.z);
|
|
float oneOverA = ambientRecipAlpha(zPlaneParams.z);
|
|
float blurRadius = 0.5f * devSpaceOutset * oneOverA;
|
|
float strokeWidth = 0.5f * (devSpaceOutset - blurRadius);
|
|
|
|
//Paint paint = new Paint {color = ambientColor, strokeWidth = strokeWidth, style = PaintingStyle.fill};
|
|
_shadowPaint.color = new Color(ambientColor.value);
|
|
_shadowPaint.strokeWidth = strokeWidth;
|
|
_shadowPaint.style = PaintingStyle.fill;
|
|
|
|
canvas.save();
|
|
_shadowMatrix.reset();
|
|
canvas.setMatrix(_shadowMatrix);
|
|
float sigma = convertRadiusToSigma(blurRadius);
|
|
_shadowPaint.maskFilter = MaskFilter.blur(BlurStyle.normal, sigma);
|
|
canvas.drawPath(_devSpacePath, _shadowPaint);
|
|
canvas.restore();
|
|
|
|
//spot light
|
|
//Matrix3 shadowMatrix = Matrix3.I();
|
|
float radius = 0.0f;
|
|
|
|
if (!getSpotShadowTransform(devLightPos, lightRadius, viewMatrix, zPlaneParams, path.getBounds(),
|
|
_shadowMatrix, ref radius)) {
|
|
return;
|
|
}
|
|
|
|
canvas.save();
|
|
canvas.setMatrix(_shadowMatrix);
|
|
|
|
_shadowPaint.color = new Color(spotColor.value);
|
|
_shadowPaint.strokeWidth = 0;
|
|
_shadowPaint.style = PaintingStyle.fill;
|
|
float sigma2 = convertRadiusToSigma(radius);
|
|
_shadowPaint.maskFilter = MaskFilter.blur(BlurStyle.normal, sigma2);
|
|
canvas.drawPath(path, _shadowPaint);
|
|
|
|
canvas.restore();
|
|
|
|
_shadowPaint.maskFilter = null;
|
|
}
|
|
|
|
|
|
static void drawShadowFast(Canvas canvas, Path path, Vector3 zPlaneParams, Vector3 devLightPos,
|
|
float lightRadius, uiColor ambientColor, uiColor spotColor, int flags) {
|
|
Matrix3 viewMatrix = canvas.getTotalMatrix();
|
|
|
|
//debug shadow
|
|
#pragma warning disable CS0162
|
|
if (debugShadow) {
|
|
var isRRect = path.isNaiveRRect;
|
|
if (isRRect) {
|
|
ambientColor = uiColor.fromColor(Colors.red);
|
|
spotColor = uiColor.fromColor(Colors.red);
|
|
}
|
|
else {
|
|
ambientColor = uiColor.fromColor(Colors.green);
|
|
spotColor = uiColor.fromColor(Colors.green);
|
|
}
|
|
}
|
|
#pragma warning restore CS0162
|
|
|
|
//ambient light
|
|
float devSpaceOutset = ambientBlurRadius(zPlaneParams.z);
|
|
float oneOverA = ambientRecipAlpha(zPlaneParams.z);
|
|
float blurRadius = 0.5f * devSpaceOutset * oneOverA;
|
|
float strokeWidth = 0.5f * (devSpaceOutset - blurRadius);
|
|
|
|
_shadowPaint.color = new Color(ambientColor.value);
|
|
_shadowPaint.strokeWidth = strokeWidth;
|
|
_shadowPaint.style = PaintingStyle.fill;
|
|
canvas.drawPath(path, _shadowPaint);
|
|
|
|
//spot light
|
|
float radius = 0.0f;
|
|
if (!getSpotShadowTransform(devLightPos, lightRadius, viewMatrix, zPlaneParams, path.getBounds(),
|
|
_shadowMatrix, ref radius)) {
|
|
return;
|
|
}
|
|
|
|
canvas.save();
|
|
canvas.setMatrix(_shadowMatrix);
|
|
|
|
_shadowPaint.color = new Color(spotColor.value);
|
|
_shadowPaint.strokeWidth = 0;
|
|
_shadowPaint.style = PaintingStyle.fill;
|
|
float sigma2 = convertRadiusToSigma(radius);
|
|
_shadowPaint.maskFilter = path.isNaiveRRect ? MaskFilter.fastShadow(sigma2) : MaskFilter.blur(BlurStyle.normal, sigma2);
|
|
canvas.drawPath(path, _shadowPaint);
|
|
|
|
canvas.restore();
|
|
|
|
_shadowPaint.maskFilter = null;
|
|
}
|
|
|
|
/*
|
|
* Check whether the RRect is a naive Round-Rect, of which
|
|
* (1) all the corner radius are the same
|
|
* (2) the corner radius is not bigger than either half the width or the height of the Round Rect's bounding box
|
|
*
|
|
* Usage: The shadow of a naive Round-Rect can be easily drawn using a ShadowRBox shader, so we can use it to
|
|
* find all the situations that a fast shadow can be drawn to tackle the performance issue
|
|
*/
|
|
public static bool isNaiveRRect(this RRect rrect) {
|
|
var radius = rrect.tlRadiusX;
|
|
return rrect.tlRadiusY == radius &&
|
|
rrect.trRadiusX == radius &&
|
|
rrect.trRadiusY == radius &&
|
|
rrect.blRadiusX == radius &&
|
|
rrect.blRadiusY == radius &&
|
|
rrect.brRadiusX == radius &&
|
|
rrect.brRadiusY == radius &&
|
|
radius <= rrect.width / 2 &&
|
|
radius <= rrect.height / 2;
|
|
}
|
|
}
|
|
}
|