You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为通过SwapChain获取的ID3D11Texture2D添加模糊效果以用于游戏GUI?

嘿,我来帮你搞定这个D3D11的模糊效果和GUI集成的问题。你已经能拿到后台缓冲了,接下来其实分几个关键步骤,一步步来就没问题:

第一步:创建可用于模糊处理的临时纹理(Render Target)

注意:后台缓冲(backBuffer)通常是不可写的,所以绝对不能直接在上面做模糊渲染,必须先把内容拷贝到我们自己创建的可读写纹理里

首先获取后台缓冲的描述,然后基于它创建一个支持渲染目标和着色器资源绑定的临时纹理:

ID3D11Device* device = nullptr;
SwapChain->GetDevice(__uuidof(ID3D11Device), (void**)&device);
ID3D11DeviceContext* context = nullptr;
device->GetImmediateContext(&context);

// 获取后台缓冲的描述
D3D11_TEXTURE2D_DESC backBufferDesc;
backBuffer->GetDesc(&backBufferDesc);

// 准备临时纹理的描述:修改绑定标志,支持渲染目标和着色器资源
D3D11_TEXTURE2D_DESC blurTempDesc = backBufferDesc;
blurTempDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
blurTempDesc.MiscFlags = 0;

// 如果后台缓冲是MSAA(多重采样)的,需要改成非MSAA,否则后续模糊无法正确采样
if (backBufferDesc.SampleDesc.Count > 1) {
    blurTempDesc.SampleDesc.Count = 1;
    blurTempDesc.SampleDesc.Quality = 0;
}

// 创建临时纹理、渲染目标视图(RTV)和着色器资源视图(SRV)
ID3D11Texture2D* blurTexA = nullptr;
ID3D11RenderTargetView* blurRTV_A = nullptr;
ID3D11ShaderResourceView* blurSRV_A = nullptr;

device->CreateTexture2D(&blurTempDesc, nullptr, &blurTexA);
device->CreateRenderTargetView(blurTexA, nullptr, &blurRTV_A);
device->CreateShaderResourceView(blurTexA, nullptr, &blurSRV_A);

// 再创建一个同样的纹理用于"乒乓渲染"(避免在同一纹理上同时读写)
ID3D11Texture2D* blurTexB = nullptr;
ID3D11RenderTargetView* blurRTV_B = nullptr;
ID3D11ShaderResourceView* blurSRV_B = nullptr;

device->CreateTexture2D(&blurTempDesc, nullptr, &blurTexB);
device->CreateRenderTargetView(blurTexB, nullptr, &blurRTV_B);
device->CreateShaderResourceView(blurTexB, nullptr, &blurSRV_B);
第二步:编写高斯模糊的HLSL着色器

高斯模糊是最常用的模糊效果,通常分水平和垂直两次渲染(比单次模糊效果更好,性能也更高)。你需要准备三个着色器:全屏Quad的顶点着色器、水平模糊像素着色器、垂直模糊像素着色器。

// 顶点着色器:输出全屏Quad的位置和UV
struct VSInput {
    float3 pos : POSITION;
    float2 uv : TEXCOORD0;
};

struct PSInput {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0;
};

PSInput VS(VSInput input) {
    PSInput output;
    output.pos = float4(input.pos, 1.0f);
    output.uv = input.uv;
    return output;
}

// 模糊常量缓冲区:传递纹理尺寸、模糊半径、高斯权重
cbuffer BlurConstants : register(b0) {
    float2 texelSize; // 单个像素的大小:(1.0f/宽度, 1.0f/高度)
    int blurRadius;
    float weights[11]; // 预计算的高斯权重数组(这里示例半径为5,共11个权重)
}

Texture2D inputTex : register(t0);
SamplerState linearSampler : register(s0);

// 水平模糊像素着色器
float4 PS_HorizontalBlur(PSInput input) : SV_TARGET {
    float4 finalColor = float4(0.0f, 0.0f, 0.0f, 0.0f);
    for (int i = -blurRadius; i <= blurRadius; i++) {
        float2 offset = float2(i * texelSize.x, 0.0f);
        finalColor += inputTex.Sample(linearSampler, input.uv + offset) * weights[i + blurRadius];
    }
    return finalColor;
}

// 垂直模糊像素着色器
float4 PS_VerticalBlur(PSInput input) : SV_TARGET {
    float4 finalColor = float4(0.0f, 0.0f, 0.0f, 0.0f);
    for (int i = -blurRadius; i <= blurRadius; i++) {
        float2 offset = float2(0.0f, i * texelSize.y);
        finalColor += inputTex.Sample(linearSampler, input.uv + offset) * weights[i + blurRadius];
    }
    return finalColor;
}

提示:高斯权重可以提前计算,比如半径5的权重数组可以是:[0.003765, 0.015019, 0.045057, 0.120982, 0.233477, 0.292311, 0.233477, 0.120982, 0.045057, 0.015019, 0.003765],这些权重的和应该接近1

第三步:执行模糊渲染流程

现在我们要把后台缓冲的内容拷贝到临时纹理,然后依次执行水平和垂直模糊:

// 1. 先把后台缓冲的内容拷贝到临时纹理A
if (backBufferDesc.SampleDesc.Count > 1) {
    // 如果是MSAA纹理,需要先解析到非MSAA纹理
    context->ResolveSubresource(blurTexA, 0, backBuffer, 0, backBufferDesc.Format);
} else {
    context->CopyResource(blurTexA, backBuffer);
}

// 2. 设置水平模糊Pass:把纹理A作为输入,渲染到纹理B
context->OMSetRenderTargets(1, &blurRTV_B, nullptr);
context->VSSetShader(blurVS, nullptr, 0); // blurVS是你编译好的顶点着色器
context->PSSetShader(horizontalBlurPS, nullptr, 0); // 水平模糊PS

// 设置常量缓冲区内容
BlurConstants cb;
cb.texelSize = float2(1.0f / backBufferDesc.Width, 1.0f / backBufferDesc.Height);
cb.blurRadius = 5;
// 填充weights数组,用刚才提到的高斯权重
cb.weights[0] = 0.003765; cb.weights[1] = 0.015019; cb.weights[2] = 0.045057;
cb.weights[3] = 0.120982; cb.weights[4] = 0.233477; cb.weights[5] = 0.292311;
cb.weights[6] = 0.233477; cb.weights[7] = 0.120982; cb.weights[8] = 0.045057;
cb.weights[9] = 0.015019; cb.weights[10] = 0.003765;
context->UpdateSubresource(blurCB, 0, nullptr, &cb, 0, 0); // blurCB是你的常量缓冲区
context->PSSetConstantBuffers(0, 1, &blurCB);

// 设置输入纹理和采样器
context->PSSetShaderResources(0, 1, &blurSRV_A);
context->PSSetSamplers(0, 1, &linearSampler); // linearSampler是线性采样器

// 绘制全屏Quad(可以用预定义的顶点缓冲区,或者直接用Draw(4,0)的方式,前提是顶点着色器能生成全屏Quad)
context->Draw(4, 0);

// 3. 设置垂直模糊Pass:把纹理B作为输入,渲染到纹理A
context->OMSetRenderTargets(1, &blurRTV_A, nullptr);
context->PSSetShader(verticalBlurPS, nullptr, 0); // 垂直模糊PS
context->PSSetShaderResources(0, 1, &blurSRV_B);
context->Draw(4, 0);

// 现在blurSRV_A就是经过两次模糊处理后的纹理资源了
第四步:将模糊后的纹理用于游戏GUI

这一步取决于你用的GUI框架,这里举两个常见的例子:

例子1:用ImGui显示模糊纹理

如果你的游戏用ImGui做GUI,直接把模糊后的SRV传给ImGui::Image即可:

// 在GUI绘制循环里
ImGui::Begin("Blurred Game View");
// 按比例缩放显示,或者直接用原始尺寸
ImVec2 displaySize(backBufferDesc.Width / 2.0f, backBufferDesc.Height / 2.0f);
ImGui::Image((void*)blurSRV_A, displaySize);
ImGui::End();

例子2:自定义GUI渲染

如果是自己写的GUI系统,只需要把模糊后的SRV绑定到GUI着色器的采样器上,然后绘制一个四边形即可,和普通的纹理绘制逻辑一样。

最后要注意的资源清理

记得在游戏退出或者不需要这些资源的时候,释放所有创建的D3D资源:

blurTexA->Release();
blurRTV_A->Release();
blurSRV_A->Release();
blurTexB->Release();
blurRTV_B->Release();
blurSRV_B->Release();
blurCB->Release();
blurVS->Release();
horizontalBlurPS->Release();
verticalBlurPS->Release();
linearSampler->Release();

内容的提问来源于stack exchange,提问作者user3494595

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 18:38:15