DirectX 11 का उपयोग करके C ++ में कणों के परस्पर क्रिया करने वाले कणों की प्रणाली लिखना


एकता के साथ कम्प्यूटेशनल शेड के उपयोग के बारे में पहले से ही हैबर पर बहुत सारे लेख हैं , हालांकि, "क्लीन" Win32 एपीआई + डायरेक्टएक्स 11 पर कम्प्यूटेशनल शेडर का उपयोग करने के बारे में एक लेख खोजना मुश्किल है। हालांकि, यह कार्य अधिक जटिल नहीं है, अधिक विवरण में - कटौती के तहत।


ऐसा करने के लिए, हम उपयोग करेंगे:


  • विंडोज 10
  • विजुअल स्टूडियो 2017 सामुदायिक संस्करण "C ++ में क्लासिक एप्लिकेशन का विकास" मॉड्यूल के साथ
    प्रोजेक्ट बनाने के बाद, हम लिंकर को `d3d11.lib` लाइब्रेरी का उपयोग करने के लिए कहेंगे।


हैडर फाइलें

प्रति सेकंड फ़्रेम की संख्या की गणना करने के लिए, हम मानक पुस्तकालय का उपयोग करेंगे


#include <time.h> 

हम विंडो शीर्षक के माध्यम से प्रति सेकंड फ़्रेम की संख्या को आउटपुट करेंगे, जिसके लिए हमें संबंधित लाइन बनाने की आवश्यकता होगी


 #include <stdio.h> 

हम त्रुटि से निपटने पर विस्तार से विचार नहीं करेंगे, हमारे मामले में यह पर्याप्त है कि एप्लिकेशन डिबग संस्करण में क्रैश हो जाता है और गिरावट के समय इंगित करता है:


 #include <assert.h> 

WinAPI के लिए हैडर फाइलें:


 #define WIN32_LEAN_AND_MEAN #include <tchar.h> #include <Windows.h> 

Direct3D 11 के लिए हैडर फाइलें:


 #include <dxgi.h> #include <d3d11.h> 

शेडर लोड करने के लिए संसाधन आईडी। इसके बजाय, आप HLSL कंपाइलर द्वारा उत्पन्न शैडर ऑब्जेक्ट फ़ाइल को मेमोरी में लोड कर सकते हैं। संसाधन फ़ाइल बनाना बाद में वर्णित किया गया है।


 #include "resource.h" 

शॉल्डर और कॉलिंग हिस्से के लिए कॉन्सटेंट को एक अलग हेडर फ़ाइल में घोषित किया जाएगा।


 #include "SharedConst.h" 

हम विंडोज घटनाओं के प्रसंस्करण के लिए एक समारोह की घोषणा करते हैं, जिसे बाद में परिभाषित किया जाएगा:


 LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); 

हम एक विंडो बनाने और नष्ट करने के लिए फ़ंक्शन लिखेंगे


 int windowWidth, windowHeight; HINSTANCE hInstance; HWND hWnd; void InitWindows() { //      hInstance = GetModuleHandle(NULL); windowWidth = 800; windowHeight = 800; WNDCLASS wc; //    wc.style = 0; //    wc.lpfnWndProc = &WndProc; //             wc.cbClsExtra = 0; wc.cbWndExtra = 0; //  (),     wc.hInstance = hInstance; //       wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION); wc.hCursor = LoadCursor(hInstance, IDC_ARROW); //    ,    "" wc.hbrBackground = NULL; //    wc.lpszMenuName = NULL; //     wc.lpszClassName = _T("WindowClass1"); //    ATOM result = RegisterClass(&wc); // ,     assert(result); //   --  ,     .. DWORD dwStyle = WS_OVERLAPPEDWINDOW; RECT rect; //   ( )       rect.left = (GetSystemMetrics(SM_CXSCREEN) - windowWidth) / 2; rect.top = (GetSystemMetrics(SM_CYSCREEN) - windowHeight) / 2; rect.right = rect.left + windowWidth; rect.bottom = rect.top + windowHeight; //     .   --   AdjustWindowRect(&rect, dwStyle, FALSE); hWnd = CreateWindow( _T("WindowClass1"), _T("WindowName1"), dwStyle, //     rect.left, rect.top, //   rect.right - rect.left, rect.bottom - rect.top, //   // HWND_DESKTOP   NULL HWND_DESKTOP, //  NULL, //  (),    hInstance, //   NULL); // ,     assert(hWnd); } void DisposeWindows() { //   DestroyWindow(hWnd); //   UnregisterClass(_T("WindowClass1"), hInstance); } 

अगला वीडियो कार्ड (डिवाइस और डिवाइस कॉनटेक्स्ट) और आउटपुट बफ़र्स (स्वैचेचिन) की श्रृंखला तक पहुँचने के लिए इंटरफेस की शुरूआत है:


 IDXGISwapChain *swapChain; ID3D11Device *device; ID3D11DeviceContext *deviceContext; void InitSwapChain() { HRESULT result; DXGI_SWAP_CHAIN_DESC swapChainDesc; //        swapChainDesc.BufferDesc.Width = windowWidth; swapChainDesc.BufferDesc.Height = windowHeight; //           // ..     ,  swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; swapChainDesc.BufferDesc.RefreshRate.Denominator = 1; //   -- 32- RGBA swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //      swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; //    swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; //  SwapChain   swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //  "" ( )  swapChainDesc.BufferCount = 1; //     swapChainDesc.OutputWindow = hWnd; //   swapChainDesc.Windowed = TRUE; //          swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; swapChainDesc.Flags = 0; //  DirectX 11.0, ..    D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; //  Debug-    DirectX #ifndef NDEBUG UINT flags = D3D11_CREATE_DEVICE_DEBUG; #else UINT flags = 0; #endif result = D3D11CreateDeviceAndSwapChain( //   - NULL, //    D3D_DRIVER_TYPE_HARDWARE, NULL, // .  flags, //    DirectX &featureLevel, 1, //  SDK D3D11_SDK_VERSION, //     &swapChainDesc, // ,    &swapChain, &device, NULL, &deviceContext); // ,     assert(SUCCEEDED(result)); } void DisposeSwapChain() { deviceContext->Release(); device->Release(); swapChain->Release(); } 

बफर से लेकर रेंडर तक पहुंच का आरंभिक प्रदर्शन किया जाएगा:


 ID3D11RenderTargetView *renderTargetView; void InitRenderTargetView() { HRESULT result; ID3D11Texture2D *backBuffer; //  ""   SwapChain result = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void **)&backBuffer); assert(SUCCEEDED(result)); //          result = device->CreateRenderTargetView(backBuffer, NULL, &renderTargetView); assert(SUCCEEDED(result)); //        //  ,       , // ..      SwapChain, // Release()    backBuffer->Release(); //   View   deviceContext->OMSetRenderTargets(1, &renderTargetView, NULL); //    D3D11_VIEWPORT viewport; viewport.TopLeftX = 0; viewport.TopLeftY = 0; viewport.Width = (FLOAT)windowWidth; viewport.Height = (FLOAT)windowHeight; viewport.MinDepth = 0; viewport.MaxDepth = 1; deviceContext->RSSetViewports(1, &viewport); } void DisposeRenderTargetView() { renderTargetView->Release(); } 

शेड्स को शुरू करने से पहले, आपको उन्हें बनाने की आवश्यकता है। Visual Studio फ़ाइल एक्सटेंशन को पहचान सकता है, इसलिए हम केवल एक्सटेंशन .hlsl साथ एक स्रोत बना सकते हैं, या सीधे मेनू के माध्यम से एक shader बना सकते हैं। मैंने पहला तरीका चुना, क्योंकि वैसे भी, गुणों के माध्यम से आपको Shader Model 5 का उपयोग सेट करना होगा।




इसी तरह, वर्टेक्स और पिक्सेल शेड्स बनाएं।


वर्टियर शेडर में, हम निर्देशांक को दो-आयामी वेक्टर से परिवर्तित करते हैं (क्योंकि हमारे पास जो बिंदु हैं वे दो आयामी हैं) को चार-आयामी (वीडियो कार्ड द्वारा प्राप्त):


 float4 main(float2 input: POSITION): SV_POSITION { return float4(input, 0, 1); } 

पिक्सेल शेडर में, हम सफेद वापस आएंगे:


 float4 main(float4 input: SV_POSITION): SV_TARGET { return float4(1, 1, 1, 1); } 

अब एक कम्प्यूटेशनल shader। हम इस सूत्र को बिंदुओं की परस्पर क्रिया के लिए परिभाषित करते हैं:


 vecFij=109 vecrij frac lvert vecrij rvert0.25lvert vecrij rvert


द्रव्यमान के साथ अपनाया 1


इस तरह से एचएलएसएल पर इसका कार्यान्वयन दिखेगा:


 #include "SharedConst.h" //  , UAV   0 RWBuffer<float2> position: register(u0); //  , UAV   1 RWBuffer<float2> velocity: register(u1); //    [numthreads(NUMTHREADS, 1, 1)] void main(uint3 id: SV_DispatchThreadID) { float2 acc = float2(0, 0); for (uint i = 0; i < PARTICLE_COUNT; i++) { //       float2 diff = position[i] - position[id.x]; //     ,     0- float len = max(1e-10, length(diff)); float k = 1e-9 * (len - 0.25) / len; acc += k * diff; } position[id.x] += velocity[id.x] + 0.5 * acc; velocity[id.x] += acc; } 

आप देख सकते हैं कि SharedConst.h फ़ाइल shader में शामिल है। यह स्थिरांक के साथ हेडर फ़ाइल है, जो main.cpp में शामिल है। यहाँ इस फ़ाइल की सामग्री है:


 #ifndef PARTICLE_COUNT #define PARTICLE_COUNT (1 << 15) #endif #ifndef NUMTHREADS #define NUMTHREADS 64 #endif 

बस एक समूह में कणों की संख्या और धाराओं की संख्या की घोषणा करना। हम प्रत्येक कण को ​​एक धारा आवंटित करेंगे, इसलिए हम PARTICLE_COUNT / NUMTHREADS रूप में समूहों PARTICLE_COUNT / NUMTHREADS संख्या PARTICLE_COUNT / NUMTHREADS करेंगे। यह संख्या पूर्णांक होनी चाहिए, इसलिए यह आवश्यक है कि कणों की संख्या समूह में प्रवाह की संख्या से विभाजित हो।


हम विंडोज संसाधन तंत्र का उपयोग करके संकलित shader bytecode को लोड करेंगे। ऐसा करने के लिए, निम्न फ़ाइलें बनाएँ:


resource.h , जिसमें संबंधित संसाधन की ID होगी:


 #pragma once #define IDR_BYTECODE_COMPUTE 101 #define IDR_BYTECODE_VERTEX 102 #define IDR_BYTECODE_PIXEL 103 

और resource.rc , निम्नलिखित सामग्री के संगत संसाधन उत्पन्न करने के लिए एक फ़ाइल:


 #include "resource.h" IDR_BYTECODE_COMPUTE ShaderObject "compute.cso" IDR_BYTECODE_VERTEX ShaderObject "vertex.cso" IDR_BYTECODE_PIXEL ShaderObject "pixel.cso" 

जहां ShaderObject संसाधन का प्रकार है, और compute.cso , vertex.cso और pixel.cso आउटपुट निर्देशिका में संकलित शेडर ऑब्जेक्ट फ़ाइलों के संबंधित नाम हैं।


फ़ाइलों को खोजने के लिए, आपको resource.rc आउटपुट निर्देशिका में प्रोजेक्ट आउटपुट निर्देशिका के लिए पथ निर्दिष्ट करना चाहिए:



विज़ुअल स्टूडियो ने स्वचालित रूप से फ़ाइल को संसाधनों के विवरण के रूप में मान्यता दी और इसे विधानसभा में जोड़ा, आपको मैन्युअल रूप से ऐसा करने की आवश्यकता नहीं है


अब आप shader आरंभीकरण कोड लिख सकते हैं:


 ID3D11ComputeShader *computeShader; ID3D11VertexShader *vertexShader; ID3D11PixelShader *pixelShader; ID3D11InputLayout *inputLayout; void InitShaders() { HRESULT result; HRSRC src; HGLOBAL res; //    //       //    , ..    //         src = FindResource(hInstance, MAKEINTRESOURCE(IDR_BYTECODE_COMPUTE), _T("ShaderObject")); res = LoadResource(hInstance, src); //   result = device->CreateComputeShader( //      res, SizeofResource(hInstance, src), //   .     , ..     NULL, //    &computeShader); assert(SUCCEEDED(result)); FreeResource(res); //      src = FindResource(hInstance, MAKEINTRESOURCE(IDR_BYTECODE_PIXEL), _T("ShaderObject")); res = LoadResource(hInstance, src); result = device->CreatePixelShader(res, SizeofResource(hInstance, src), NULL, &pixelShader); assert(SUCCEEDED(result)); FreeResource(res); //     src = FindResource(hInstance, MAKEINTRESOURCE(IDR_BYTECODE_VERTEX), _T("ShaderObject")); res = LoadResource(hInstance, src); result = device->CreateVertexShader(res, SizeofResource(hInstance, src), NULL, &vertexShader); assert(SUCCEEDED(result)); // ,        //   ( )   D3D11_INPUT_ELEMENT_DESC inputDesc; //    inputDesc.SemanticName = "POSITION"; //    ,         inputDesc.SemanticIndex = 0; //    32-   inputDesc.Format = DXGI_FORMAT_R32G32_FLOAT; //   inputDesc.AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; //    inputDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; //   inputDesc.InputSlot = 0; //     inputDesc.InstanceDataStepRate = 0; result = device->CreateInputLayout( //       &inputDesc, 1, //     res, SizeofResource(hInstance, src), //   &inputLayout); assert(SUCCEEDED(result)); FreeResource(res); } void DisposeShaders() { inputLayout->Release(); computeShader->Release(); vertexShader->Release(); pixelShader->Release(); } 

बफर इनिशियलाइज़ेशन कोड:


 ID3D11Buffer *positionBuffer; ID3D11Buffer *velocityBuffer; void InitBuffers() { HRESULT result; float *data = new float[2 * PARTICLE_COUNT]; //  ,        D3D11_SUBRESOURCE_DATA subresource; //    subresource.pSysMem = data; //      subresource.SysMemPitch = 0; //       subresource.SysMemSlicePitch = 0; //   D3D11_BUFFER_DESC desc; //   desc.ByteWidth = sizeof(float[2 * PARTICLE_COUNT]); //      desc.Usage = D3D11_USAGE_DEFAULT; //      ,      desc.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_UNORDERED_ACCESS; //      desc.CPUAccessFlags = 0; //     desc.MiscFlags = 0; //     desc.StructureByteStride = sizeof(float[2]); //    for (int i = 0; i < 2 * PARTICLE_COUNT; i++) data[i] = 2.0f * rand() / RAND_MAX - 1.0f; //    result = device->CreateBuffer(&desc, &subresource, &positionBuffer); assert(SUCCEEDED(result)); //         desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS; //    for (int i = 0; i < 2 * PARTICLE_COUNT; i++) data[i] = 0.0f; //    result = device->CreateBuffer(&desc, &subresource, &velocityBuffer); assert(SUCCEEDED(result)); //  ,    delete[] data; } void DisposeBuffers() { positionBuffer->Release(); velocityBuffer->Release(); } 

और कम्प्यूटेशनल shader से बफर एक्सेस आरंभीकरण कोड:


 ID3D11UnorderedAccessView *positionUAV; ID3D11UnorderedAccessView *velocityUAV; void InitUAV() { HRESULT result; //          D3D11_UNORDERED_ACCESS_VIEW_DESC desc; //    32-   desc.Format = DXGI_FORMAT_R32G32_FLOAT; //   ,      desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; //     desc.Buffer.FirstElement = 0; //   desc.Buffer.NumElements = PARTICLE_COUNT; //    desc.Buffer.Flags = 0; //      result = device->CreateUnorderedAccessView(positionBuffer, &desc, &positionUAV); assert(!result); //      result = device->CreateUnorderedAccessView(velocityBuffer, &desc, &velocityUAV); assert(!result); } void DisposeUAV() { positionUAV->Release(); velocityUAV->Release(); } 

अगला, आपको ड्राइवर को बफ़र्स के साथ बनाए गए शेड और बंडलों का उपयोग करना चाहिए:


 void InitBindings() { //    //  deviceContext->CSSetShader(computeShader, NULL, 0); //  deviceContext->VSSetShader(vertexShader, NULL, 0); //  deviceContext->PSSetShader(pixelShader, NULL, 0); //         deviceContext->CSSetUnorderedAccessViews(1, 1, &velocityUAV, NULL); //       deviceContext->IASetInputLayout(inputLayout); //    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); } 

औसत फ्रेम समय की गणना करने के लिए, हम निम्नलिखित कोड का उपयोग करेंगे:


 const int FRAME_TIME_COUNT = 128; clock_t frameTime[FRAME_TIME_COUNT]; int currentFrame = 0; float AverageFrameTime() { frameTime[currentFrame] = clock(); int nextFrame = (currentFrame + 1) % FRAME_TIME_COUNT; clock_t delta = frameTime[currentFrame] - frameTime[nextFrame]; currentFrame = nextFrame; return (float)delta / CLOCKS_PER_SEC / FRAME_TIME_COUNT; } 

और प्रत्येक फ्रेम पर - इस फ़ंक्शन को कॉल करें:


 void Frame() { float frameTime = AverageFrameTime(); //   char buf[256]; sprintf_s(buf, "average framerate: %.1f", 1.0f / frameTime); SetWindowTextA(hWnd, buf); //     float clearColor[] = { 0.0f, 0.0f, 0.0f, 0.0f }; deviceContext->ClearRenderTargetView(renderTargetView, clearColor); //    32-    UINT stride = sizeof(float[2]); UINT offset = 0; ID3D11Buffer *nullBuffer = NULL; ID3D11UnorderedAccessView *nullUAV = NULL; //        deviceContext->IASetVertexBuffers(0, 1, &nullBuffer, &stride, &offset); //        deviceContext->CSSetUnorderedAccessViews(0, 1, &positionUAV, NULL); //    deviceContext->Dispatch(PARTICLE_COUNT / NUMTHREADS, 1, 1); //        deviceContext->CSSetUnorderedAccessViews(0, 1, &nullUAV, NULL); //        deviceContext->IASetVertexBuffers(0, 1, &positionBuffer, &stride, &offset); //   deviceContext->Draw(PARTICLE_COUNT, 0); //     swapChain->Present(0, 0); } 

यदि विंडो का आकार बदल गया है, तो हमें रेंडरिंग बफ़र्स के आकार को भी बदलना होगा:


 void ResizeSwapChain() { HRESULT result; RECT rect; //     GetClientRect(hWnd, &rect); windowWidth = rect.right - rect.left; windowHeight = rect.bottom - rect.top; //  ,    ,  //     ""  DisposeRenderTargetView(); //    result = swapChain->ResizeBuffers( //     0, //   windowWidth, windowHeight, //      DXGI_FORMAT_UNKNOWN, 0); assert(SUCCEEDED(result)); //     ""  InitRenderTargetView(); } 

अंत में, आप एक संदेश संसाधन फ़ंक्शन को परिभाषित कर सकते हैं:


 LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { switch (Msg) { case WM_CLOSE: PostQuitMessage(0); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) PostQuitMessage(0); break; case WM_SIZE: ResizeSwapChain(); break; default: return DefWindowProc(hWnd, Msg, wParam, lParam); } return 0; } 

और main कार्य:


 int main() { InitWindows(); InitSwapChain(); InitRenderTargetView(); InitShaders(); InitBuffers(); InitUAV(); InitBindings(); ShowWindow(hWnd, SW_SHOW); bool shouldExit = false; while (!shouldExit) { Frame(); MSG msg; while (!shouldExit && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) shouldExit = true; } } DisposeUAV(); DisposeBuffers(); DisposeShaders(); DisposeRenderTargetView(); DisposeSwapChain(); DisposeWindows(); } 

लेख के शीर्षक में रनिंग प्रोग्राम का स्क्रीनशॉट देखा जा सकता है।


→ प्रोजेक्ट पूरी तरह से GitHub में अपलोड किया गया है

Source: https://habr.com/ru/post/hi430202/


All Articles