这篇文章主要介绍渲染管线输出部分的内容:交换链和渲染目标对象,并且介绍多视口渲染方法。
交换链:
要创建交换链,必须先设置交换链描述。交换链描述定义了将由交换链使用的渲染缓冲区的大小和数量。它还将窗口与交换链相关联,从而确定最终图像的显示位置。交换链描述还定义了该应用的消除锯齿(如果有的话)的质量以及在展示过程中后端缓冲区的翻转方式。
UINT create_device_flags = 0; #ifdef _DEBUG create_device_flags |= D3D11_CREATE_DEVICE_DEBUG; #endif D3D_DRIVER_TYPE driver_types[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE, }; UINT num_driver_types = ARRAYSIZE(driver_types); D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, }; UINT numFeatureLevels = ARRAYSIZE(featureLevels); DXGI_SWAP_CHAIN_DESC swap_desc; ZeroMemory(&swap_desc, sizeof(swap_desc)); swap_desc.BufferCount = 1; swap_desc.BufferDesc.Width = width; swap_desc.BufferDesc.Height = height; swap_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swap_desc.BufferDesc.RefreshRate.Numerator = 60; swap_desc.BufferDesc.RefreshRate.Denominator = 1; swap_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_desc.OutputWindow = g_hWnd; swap_desc.SampleDesc.Count = 1; swap_desc.SampleDesc.Quality = 0; swap_desc.Windowed = TRUE; for ( UINT driver_type_index = 0; driver_type_index < num_driver_types; driver_type_index++ ) { g_driverType = driver_types[driver_type_index]; hr = D3D11CreateDeviceAndSwapChain(NULL, g_driverType, NULL, create_device_flags, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &swap_desc, &g_pSwapChain, &g_pd3dDevice, &g_featureLevel, &g_pImmediateContext); if ( SUCCEEDED(hr) ) break; }
创建交换链是指定了一个窗口的句柄,并且设置了一个 D3D11_CREATE_DEVICE_DEBUG 标志,这个标志可以创建一个支持调试层的设备。如果我们做错了事,调试层为渲染管线的正确性和一致性提供了额外的检查并提供了更好的反馈。但是,如果在启用调试层的情况下运行应用程序,则应用程序将显着变慢。
要创建支持调试层的设备,必须安装DirectX SDK(以获取D3D11SDKLayers.dll)
渲染管线输出:
渲染管线并不能将渲染结果直接输出到窗口,只能输出到一个叫渲染目标视图(ID3D11RenderTargetView)的对象,在创建 ID3D11RenderTargetView 时需要和一个纹理对象绑定。可以使用交换链的 GetBuffer 方法来获取交换链后台纹理缓冲区指针,并创建一个渲染目标视图,最后将该视图设置到渲染管线,就可以将渲染管线的结果呈现给窗口了。
/* 获取交换链的后缓冲 */ ID3D11Texture2D* pBackBuffer = NULL; hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), ( LPVOID* ) &pBackBuffer); if ( FAILED(hr) ) return hr; /* 创建渲染目标视图 */ hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView); pBackBuffer->Release(); if ( FAILED(hr) ) return hr; g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, 0);
使用函数 OMSetRenderTargets 设置渲染目标视图到渲染管线时,可以设置 1 - D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT 之间数量的渲染目标视图。默认情况下使用第一个渲染目标视图,如果你要使用其它的渲染目标视图,必须在像素着色器中映射管线多输出值到 SV_Target[n] (其中 n 介于 0 和 D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT 之间)语义,实现多渲染目标(MRT)。像素着色器代码如下:
struct PS_OUTPUT { float4 color0 : SV_Target0; float4 color1 : SV_Target1; }; //-------------------------------------------------------------------------------------- // Pixel Shader //-------------------------------------------------------------------------------------- PS_OUTPUT PS( VS_OUTPUT input ) { PS_OUTPUT o; o.color0 = input.Color; o.color1 = float4(1, 0, 0, 1); return o; }因为没有多少文章介绍 DirectX11 多渲染目标(MRT)的实现,所以在这里简单介绍一下。
视口(ViewPort):
然不是严格考虑Direct3D初始化阶段的一部分,但设置视口定义是初始化光栅化器阶段的必要组件。视口定义了我们的最终渲染将进入的屏幕空间区域。对于这个应用程序,我们将渲染到应用程序窗口的整个客户区,但是如果我们想实现分屏多人或画中画效果,我们也可以定义两个视口。
D3D11_VIEWPORT view_port; view_port.Width = ( FLOAT ) width; view_port.Height = ( FLOAT ) height; view_port.MinDepth = 0.0f; view_port.MaxDepth = 1.0f; view_port.TopLeftX = 0; view_port.TopLeftY = 0; g_pImmediateContext->RSSetViewports(1, &view_port);
使用 RSSetViewports 设置视口到渲染管线时,可以设置 1 - D3D11_VIEWPORT_AND_SCISSORRECT_MAX_INDEX 之间数量的视口。默认情况下使用第一个视口,如果你想使用其它的视口,必须在几何着色器中设置 SV_ViewportArrayIndex 语义确定输出到哪个视口。如果想将窗口或分为 4 部分,先创建 4 个视口:
D3D11_VIEWPORT view_port[4]; for ( int i = 0; i < 4; i++ ) { view_port[i].Width = ( FLOAT ) width / 2; view_port[i].Height = ( FLOAT ) height / 2; view_port[i].MinDepth = 0.0f; view_port[i].MaxDepth = 1.0f; view_port[i].TopLeftX = 0; view_port[i].TopLeftY = 0; } view_port[1].TopLeftX = ( FLOAT ) width / 2; view_port[1].TopLeftY = 0; view_port[2].TopLeftX = 0; view_port[2].TopLeftY = ( FLOAT ) height / 2; view_port[3].TopLeftX = ( FLOAT ) width / 2; view_port[3].TopLeftY = ( FLOAT ) height / 2; g_pImmediateContext->RSSetViewports(4, view_port);接着是几何着色器的设置:
//-------------------------------------------------------------------------------------- // Constant Buffer Variables //-------------------------------------------------------------------------------------- cbuffer ConstantBuffer : register( b0 ) { matrix World; matrix View; matrix Projection; } //-------------------------------------------------------------------------------------- struct VS_OUTPUT { float4 Pos : SV_POSITION; float4 Color : COLOR0; }; //-------------------------------------------------------------------------------------- struct GS_OUTPUT { float4 Pos : SV_POSITION; float4 Color : COLOR0; uint view_idx : SV_VIEWPORTARRAYINDEX; }; //-------------------------------------------------------------------------------------- // Vertex Shader //-------------------------------------------------------------------------------------- VS_OUTPUT VS( float4 Pos : POSITION, float4 Color : COLOR ) { VS_OUTPUT output = (VS_OUTPUT)0; output.Pos = mul( Pos, World ); output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Color = Color; return output; } //-------------------------------------------------------------------------------------- // Geometry Shader //-------------------------------------------------------------------------------------- [maxvertexcount(12)] void GS(triangle VS_OUTPUT input[3], inout TriangleStream<GS_OUTPUT> output) { for(int j = 0; j < 4; j++) { GS_OUTPUT element; element.view_idx = j; for (uint i = 0; i < 3;i++) { element.Pos = input[i].Pos; element.Color = input[i].Color; output.Append(element); } output.RestartStrip(); } } //-------------------------------------------------------------------------------------- // Pixel Shader //-------------------------------------------------------------------------------------- float4 PS( VS_OUTPUT input ) : SV_Target { return input.Color; }在编译并设置几何着色器到渲染管线后,得到以下结果:
因为没有什么文章介绍这种方法的实现,所以在这里简单介绍一下。当然你要可以不这样做,你可以渲染 4 次,每次使用不同的视口,得到和上面一样的结果。
源码下载:MMDViewer 04.zip