
هناك بالفعل الكثير من المقالات حول هابر حول استخدام تظليل الحوسبة مع 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