D3D11 视频渲染中的变换(平移、缩放、裁剪、镜像、旋转)

memetao 于 2023-12-24 发布

需求

视频渲染通常会有如下功能:

输入数据

一个渲染器的输入源目前看上去会有两种:

因此, 输入格式会有: YUV\RGBA(分为内存和纹理、8Bit和10Bit)

顶点描述

通常, 画面是一个矩形, 也就是需要4个顶点来描述。指定两个属性: 1. 顶点的位置 2. 要采样的像素坐标

struct VERTEX {
    DirectX::XMFLOAT3 pos;
    DirectX::XMFLOAT2 tex;
    static const D3D11_INPUT_ELEMENT_DESC input_desc[2];
};

const D3D11_INPUT_ELEMENT_DESC VERTEX::input_desc[2] = {
    {"SV_POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD",    0, DXGI_FORMAT_R32G32_FLOAT,    0, 12,  D3D11_INPUT_PER_VERTEX_DATA, 0},
};
//这里jekll不知道为什么会解析错误, 实际上需要多个花括号
static const DirectX::XMVECTORF32 points[4] = {
    {0, 0, 0, 0},
    {1, 0, 0, 0},
    {0, 1, 0, 0},
    {1, 1, 0, 0},
};

vertices

顶点处理

需要的变换有: 镜像、旋转、缩放、裁剪、平移。 这5个效果都可以通过改变顶点的”位置属性”来做到。

镜像

mirror

注意: 我们交换的是UV值.

enum class Mirror : uint8_t {
    None = 0,
    FlipHorizontally = 1,
    FlipVertically = 2,
    FlipBoth = FlipHorizontally | FlipVertically,
};

const uint8_t mirror = static_cast<uint8_t>(mirror_mode) & 3u;
for (size_t i = 0; i <4 ;i ++) {
    auto uv = points[i ^ mirror].uv;
}

旋转

旋转复杂一些,需要用到高中的数学知识: 点绕着点旋转变换。

rotate

为了不破坏图像对于坐标系的相对位置关系,可以选择点0作为旋转的中心点。

在平面坐标上,任意点P(x1,y1),绕一个坐标点Q(x2,y2)逆时针旋转θ角度后,新的坐标设为(x, y)的计算公式:

x= (x1 - x2) * cos(θ) - (y1 - y2) * sin(θ) + x2 ;
y= (x1 - x2) * sin(θ) + (y1 - y2) * cos(θ) + y2 ;

这个公式可以整理成矩阵运算。

缩放

类似于旋转,我们使用O点作为缩放后的图像的中心点:

scaling

在平面坐标上,任意点P(x1,y1),以坐标点Q(x2,y2)缩放a倍之后,新的坐标设为(x, y)的计算公式:

x= (x1 - x2) * cos(θ) - (y1 - y2) * sin(θ) + x2 ;
y= (x1 - x2) * sin(θ) + (y1 - y2) * cos(θ) + y2 ;

注意: 这个公式当中并没有要求P和Q的初始位置(任意位置都可以)

同样,这个公式可以抽象成矩阵运算。

裁剪

裁剪就简单了, 计算出要裁剪的区域即可。

// 输入参数 SIZE src_dimension, RECT source_region
const DirectX::XMVECTOR source = LoadRect(&source_region);
const DirectX::XMVECTOR source_size = DirectX::XMVectorSwizzle<2, 3, 2, 3>(source);
const DirectX::XMVECTOR dest_size = LoadSize(dst_dimension);
const DirectX::XMVECTOR inverse_source_size =
    DirectX::XMVectorReciprocal(LoadSize(src_dimension));
        const auto source_tex = DirectX::XMVectorMultiply(source, inverse_source_size);
const auto source_size_tex = DirectX::XMVectorMultiply(source_size, inverse_source_size);
   for (size_t i = 0; i < 4; i++) {
     auto P1 = DirectX::XMVectorMultiplyAdd(points[i], source_size, source);
     // 点P1就是新的位置
   }

平移

scaling

在平面坐标上,任意点P(x1,y1),沿向量(a,b)平移, 计算公式:

x= x + a;
y= y + b;

注意: 我们描述的平移向量(比如图像的原中心点和目标中心点)

END

最后, 以一个通用的函数结尾:

// 在src_dimension大小的图形中, 对source_region区域
// * 旋转rotation
// * 做镜像
// * 缩放到dst_dimension
// * 处理后的图像要以center点中心对称
// 将处理的顶点存在vertices中
static void renderVertices(SIZE src_dimension, RECT source_region, int rotation,
                           Mirror mirror_mode, SIZE dst_dimension, POINT center,
                           VERTEX* vertices) {
     //这里jekll不知道为什么会解析错误, 实际上需要多个花括号
    static const DirectX::XMVECTORF32 points[4] = {
        {0, 0, 0, 0},
        {1, 0, 0, 0},
        {0, 1, 0, 0},
        {1, 1, 0, 0},
    };
    const uint8_t mirror = static_cast<uint8_t>(mirror_mode) & 3u;
    const DirectX::XMVECTOR source = LoadRect(&source_region);
    const DirectX::XMVECTOR source_size = DirectX::XMVectorSwizzle<2, 3, 2, 3>(source);
    const DirectX::XMVECTOR dest_size = LoadSize(dst_dimension);
    const DirectX::XMVECTOR inverse_source_size =
        DirectX::XMVectorReciprocal(LoadSize(src_dimension));
    const DirectX::XMVECTOR P2 = LoadMiddlePoint(source);
    const DirectX::XMVECTOR C = LoadPoint(center);
    // texture coordinate format.
    const auto source_tex = DirectX::XMVectorMultiply(source, inverse_source_size);
    const auto source_size_tex = DirectX::XMVectorMultiply(source_size, inverse_source_size);

    DirectX::XMVECTOR rotation_matrix1;
    DirectX::XMVECTOR rotation_matrix2;
    if (rotation != 0) {
        float sin = 0.f, cos = 0.f;
        DirectX::XMScalarSinCos(&sin, &cos, rotation / 360.f * DirectX::XM_2PI);
        const auto sinV = DirectX::XMLoadFloat(&sin);
        const auto cosV = DirectX::XMLoadFloat(&cos);
        rotation_matrix1 = DirectX::XMVectorMergeXY(cosV, sinV);
        rotation_matrix2 = DirectX::XMVectorMergeXY(DirectX::XMVectorNegate(sinV), cosV);
    }
    else {
        rotation_matrix1 = DirectX::g_XMIdentityR0;
        rotation_matrix2 = DirectX::g_XMIdentityR1;
    }
    const auto scale_matrix1 = DirectX::XMVectorDivide(dest_size, source_size);
    const auto scale_matrix2 = DirectX::XMVectorSubtract(
        DirectX::g_XMOne, DirectX::XMVectorDivide(dest_size, source_size));
    const auto move =
        DirectX::XMVectorPermute<0, 1, 4, 5>(DirectX::XMVectorSubtract(C, P2), DirectX::g_XMZero);
    for (size_t i = 0; i < 4; i++) {
        auto P1 = DirectX::XMVectorMultiplyAdd(points[i], source_size, source);
        P1 = DirectX::XMVectorAdd(DirectX::XMVectorMultiply(P2, scale_matrix2),
                                  DirectX::XMVectorMultiply(P1, scale_matrix1));

        const auto V1 = DirectX::XMVectorSubtract(P1, P2);
        auto pos = DirectX::XMVectorMultiplyAdd(DirectX::XMVectorSplatX(V1), rotation_matrix1, P2);
        pos = DirectX::XMVectorPermute<0, 1, 7, 7>(pos, DirectX::g_XMZero);
        pos = DirectX::XMVectorMultiplyAdd(DirectX::XMVectorSplatY(V1), rotation_matrix2, pos);
        pos = DirectX::XMVectorAdd(pos, move);
        DirectX::XMStoreFloat4(reinterpret_cast<DirectX::XMFLOAT4*>(&((VERTEX*)vertices)[i].pos),
                               pos);
        const auto uv =
            DirectX::XMVectorMultiplyAdd(points[i ^ mirror], source_size_tex, source_tex);
        DirectX::XMStoreFloat2(&((VERTEX*)vertices)[i].tex, uv);
    }
}

最后一个for循环将平移、缩放、旋转、镜像结合在了一起, 我尽量写的清晰。

HDR

太复杂了,单独开篇。