65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2012年11月20日

CPOL

2分钟阅读

viewsIcon

41265

downloadIcon

3432

本文展示了如何在 Direct3D 中使用像素着色器轻松创建各种视频特效。

引言

在这里,我修改了我之前文章 使用 Direct3D 视频渲染的 C# 中的 VMR9 分配器呈现器 中的示例,添加了使用 HLSL 的像素着色器效果进行场景渲染的支持。

背景

本文描述了如何构建支持视频播放应用程序的效果。阅读之前,您应该查看有关 GPU 处理管线 以及 HLSL 编程 的信息。 我在像素着色器上实现简单的效果,这些效果也可以在 WPFXNA 应用程序中使用。 与之前的文章实现一样,这里我也使用 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 - 初始版本。

© . All rights reserved.