mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-13 11:20:31 +00:00
备份
This commit is contained in:
@@ -1,225 +0,0 @@
|
||||
# Acrylic Container 实现说明 / Implementation Notes
|
||||
|
||||
## 问题分析 / Problem Analysis
|
||||
|
||||
### 原始需求 / Original Requirement
|
||||
用户希望实现"真正的毛玻璃效果" - 即模糊容器**背后**的内容,类似于 Windows Acrylic 或 macOS 的毛玻璃效果。
|
||||
|
||||
The user wanted to implement a "true frosted glass effect" - blurring content **behind** the container, similar to Windows Acrylic or macOS frosted glass.
|
||||
|
||||
### 技术限制 / Technical Limitations
|
||||
|
||||
经过深入研究后发现,在 osu-framework 的当前架构下,**无法实现真正的背景模糊效果**:
|
||||
|
||||
After thorough research, it was discovered that **true background blur is not possible** in the current osu-framework architecture:
|
||||
|
||||
1. **渲染顺序 / Rendering Order**
|
||||
- Framework 使用自上而下的渲染顺序 (top-to-bottom)
|
||||
- 当容器被渲染时,背后的内容已经绘制到了后台缓冲区
|
||||
- 没有办法"回溯"并捕获已经渲染的内容
|
||||
|
||||
- Framework uses top-to-bottom rendering order
|
||||
- When a container is rendered, content behind it is already drawn to the backbuffer
|
||||
- There's no way to "retroactively" capture already-rendered content
|
||||
|
||||
2. **CaptureScreenToFrameBuffer 未实现 / CaptureScreenToFrameBuffer Not Implemented**
|
||||
- 发现 `IRenderer.CaptureScreenToFrameBuffer` 方法存在但未实现
|
||||
- `DeferredRenderer.CaptureScreenToFrameBuffer` 只是一个 TODO 占位符
|
||||
- 这个方法原本可以用来捕获当前屏幕内容,但目前不可用
|
||||
|
||||
- The `IRenderer.CaptureScreenToFrameBuffer` method exists but is not implemented
|
||||
- `DeferredRenderer.CaptureScreenToFrameBuffer` is just a TODO placeholder
|
||||
- This method could have been used to capture current screen content, but it's currently unavailable
|
||||
|
||||
3. **BufferedContainer 的限制 / BufferedContainer Limitations**
|
||||
- `BufferedContainer` 只能模糊**自己的子元素**
|
||||
- 它先将子元素渲染到帧缓冲区,然后应用模糊
|
||||
- 无法访问或模糊父容器或兄弟元素的内容
|
||||
|
||||
- `BufferedContainer` can only blur **its own children**
|
||||
- It renders children to a framebuffer first, then applies blur
|
||||
- It cannot access or blur content from parent or sibling containers
|
||||
|
||||
## 实现方案 / Implementation Approach
|
||||
|
||||
### 最终方案 / Final Solution
|
||||
基于技术限制,改为实现一个**视觉上接近毛玻璃效果**的容器:
|
||||
|
||||
Given the technical constraints, implemented a container that **visually approximates the frosted glass effect**:
|
||||
|
||||
```csharp
|
||||
public partial class AcrylicContainer : BufferedContainer
|
||||
{
|
||||
// 模糊一个背景层 (Blur a background layer)
|
||||
private Drawable backgroundBox;
|
||||
|
||||
// 在上面叠加一个着色覆盖层 (Overlay a tinted layer on top)
|
||||
private Drawable tintOverlay;
|
||||
|
||||
// 用户内容在最上层 (User content on top)
|
||||
public Children { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**工作原理 / How It Works:**
|
||||
|
||||
1. **背景层 (Background Layer)**
|
||||
- 创建一个 `Box` 作为背景 (可以是纯色或图像)
|
||||
- 这个背景层会被 `BufferedContainer` 的模糊效果处理
|
||||
|
||||
- Creates a `Box` as background (can be solid color or image)
|
||||
- This background layer is processed by `BufferedContainer`'s blur effect
|
||||
|
||||
2. **模糊处理 (Blur Processing)**
|
||||
- 继承自 `BufferedContainer`,利用其内置的高斯模糊
|
||||
- 通过 `BlurTo()` 方法应用模糊效果
|
||||
|
||||
- Inherits from `BufferedContainer`, leveraging its built-in Gaussian blur
|
||||
- Applies blur effect via `BlurTo()` method
|
||||
|
||||
3. **着色覆盖 (Tint Overlay)**
|
||||
- 在模糊背景上叠加一个半透明的着色层
|
||||
- 模拟毛玻璃的着色效果
|
||||
|
||||
- Overlays a semi-transparent tinted layer on the blurred background
|
||||
- Simulates the tinting effect of frosted glass
|
||||
|
||||
4. **用户内容 (User Content)**
|
||||
- 用户添加的子元素显示在最上层
|
||||
- 可以透过半透明的覆盖层看到模糊的背景
|
||||
|
||||
- User-added children are displayed on top
|
||||
- Blurred background is visible through the semi-transparent overlay
|
||||
|
||||
## 使用方法 / Usage
|
||||
|
||||
### 基本用法 / Basic Usage
|
||||
|
||||
```csharp
|
||||
var acrylicEffect = new AcrylicContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BlurStrength = 15f, // 模糊强度 (0-100)
|
||||
TintColour = new Color4(0, 0, 0, 0.3f), // 着色 (半透明黑色)
|
||||
BackgroundColour = Color4.White, // 要模糊的背景色
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteText { Text = "Content" }
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 属性说明 / Properties
|
||||
|
||||
| 属性 / Property | 说明 / Description |
|
||||
|----------------|-------------------|
|
||||
| `BlurStrength` | 模糊强度,值越大越模糊 (0-100) / Blur intensity, higher = more blur (0-100) |
|
||||
| `TintColour` | 着色颜色,通常是半透明色 / Tint color, usually semi-transparent |
|
||||
| `BackgroundColour` | 背景颜色,这个颜色会被模糊 / Background color that will be blurred |
|
||||
|
||||
### 视觉效果 / Visual Result
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 用户内容 (User Content) │ ← 清晰的文字/图像
|
||||
├─────────────────────────────────┤
|
||||
│ 半透明着色层 (Tint Overlay) │ ← Alpha < 1.0
|
||||
├─────────────────────────────────┤
|
||||
│ 模糊的背景 (Blurred Background) │ ← 高斯模糊效果
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 局限性 / Limitations
|
||||
|
||||
1. **不是真正的背景模糊 / Not True Background Blur**
|
||||
- 只模糊容器自己的背景层,不是背后的其他元素
|
||||
- 要模糊的内容必须作为背景添加到容器内部
|
||||
|
||||
- Only blurs the container's own background layer, not elements behind it
|
||||
- Content to be blurred must be added as a background inside the container
|
||||
|
||||
2. **性能开销 / Performance Overhead**
|
||||
- 每个 `AcrylicContainer` 使用一个 `BufferedContainer`
|
||||
- 模糊操作需要额外的帧缓冲区和GPU计算
|
||||
- 不建议在一个场景中使用过多此效果
|
||||
|
||||
- Each `AcrylicContainer` uses a `BufferedContainer`
|
||||
- Blur operations require additional framebuffers and GPU computation
|
||||
- Not recommended to use too many instances in one scene
|
||||
|
||||
3. **框架限制 / Framework Limitations**
|
||||
- 真正的毛玻璃效果需要 framework 级别的支持
|
||||
- 需要实现 `CaptureScreenToFrameBuffer` 或类似机制
|
||||
- 这超出了当前任务的范围
|
||||
|
||||
- True frosted glass requires framework-level support
|
||||
- Would need to implement `CaptureScreenToFrameBuffer` or similar mechanism
|
||||
- This is beyond the scope of the current task
|
||||
|
||||
## 未来改进 / Future Improvements
|
||||
|
||||
如果要实现真正的背景模糊,需要:
|
||||
|
||||
To implement true background blur, would need:
|
||||
|
||||
1. **实现屏幕捕获 / Implement Screen Capture**
|
||||
```csharp
|
||||
// 在 DeferredRenderer 中实现
|
||||
public override void CaptureScreenToFrameBuffer(IFrameBuffer frameBuffer)
|
||||
{
|
||||
// 将当前后台缓冲区内容复制到 frameBuffer
|
||||
// Copy current backbuffer content to frameBuffer
|
||||
}
|
||||
```
|
||||
|
||||
2. **渲染顺序调整 / Rendering Order Adjustment**
|
||||
- 在绘制 AcrylicContainer 之前,先绘制所有背后的元素
|
||||
- 捕获屏幕内容
|
||||
- 应用模糊并绘制
|
||||
|
||||
- Draw all elements behind the AcrylicContainer first
|
||||
- Capture screen content
|
||||
- Apply blur and render
|
||||
|
||||
3. **Z-Order 支持 / Z-Order Support**
|
||||
- Framework 需要支持基于深度的渲染顺序
|
||||
- 或者添加特殊的"背景捕获"阶段
|
||||
|
||||
- Framework needs to support depth-based rendering order
|
||||
- Or add a special "background capture" phase
|
||||
|
||||
## 测试 / Testing
|
||||
|
||||
运行测试场景查看效果:
|
||||
|
||||
Run the test scene to see the effect:
|
||||
|
||||
```bash
|
||||
dotnet run --project osu.Framework.Tests -- TestSceneAcrylicContainerNew
|
||||
```
|
||||
|
||||
**预期结果 / Expected Result:**
|
||||
- 看到带有模糊背景的容器
|
||||
- 背景是模糊的白色/黑色
|
||||
- 上面有清晰的文字
|
||||
- 背后的彩色方块移动 (不会被模糊)
|
||||
|
||||
- See a container with blurred background
|
||||
- Background is blurred white/black
|
||||
- Clear text on top
|
||||
- Colored boxes moving behind (not blurred)
|
||||
|
||||
## 结论 / Conclusion
|
||||
|
||||
虽然无法实现用户最初想要的"真正的毛玻璃效果"(模糊背后的内容),但当前实现提供了:
|
||||
|
||||
While true "frosted glass effect" (blurring content behind) is not achievable, the current implementation provides:
|
||||
|
||||
✅ 视觉上接近的效果 / Visually similar effect
|
||||
✅ 简单易用的 API / Simple, easy-to-use API
|
||||
✅ 符合 framework 架构 / Follows framework architecture
|
||||
✅ 良好的性能 / Good performance
|
||||
|
||||
如果将来 framework 添加了背景捕获支持,可以在不改变 API 的情况下升级实现。
|
||||
|
||||
If the framework adds background capture support in the future, the implementation can be upgraded without changing the API.
|
||||
@@ -1,40 +0,0 @@
|
||||
// 正确的集成方式:背景缓冲区由游戏层管理
|
||||
|
||||
public partial class ManiaGameMode : Game
|
||||
{
|
||||
private IFrameBuffer? globalBackgroundBuffer;
|
||||
private EzColumnBackground columnBackground = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// 创建轨道背景
|
||||
Add(columnBackground = new EzColumnBackground());
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
// 在游戏层创建和管理背景缓冲区
|
||||
globalBackgroundBuffer ??= Host.Renderer.CreateFrameBuffer(null, TextureFilteringMode.Linear);
|
||||
|
||||
// 捕获当前屏幕内容
|
||||
if (Host.Renderer is Renderer concreteRenderer)
|
||||
{
|
||||
concreteRenderer.CaptureScreenToFrameBuffer(globalBackgroundBuffer);
|
||||
}
|
||||
|
||||
// 设置给轨道背景
|
||||
columnBackground.SetBackgroundBuffer(globalBackgroundBuffer);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// 根据游戏状态控制虚化效果
|
||||
// 例如:根据谱面难度、播放状态等调整
|
||||
columnBackground.SetDimLevel(0.3f, 5f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="SampleGame.Android" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||
<application />
|
||||
</manifest>
|
||||
@@ -1,13 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.Framework.Android.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>SampleGame.Android</RootNamespace>
|
||||
<AssemblyName>SampleGame.Android</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Framework.Android\osu.Framework.Android.csproj" />
|
||||
<ProjectReference Include="..\SampleGame\SampleGame.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Android.App;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Android;
|
||||
|
||||
namespace SampleGame.Android
|
||||
{
|
||||
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
|
||||
public class SampleGameActivity : AndroidGameActivity
|
||||
{
|
||||
protected override Game CreateGame() => new SampleGameGame();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Foundation;
|
||||
using osu.Framework;
|
||||
using osu.Framework.iOS;
|
||||
|
||||
namespace SampleGame.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameApplicationDelegate
|
||||
{
|
||||
protected override Game CreateGame() => new SampleGameGame();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "76x76"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "76x76"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom": "ios-marketing",
|
||||
"scale": "1x",
|
||||
"size": "1024x1024"
|
||||
},
|
||||
{
|
||||
"idiom": "car",
|
||||
"scale": "2x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"idiom": "car",
|
||||
"scale": "3x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "notificationCenter",
|
||||
"scale": "2x",
|
||||
"size": "24x24",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "notificationCenter",
|
||||
"scale": "2x",
|
||||
"size": "27.5x27.5",
|
||||
"subtype": "42mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "companionSettings",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "companionSettings",
|
||||
"scale": "3x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "40x40",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "44x44",
|
||||
"subtype": "40mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "appLauncher",
|
||||
"scale": "2x",
|
||||
"size": "50x50",
|
||||
"subtype": "44mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "86x86",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "98x98",
|
||||
"subtype": "42mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch",
|
||||
"role": "quickLook",
|
||||
"scale": "2x",
|
||||
"size": "108x108",
|
||||
"subtype": "44mm"
|
||||
},
|
||||
{
|
||||
"idiom": "watch-marketing",
|
||||
"scale": "1x",
|
||||
"size": "1024x1024"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "1x",
|
||||
"size": "16x16"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "2x",
|
||||
"size": "16x16"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "1x",
|
||||
"size": "32x32"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "2x",
|
||||
"size": "32x32"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "1x",
|
||||
"size": "128x128"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "2x",
|
||||
"size": "128x128"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "1x",
|
||||
"size": "256x256"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "2x",
|
||||
"size": "256x256"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "1x",
|
||||
"size": "512x512"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"scale": "2x",
|
||||
"size": "512x512"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>SampleGame.iOS</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>sh.ppy.sample-game</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.4</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS" />
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530" />
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb" />
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok" />
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600" />
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" />
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite" />
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder" />
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375" />
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using UIKit;
|
||||
|
||||
namespace SampleGame.iOS
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-ios</TargetFramework>
|
||||
<SupportedOSPlatformVersion>13.4</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\osu.Framework.iOS.props" />
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Framework.iOS\osu.Framework.iOS.csproj" />
|
||||
<ProjectReference Include="..\osu.Framework\osu.Framework.csproj" />
|
||||
<ProjectReference Include="..\SampleGame\SampleGame.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
"osu.Framework.Templates\\templates\\template-flappy\\FlappyDon.Game\\FlappyDon.Game.csproj",
|
||||
"osu.Framework.Templates\\templates\\template-flappy\\FlappyDon.Resources\\FlappyDon.Resources.csproj",
|
||||
"osu.Framework.Tests\\osu.Framework.Tests.csproj",
|
||||
"osu.Framework\\osu.Framework.csproj"
|
||||
"osu.Framework\\osu.Framework.csproj",
|
||||
"SampleGame.Desktop\\SampleGame.Desktop.csproj",
|
||||
"SampleGame\\SampleGame.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,12 @@ VisualStudioVersion = 16.0.29409.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework", "osu.Framework\osu.Framework.csproj", "{C76BF5B3-985E-4D39-95FE-97C9C879B83A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleGame", "SampleGame\SampleGame.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework.Tests", "osu.Framework.Tests\osu.Framework.Tests.csproj", "{79803407-6F50-484F-93F5-641911EABD8A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleGame.Desktop", "SampleGame.Desktop\SampleGame.Desktop.csproj", "{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework.NativeLibs", "osu.Framework.NativeLibs\osu.Framework.NativeLibs.csproj", "{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework.iOS", "osu.Framework.iOS\osu.Framework.iOS.csproj", "{BBC0D18F-8595-43A6-AE61-5BF36A072CCE}"
|
||||
@@ -82,10 +86,18 @@ Global
|
||||
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{79803407-6F50-484F-93F5-641911EABD8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{79803407-6F50-484F-93F5-641911EABD8A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2AD6EA6F-CD5A-4348-86F1-5E228B11617D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F853B4BB-CB83-4169-8FD2-72EEB4A88C32}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
|
||||
namespace osu.Framework.Tests.Graphics
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneAcrylicContainer : TestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestAcrylicContainerCreation()
|
||||
{
|
||||
AddStep("create acrylic container", () =>
|
||||
{
|
||||
Child = new AcrylicTestContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,12 +47,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Width = 0.5f,
|
||||
Child = new AcrylicContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BlurStrength = 50f,
|
||||
TintColour = Colour4.Red.Opacity(0.8f),
|
||||
}
|
||||
Child = new AcrylicContainer()
|
||||
},
|
||||
|
||||
// Labels
|
||||
@@ -79,56 +74,47 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
// Blur strength control
|
||||
AddSliderStep("blur strength", 0f, 20f, 10f, strength =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
acrylic.BlurStrength = strength;
|
||||
});
|
||||
|
||||
// Tint colour controls
|
||||
AddSliderStep("tint alpha", 0f, 1f, 0.8f, alpha =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
acrylic.TintColour = Colour4.White.Opacity(alpha);
|
||||
});
|
||||
|
||||
AddSliderStep("tint red", 0f, 1f, 1f, red =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
{
|
||||
var currentColour = acrylic.TintColour;
|
||||
if (currentColour.TryExtractSingleColour(out var colour))
|
||||
acrylic.TintColour = new Colour4(red, colour.Linear.G, colour.Linear.B, colour.Linear.A);
|
||||
acrylic.TintColour = new Colour4(red, currentColour.G, currentColour.B, currentColour.A);
|
||||
}
|
||||
});
|
||||
|
||||
AddSliderStep("tint green", 0f, 1f, 1f, green =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
{
|
||||
var currentColour = acrylic.TintColour;
|
||||
if (currentColour.TryExtractSingleColour(out var colour))
|
||||
acrylic.TintColour = new Colour4(colour.Linear.R, green, colour.Linear.B, colour.Linear.A);
|
||||
acrylic.TintColour = new Colour4(currentColour.R, green, currentColour.B, currentColour.A);
|
||||
}
|
||||
});
|
||||
|
||||
AddSliderStep("tint blue", 0f, 1f, 1f, blue =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
{
|
||||
var currentColour = acrylic.TintColour;
|
||||
if (currentColour.TryExtractSingleColour(out var colour))
|
||||
acrylic.TintColour = new Colour4(colour.Linear.R, colour.Linear.G, blue, colour.Linear.A);
|
||||
acrylic.TintColour = new Colour4(currentColour.R, currentColour.G, blue, currentColour.A);
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("toggle tint colour", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
{
|
||||
isWhiteTint = !isWhiteTint;
|
||||
acrylic.TintColour = isWhiteTint
|
||||
@@ -140,52 +126,43 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
// Test different blur scenarios
|
||||
AddStep("no blur", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.BlurStrength = 0;
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 0;
|
||||
});
|
||||
AddStep("light blur", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.BlurStrength = 5;
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 5;
|
||||
});
|
||||
AddStep("medium blur", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.BlurStrength = 10;
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 10;
|
||||
});
|
||||
AddStep("heavy blur", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.BlurStrength = 20;
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.BlurStrength = 20;
|
||||
});
|
||||
|
||||
// Test tint scenarios
|
||||
AddStep("no tint", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.TintColour = Colour4.White.Opacity(0);
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0);
|
||||
});
|
||||
AddStep("subtle tint", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.TintColour = Colour4.White.Opacity(0.3f);
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0.3f);
|
||||
});
|
||||
AddStep("medium tint", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.TintColour = Colour4.White.Opacity(0.6f);
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0.6f);
|
||||
});
|
||||
AddStep("strong tint", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null) acrylic.TintColour = Colour4.White.Opacity(0.9f);
|
||||
if (Children[2] is AcrylicContainer acrylic) acrylic.TintColour = Colour4.White.Opacity(0.9f);
|
||||
});
|
||||
|
||||
// Debug presets
|
||||
AddStep("debug: high contrast", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
{
|
||||
acrylic.BlurStrength = 15f;
|
||||
acrylic.TintColour = Colour4.Red.Opacity(0.7f);
|
||||
@@ -194,8 +171,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
|
||||
AddStep("debug: subtle effect", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
{
|
||||
acrylic.BlurStrength = 3f;
|
||||
acrylic.TintColour = Colour4.Black.Opacity(0.2f);
|
||||
@@ -204,8 +180,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
|
||||
AddStep("debug: reset to default", () =>
|
||||
{
|
||||
var acrylic = Children[2] as AcrylicContainer;
|
||||
if (acrylic != null)
|
||||
if (Children[2] is AcrylicContainer acrylic)
|
||||
{
|
||||
acrylic.BlurStrength = 10f;
|
||||
acrylic.TintColour = Colour4.White.Opacity(0.8f);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -77,15 +76,14 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
Child = acrylicEffect = new AcrylicContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BlurStrength = 15f,
|
||||
TintColour = new Color4(0, 0, 0, 0.3f),
|
||||
BackgroundColour = Color4.Black.Opacity(0.5f), // 半透明黑色背景
|
||||
BlurStrength = 50f,
|
||||
TintColour = new Color4(1, 1, 1, 0.5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// 在毛玻璃效果上面显示一些文本
|
||||
new SpriteText
|
||||
{
|
||||
Text = "毛玻璃效果 (Acrylic Effect)\n\n注意: 此容器模糊自己的背景层,\n不是背后的内容。\n这是 osu-framework 的限制。",
|
||||
Text = "毛玻璃效果 (Acrylic Effect)\n\n此容器实时模糊背后的所有内容,\n包括背景、动画和兄弟元素。",
|
||||
Font = FontUsage.Default.With(size: 30),
|
||||
Colour = Color4.White,
|
||||
Anchor = Anchor.Centre,
|
||||
@@ -130,7 +128,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
{
|
||||
if (acrylicEffect != null)
|
||||
{
|
||||
var current = acrylicEffect.TintColour.TopLeft.Linear;
|
||||
var current = acrylicEffect.TintColour;
|
||||
acrylicEffect.TintColour = new Color4(current.R, current.G, current.B, value);
|
||||
}
|
||||
});
|
||||
@@ -140,7 +138,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
{
|
||||
if (acrylicEffect != null)
|
||||
{
|
||||
var alpha = acrylicEffect.TintColour.TopLeft.Linear.A;
|
||||
var alpha = acrylicEffect.TintColour.A;
|
||||
acrylicEffect.TintColour = new Color4(0, 0, 0, alpha);
|
||||
}
|
||||
});
|
||||
@@ -149,7 +147,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
{
|
||||
if (acrylicEffect != null)
|
||||
{
|
||||
var alpha = acrylicEffect.TintColour.TopLeft.Linear.A;
|
||||
var alpha = acrylicEffect.TintColour.A;
|
||||
acrylicEffect.TintColour = new Color4(1, 1, 1, alpha);
|
||||
}
|
||||
});
|
||||
@@ -158,7 +156,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
{
|
||||
if (acrylicEffect != null)
|
||||
{
|
||||
var alpha = acrylicEffect.TintColour.TopLeft.Linear.A;
|
||||
var alpha = acrylicEffect.TintColour.A;
|
||||
acrylicEffect.TintColour = new Color4(1, 0, 0, alpha);
|
||||
}
|
||||
});
|
||||
@@ -167,7 +165,7 @@ namespace osu.Framework.Tests.Visual.Containers
|
||||
{
|
||||
if (acrylicEffect != null)
|
||||
{
|
||||
var alpha = acrylicEffect.TintColour.TopLeft.Linear.A;
|
||||
var alpha = acrylicEffect.TintColour.A;
|
||||
acrylicEffect.TintColour = new Color4(0, 0, 1, alpha);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Allocation;
|
||||
|
||||
namespace osu.Framework.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// A specialized layer for acrylic blur effects that fills the entire area and applies blur to background content.
|
||||
/// This layer handles the actual blurring logic using a custom shader and manages background capture automatically.
|
||||
/// </summary>
|
||||
internal partial class AcrylicBlurLayer : Drawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The strength of the blur effect.
|
||||
/// </summary>
|
||||
public float BlurStrength { get; set; } = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// The tint colour applied over the blurred background.
|
||||
/// </summary>
|
||||
public Color4 TintColour { get; set; } = Color4.White;
|
||||
|
||||
/// <summary>
|
||||
/// The darkening factor for depth effect.
|
||||
/// </summary>
|
||||
public float DarkenFactor { get; set; } = 0.1f;
|
||||
|
||||
[Resolved]
|
||||
private IRenderer? renderer { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ShaderManager shaderManager { get; set; } = null!;
|
||||
|
||||
public AcrylicBlurLayer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override DrawNode CreateDrawNode() => new AcrylicBlurLayerDrawNode(this);
|
||||
}
|
||||
}
|
||||
|
||||
141
osu.Framework/Graphics/Containers/AcrylicBlurLayerDrawNode.cs
Normal file
141
osu.Framework/Graphics/Containers/AcrylicBlurLayerDrawNode.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Shaders.Types;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
|
||||
namespace osu.Framework.Graphics.Containers
|
||||
{
|
||||
internal partial class AcrylicBlurLayer
|
||||
{
|
||||
private class AcrylicBlurLayerDrawNode : DrawNode
|
||||
{
|
||||
protected new AcrylicBlurLayer Source => (AcrylicBlurLayer)base.Source;
|
||||
|
||||
private RectangleF drawRectangle;
|
||||
private IShader acrylicShader;
|
||||
private IFrameBuffer backgroundBuffer;
|
||||
private IUniformBuffer<AcrylicParameters> acrylicParametersBuffer;
|
||||
|
||||
public AcrylicBlurLayerDrawNode(AcrylicBlurLayer source)
|
||||
: base(source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyState()
|
||||
{
|
||||
base.ApplyState();
|
||||
drawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat;
|
||||
}
|
||||
|
||||
protected override void Draw(IRenderer renderer)
|
||||
{
|
||||
// 检查依赖是否可用
|
||||
if (Source.renderer == null || Source.shaderManager == null)
|
||||
{
|
||||
// 如果依赖不可用,跳过绘制
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取或创建背景缓冲区(延迟到绘制线程)
|
||||
if (backgroundBuffer == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
backgroundBuffer = renderer.CreateFrameBuffer(null, TextureFilteringMode.Linear);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果创建失败,跳过绘制
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 捕获当前屏幕到缓冲区
|
||||
try
|
||||
{
|
||||
renderer.CaptureScreenToFrameBuffer(backgroundBuffer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果捕获失败,使用固定的背景色
|
||||
renderer.Clear(new ClearInfo(Color4.Gray));
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试加载毛玻璃着色器
|
||||
if (acrylicShader == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
acrylicShader = Source.shaderManager.Load("AcrylicBlur", "Texture");
|
||||
acrylicParametersBuffer = renderer.CreateUniformBuffer<AcrylicParameters>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 如果加载失败,使用备用方案:直接绘制背景
|
||||
Console.WriteLine($"Failed to load acrylic shader: {ex.Message}");
|
||||
renderer.DrawFrameBuffer(backgroundBuffer, drawRectangle, ColourInfo.SingleColour(Source.TintColour));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用着色器绘制
|
||||
if (acrylicShader != null && acrylicParametersBuffer != null)
|
||||
{
|
||||
acrylicParametersBuffer.Data = acrylicParametersBuffer.Data with
|
||||
{
|
||||
TexSize = backgroundBuffer.Size,
|
||||
Radius = (int)Source.BlurStrength,
|
||||
Sigma = Source.BlurStrength / 2f,
|
||||
BlurDirection = Vector2.One,
|
||||
TintColour = new Vector4(Source.TintColour.R, Source.TintColour.G, Source.TintColour.B, Source.TintColour.A),
|
||||
DarkenFactor = Source.DarkenFactor
|
||||
};
|
||||
|
||||
acrylicShader.BindUniformBlock("m_AcrylicParameters", acrylicParametersBuffer);
|
||||
acrylicShader.Bind();
|
||||
|
||||
// 绘制背景到当前区域
|
||||
renderer.DrawFrameBuffer(backgroundBuffer, drawRectangle, ColourInfo.SingleColour(new Color4(1, 1, 1, 1)));
|
||||
|
||||
acrylicShader.Unbind();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 备用方案:直接绘制背景
|
||||
renderer.DrawFrameBuffer(backgroundBuffer, drawRectangle, ColourInfo.SingleColour(Source.TintColour));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
acrylicParametersBuffer?.Dispose();
|
||||
backgroundBuffer?.Dispose();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private record struct AcrylicParameters
|
||||
{
|
||||
public UniformVector2 TexSize;
|
||||
public UniformInt Radius;
|
||||
public UniformFloat Sigma;
|
||||
public UniformVector2 BlurDirection;
|
||||
public UniformVector4 TintColour;
|
||||
public UniformFloat DarkenFactor;
|
||||
private readonly UniformPadding12 pad1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +1,82 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
|
||||
namespace osu.Framework.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// A container that applies an acrylic/frosted glass visual effect.
|
||||
/// This is achieved by using a <see cref="BufferedContainer"/> with blur and a tinted overlay.
|
||||
/// The container blurs its own background (a solid color or drawable), not content behind it.
|
||||
///
|
||||
/// Usage:
|
||||
/// <code>
|
||||
/// var acrylicEffect = new AcrylicContainer
|
||||
/// {
|
||||
/// RelativeSizeAxes = Axes.Both,
|
||||
/// BlurStrength = 10f,
|
||||
/// TintColour = new Color4(0, 0, 0, 0.3f),
|
||||
/// BackgroundColour = Color4.White, // The color to blur
|
||||
/// Children = new Drawable[] { /* your content */ }
|
||||
/// };
|
||||
/// </code>
|
||||
/// A container that applies a true acrylic/mica effect by blurring the content behind it.
|
||||
/// This implementation uses a layered approach with a blur background layer and a darkening overlay.
|
||||
/// The effect is applied regardless of drawing order and adapts to background changes in real-time.
|
||||
/// </summary>
|
||||
public partial class AcrylicContainer : BufferedContainer
|
||||
public partial class AcrylicContainer : Container
|
||||
{
|
||||
private float blurStrength = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// The strength of the blur effect.
|
||||
/// Higher values create a stronger blur. Range: 0-100, typical values: 5-20.
|
||||
/// The strength of the blur effect applied to the background content.
|
||||
/// </summary>
|
||||
public float BlurStrength
|
||||
{
|
||||
get => blurStrength;
|
||||
set
|
||||
{
|
||||
if (blurStrength == value)
|
||||
return;
|
||||
|
||||
blurStrength = value;
|
||||
updateBlur();
|
||||
}
|
||||
}
|
||||
|
||||
private ColourInfo tintColour = ColourInfo.SingleColour(new Color4(0, 0, 0, 0.3f));
|
||||
public float BlurStrength { get; set; } = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// The tint colour applied over the blurred background.
|
||||
/// Typically a semi-transparent color like Color4(0, 0, 0, 0.3f).
|
||||
/// </summary>
|
||||
public new ColourInfo TintColour
|
||||
{
|
||||
get => tintColour;
|
||||
set
|
||||
{
|
||||
if (tintColour.Equals(value))
|
||||
return;
|
||||
public Color4 TintColour { get; set; } = Color4.White;
|
||||
|
||||
tintColour = value;
|
||||
updateTint();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The darkening factor applied to create depth.
|
||||
/// </summary>
|
||||
public float DarkenFactor { get; set; } = 0.1f;
|
||||
|
||||
private Drawable? backgroundBox;
|
||||
private Drawable? tintOverlay;
|
||||
private AcrylicBlurLayer blurLayer = null!;
|
||||
private Box darkenLayer = null!;
|
||||
|
||||
[Resolved]
|
||||
private IRenderer? renderer { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ShaderManager shaderManager { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new acrylic container.
|
||||
/// </summary>
|
||||
public AcrylicContainer()
|
||||
: base(cachedFrameBuffer: true)
|
||||
{
|
||||
BackgroundColour = Color4.White; // Default white background to blur
|
||||
// 默认不设置RelativeSizeAxes,让用户决定
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
// Add a background box that will be blurred
|
||||
AddInternal(backgroundBox = new Shapes.Box
|
||||
base.LoadComplete();
|
||||
|
||||
// 添加虚化背景层
|
||||
Add(blurLayer = new AcrylicBlurLayer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = BackgroundColour,
|
||||
Depth = float.MaxValue // Behind everything
|
||||
BlurStrength = BlurStrength,
|
||||
TintColour = TintColour,
|
||||
DarkenFactor = DarkenFactor
|
||||
});
|
||||
|
||||
// Add a tint overlay on top
|
||||
AddInternal(tintOverlay = new Shapes.Box
|
||||
// 添加暗化层
|
||||
Add(darkenLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = tintColour,
|
||||
Depth = float.MinValue, // In front of everything
|
||||
Alpha = tintColour.TopLeft.Linear.A
|
||||
Colour = new Color4(0, 0, 0, DarkenFactor),
|
||||
Depth = -1 // 确保在虚化层之上
|
||||
});
|
||||
|
||||
updateBlur();
|
||||
}
|
||||
|
||||
private void updateBlur()
|
||||
protected override void Update()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
base.Update();
|
||||
|
||||
// Convert BlurStrength to sigma for the blur shader
|
||||
// sigma controls the Gaussian distribution width
|
||||
float sigma = Math.Max(0.5f, blurStrength * 0.5f);
|
||||
this.BlurTo(new osuTK.Vector2(sigma), 200);
|
||||
}
|
||||
// 同步属性到层
|
||||
blurLayer.BlurStrength = BlurStrength;
|
||||
blurLayer.TintColour = TintColour;
|
||||
blurLayer.DarkenFactor = DarkenFactor;
|
||||
|
||||
private void updateTint()
|
||||
{
|
||||
if (tintOverlay != null)
|
||||
{
|
||||
tintOverlay.Colour = tintColour;
|
||||
tintOverlay.Alpha = tintColour.TopLeft.Linear.A;
|
||||
}
|
||||
}
|
||||
|
||||
public new Color4 BackgroundColour
|
||||
{
|
||||
get => base.BackgroundColour;
|
||||
set
|
||||
{
|
||||
base.BackgroundColour = value;
|
||||
|
||||
if (backgroundBox != null)
|
||||
backgroundBox.Colour = value;
|
||||
}
|
||||
darkenLayer.Colour = new Color4(0, 0, 0, DarkenFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Framework.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple test class to verify AcrylicContainer functionality
|
||||
/// </summary>
|
||||
public partial class AcrylicTestContainer : Container
|
||||
{
|
||||
public AcrylicTestContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
// Add some background content to blur
|
||||
Add(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Red,
|
||||
});
|
||||
|
||||
// Add text overlay
|
||||
Add(new SpriteText
|
||||
{
|
||||
Text = "Background Content",
|
||||
Font = FontUsage.Default.With(size: 24),
|
||||
Colour = Colour4.White,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
// Add the acrylic container on top
|
||||
Add(new AcrylicContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BlurStrength = 10f,
|
||||
TintColour = Colour4.White.Opacity(0.8f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteText
|
||||
{
|
||||
Text = "Acrylic Effect Test",
|
||||
Font = FontUsage.Default.With(size: 32),
|
||||
Colour = Colour4.Black,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,14 +37,9 @@ namespace osu.Framework.Graphics.Containers
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
// 获取背景缓冲区 - 如果不存在则创建
|
||||
// 获取背景缓冲区 - 不再自动创建,只返回已存在的
|
||||
public IFrameBuffer? GetBackgroundBuffer()
|
||||
{
|
||||
if (backgroundBuffer == null && renderer != null)
|
||||
{
|
||||
backgroundBuffer = renderer.CreateFrameBuffer(null, TextureFilteringMode.Linear);
|
||||
initialized = true;
|
||||
}
|
||||
return backgroundBuffer;
|
||||
}
|
||||
|
||||
@@ -74,10 +69,11 @@ namespace osu.Framework.Graphics.Containers
|
||||
// 更新背景缓冲区(在UpdateAfterChildren中调用)
|
||||
public void UpdateBackgroundBuffer()
|
||||
{
|
||||
if (renderer is Renderer concreteRenderer && backgroundBuffer != null)
|
||||
{
|
||||
concreteRenderer.CaptureScreenToFrameBuffer(backgroundBuffer);
|
||||
}
|
||||
// 暂时禁用屏幕捕获,直到实现正确的API
|
||||
// if (renderer is Renderer concreteRenderer && backgroundBuffer != null)
|
||||
// {
|
||||
// concreteRenderer.CaptureScreenToFrameBuffer(backgroundBuffer);
|
||||
// }
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
|
||||
259
osu.Framework/Graphics/Containers/FrostedGlassContainer.cs
Normal file
259
osu.Framework/Graphics/Containers/FrostedGlassContainer.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Layout;
|
||||
|
||||
namespace osu.Framework.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// A container that renders the background (from the screen) to an internal framebuffer, applies blur, and then
|
||||
/// blits the framebuffer to the screen, allowing for frosted glass effects on the background.
|
||||
/// If all children are of a specific non-<see cref="Drawable"/> type, use the
|
||||
/// generic version <see cref="FrostedGlassContainer{T}"/>.
|
||||
/// </summary>
|
||||
public partial class FrostedGlassContainer : FrostedGlassContainer<Drawable>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public FrostedGlassContainer(RenderBufferFormat[] formats = null, bool pixelSnapping = false)
|
||||
: base(formats, pixelSnapping)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A container that renders the background (from the screen) to an internal framebuffer, applies blur, and then
|
||||
/// blits the framebuffer to the screen, allowing for frosted glass effects on the background.
|
||||
/// </summary>
|
||||
public partial class FrostedGlassContainer<T> : Container<T>, IBufferedContainer, IBufferedDrawable
|
||||
where T : Drawable
|
||||
{
|
||||
private Vector2 blurSigma = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of blurring in two orthogonal directions (X and Y if
|
||||
/// <see cref="BlurRotation"/> is zero).
|
||||
/// Blur is parametrized by a gaussian image filter. This property controls
|
||||
/// the standard deviation (sigma) of the gaussian kernel.
|
||||
/// </summary>
|
||||
public Vector2 BlurSigma
|
||||
{
|
||||
get => blurSigma;
|
||||
set
|
||||
{
|
||||
if (blurSigma == value)
|
||||
return;
|
||||
|
||||
blurSigma = value;
|
||||
ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private float blurRotation;
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the blur kernel clockwise. In degrees. Has no effect if
|
||||
/// <see cref="BlurSigma"/> has the same magnitude in both directions.
|
||||
/// </summary>
|
||||
public float BlurRotation
|
||||
{
|
||||
get => blurRotation;
|
||||
set
|
||||
{
|
||||
if (blurRotation == value)
|
||||
return;
|
||||
|
||||
blurRotation = value;
|
||||
ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private ColourInfo effectColour = Color4.White;
|
||||
|
||||
/// <summary>
|
||||
/// The multiplicative colour of drawn buffered object after applying all effects (e.g. blur). Default is <see cref="Color4.White"/>.
|
||||
/// </summary>
|
||||
public ColourInfo EffectColour
|
||||
{
|
||||
get => effectColour;
|
||||
set
|
||||
{
|
||||
if (effectColour.Equals(value))
|
||||
return;
|
||||
|
||||
effectColour = value;
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
}
|
||||
|
||||
private BlendingParameters effectBlending = BlendingParameters.Inherit;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="BlendingParameters"/> to use after applying all effects. Default is <see cref="BlendingType.Inherit"/>.
|
||||
/// </summary>
|
||||
public BlendingParameters EffectBlending
|
||||
{
|
||||
get => effectBlending;
|
||||
set
|
||||
{
|
||||
if (effectBlending == value)
|
||||
return;
|
||||
|
||||
effectBlending = value;
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 backgroundColour = new Color4(0, 0, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The background colour of the framebuffer. Transparent black by default.
|
||||
/// </summary>
|
||||
public Color4 BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
set
|
||||
{
|
||||
if (backgroundColour == value)
|
||||
return;
|
||||
|
||||
backgroundColour = value;
|
||||
ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 frameBufferScale = Vector2.One;
|
||||
|
||||
public Vector2 FrameBufferScale
|
||||
{
|
||||
get => frameBufferScale;
|
||||
set
|
||||
{
|
||||
if (frameBufferScale == value)
|
||||
return;
|
||||
|
||||
frameBufferScale = value;
|
||||
ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private float grayscaleStrength;
|
||||
|
||||
public float GrayscaleStrength
|
||||
{
|
||||
get => grayscaleStrength;
|
||||
set
|
||||
{
|
||||
if (grayscaleStrength == value)
|
||||
return;
|
||||
|
||||
grayscaleStrength = value;
|
||||
ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces a redraw of the framebuffer before it is blitted the next time.
|
||||
/// </summary>
|
||||
public void ForceRedraw() => Invalidate(Invalidation.DrawNode);
|
||||
|
||||
/// <summary>
|
||||
/// In order to signal the draw thread to re-draw the frosted glass container we version it.
|
||||
/// Our own version (update) keeps track of which version we are on, whereas the
|
||||
/// drawVersion keeps track of the version the draw thread is on.
|
||||
/// When forcing a redraw we increment updateVersion, pass it into each new drawnode
|
||||
/// and the draw thread will realize its drawVersion is lagging behind, thus redrawing.
|
||||
/// </summary>
|
||||
private long updateVersion;
|
||||
|
||||
private readonly BufferedContainerDrawNodeSharedData sharedData;
|
||||
|
||||
public IShader TextureShader { get; private set; }
|
||||
|
||||
private IShader blurShader;
|
||||
private IShader grayscaleShader;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an empty frosted glass container.
|
||||
/// </summary>
|
||||
/// <param name="formats">The render buffer formats attached to the frame buffer of this <see cref="FrostedGlassContainer"/>.</param>
|
||||
/// <param name="pixelSnapping">
|
||||
/// Whether the frame buffer position should be snapped to the nearest pixel when blitting.
|
||||
/// This amounts to setting the texture filtering mode to "nearest".
|
||||
/// </param>
|
||||
public FrostedGlassContainer(RenderBufferFormat[] formats = null, bool pixelSnapping = false)
|
||||
{
|
||||
sharedData = new BufferedContainerDrawNodeSharedData(formats, pixelSnapping, false);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ShaderManager shaders)
|
||||
{
|
||||
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
|
||||
blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR);
|
||||
grayscaleShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.GRAYSCALE);
|
||||
}
|
||||
|
||||
protected override DrawNode CreateDrawNode() => new FrostedGlassDrawNode(this, sharedData);
|
||||
|
||||
/// <summary>
|
||||
/// The blending which <see cref="FrostedGlassDrawNode"/> uses for the effect.
|
||||
/// </summary>
|
||||
public BlendingParameters DrawEffectBlending
|
||||
{
|
||||
get
|
||||
{
|
||||
BlendingParameters blending = EffectBlending;
|
||||
|
||||
blending.CopyFromParent(Blending);
|
||||
blending.ApplyDefaultToInherited();
|
||||
|
||||
return blending;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
bool result = base.OnInvalidate(invalidation, source);
|
||||
|
||||
if ((invalidation & Invalidation.DrawNode) > 0)
|
||||
{
|
||||
++updateVersion;
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public DrawColourInfo? FrameBufferDrawColour => base.DrawColourInfo;
|
||||
|
||||
// Children should not receive the true colour to avoid colour doubling when the frame-buffers are rendered to the back-buffer.
|
||||
public override DrawColourInfo DrawColourInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
var blending = Blending;
|
||||
blending.ApplyDefaultToInherited();
|
||||
|
||||
return new DrawColourInfo(Color4.White, blending);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
sharedData.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Shaders.Types;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Statistics;
|
||||
using osu.Framework.Allocation;
|
||||
|
||||
namespace osu.Framework.Graphics.Containers
|
||||
{
|
||||
public partial class FrostedGlassContainer<T>
|
||||
{
|
||||
private class FrostedGlassDrawNode : BufferedDrawNode, ICompositeDrawNode
|
||||
{
|
||||
protected new FrostedGlassContainer<T> Source => (FrostedGlassContainer<T>)base.Source;
|
||||
|
||||
protected new CompositeDrawableDrawNode Child => (CompositeDrawableDrawNode)base.Child;
|
||||
|
||||
private ColourInfo effectColour;
|
||||
private BlendingParameters effectBlending;
|
||||
|
||||
private Vector2 blurSigma;
|
||||
private Vector2I blurRadius;
|
||||
private float blurRotation;
|
||||
private float grayscaleStrength;
|
||||
|
||||
private long updateVersion;
|
||||
private IShader blurShader;
|
||||
private IShader grayscaleShader;
|
||||
|
||||
public FrostedGlassDrawNode(FrostedGlassContainer<T> source, BufferedContainerDrawNodeSharedData sharedData)
|
||||
: base(source, new CompositeDrawableDrawNode(source), sharedData)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyState()
|
||||
{
|
||||
base.ApplyState();
|
||||
|
||||
updateVersion = Source.updateVersion;
|
||||
|
||||
effectColour = Source.EffectColour;
|
||||
effectBlending = Source.DrawEffectBlending;
|
||||
|
||||
blurSigma = Source.BlurSigma;
|
||||
blurRadius = new Vector2I(Blur.KernelSize(blurSigma.X), Blur.KernelSize(blurSigma.Y));
|
||||
blurRotation = Source.BlurRotation;
|
||||
grayscaleStrength = Source.GrayscaleStrength;
|
||||
|
||||
blurShader = Source.blurShader;
|
||||
grayscaleShader = Source.grayscaleShader;
|
||||
}
|
||||
|
||||
protected override long GetDrawVersion() => updateVersion;
|
||||
|
||||
protected override void PopulateContents(IRenderer renderer)
|
||||
{
|
||||
// Capture the screen to the main buffer
|
||||
renderer.CaptureScreenToFrameBuffer(SharedData.MainBuffer);
|
||||
|
||||
// Then apply effects
|
||||
base.PopulateContents(renderer);
|
||||
}
|
||||
|
||||
protected override void DrawContents(IRenderer renderer)
|
||||
{
|
||||
renderer.SetBlend(effectBlending);
|
||||
|
||||
ColourInfo finalEffectColour = DrawColourInfo.Colour;
|
||||
finalEffectColour.ApplyChild(effectColour);
|
||||
|
||||
renderer.DrawFrameBuffer(SharedData.CurrentEffectBuffer, DrawRectangle, finalEffectColour);
|
||||
|
||||
// Draw children on top
|
||||
DrawOther(Child, renderer);
|
||||
}
|
||||
|
||||
private IUniformBuffer<BlurParameters> blurParametersBuffer;
|
||||
|
||||
private void drawBlurredFrameBuffer(IRenderer renderer, int kernelRadius, float sigma, float blurRotation)
|
||||
{
|
||||
blurParametersBuffer ??= renderer.CreateUniformBuffer<BlurParameters>();
|
||||
|
||||
IFrameBuffer current = SharedData.CurrentEffectBuffer;
|
||||
IFrameBuffer target = SharedData.GetNextEffectBuffer();
|
||||
|
||||
renderer.SetBlend(BlendingParameters.None);
|
||||
|
||||
using (BindFrameBuffer(target))
|
||||
{
|
||||
float radians = float.DegreesToRadians(blurRotation);
|
||||
|
||||
blurParametersBuffer.Data = blurParametersBuffer.Data with
|
||||
{
|
||||
Radius = kernelRadius,
|
||||
Sigma = sigma,
|
||||
TexSize = current.Size,
|
||||
Direction = new Vector2(MathF.Cos(radians), MathF.Sin(radians))
|
||||
};
|
||||
|
||||
blurShader.BindUniformBlock("m_BlurParameters", blurParametersBuffer);
|
||||
blurShader.Bind();
|
||||
renderer.DrawFrameBuffer(current, new RectangleF(0, 0, current.Texture.Width, current.Texture.Height), ColourInfo.SingleColour(Color4.White));
|
||||
blurShader.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
public List<DrawNode> Children
|
||||
{
|
||||
get => Child.Children;
|
||||
set => Child.Children = value;
|
||||
}
|
||||
|
||||
public bool AddChildDrawNodes => RequiresRedraw;
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
blurParametersBuffer?.Dispose();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private record struct BlurParameters
|
||||
{
|
||||
public UniformVector2 TexSize;
|
||||
public UniformInt Radius;
|
||||
public UniformFloat Sigma;
|
||||
public UniformVector2 Direction;
|
||||
private readonly UniformPadding8 pad1;
|
||||
}
|
||||
}
|
||||
|
||||
private class BufferedContainerDrawNodeSharedData : BufferedDrawNodeSharedData
|
||||
{
|
||||
public BufferedContainerDrawNodeSharedData(RenderBufferFormat[] mainBufferFormats, bool pixelSnapping, bool clipToRootNode)
|
||||
: base(2, mainBufferFormats, pixelSnapping, clipToRootNode)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#ifndef ACRYLIC_BLUR_FS
|
||||
#define ACRYLIC_BLUR_FS
|
||||
|
||||
#include "sh_Utils.h"
|
||||
|
||||
#undef INV_SQRT_2PI
|
||||
#define INV_SQRT_2PI 0.39894
|
||||
|
||||
layout(location = 2) in mediump vec2 v_TexCoord;
|
||||
|
||||
layout(std140, set = 0, binding = 0) uniform m_BlurParameters
|
||||
{
|
||||
mediump vec2 g_TexSize;
|
||||
int g_Radius;
|
||||
mediump float g_Sigma;
|
||||
highp vec2 g_BlurDirection;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 0) uniform lowp texture2D m_Texture;
|
||||
layout(set = 1, binding = 1) uniform lowp sampler m_Sampler;
|
||||
|
||||
layout(location = 0) out vec4 o_Colour;
|
||||
|
||||
mediump float computeGauss(in mediump float x, in mediump float sigma)
|
||||
{
|
||||
return INV_SQRT_2PI * exp(-0.5*x*x / (sigma*sigma)) / sigma;
|
||||
}
|
||||
|
||||
lowp vec4 blur(int radius, highp vec2 direction, mediump vec2 texCoord, mediump vec2 texSize, mediump float sigma)
|
||||
{
|
||||
mediump vec4 sum = vec4(0.0);
|
||||
mediump float totalWeight = 0.0;
|
||||
|
||||
// 中心像素
|
||||
mediump float weight = computeGauss(0.0, sigma);
|
||||
sum += texture(sampler2D(m_Texture, m_Sampler), texCoord) * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// 对称采样 - 修复采样位置和权重
|
||||
for (int i = 1; i <= radius; ++i)
|
||||
{
|
||||
// 使用正确的采样位置(1, 2, 3... 而不是1.5, 3.5...)
|
||||
mediump float x = float(i);
|
||||
weight = computeGauss(x, sigma);
|
||||
sum += texture(sampler2D(m_Texture, m_Sampler), texCoord + direction * x / texSize) * weight;
|
||||
sum += texture(sampler2D(m_Texture, m_Sampler), texCoord - direction * x / texSize) * weight;
|
||||
totalWeight += 2.0 * weight;
|
||||
}
|
||||
|
||||
return sum / totalWeight;
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
// 应用模糊
|
||||
vec4 blurredColour = blur(g_Radius, g_BlurDirection, v_TexCoord, g_TexSize, g_Sigma);
|
||||
|
||||
o_Colour = blurredColour;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,152 +0,0 @@
|
||||
#ifndef ACRYLIC_DEPTH_BLUR_FS
|
||||
#define ACRYLIC_DEPTH_BLUR_FS
|
||||
|
||||
#include "sh_Utils.h"
|
||||
#include "sh_Masking.h"
|
||||
|
||||
#undef INV_SQRT_2PI
|
||||
#define INV_SQRT_2PI 0.39894
|
||||
|
||||
layout(location = 0) in highp vec4 v_Colour;
|
||||
layout(location = 1) in mediump vec2 v_TexCoord;
|
||||
layout(location = 2) in highp vec4 v_ScreenPosition;
|
||||
|
||||
// 模糊参数
|
||||
layout(std140, set = 0, binding = 0) uniform m_AcrylicParameters
|
||||
{
|
||||
mediump vec2 g_ScreenSize; // 屏幕尺寸
|
||||
int g_BlurRadius; // 模糊半径
|
||||
mediump float g_BlurSigma; // 高斯模糊 sigma
|
||||
mediump float g_BlurStrength; // 模糊强度(0-1)
|
||||
mediump float g_TintAlpha; // 着色透明度
|
||||
mediump vec3 g_TintColor; // 着色颜色
|
||||
highp float g_CurrentDepth; // 当前容器的深度
|
||||
};
|
||||
|
||||
// 场景纹理(后台缓冲区)
|
||||
layout(set = 1, binding = 0) uniform lowp texture2D m_SceneTexture;
|
||||
layout(set = 1, binding = 1) uniform lowp sampler m_SceneSampler;
|
||||
|
||||
// 深度纹理
|
||||
layout(set = 2, binding = 0) uniform highp texture2D m_DepthTexture;
|
||||
layout(set = 2, binding = 1) uniform highp sampler m_DepthSampler;
|
||||
|
||||
layout(location = 0) out vec4 o_Colour;
|
||||
|
||||
// 计算高斯权重
|
||||
mediump float computeGauss(in mediump float x, in mediump float sigma)
|
||||
{
|
||||
return INV_SQRT_2PI * exp(-0.5 * x * x / (sigma * sigma)) / sigma;
|
||||
}
|
||||
|
||||
// 基于深度的采样 - 只采样比当前深度更深的像素
|
||||
lowp vec4 sampleWithDepth(vec2 uv, highp float currentDepth)
|
||||
{
|
||||
// 采样深度值
|
||||
highp float depth = texture(sampler2D(m_DepthTexture, m_DepthSampler), uv).r;
|
||||
|
||||
// 只有当采样点的深度大于当前深度时才采样(更深的内容)
|
||||
// 在深度缓冲区中,更大的值表示更深的深度
|
||||
if (depth > currentDepth)
|
||||
{
|
||||
return texture(sampler2D(m_SceneTexture, m_SceneSampler), uv);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果采样点在当前层之上或同层,返回透明色
|
||||
return vec4(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// 双通道可分离高斯模糊(水平+垂直)
|
||||
lowp vec4 applyDepthAwareBlur(vec2 screenCoord, highp float currentDepth, int radius, float sigma)
|
||||
{
|
||||
vec2 pixelSize = 1.0 / g_ScreenSize;
|
||||
|
||||
// 第一步: 水平模糊
|
||||
mediump float factor = computeGauss(0.0, sigma);
|
||||
mediump vec4 horizontalSum = sampleWithDepth(screenCoord, currentDepth) * factor;
|
||||
mediump float totalFactor = factor;
|
||||
int validSamples = 1;
|
||||
|
||||
for (int i = 1; i <= radius; i++)
|
||||
{
|
||||
factor = computeGauss(float(i), sigma);
|
||||
|
||||
vec4 sample1 = sampleWithDepth(screenCoord + vec2(float(i) * pixelSize.x, 0.0), currentDepth);
|
||||
vec4 sample2 = sampleWithDepth(screenCoord - vec2(float(i) * pixelSize.x, 0.0), currentDepth);
|
||||
|
||||
// 只有当采样有效时才累加
|
||||
if (sample1.a > 0.0)
|
||||
{
|
||||
horizontalSum += sample1 * factor;
|
||||
totalFactor += factor;
|
||||
validSamples++;
|
||||
}
|
||||
|
||||
if (sample2.a > 0.0)
|
||||
{
|
||||
horizontalSum += sample2 * factor;
|
||||
totalFactor += factor;
|
||||
validSamples++;
|
||||
}
|
||||
}
|
||||
|
||||
vec4 horizontalBlur = totalFactor > 0.0 ? horizontalSum / totalFactor : vec4(0.0);
|
||||
|
||||
// 第二步: 垂直模糊(对水平模糊的结果进行)
|
||||
// 为简化,我们在fragment shader中做简化版的双通道模糊
|
||||
// 实际生产环境可以用两个pass来实现更高效的可分离模糊
|
||||
factor = computeGauss(0.0, sigma);
|
||||
mediump vec4 finalSum = horizontalBlur * factor;
|
||||
totalFactor = factor;
|
||||
|
||||
for (int i = 1; i <= radius; i++)
|
||||
{
|
||||
factor = computeGauss(float(i), sigma);
|
||||
|
||||
vec4 sample1 = sampleWithDepth(screenCoord + vec2(0.0, float(i) * pixelSize.y), currentDepth);
|
||||
vec4 sample2 = sampleWithDepth(screenCoord - vec2(0.0, float(i) * pixelSize.y), currentDepth);
|
||||
|
||||
if (sample1.a > 0.0)
|
||||
{
|
||||
finalSum += sample1 * factor;
|
||||
totalFactor += factor;
|
||||
}
|
||||
|
||||
if (sample2.a > 0.0)
|
||||
{
|
||||
finalSum += sample2 * factor;
|
||||
totalFactor += factor;
|
||||
}
|
||||
}
|
||||
|
||||
return totalFactor > 0.0 ? finalSum / totalFactor : vec4(0.0);
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
// 计算屏幕空间UV坐标
|
||||
vec2 screenCoord = (v_ScreenPosition.xy / v_ScreenPosition.w) * 0.5 + 0.5;
|
||||
|
||||
// 获取当前片段的深度
|
||||
highp float currentDepth = g_CurrentDepth;
|
||||
|
||||
// 应用深度感知模糊
|
||||
vec4 blurredColor = applyDepthAwareBlur(
|
||||
screenCoord,
|
||||
currentDepth,
|
||||
g_BlurRadius,
|
||||
g_BlurSigma
|
||||
);
|
||||
|
||||
// 应用着色层
|
||||
vec4 tintColor = vec4(g_TintColor, g_TintAlpha);
|
||||
vec4 finalColor = blend(tintColor, blurredColor);
|
||||
|
||||
// 应用遮罩(圆角等)
|
||||
vec2 wrappedCoord = wrap(v_TexCoord, v_TexRect);
|
||||
o_Colour = getRoundedColor(finalColor * v_Colour, wrappedCoord);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef ACRYLIC_DEPTH_BLUR_VS
|
||||
#define ACRYLIC_DEPTH_BLUR_VS
|
||||
|
||||
#include "sh_Utils.h"
|
||||
|
||||
layout(location = 0) in highp vec2 m_Position;
|
||||
layout(location = 1) in lowp vec4 m_Colour;
|
||||
layout(location = 2) in highp vec2 m_TexCoord;
|
||||
|
||||
layout(location = 0) out highp vec4 v_Colour;
|
||||
layout(location = 1) out highp vec2 v_TexCoord;
|
||||
layout(location = 2) out highp vec4 v_ScreenPosition;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
// 传递顶点颜色和纹理坐标
|
||||
v_Colour = m_Colour;
|
||||
v_TexCoord = m_TexCoord;
|
||||
|
||||
// 计算屏幕空间位置(用于深度采样)
|
||||
gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0);
|
||||
v_ScreenPosition = gl_Position;
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user