diff --git a/SpineViewer/Exporter/ExportArgs.cs b/SpineViewer/Exporter/ExportArgs.cs
index a40b51f..6abfa1f 100644
--- a/SpineViewer/Exporter/ExportArgs.cs
+++ b/SpineViewer/Exporter/ExportArgs.cs
@@ -72,7 +72,27 @@ namespace SpineViewer.Exporter
[Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(SFMLColorConverter))]
[Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")]
- public SFML.Graphics.Color BackgroundColor { get; set; } = SFML.Graphics.Color.Transparent;
+ public SFML.Graphics.Color BackgroundColor
+ {
+ get => backgroundColor;
+ set
+ {
+ backgroundColor = value;
+ var bcPma = value;
+ var a = bcPma.A / 255f;
+ bcPma.R = (byte)(bcPma.R * a);
+ bcPma.G = (byte)(bcPma.G * a);
+ bcPma.B = (byte)(bcPma.B * a);
+ BackgroundColorPma = bcPma;
+ }
+ }
+ private SFML.Graphics.Color backgroundColor = SFML.Graphics.Color.Transparent;
+
+ ///
+ /// 预乘后的背景颜色
+ ///
+ [Browsable(false)]
+ public SFML.Graphics.Color BackgroundColorPma { get; private set; } = SFML.Graphics.Color.Transparent;
///
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
diff --git a/SpineViewer/Exporter/Exporter.cs b/SpineViewer/Exporter/Exporter.cs
index 2541937..6fe6a2e 100644
--- a/SpineViewer/Exporter/Exporter.cs
+++ b/SpineViewer/Exporter/Exporter.cs
@@ -1,5 +1,4 @@
using NLog;
-using SpineViewer.Spine;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -52,27 +51,54 @@ namespace SpineViewer.Exporter
///
/// 获取单个模型的单帧画面
///
- protected SFMLImageVideoFrame GetFrame(Spine.Spine spine)
- {
- // tex 必须临时创建, 随用随取, 防止出现跨线程的情况
- using var tex = GetRenderTexture();
- tex.Clear(ExportArgs.BackgroundColor);
- tex.Draw(spine);
- tex.Display();
- return new(tex.Texture.CopyToImage());
- }
+ protected SFMLImageVideoFrame GetFrame(Spine.Spine spine) => GetFrame([spine]);
///
/// 获取模型列表的单帧画面
///
protected SFMLImageVideoFrame GetFrame(Spine.Spine[] spinesToRender)
{
- // tex 必须临时创建, 随用随取, 防止出现跨线程的情况
- using var tex = GetRenderTexture();
- tex.Clear(ExportArgs.BackgroundColor);
- foreach (var spine in spinesToRender) tex.Draw(spine);
- tex.Display();
- return new(tex.Texture.CopyToImage());
+ // RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况
+ using var texPma = GetRenderTexture();
+
+ // 先将预乘结果准确绘制出来, 注意背景色也应当是预乘的
+ texPma.Clear(ExportArgs.BackgroundColorPma);
+ foreach (var spine in spinesToRender) texPma.Draw(spine);
+ texPma.Display();
+
+ // 背景色透明度不为 1 时需要处理反预乘, 否则直接就是结果
+ if (ExportArgs.BackgroundColor.A < 255)
+ {
+ // 从预乘结果构造渲染对象, 并正确设置变换
+ using var view = texPma.GetView();
+ using var img = texPma.Texture.CopyToImage();
+ using var texSprite = new SFML.Graphics.Texture(img);
+ using var sp = new SFML.Graphics.Sprite(texSprite)
+ {
+ Origin = new(texPma.Size.X / 2f, texPma.Size.Y / 2f),
+ Position = new(view.Center.X, view.Center.Y),
+ Scale = new(view.Size.X / texPma.Size.X, view.Size.Y / texPma.Size.Y),
+ Rotation = view.Rotation
+ };
+
+ // 混合模式用直接覆盖的方式, 保证得到的图像区域是反预乘的颜色和透明度, 同时使用反预乘着色器
+ var st = SFML.Graphics.RenderStates.Default;
+ st.BlendMode = new(SFML.Graphics.BlendMode.Factor.One, SFML.Graphics.BlendMode.Factor.Zero); // 用源的颜色和透明度直接覆盖
+ st.Shader = Shader.InversePma;
+
+ // 在最终结果上二次渲染非预乘画面
+ using var tex = GetRenderTexture();
+
+ // 将非预乘结果覆盖式绘制在目标对象上, 注意背景色应该用非预乘的
+ tex.Clear(ExportArgs.BackgroundColor);
+ tex.Draw(sp, st);
+ tex.Display();
+ return new(tex.Texture.CopyToImage());
+ }
+ else
+ {
+ return new(texPma.Texture.CopyToImage());
+ }
}
///
diff --git a/SpineViewer/Exporter/Implementations/ExportArgs/GifExportArgs.cs b/SpineViewer/Exporter/Implementations/ExportArgs/GifExportArgs.cs
index ebe6b53..3919f6b 100644
--- a/SpineViewer/Exporter/Implementations/ExportArgs/GifExportArgs.cs
+++ b/SpineViewer/Exporter/Implementations/ExportArgs/GifExportArgs.cs
@@ -16,9 +16,6 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
{
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
{
- // 给一个纯白的背景
- BackgroundColor = new(255, 255, 255, 0);
-
// GIF 的帧率不能太高, 超过 50 帧反而会变慢
FPS = 12;
}
diff --git a/SpineViewer/Exporter/Implementations/ExportArgs/MkvExportArgs.cs b/SpineViewer/Exporter/Implementations/ExportArgs/MkvExportArgs.cs
index b8145d6..43bef4e 100644
--- a/SpineViewer/Exporter/Implementations/ExportArgs/MkvExportArgs.cs
+++ b/SpineViewer/Exporter/Implementations/ExportArgs/MkvExportArgs.cs
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
{
public MkvExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
{
- BackgroundColor = new(0, 255, 0, 0);
+ BackgroundColor = new(0, 255, 0);
}
public override string Format => "matroska";
diff --git a/SpineViewer/Exporter/Implementations/ExportArgs/MovExportArgs.cs b/SpineViewer/Exporter/Implementations/ExportArgs/MovExportArgs.cs
index 172c274..9af32a1 100644
--- a/SpineViewer/Exporter/Implementations/ExportArgs/MovExportArgs.cs
+++ b/SpineViewer/Exporter/Implementations/ExportArgs/MovExportArgs.cs
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
{
public MovExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
{
- BackgroundColor = new(0, 255, 0, 0);
+ BackgroundColor = new(0, 255, 0);
}
public override string Format => "mov";
diff --git a/SpineViewer/Exporter/Implementations/ExportArgs/Mp4ExportArgs.cs b/SpineViewer/Exporter/Implementations/ExportArgs/Mp4ExportArgs.cs
index c6d3ec3..2e928a2 100644
--- a/SpineViewer/Exporter/Implementations/ExportArgs/Mp4ExportArgs.cs
+++ b/SpineViewer/Exporter/Implementations/ExportArgs/Mp4ExportArgs.cs
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
{
public Mp4ExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
{
- BackgroundColor = new(0, 255, 0, 0);
+ BackgroundColor = new(0, 255, 0);
}
public override string Format => "mp4";
diff --git a/SpineViewer/MainForm.cs b/SpineViewer/MainForm.cs
index dc22e50..5ac910d 100644
--- a/SpineViewer/MainForm.cs
+++ b/SpineViewer/MainForm.cs
@@ -28,7 +28,7 @@ namespace SpineViewer
// 执行一些初始化工作
try
{
- Spine.Shader.Init();
+ Shader.Init();
}
catch (Exception ex)
{
diff --git a/SpineViewer/Spine/Shader.cs b/SpineViewer/Shader.cs
similarity index 75%
rename from SpineViewer/Spine/Shader.cs
rename to SpineViewer/Shader.cs
index a2b2ded..f62001c 100644
--- a/SpineViewer/Spine/Shader.cs
+++ b/SpineViewer/Shader.cs
@@ -4,39 +4,39 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.Spine
+namespace SpineViewer
{
public static class Shader
{
///
/// 用于非预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘), 并且输出预乘后的像素值
///
- private const string FRAGMENT_VertexAlpha = (
+ private const string FRAGMENT_VertexAlpha =
"uniform sampler2D t;" +
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
"p.rgb *= p.a * gl_Color.a;" +
"gl_FragColor = gl_Color * p; }"
- );
+ ;
///
/// 用于预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘)
///
- private const string FRAGMENT_VertexAlphaPma = (
+ private const string FRAGMENT_VertexAlphaPma =
"uniform sampler2D t;" +
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
"p.rgb *= gl_Color.a;" +
"gl_FragColor = gl_Color * p; }"
- );
+ ;
///
/// 预乘转非预乘 fragment shader
///
- private const string FRAGMENT_PmaInv = (
+ private const string FRAGMENT_InvPma =
"uniform sampler2D t;" +
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
- "p.rgb *= gl_Color.a;" +
- "gl_FragColor = gl_Color * p; }"
- );
+ "if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
+ "gl_FragColor = p; }"
+ ;
///
/// 考虑了顶点透明度变化的着色器, 输入是非预乘纹理像素, 输出是预乘像素
@@ -48,6 +48,11 @@ namespace SpineViewer.Spine
///
private static SFML.Graphics.Shader? VertexAlphaPma = null;
+ ///
+ /// 反预乘着色器, 用于得到正确透明度的非预乘画面
+ ///
+ public static SFML.Graphics.Shader? InversePma { get; private set; }
+
///
/// 加载 Shader, 可能会存在异常导致着色器加载失败
///
@@ -56,14 +61,15 @@ namespace SpineViewer.Spine
{
VertexAlpha = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlpha);
VertexAlphaPma = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlphaPma);
+ InversePma = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_InvPma);
}
///
- /// 获取合适的着色器
+ /// 获取绘制 Spine 的着色器
///
/// 纹理是否是预乘的
/// 是否是双色着色的(TODO)
- public static SFML.Graphics.Shader? GetShader(bool pma, bool twoColor = false)
+ public static SFML.Graphics.Shader? GetSpineShader(bool pma, bool twoColor = false)
{
if (pma)
return VertexAlphaPma;
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine21.cs b/SpineViewer/Spine/Implementations/Spine/Spine21.cs
index f91c6d4..a4adf4a 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine21.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine21.cs
@@ -261,7 +261,7 @@ namespace SpineViewer.Spine.Implementations.Spine
{
vertexArray.Clear();
states.Texture = null;
- states.Shader = Shader.GetShader(usePremultipliedAlpha);
+ states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine36.cs b/SpineViewer/Spine/Implementations/Spine/Spine36.cs
index ddc365a..abadf04 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine36.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine36.cs
@@ -220,7 +220,7 @@ namespace SpineViewer.Spine.Implementations.Spine
{
vertexArray.Clear();
states.Texture = null;
- states.Shader = Shader.GetShader(usePremultipliedAlpha);
+ states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine37.cs b/SpineViewer/Spine/Implementations/Spine/Spine37.cs
index 61d66ce..9b17147 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine37.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine37.cs
@@ -190,7 +190,7 @@ namespace SpineViewer.Spine.Implementations.Spine
{
vertexArray.Clear();
states.Texture = null;
- states.Shader = Shader.GetShader(usePremultipliedAlpha);
+ states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine38.cs b/SpineViewer/Spine/Implementations/Spine/Spine38.cs
index b8f2073..09e1d8f 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine38.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine38.cs
@@ -196,7 +196,7 @@ namespace SpineViewer.Spine.Implementations.Spine
{
vertexArray.Clear();
states.Texture = null;
- states.Shader = Shader.GetShader(usePremultipliedAlpha);
+ states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine40.cs b/SpineViewer/Spine/Implementations/Spine/Spine40.cs
index b6ec861..e6f3765 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine40.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine40.cs
@@ -192,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.Spine
{
vertexArray.Clear();
states.Texture = null;
- states.Shader = Shader.GetShader(usePremultipliedAlpha);
+ states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine41.cs b/SpineViewer/Spine/Implementations/Spine/Spine41.cs
index 3471db1..52a5f68 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine41.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine41.cs
@@ -192,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.Spine
{
vertexArray.Clear();
states.Texture = null;
- states.Shader = Shader.GetShader(usePremultipliedAlpha);
+ states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine42.cs b/SpineViewer/Spine/Implementations/Spine/Spine42.cs
index 1697385..46394e7 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine42.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine42.cs
@@ -192,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.Spine
{
vertexArray.Clear();
states.Texture = null;
- states.Shader = Shader.GetShader(usePremultipliedAlpha);
+ states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
diff --git a/SpineViewer/Spine/Spine.cs b/SpineViewer/Spine/Spine.cs
index 80f1829..ac2ccaf 100644
--- a/SpineViewer/Spine/Spine.cs
+++ b/SpineViewer/Spine/Spine.cs
@@ -370,7 +370,7 @@ namespace SpineViewer.Spine
protected abstract RectangleF bounds { get; }
///
- /// 骨骼预览图
+ /// 骨骼预览图, 并没有去除预乘, 画面可能偏暗
///
[Browsable(false)]
public Image Preview { get; private set; }
@@ -405,8 +405,15 @@ namespace SpineViewer.Spine
///
/// SFML.Graphics.Drawable 接口实现
+ /// 这个渲染实现绘制出来的像素将是预乘的, 当渲染的背景透明度是 1 时, 则等价于非预乘的结果, 即正常画面, 否则画面偏暗
+ /// 可以用于 的渲染, 因为直接在窗口上绘制时窗口始终是不透明的
///
public void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states) { lock (_lock) draw(target, states); }
+
+ ///
+ /// 这个渲染实现绘制出来的像素将是预乘的, 当渲染的背景透明度是 1 时, 则等价于非预乘的结果, 即正常画面, 否则画面偏暗
+ /// 可以用于 的渲染, 因为直接在窗口上绘制时窗口始终是不透明的
+ ///
protected abstract void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
#endregion