DirectX 3D 简单渲染流程

本文详细介绍使用DirectX渲染一个简单三角形的过程,包括创建窗口、初始化DirectX、创建各种缓冲区及着色器,并最终实现绘制与呈现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阅读了 龙书网页版前六章,通过渲染一个三角形,总结渲染的最简步骤。

  1. 创建窗口。
  2. 创建设备和上下文。
  3. 创建交换链和渲染视图。
  4. 创建顶点着色器。
  5. 创建并绑定输入布局。
  6. 创建片段着色器。
  7. 创建顶点、索引、常量缓冲区,并填充和绑定。
  8. 设置图元拓扑(图元类型)。
  9. 将着色器和常量缓冲区绑定到渲染管线。
  10. 清空渲染视图。
  11. 绘制
  12. 呈现
  13. 转到步骤10.

以下是每个步骤的代码
准备工作
需要包含的头文件

#include <windows.h>
#include <wrl/client.h> // Comptr智能指针
#include <d3d11_1.h>
#include <d3d11_2.h>
#include <string>
#include <DirectXMath.h>
#include <d3dcompiler.h>

// 添加DirectX11所有要引用的库
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "D3DCompiler.lib")
#pragma comment(lib, "winmm.lib")

整个流程所需要的变量和结构体

template <class T>
using ComPtr = Microsoft::WRL::ComPtr<T>; // 微软的智能指针

using namespace DirectX;
// 需要的所有变量
int winX = 0;
int winY = 0;
int winWidth = 1920;
int winHeight = 1080;


struct VertexPosColor
{
    DirectX::XMFLOAT3 pos;
    DirectX::XMFLOAT4 color;
    static const D3D11_INPUT_ELEMENT_DESC inputLayout[2];
};

const D3D11_INPUT_ELEMENT_DESC VertexPosColor::inputLayout[2] = {
    //-|-语义名字-|--|语义索引-|----|---------数据格式-----------|--|-输入槽索引[0-15]-|---|-字节对齐偏移量-|---|----输入槽类(顶点or索引)-----|---|-多实例渲染个数-|- 
    {  "POSITION",       0,           DXGI_FORMAT_R32G32B32_FLOAT,          0,                   0,                D3D11_INPUT_PER_VERTEX_DATA,          0     },
    {  "COLOR",          0,           DXGI_FORMAT_R32G32B32_FLOAT,          0,                   12,               D3D11_INPUT_PER_VERTEX_DAT
    struct VSConstantBuffer  //顶点常量缓冲区
{
    XMMATRIX world;
    XMMATRIX view;
    XMMATRIX proj;
};

struct PSConstantBuffer
{
    XMFLOAT4 color;
    XMMATRIX proj;
};

HWND ghMainWnd = 0;   // 窗口句柄


ComPtr<ID3D11Device> pd3dDevice = nullptr;                    // D3D11设备
ComPtr<ID3D11DeviceContext> pd3dDeviceContext = nullptr;      // D3D11设备上下文

DXGI_SWAP_CHAIN_DESC sd;
ComPtr<IDXGISwapChain> pSwapChain = nullptr;                  // D3D11交换链
ComPtr<IDXGIFactory1> dxgiFactory = nullptr;

//常用资源
ComPtr<ID3D11Texture2D> m_pDepthStencilBuffer = nullptr;        // 深度模板缓冲区
ComPtr<ID3D11RenderTargetView> m_pRenderTargetView = nullptr;   // 渲染目标视图
ComPtr<ID3D11DepthStencilView> m_pDepthStencilView = nullptr;   // 深度模板视图
D3D11_VIEWPORT m_ScreenViewport;                                // 视口

//着色器资源

ComPtr<ID3DBlob> vertexBlob;  // 顶点着色器
ComPtr<ID3DBlob> pixBlob;     //像素着色器


ComPtr<ID3D11InputLayout> pVertexLayout = nullptr;     // 顶点输入布局
ComPtr<ID3D11Buffer> pVertexBuffer = nullptr;          // 顶点缓冲区
ComPtr<ID3D11VertexShader> pVertexShader = nullptr;    // 顶点着色器
ComPtr<ID3D11PixelShader> pPixelShader = nullptr;      // 像素着色器
ComPtr<ID3D11Buffer> pIndexBuffer = nullptr;           // 索引缓冲区

ComPtr<ID3D11Buffer> pVSConstBuffer = nullptr;         // 顶点常量缓冲区
ComPtr<ID3D11Buffer> pPSConstBuffer = nullptr;         // 顶点常量缓冲区
VSConstantBuffer VSCBuffer;                           // 用于修改顶点GPU常量缓冲区的变量
PSConstantBuffer PSCBuffer;                           // 用于修改顶点GPU常量缓冲区的变量

创建窗口

/*************************************************创建窗口*************************************************/
/* 注意
* 1. 必须设置窗口事件处理回调函数 LRESULT  CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
* 2. 窗口的信息可以在 WNDCLASS 中设置,比如窗口的图标,标题等。
* 3. 调用CreateWindow创建窗口。
* 4. 创建完窗口,可以得到一个窗口句柄(HWND ghMainWnd),后面创建交换链会用到这个窗口句柄。
*/
// 窗口事件处理回调函数
LRESULT  CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 

{
    switch (msg)
    {
    case WM_LBUTTONDOWN:
        MessageBox(0, L"Hello,world!", L"Hello", MB_OK);
        return 0;
    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE)
            DestroyWindow(ghMainWnd);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

//创建窗口函数
bool InitWindowsApp(HINSTANCE instanceHandle, int show) 
{
    WNDCLASS wc;
    wc.style = CS_HREDRAW | CS_VREDRAW;  // 水平或者垂直方向发生变化时,重新绘制 redraw
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = instanceHandle;
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hCursor = LoadCursor(0,IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = 0;
    wc.lpszClassName = L"BasicWndClass";
   if (!RegisterClass(&wc))
    {
        MessageBox(0, L"RegisterClass FAILED", 0, 0);
        return false;
    }

    ghMainWnd = CreateWindow(
        L"BasicWndClass",
        L"Win32Basic",
        WS_OVERLAPPEDWINDOW,
        winX,
        winY,
        winWidth,
        winHeight,
        0,
        0,
        instanceHandle,
        0
    );
    if (ghMainWnd == 0)
    {
        MessageBox(0, L"CreateWindow FAILED", 0, 0);
        return false;
    }
    ShowWindow(ghMainWnd,show);
    UpdateWindow(ghMainWnd);
    return true;
}

DirectX 初始化

  • 创建设备和设备上下文
  • 创建Factory
  • 填充交换链描述符
  • 创建交换链
  • 创建渲染目标视图(窗口大小发生变化时)
  • 设置视口变换

创建设备和设备上下文

// 创建设备和设备上下文
bool createDirectDevice(){
    UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    // 驱动类型数组
    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,   //硬件驱动
        //D3D_DRIVER_TYPE_WARP,       //WARP驱动
        D3D_DRIVER_TYPE_REFERENCE,  //软件驱动
    };
    UINT numDriverTypes = ARRAYSIZE(driverTypes);

      // 特性等级数组
    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
    };
    UINT numFeatureLevels = ARRAYSIZE(featureLevels);

    D3D_FEATURE_LEVEL featureLevel;
    D3D_DRIVER_TYPE d3dDriverType;

    HRESULT hr;
        for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
    {
        d3dDriverType = driverTypes[driverTypeIndex];
        hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
            D3D11_SDK_VERSION, pd3dDevice.GetAddressOf(), &featureLevel, pd3dDeviceContext.GetAddressOf());

        // 如果你的系统不支持Direct3D 11.1的API,D3D11CreateDevice会立即停止特性数组的轮询并返回E_INVALIDARG
              if (hr == E_INVALIDARG)
        {
            // Direct3D 11.0 的API不承认D3D_FEATURE_LEVEL_11_1,所以我们需要尝试特性等级11.0以及以下的版本
            hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,
                D3D11_SDK_VERSION, pd3dDevice.GetAddressOf(), &featureLevel, pd3dDeviceContext.GetAddressOf());
        }

        if (SUCCEEDED(hr))
            break;
       if (FAILED(hr))
    {
        MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
        return false;
    }
        // 检测是否支持特性等级11.0或11.1
    if (featureLevel != D3D_FEATURE_LEVEL_11_0 && featureLevel != D3D_FEATURE_LEVEL_11_1)
    {
        MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);
        return false;
    }
    return hr == S_OK;

创建Factroy

// 创建Factroy
bool createFactroy()
{
    //
    HRESULT hr;
    ComPtr<IDXGIDevice> dxgiDevice = nullptr;
    ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
    hr =  pd3dDevice.As(&dxgiDevice);
    dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf());
    dxgiAdapter->GetParent(__uuidof(IDXGIFactory1),reinterpret_cast<void**>(dxgiFactory.GetAddressOf()));

    return hr == S_OK;
}

填充交换链描述符

// 填充交换链描述符
bool initSwapChainDesc()
{

    HRESULT hr;

    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 1;
    sd.BufferDesc.Width = winWidth;
    sd.BufferDesc.Height = winHeight;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    sd.SampleDe

创建交换链

// 创建交换链
bool createSwapChain()
{
    HRESULT hr;
    hr = dxgiFactory->CreateSwapChain(pd3dDevice.Get(),&sd,pSwapChain.GetAddressOf());
    return hr == S_OK;
}

创建渲染目标视图,设置视口大小

// 创建渲染目标视图,设置视口大小(窗口大小发生变化时重新创建)
void resizeWin()
{
    assert(pd3dDevice);
    assert(pd3dDeviceContext);
    assert(pSwapChain);

    m_pRenderTargetView.Reset();

    HRESULT hr;

    ComPtr<ID3D11Texture2D> backBuffer;
    hr = pSwapChain->ResizeBuffers(1,winWidth,winHeight,DXGI_FORMAT_R8G8B8A8_UNORM,0);
    hr = pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf()));
      hr = pd3dDevice->CreateRenderTargetView(backBuffer.Get(),nullptr,m_pRenderTargetView.GetAddressOf());

    backBuffer.Reset();

    pd3dDeviceContext->OMSetRenderTargets(1,m_pRenderTargetView.GetAddressOf(),nullptr);
    m_ScreenViewport.TopLeftX = 0.0f;
    m_ScreenViewport.TopLeftY = 0.0f;
    m_ScreenViewport.Width = static_cast<float>(winWidth);
    m_ScreenViewport.Height = static_cast<float>(winHeight);
    m_ScreenViewport.MinDepth = 0.0f;
    m_ScreenViewport.MaxDepth = 1.0f;
    pd3dDeviceContext->RSSetViewports(1,&m_ScreenViewport);

创建着色器

  • 编译顶点着色器到Blob,同时写入到cso文件,着色器若未修改,下次直接读取,不用编译。
  • 使用设备创建顶点着色器。
  • 编译像素着色器到Blob,同时写入到cso文件,着色器若未修改,下次直接读取,不用编译。
  • 使用设备创建像素着色器。
/*************************************************创建着色器*************************************************/
bool createShader()
{
    HRESULT hr;
    DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
    // 设置 D3DCOMPILE_DEBUG 标志用于获取着色器调试信息。该标志可以提升调试体验,
    // 但仍然允许着色器进行优化操作
    dwShaderFlags |= D3DCOMPILE_DEBUG;

    // 在Debug环境下禁用优化以避免出现一些不合理的情况
    dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
   //创建顶点着色器
    {
        hr = D3DCompileFromFile(L"C:\\Users\\MultiMediaServer\\Desktop\\learn\\\D3D11Learn\\HLSL\\D3D11Learn_VS.hlsl",
            nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "VS", "vs_5_0",
            dwShaderFlags, 0, vertexBlob.ReleaseAndGetAddressOf(), nullptr);

        // 若指定了输出文件名,则将着色器二进制信息输出
        hr = D3DWriteBlobToFile(vertexBlob.Get(), L"HLSL\\D3D11Learn_VS.cso", FALSE);
 		hr = pd3dDevice->CreateVertexShader(vertexBlob->GetBufferPointer(), vertexBlob->GetBufferSize(), nullptr, pVertexShader.GetAddressOf());
 	}
 	{
        hr = D3DCompileFromFile(L"C:\\Users\\MultiMediaServer\\Desktop\\learn\\\D3D11Learn\\HLSL\\D3D11Learn_PS.hlsl",
            nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "PS", "ps_5_0",
            dwShaderFlags, 0, pixBlob.ReleaseAndGetAddressOf(), nullptr);
                   // 若指定了输出文件名,则将着色器二进制信息输出
        hr = D3DWriteBlobToFile(pixBlob.Get(), L"HLSL\\D3D11Learn_PS.cso", FALSE);
        hr = pd3dDevice->CreatePixelShader(pixBlob->GetBufferPointer(),pixBlob->GetBufferSize(),nullptr,pPixelShader.GetAddressOf());
    }
       return hr == S_OK;
}

填充缓冲区并绑定到渲染管线

定义顶点数据结构体

创建顶点缓冲区、填充并绑定

创建索引缓冲区、填充并绑定

创建顶点常量缓冲区、填充并绑定

创建像素常量缓冲区、填充并绑定

设置图元类型

创建并绑定输入布局(也可以在创建完顶点着色器后立马创建)

将着色器和相应阶段的常量缓冲区绑定到渲染管线。

bool initResource()
{
    HRESULT hr;
    // 设置三角形顶点和颜色
    VertexPosColor vertices[] =
    {
        { XMFLOAT3(0.0f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
        { XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
    };
    
    //设置顶点缓冲区描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd,sizeof(vbd));
    vbd.Usage = D3D11_USAGE_IMMUTABLE;
    vbd.ByteWidth = sizeof(vertices);
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    
    // 创建顶点缓冲区
    D3D11_SUBRESOURCE_DATA initData;
    ZeroMemory(&initData, sizeof(initData));
    initData.pSysMem = vertices;
    hr = pd3dDevice->CreateBuffer(&vbd,&initData, pVertexBuffer.GetAddressOf());
    
    //绑定顶点缓冲区
    UINT stride = sizeof(VertexPosColor);   // 跨越字节数
    UINT offset = 0;                        // 起始偏移量
    pd3dDeviceContext->IASetVertexBuffers(0, 1, pVertexBuffer.GetAddressOf(), &stride, &offset);

   DWORD indices[] = {
        0, 1, 2,0
    };
    //设置索引缓冲区描述
    D3D11_BUFFER_DESC ibd;
    ZeroMemory(&ibd, sizeof(ibd));
    ibd.Usage = D3D11_USAGE_IMMUTABLE;
    ibd.ByteWidth = sizeof(indices);
    ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    ibd.CPUAccessFlags = 0;
    initData.pSysMem = indices; // D3D11_SUBRESOURCE_DATA 
    
    // 新建索引缓冲区
    hr = pd3dDevice->CreateBuffer(&ibd, &initData, pIndexBuffer.GetAddressOf());
    pd3dDeviceContext->IASetIndexBuffer(pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT,0);
    
	/****************************常量缓冲区***********************************/
    //设置顶点常量缓冲区描述
    D3D11_BUFFER_DESC cbd;
    ZeroMemory(&cbd, sizeof(cbd));
    cbd.Usage = D3D11_USAGE_DYNAMIC;
    cbd.ByteWidth = sizeof(VSConstantBuffer);
    cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    
    //创建顶点常量缓冲区
    hr = pd3dDevice->CreateBuffer(&cbd,nullptr,pVSConstBuffer.GetAddressOf());
    VSCBuffer.world = XMMatrixTranspose(XMMatrixTranslation(0.4, 0.0, 0.0));
    VSCBuffer.view = XMMatrixIdentity();
    VSCBuffer.proj = XMMatrixIdentity();

    //填充顶点常量缓冲区
  	VSCBuffer.world = XMMatrixTranspose(XMMatrixTranslation(0.4, 0.0, 0.0));
    VSCBuffer.view = XMMatrixIdentity();
    VSCBuffer.proj = XMMatrixIdentity();

    D3D11_MAPPED_SUBRESOURCE mappedData;
    hr = pd3dDeviceContext->Map(pVSConstBuffer.Get(),0, D3D11_MAP_WRITE_DISCARD,0,&mappedData);
    memcpy_s(mappedData.pData, sizeof(VSCBuffer), &VSCBuffer, sizeof(VSCBuffer));
    pd3dDeviceContext->Unmap(pVSConstBuffer.Get(), 0);

     //设置像素常量缓冲区描述
    D3D11_BUFFER_DESC cbd1;
    ZeroMemory(&cbd1, sizeof(cbd1));
    cbd1.Usage = D3D11_USAGE_DYNAMIC;
    cbd1.ByteWidth = sizeof(PSConstantBuffer);
    cbd1.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbd1.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    //创建像素常量缓冲区
    hr = pd3dDevice->CreateBuffer(&cbd,nullptr,pPSConstBuffer.GetAddressOf());

    
    //填充像素常量缓冲区
    PSCBuffer.color = XMFLOAT4(1.0, 0.0, 0.0, 1.0);
    PSCBuffer.proj = XMMatrixIdentity();
    D3D11_MAPPED_SUBRESOURCE mappedData1;
    hr = pd3dDeviceContext->Map(pPSConstBuffer.Get(),0, D3D11_MAP_WRITE_DISCARD,0,&mappedData1);
    memcpy_s(mappedData1.pData, sizeof(PSCBuffer), &PSCBuffer, sizeof(PSCBuffer));
    pd3dDeviceContext->Unmap(pPSConstBuffer.Get(), 0);
    
    //创建输入布局并绑定
     pd3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),  vertexBlob->GetBufferPointer(), vertexBlob->GetBufferSize(), pVertexLayout.GetAddressOf());
    pd3dDeviceContext->IASetInputLayout(pVertexLayout.Get());
    
    //设置图元类型
    pd3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);

    // 将着色器绑定到渲染管线
    pd3dDeviceContext->VSSetShader(pVertexShader.Get(), nullptr, 0);
    pd3dDeviceContext->VSSetConstantBuffers(0, 1, pVSConstBuffer.GetAddressOf());
    pd3dDeviceContext->PSSetShader(pPixelShader.Get(), nullptr, 0);
    pd3dDeviceContext->PSSetConstantBuffers(1, 1, pPSConstBuffer.GetAddressOf());
    
    return hr == S_OK;
}

渲染

int Run()
{
    MSG msg = { 0 };
    BOOL bRet = 1;
    assert(pd3dDeviceContext);
    assert(pSwapChain);
    while ((bRet = GetMessage(&msg,0,0,0)) != 0)
    {
        static float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
        pd3dDeviceContext->ClearRenderTargetView(m_pRenderTargetView.Get(), black);
        pd3dDeviceContext->DrawIndexed(3, 0, 0);
        pSwapChain->Present(1, 0);
    }
	return (int)msg.wParam;
}

主函数调用所有流程

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow)
{
    if (!InitWindowsApp(hInstance, nCmdShow))
        return 0;

    bool b = createDirectDevice();
    b = createFactroy();
    b = initSwapChainDesc();
    b = createSwapChain();
    resizeWin();
    b = createShader();
    b = initResource();
    return Run();
}

着色器代码

头文件

cbuffer VSConstantBuffer : register(b0)
{
    matrix g_World;
    matrix g_View;
    matrix g_Proj;
}

cbuffer PSConstantBuffer : register(b1)
{
    float4 g_color;
    matrix temp;
}
struct VertexIn
{
    float3 pos : POSITION;
};
struct VertexOut
{
    float4 pos : SV_POSITION;
};

像素着色器

#include "D3D11Learn.hlsli"


float4 PS() : SV_Target
{
    return g_color;
}

// 顶点着色器

#include "D3D11Learn.hlsli"
VertexOut VS(VertexIn vIn)
{
    VertexOut vOut;
    vOut.pos = mul(float4(vIn.pos, 1.0f), g_World);
    vOut.pos = mul(vOut.pos, g_View);
    vOut.pos = mul(vOut.pos, g_Proj);
    //vOut.pos = float4(vIn.pos, 1.0f);
    return vOut;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xhh-cy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值