
هناك بالفعل الكثير من المقالات حول هابر حول استخدام تظليل الحوسبة مع Unity ، ومع ذلك ، من الصعب العثور على مقال حول استخدام تظليل الحوسبة على Win32 API + DirectX 11 "النظيف". ومع ذلك ، فإن هذه المهمة ليست أكثر تعقيدًا بكثير ، بمزيد من التفصيل - تحت الخفض.
للقيام بذلك ، سوف نستخدم:
- نظام التشغيل Windows 10
- Visual Studio 2017 Community Edition مع الوحدة النمطية "تطوير التطبيقات الكلاسيكية في 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" 
 نعلن وظيفة لمعالجة أحداث Windows ، والتي سيتم تحديدها لاحقًا:
 LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); 
سنكتب وظائف لإنشاء نافذة وتدميرها
 int windowWidth, windowHeight; HINSTANCE hInstance; HWND hWnd; void InitWindows() {  
التالي هو تهيئة الواجهة للوصول إلى بطاقة الفيديو (Device and DeviceContext) وسلسلة المخازن المؤقتة للإخراج (SwapChain):
 IDXGISwapChain *swapChain; ID3D11Device *device; ID3D11DeviceContext *deviceContext; void InitSwapChain() { HRESULT result; DXGI_SWAP_CHAIN_DESC swapChainDesc;  
تهيئة الوصول من تظليل إلى المخزن المؤقت الذي سيتم تنفيذ العرض:
 ID3D11RenderTargetView *renderTargetView; void InitRenderTargetView() { HRESULT result; ID3D11Texture2D *backBuffer;  
قبل تهيئة التظليل ، تحتاج إلى إنشائها. يمكن لـ Visual Studio التعرف على امتداد الملف ، لذلك يمكننا ببساطة إنشاء مصدر بالملحق .hlsl ، أو إنشاء تظليل مباشرة من خلال القائمة. اخترت الطريقة الأولى ، لأن على أي حال ، من خلال الخصائص عليك تعيين استخدام 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); } 
الآن تظليل حسابي. نحدد هذه الصيغة لتفاعلات النقاط:
مع اعتماد الكتلة 1
سيبدو تنفيذ هذا على HLSL:
 #include "SharedConst.h"  
قد تلاحظ أن ملف SharedConst.h مضمن في التظليل. هذا هو ملف الرأس الذي يحتوي على ثوابت ، والذي تم تضمينه في 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 . يجب أن يكون هذا العدد عددًا صحيحًا ، لذلك من الضروري أن يتم تقسيم عدد الجسيمات على عدد التدفقات في المجموعة.
سنقوم بتحميل كود التظليل الثانوي المترجم باستخدام آلية موارد Windows. للقيام بذلك ، قم بإنشاء الملفات التالية:
resource.h . resource.h ، والذي سيحتوي على معرف المورد المقابل:
 #pragma once #define IDR_BYTECODE_COMPUTE 101 #define IDR_BYTECODE_VERTEX 102 #define IDR_BYTECODE_PIXEL 103 
And 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 هي الأسماء المقابلة لملفات كائن Shader المترجمة في دليل الإخراج.
من أجل العثور على الملفات ، يجب تحديد المسار إلى دليل إخراج المشروع في خصائص resource.rc rc:

يتعرف Visual Studio تلقائيًا على الملف على أنه وصف للمصادر ويضيفه إلى التجميع ، ولا تحتاج إلى القيام بذلك يدويًا
الآن يمكنك كتابة كود التهيئة التظليل:
 ID3D11ComputeShader *computeShader; ID3D11VertexShader *vertexShader; ID3D11PixelShader *pixelShader; ID3D11InputLayout *inputLayout; void InitShaders() { HRESULT result; HRSRC src; HGLOBAL res;  
رمز تهيئة المخزن المؤقت:
 ID3D11Buffer *positionBuffer; ID3D11Buffer *velocityBuffer; void InitBuffers() { HRESULT result; float *data = new float[2 * PARTICLE_COUNT];  
ورمز تهيئة الوصول إلى المخزن المؤقت من جهاز التظليل الحسابي:
 ID3D11UnorderedAccessView *positionUAV; ID3D11UnorderedAccessView *velocityUAV; void InitUAV() { HRESULT result;  
بعد ذلك ، يجب أن تخبر السائق باستخدام المظلات والحزم التي تم إنشاؤها مع المخازن المؤقتة:
 void InitBindings() {  
لحساب متوسط وقت الإطار ، سنستخدم الكود التالي:
 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();  
في حالة تغيير حجم النافذة ، نحتاج أيضًا إلى تغيير حجم المخازن المؤقتة للعرض:
 void ResizeSwapChain() { HRESULT result; RECT rect;  
أخيرًا ، يمكنك تحديد وظيفة معالجة الرسائل:
 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