Direct3D 中的视频特效。第一部分





5.00/5 (11投票s)
本文展示了如何在 Direct3D 中使用像素着色器轻松创建各种视频特效。

引言
在这里,我修改了我之前文章 使用 Direct3D 视频渲染的 C# 中的 VMR9 分配器呈现器 中的示例,添加了使用 HLSL 的像素着色器效果进行场景渲染的支持。
背景
本文描述了如何构建支持视频播放应用程序的效果。阅读之前,您应该查看有关 GPU 处理管线 以及 HLSL 编程 的信息。 我在像素着色器上实现简单的效果,这些效果也可以在 WPF 或 XNA 应用程序中使用。 与之前的文章实现一样,这里我也使用 SlimDX。
实现场景呈现
在之前的文章中,我简化了将视频表面复制到后缓冲区,但在这里我们应该支持渲染视频。
自定义顶点
首先,我们需要声明用于绘制视频的顶点(顶点声明)
[ComVisible(false)]
[StructLayout(LayoutKind.Sequential)]
private struct CustomVertex
{
public float x, y, z, rhw;
public float tu, tv;
public float tu2, tv2;
public static CustomVertex Create(Vector3 _position, float rhw, float tu, float tv, float tu2, float tv2)
{
return Create(_position.X, _position.Y, _position.Z, rhw, tu, tv, tu2, tv2);
}
public static CustomVertex Create(float x, float y, float z, float rhw, float tu, float tv, float tu2, float tv2)
{
CustomVertex _vertex = new CustomVertex();
_vertex.x = x;
_vertex.y = y;
_vertex.z = z;
_vertex.rhw = rhw;
_vertex.tu = tu;
_vertex.tv = tv;
_vertex.tu2 = tu2;
_vertex.tv2 = tv2;
return _vertex;
}
public static VertexElement[] Decl
{
get
{
return new VertexElement[] {
new VertexElement(0, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.PositionTransformed,0),
new VertexElement(0, 16, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate,0),
new VertexElement(0, 24, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate,1),
VertexElement.VertexDeclarationEnd };
}
}
}
我们的顶点由坐标位置和源和目标纹理映射组成。顶点缓冲区初始化如下:
SurfaceDescription _desc = m_RenderTarget.Description;
{
m_VertexBuffer = new VertexBuffer(m_Device, 8 * Marshal.SizeOf(typeof(CustomVertex)), Usage.WriteOnly | Usage.Dynamic, VertexFormat.None, Pool.Default);
DataStream _stream = m_VertexBuffer.Lock(0, 8 * Marshal.SizeOf(typeof(CustomVertex)), LockFlags.None);
_stream.WriteRange(new[]
{
CustomVertex.Create(-0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f),
CustomVertex.Create((float)_desc.Width-0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f),
CustomVertex.Create(-0.5f, (float)_desc.Height-0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f),
CustomVertex.Create((float)_desc.Width-0.5f, (float)_desc.Height-0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f)
}
);
m_VertexBuffer.Unlock();
}
{
m_VertexDeclaration = new VertexDeclaration(m_Device, CustomVertex.Decl);
}
调整大小处理和交换链
由于我们使用窗口化渲染,因此我们需要在调整渲染窗口大小的同时调整渲染目标大小,否则在用户调整主窗口大小时,我们会丢失预览质量。 为了处理这个问题,我们使用交换链并在用户调整窗口大小时重新创建目标交换链。 首先,我们添加用于控制调整大小的处理程序
m_Control.Resize += new EventHandler(Control_Resize);
在该处理程序中,我们执行交换链重新创建
private void Control_Resize(object sender, EventArgs e)
{
lock (m_csLock)
{
if (m_Device != null)
{
if (m_RenderTarget != null)
{
m_RenderTarget.Dispose();
m_RenderTarget = null;
}
if (m_SwapChain != null)
{
m_SwapChain.Dispose();
m_SwapChain = null;
}
PresentParameters d3dpp = m_d3dpp.Clone();
d3dpp.BackBufferWidth = 0;
d3dpp.BackBufferHeight = 0;
d3dpp.DeviceWindowHandle = m_Control.Handle;
m_SwapChain = new SwapChain(m_Device, d3dpp);
m_RenderTarget = m_SwapChain.GetBackBuffer(0);
}
}
}
注意:我们使用 lock
语句来保证线程安全
渲染
渲染代码现在看起来会是这样
public void OnSurfaceReady(ref Surface _surface)
{
lock (m_csLock)
{
m_Device.SetRenderTarget(0, m_RenderTarget);
m_Device.Clear(ClearFlags.Target, Color.Blue, 1.0f, 0);
m_Device.BeginScene();
m_Device.SetRenderState(RenderState.DitherEnable, true);
m_Device.SetRenderState(RenderState.ZEnable, true);
m_Device.SetSamplerState(0, SamplerState.MagFilter, TextureFilter.Linear);
m_Device.SetSamplerState(0, SamplerState.MinFilter, TextureFilter.Linear);
m_Device.SetRenderState(RenderState.CullMode, 1);
m_Device.SetRenderState(RenderState.Lighting, false);
Texture _texture = _surface.GetContainer<Texture>();
m_Device.VertexDeclaration = m_VertexDeclaration;
m_Device.SetTexture(0, _texture);
m_Device.SetStreamSource(0, m_VertexBuffer, 0, Marshal.SizeOf(typeof(CustomVertex)));
m_Device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
m_Device.SetTexture(0, null);
_texture.Dispose();
m_Device.EndScene();
if (m_SwapChain != null) m_SwapChain.Present(Present.None); else m_Device.Present();
}
}
创建特效支持
在场景类中,我们添加变量来存储效果、技术和纹理参数处理程序。 这些变量我们在指定技术字符串的方法中初始化。
public void CreateEffect(string _technique)
{
EffectHandle hTexture = null;
EffectHandle hTechnique = null;
Effect _effect = null;
try
{
_effect = Effect.FromString(
m_Device,
EffectsPlayback.Properties.Resources.effects, ShaderFlags.None
);
if (string.IsNullOrEmpty(_technique))
{
hTechnique = _effect.GetTechnique(0);
}
else
{
hTechnique = _effect.GetTechnique(_technique);
}
int nIndex = 0;
while (true)
{
EffectHandle _handle = _effect.GetParameter(null, nIndex++);
if (_handle != null)
{
ParameterDescription _ParamDesc = _effect.GetParameterDescription(_handle);
if (_ParamDesc.Type == ParameterType.Texture && hTexture == null)
{
hTexture = _handle;
}
continue;
}
break;
}
}
catch
{
}
if (hTexture != null && hTechnique != null && _effect != null)
{
lock (m_csLock)
{
if (m_Effect != null) m_Effect.Dispose();
if (m_hTextureHandle != null) m_hTextureHandle.Dispose();
if (m_hTehniqueHandle != null) m_hTehniqueHandle.Dispose();
m_Effect = _effect;
m_hTextureHandle = hTexture;
m_hTehniqueHandle = hTechnique;
}
}
else
{
Sonic.COMHelper.ASSERT(false);
}
}
使用效果渲染会有点不同
public void OnSurfaceReady(ref Surface _surface)
{
lock (m_csLock)
{
m_Device.SetRenderTarget(0, m_RenderTarget);
m_Device.Clear(ClearFlags.Target, Color.Blue, 1.0f, 0);
m_Device.BeginScene();
m_Device.SetRenderState(RenderState.DitherEnable, true);
m_Device.SetRenderState(RenderState.ZEnable, true);
m_Device.SetSamplerState(0, SamplerState.MagFilter, TextureFilter.Linear);
m_Device.SetSamplerState(0, SamplerState.MinFilter, TextureFilter.Linear);
m_Device.SetRenderState(RenderState.CullMode, 1);
m_Device.SetRenderState(RenderState.Lighting, false);
Texture _texture = _surface.GetContainer<Texture>();
m_Effect.Technique = m_hTehniqueHandle;
m_Device.VertexDeclaration = m_VertexDeclaration;
int nPasses = m_Effect.Begin();
for (int i = 0; i < nPasses; i++)
{
m_Effect.SetTexture(m_hTextureHandle, _texture);
m_Effect.BeginPass(i);
m_Device.SetStreamSource(0, m_VertexBuffer, 0, Marshal.SizeOf(typeof(CustomVertex)));
m_Device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
m_Effect.EndPass();
}
m_Effect.End();
_texture.Dispose();
m_Device.EndScene();
if (m_SwapChain != null) m_SwapChain.Present(Present.None); else m_Device.Present();
}
}
现在让我们编写一个简单的像素着色器。 首先声明效果的常见内容
texture _texture;
sampler2D _sampler =
sampler_state
{
Texture = <_texture>;
AddressU = Clamp;
AddressV = Clamp;
MinFilter = Point;
MagFilter = Linear;
MipFilter = Linear;
};
特效技术
technique Simple_Technique
{
pass p0
{
VertexShader = null;
PixelShader = compile ps_2_0 Simple_Proc();
}
}
在这里,我们声明一项具有一个通道的技术。 该通道只有一个带有 Simple_Proc
函数的像素着色器,它使用 PS 2.0 语义。 示例效果
float4 Simple_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
return tex2D( _sampler, _pos);
}
在这种效果中,我们什么也不做,只是传递像素而不进行更改。
特效概览
示例应用程序中的效果可以在运行时更改。 以下是已实现的效果列表以及它们的外观
灰度
这是通过 基本公式 简单实现此效果的方法。
const float4 g_cf4Luminance = { 0.2125f, 0.7154f, 0.0721f, 0.0f };
float4 GrayScale_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
return dot((float4)tex2D( _sampler, _pos), g_cf4Luminance);
}
锐化
为了实现这种效果,我们用附近的像素调整当前像素。
float4 Sharp_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
float4 _color = tex2D( _sampler, _pos);
_color -= tex2D( _sampler, _pos+0.001)*3.0f;
_color += tex2D( _sampler, _pos-0.001)*3.0f;
return _color;
}
棕褐色
为了制作棕褐色效果,我们获取灰度像素并调整每个颜色分量
const float4 g_cf4Luminance = { 0.2125f, 0.7154f, 0.0721f, 0.0f };
const float g_cfSepiaDepth = 0.15;
float4 Sepia_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
float4 _color = dot( (float4)tex2D( _sampler, _pos ), g_cf4Luminance );
_color.xyz += float3(g_cfSepiaDepth * 2,g_cfSepiaDepth,g_cfSepiaDepth / 2);
return _color;
}
反转
也是一个简单的效果。 我们只是反转颜色分量值
float4 Invert_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
return 1.0f - tex2D( _sampler, _pos);
}
浮雕
这是我们如何获得浮雕图像
float4 Emboss_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
float4 _color;
_color.a = 1.0f;
_color.rgb = 0.5f;
_color -= tex2D( _sampler, _pos.xy-0.001)*2.0f;
_color += tex2D( _sampler, _pos.xy+0.001)*2.0f;
_color.rgb = (_color.r+_color.g+_color.b)/3.0f;
return _color;
}
失去焦点
模糊实现算法之一。
float4 Blur_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
const int nSamples = 13;
const float2 cSamples[nSamples] = {
0.000000, 0.000000,
-0.326212, -0.405805,
-0.840144, -0.073580,
-0.695914, 0.457137,
-0.203345, 0.620716,
0.962340, -0.194983,
0.473434, -0.480026,
0.519456, 0.767022,
0.185461, -0.893124,
0.507431, 0.064425,
0.896420, 0.412458,
-0.321940, -0.932615,
-0.791559, -0.597705,
};
float4 sum = 0;
for (int i = 0; i < nSamples - 1; i++)
{
sum += tex2D(_sampler, _pos + 0.025 * cSamples[i]);
}
return sum / nSamples;
}
海报化
海报化 效果实现。
float4 Posterize_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
const float cColors = 8.0f;
const float cGamma = 0.6f;
float4 _color = tex2D(_sampler, _pos);
float3 tc = _color.xyz;
tc = pow(tc, cGamma);
tc = tc * cColors;
tc = floor(tc);
tc = tc / cColors;
tc = pow(tc,1.0/cGamma);
return float4(tc,_color.w);
}
亮度
亮度 调整效果。
float4 Brightness_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
const float cBrightness = 2.0;
float4 _color = tex2D( _sampler, _pos);
_color.xyz *= cBrightness;
return _color;
}
红-灰
执行灰度调整并调整红色
float4 Red_Proc(float2 _pos: TEXCOORD0) : COLOR0
{
const float cRedCutOff = 0.5;
const float cRedBrightness = 1.2;
float4 _color = tex2D( _sampler, _pos);
float4 _result = dot( _color, g_cf4Luminance );
if (_color.r * 2 - cRedCutOff > _color.g + _color.b)
{
_result.r = _color.r * cRedBrightness;
}
return _result;
}
历史
2012.11.19 - 初始版本。