修复导出过程中的PMA问题
This commit is contained in:
@@ -72,7 +72,27 @@ namespace SpineViewer.Exporter
|
|||||||
[Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
|
[Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
|
||||||
[TypeConverter(typeof(SFMLColorConverter))]
|
[TypeConverter(typeof(SFMLColorConverter))]
|
||||||
[Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")]
|
[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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预乘后的背景颜色
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public SFML.Graphics.Color BackgroundColorPma { get; private set; } = SFML.Graphics.Color.Transparent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
|
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using SpineViewer.Spine;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -52,27 +51,54 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取单个模型的单帧画面
|
/// 获取单个模型的单帧画面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFMLImageVideoFrame GetFrame(Spine.Spine spine)
|
protected SFMLImageVideoFrame GetFrame(Spine.Spine spine) => GetFrame([spine]);
|
||||||
{
|
|
||||||
// tex 必须临时创建, 随用随取, 防止出现跨线程的情况
|
|
||||||
using var tex = GetRenderTexture();
|
|
||||||
tex.Clear(ExportArgs.BackgroundColor);
|
|
||||||
tex.Draw(spine);
|
|
||||||
tex.Display();
|
|
||||||
return new(tex.Texture.CopyToImage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取模型列表的单帧画面
|
/// 获取模型列表的单帧画面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFMLImageVideoFrame GetFrame(Spine.Spine[] spinesToRender)
|
protected SFMLImageVideoFrame GetFrame(Spine.Spine[] spinesToRender)
|
||||||
{
|
{
|
||||||
// tex 必须临时创建, 随用随取, 防止出现跨线程的情况
|
// RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况
|
||||||
using var tex = GetRenderTexture();
|
using var texPma = GetRenderTexture();
|
||||||
tex.Clear(ExportArgs.BackgroundColor);
|
|
||||||
foreach (var spine in spinesToRender) tex.Draw(spine);
|
// 先将预乘结果准确绘制出来, 注意背景色也应当是预乘的
|
||||||
tex.Display();
|
texPma.Clear(ExportArgs.BackgroundColorPma);
|
||||||
return new(tex.Texture.CopyToImage());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||||
{
|
{
|
||||||
// 给一个纯白的背景
|
|
||||||
BackgroundColor = new(255, 255, 255, 0);
|
|
||||||
|
|
||||||
// GIF 的帧率不能太高, 超过 50 帧反而会变慢
|
// GIF 的帧率不能太高, 超过 50 帧反而会变慢
|
||||||
FPS = 12;
|
FPS = 12;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public MkvExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
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";
|
public override string Format => "matroska";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public MovExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
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";
|
public override string Format => "mov";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public Mp4ExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
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";
|
public override string Format => "mp4";
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace SpineViewer
|
|||||||
// 执行一些初始化工作
|
// 执行一些初始化工作
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Spine.Shader.Init();
|
Shader.Init();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,39 +4,39 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer
|
||||||
{
|
{
|
||||||
public static class Shader
|
public static class Shader
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于非预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘), 并且输出预乘后的像素值
|
/// 用于非预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘), 并且输出预乘后的像素值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string FRAGMENT_VertexAlpha = (
|
private const string FRAGMENT_VertexAlpha =
|
||||||
"uniform sampler2D t;" +
|
"uniform sampler2D t;" +
|
||||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||||
"p.rgb *= p.a * gl_Color.a;" +
|
"p.rgb *= p.a * gl_Color.a;" +
|
||||||
"gl_FragColor = gl_Color * p; }"
|
"gl_FragColor = gl_Color * p; }"
|
||||||
);
|
;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘)
|
/// 用于预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string FRAGMENT_VertexAlphaPma = (
|
private const string FRAGMENT_VertexAlphaPma =
|
||||||
"uniform sampler2D t;" +
|
"uniform sampler2D t;" +
|
||||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||||
"p.rgb *= gl_Color.a;" +
|
"p.rgb *= gl_Color.a;" +
|
||||||
"gl_FragColor = gl_Color * p; }"
|
"gl_FragColor = gl_Color * p; }"
|
||||||
);
|
;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 预乘转非预乘 fragment shader
|
/// 预乘转非预乘 fragment shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string FRAGMENT_PmaInv = (
|
private const string FRAGMENT_InvPma =
|
||||||
"uniform sampler2D t;" +
|
"uniform sampler2D t;" +
|
||||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||||
"p.rgb *= gl_Color.a;" +
|
"if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
|
||||||
"gl_FragColor = gl_Color * p; }"
|
"gl_FragColor = p; }"
|
||||||
);
|
;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 考虑了顶点透明度变化的着色器, 输入是非预乘纹理像素, 输出是预乘像素
|
/// 考虑了顶点透明度变化的着色器, 输入是非预乘纹理像素, 输出是预乘像素
|
||||||
@@ -48,6 +48,11 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static SFML.Graphics.Shader? VertexAlphaPma = null;
|
private static SFML.Graphics.Shader? VertexAlphaPma = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反预乘着色器, 用于得到正确透明度的非预乘画面
|
||||||
|
/// </summary>
|
||||||
|
public static SFML.Graphics.Shader? InversePma { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加载 Shader, 可能会存在异常导致着色器加载失败
|
/// 加载 Shader, 可能会存在异常导致着色器加载失败
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -56,14 +61,15 @@ namespace SpineViewer.Spine
|
|||||||
{
|
{
|
||||||
VertexAlpha = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlpha);
|
VertexAlpha = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlpha);
|
||||||
VertexAlphaPma = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlphaPma);
|
VertexAlphaPma = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlphaPma);
|
||||||
|
InversePma = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_InvPma);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取合适的着色器
|
/// 获取绘制 Spine 的着色器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pma">纹理是否是预乘的</param>
|
/// <param name="pma">纹理是否是预乘的</param>
|
||||||
/// <param name="twoColor">是否是双色着色的(TODO)</param>
|
/// <param name="twoColor">是否是双色着色的(TODO)</param>
|
||||||
public static SFML.Graphics.Shader? GetShader(bool pma, bool twoColor = false)
|
public static SFML.Graphics.Shader? GetSpineShader(bool pma, bool twoColor = false)
|
||||||
{
|
{
|
||||||
if (pma)
|
if (pma)
|
||||||
return VertexAlphaPma;
|
return VertexAlphaPma;
|
||||||
@@ -261,7 +261,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
states.Shader = Shader.GetShader(usePremultipliedAlpha);
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
states.Shader = Shader.GetShader(usePremultipliedAlpha);
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
states.Shader = Shader.GetShader(usePremultipliedAlpha);
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
states.Shader = Shader.GetShader(usePremultipliedAlpha);
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
states.Shader = Shader.GetShader(usePremultipliedAlpha);
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
states.Shader = Shader.GetShader(usePremultipliedAlpha);
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
states.Shader = Shader.GetShader(usePremultipliedAlpha);
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ namespace SpineViewer.Spine
|
|||||||
protected abstract RectangleF bounds { get; }
|
protected abstract RectangleF bounds { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 骨骼预览图
|
/// 骨骼预览图, 并没有去除预乘, 画面可能偏暗
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public Image Preview { get; private set; }
|
public Image Preview { get; private set; }
|
||||||
@@ -405,8 +405,15 @@ namespace SpineViewer.Spine
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SFML.Graphics.Drawable 接口实现
|
/// SFML.Graphics.Drawable 接口实现
|
||||||
|
/// <para>这个渲染实现绘制出来的像素将是预乘的, 当渲染的背景透明度是 1 时, 则等价于非预乘的结果, 即正常画面, 否则画面偏暗</para>
|
||||||
|
/// <para>可以用于 <see cref="SFML.Graphics.RenderWindow"/> 的渲染, 因为直接在窗口上绘制时窗口始终是不透明的</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states) { lock (_lock) draw(target, states); }
|
public void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states) { lock (_lock) draw(target, states); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 这个渲染实现绘制出来的像素将是预乘的, 当渲染的背景透明度是 1 时, 则等价于非预乘的结果, 即正常画面, 否则画面偏暗
|
||||||
|
/// <para>可以用于 <see cref="SFML.Graphics.RenderWindow"/> 的渲染, 因为直接在窗口上绘制时窗口始终是不透明的</para>
|
||||||
|
/// </summary>
|
||||||
protected abstract void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
protected abstract void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user