Menambahkan konten visual ke sampel Marble Maze

Dokumen ini menjelaskan bagaimana game Marble Maze menggunakan Direct3D dan Direct2D di lingkungan aplikasi Platform Windows Universal (UWP) sehingga Anda dapat mempelajari pola dan menyesuaikannya ketika Anda bekerja dengan konten game Anda sendiri. Untuk mempelajari bagaimana komponen game visual cocok dalam struktur aplikasi keseluruhan Marble Maze, lihat struktur aplikasi Marble Maze.

Kami mengikuti langkah-langkah dasar ini saat kami mengembangkan aspek visual Marble Maze:

  1. Buat kerangka kerja dasar yang menginisialisasi lingkungan Direct3D dan Direct2D.
  2. Gunakan program pengeditan gambar dan model untuk merancang aset 2D dan 3D yang muncul dalam game.
  3. Pastikan bahwa aset 2D dan 3D dimuat dengan benar dan muncul dalam permainan.
  4. Integrasikan vertex dan pixel shader yang meningkatkan kualitas visual aset game.
  5. Integrasikan logika game, seperti animasi dan input pengguna.

Kami juga berfokus terlebih dahulu untuk menambahkan aset 3D dan kemudian pada aset 2D. Misalnya, kami berfokus pada logika game inti sebelum kami menambahkan sistem menu dan timer.

Kami juga perlu melakukan iterasi melalui beberapa langkah ini beberapa kali selama proses pengembangan. Misalnya, saat kami membuat perubahan pada model jala dan marmer, kami juga harus mengubah beberapa kode shader yang mendukung model tersebut.

Catatan

Kode sampel yang sesuai dengan dokumen ini ditemukan dalam sampel permainan DirectX Marble Maze.

  Berikut adalah beberapa poin penting yang dibahas dokumen ini ketika Anda bekerja dengan DirectX dan konten game visual, yaitu, ketika Anda menginisialisasi pustaka grafik DirectX, memuat sumber daya adegan, dan memperbarui dan merender adegan:

  • Menambahkan konten game biasanya melibatkan banyak langkah. Langkah-langkah ini juga sering memerlukan perulangan. Pengembang game sering fokus terlebih dahulu untuk menambahkan konten game 3D dan kemudian menambahkan konten 2D.
  • Jangkau lebih banyak pelanggan dan berikan mereka semua pengalaman hebat dengan mendukung berbagai perangkat keras grafis terbesar mungkin.
  • Memisahkan format waktu desain dan run-time dengan bersih. Susun aset waktu desain Anda untuk memaksimalkan fleksibilitas dan mengaktifkan iterasi cepat pada konten. Format dan kompres aset Anda untuk memuat dan merender seefisien mungkin pada waktu proses.
  • Anda membuat perangkat Direct3D dan Direct2D di aplikasi UWP seperti yang Anda lakukan di aplikasi desktop Windows klasik. Salah satu perbedaan penting adalah bagaimana rantai pertukaran dikaitkan dengan jendela output.
  • Saat merancang game, pastikan format jala yang Anda pilih mendukung skenario utama Anda. Misalnya, jika game Anda memerlukan tabrakan, pastikan Anda dapat memperoleh data tabrakan dari jala Anda.
  • Logika permainan terpisah dari logika penyajian dengan terlebih dahulu memperbarui semua objek adegan sebelum Anda merendernya.
  • Anda biasanya menggambar objek adegan 3D Anda, lalu objek 2D apa pun yang muncul di depan adegan.
  • Sinkronkan gambar ke vertikal kosong untuk memastikan bahwa permainan Anda tidak menghabiskan waktu menggambar bingkai yang tidak akan pernah benar-benar ditampilkan pada tampilan. Kosong vertikal adalah waktu antara ketika satu bingkai selesai menggambar ke monitor dan bingkai berikutnya dimulai.

Mulai menggunakan grafik DirectX

Ketika kami merencanakan game Marble Maze Platform Windows Universal (UWP), kami memilih C++ dan Direct3D 11.1 karena mereka adalah pilihan yang sangat baik untuk membuat game 3D yang membutuhkan kontrol maksimum atas penyajian dan performa tinggi. DirectX 11.1 mendukung perangkat keras dari DirectX 9 ke DirectX 11, dan oleh karena itu dapat membantu Anda menjangkau lebih banyak pelanggan secara lebih efisien karena Anda tidak perlu menulis ulang kode untuk setiap versi DirectX sebelumnya.

Marble Maze menggunakan Direct3D 11.1 untuk merender aset game 3D, yaitu marmer dan labirin. Marble Maze juga menggunakan Direct2D, DirectWrite, dan Windows Imaging Component (WIC) untuk menggambar aset game 2D, seperti menu dan timer.

Pengembangan game membutuhkan perencanaan. Jika Anda baru menggunakan grafik DirectX, kami sarankan Anda membaca DirectX: Mulai membiasakan diri dengan konsep dasar membuat game DirectX UWP. Saat Anda membaca dokumen ini dan bekerja melalui kode sumber Marble Maze, Anda dapat merujuk ke sumber daya berikut untuk informasi yang lebih mendalam tentang grafik DirectX:

  • Grafik Direct3D 11: Menjelaskan Direct3D 11, API grafis 3D yang kuat dan dipercepat perangkat keras untuk merender geometri 3D pada platform Windows.
  • Direct2D: Menjelaskan Direct2D, API grafis 2D yang dipercepat perangkat keras yang menyediakan performa tinggi dan penyajian berkualitas tinggi untuk geometri 2D, bitmap, dan teks.
  • DirectWrite: Menjelaskan DirectWrite, yang mendukung penyajian teks berkualitas tinggi.
  • Komponen Pencitraan Windows: Menjelaskan WIC, platform yang dapat diperluas yang menyediakan API tingkat rendah untuk gambar digital.

Tingkat fitur

Direct3D 11 memperkenalkan paradigma bernama tingkat fitur. Tingkat fitur adalah sekumpulan fungsionalitas GPU yang terdefinisi dengan baik. Gunakan tingkat fitur untuk menargetkan game Anda agar berjalan pada versi perangkat keras Direct3D yang lebih lama. Marble Maze mendukung tingkat fitur 9.1 karena tidak memerlukan fitur canggih dari tingkat yang lebih tinggi. Kami menyarankan agar Anda mendukung berbagai perangkat keras terbesar yang mungkin dan menskalakan konten game Anda sehingga pelanggan Anda yang memiliki komputer tinggi atau low-end semuanya memiliki pengalaman hebat. Untuk informasi selengkapnya tentang tingkat fitur, lihat Direct3D 11 di Downlevel Hardware.

Menginisialisasi Direct3D dan Direct2D

Perangkat mewakili adaptor tampilan. Anda membuat perangkat Direct3D dan Direct2D di aplikasi UWP seperti yang Anda lakukan di aplikasi desktop Windows klasik. Perbedaan utamanya adalah cara Anda menyambungkan rantai pertukaran Direct3D ke sistem windowing.

Kelas DeviceResources adalah fondasi untuk mengelola Direct3D dan Direct2D. Kelas ini menangani infrastruktur umum, bukan aset khusus game. Marble Maze mendefinisikan kelas MarbleMazeMain untuk menangani aset khusus game, yang memiliki referensi ke objek DeviceResources untuk memberinya akses ke Direct3D dan Direct2D.

Selama inisialisasi, konstruktor DeviceResources membuat sumber daya independen perangkat dan perangkat Direct3D dan Direct2D.

// Initialize the Direct3D resources required to run. 
DX::DeviceResources::DeviceResources() :
    m_screenViewport(),
    m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
    m_d3dRenderTargetSize(),
    m_outputSize(),
    m_logicalSize(),
    m_nativeOrientation(DisplayOrientations::None),
    m_currentOrientation(DisplayOrientations::None),
    m_dpi(-1.0f),
    m_deviceNotify(nullptr)
{
    CreateDeviceIndependentResources();
    CreateDeviceResources();
}

Kelas DeviceResources memisahkan fungsionalitas ini sehingga dapat lebih mudah merespons saat lingkungan berubah. Misalnya, metode ini memanggil metode CreateWindowSizeDependentResources saat ukuran jendela berubah.

Menginisialisasi pabrik Direct2D, DirectWrite, dan WIC

Metode DeviceResources::CreateDeviceIndependentResources membuat pabrik untuk Direct2D, DirectWrite, dan WIC. Dalam grafik DirectX, pabrik adalah titik awal untuk membuat sumber daya grafis. Marble Maze menentukan D2D1_FACTORY_TYPE_SINGLE_THREADED karena melakukan semua gambar pada utas utama.

// These are the resources required independent of hardware. 
void DX::DeviceResources::CreateDeviceIndependentResources()
{
    // Initialize Direct2D resources.
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
    // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    // Initialize the Direct2D Factory.
    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory2),
            &options,
            &m_d2dFactory
            )
        );

    // Initialize the DirectWrite Factory.
    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory2),
            &m_dwriteFactory
            )
        );

    // Initialize the Windows Imaging Component (WIC) Factory.
    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory2,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

Membuat perangkat Direct3D dan Direct2D

Metode DeviceResources::CreateDeviceResources memanggil D3D11CreateDevice untuk membuat objek perangkat yang mewakili adaptor tampilan Direct3D. Karena Marble Maze mendukung tingkat fitur 9.1 ke atas, metode DeviceResources::CreateDeviceResources menentukan tingkat 9.1 hingga 11.1 dalam array featureLevels . Direct3D memandu daftar secara berurutan dan memberi aplikasi tingkat fitur pertama yang tersedia. Oleh karena itu entri array D3D_FEATURE_LEVEL tercantum dari tertinggi hingga terendah sehingga aplikasi akan mendapatkan tingkat fitur tertinggi yang tersedia. Metode DeviceResources::CreateDeviceResources mendapatkan perangkat Direct3D 11.1 dengan mengkueri perangkat Direct3D 11 yang dikembalikan dari D3D11CreateDevice.

// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
    if (DX::SdkLayersAvailable())
    {
        // If the project is in a debug build, enable debugging via SDK Layers 
        // with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
    }
#endif

// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description.  All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1
};

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

HRESULT hr = D3D11CreateDevice(
    nullptr,                    // Specify nullptr to use the default adapter.
    D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for UWP apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
    );

if (FAILED(hr))
{
    // If the initialization fails, fall back to the WARP device.
    // For more information on WARP, see:
    // https://go.microsoft.com/fwlink/?LinkId=286690
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            &device,
            &m_d3dFeatureLevel,
            &context
            )
        );
}

// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

DX::ThrowIfFailed(
    context.As(&m_d3dContext)
    );

Metode DeviceResources::CreateDeviceResources kemudian membuat perangkat Direct2D. Direct2D menggunakan Microsoft DirectX Graphics Infrastructure (DXGI) untuk beroperasi dengan Direct3D. DXGI memungkinkan permukaan memori video dibagikan di antara runtime grafis. Marble Maze menggunakan perangkat DXGI yang mendasar dari perangkat Direct3D untuk membuat perangkat Direct2D dari pabrik Direct2D.

// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );

Untuk informasi selengkapnya tentang DXGI dan interoperabilitas antara Direct2D dan Direct3D, lihat Gambaran Umum DXGI dan Gambaran Umum Interoperabilitas Direct2D dan Direct3D.

Mengaitkan Direct3D dengan tampilan

Metode DeviceResources::CreateWindowSizeDependentResources membuat sumber daya grafis yang bergantung pada ukuran jendela tertentu seperti rantai pertukaran dan target render Direct3D dan Direct2D. Salah satu cara penting agar aplikasi DirectX UWP berbeda dari aplikasi desktop adalah bagaimana rantai pertukaran dikaitkan dengan jendela output. Rantai pertukaran bertanggung jawab untuk menampilkan buffer yang dirender perangkat pada monitor. Struktur aplikasi Marble Maze menjelaskan bagaimana sistem windowing untuk aplikasi UWP berbeda dari aplikasi desktop. Karena aplikasi UWP tidak berfungsi dengan objek HWND , Marble Maze harus menggunakan metode IDXGIFactory2::CreateSwapChainForCoreWindow untuk mengaitkan output perangkat ke tampilan. Contoh berikut menunjukkan bagian dari metode DeviceResources::CreateWindowSizeDependentResources yang membuat rantai pertukaran.

// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &m_swapChain
        )
    );

Untuk meminimalkan konsumsi daya, yang penting dilakukan pada perangkat bertenaga baterai seperti laptop dan tablet, metode DeviceResources::CreateWindowSizeDependentResources memanggil metode IDXGIDevice1::SetMaximumFrameLatency untuk memastikan bahwa game dirender hanya setelah kosong vertikal. Menyinkronkan dengan kosong vertikal dijelaskan secara lebih rinci di bagian Menyajikan adegan dalam dokumen ini.

// Ensure that DXGI does not queue more than one frame at a time. This both 
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
    dxgiDevice->SetMaximumFrameLatency(1)
    );

Metode DeviceResources::CreateWindowSizeDependentResources menginisialisasi sumber daya grafis dengan cara yang berfungsi untuk sebagian besar game.

Catatan

Tampilan istilah memiliki arti yang berbeda dalam Windows Runtime daripada yang ada di Direct3D. Di Windows Runtime, tampilan mengacu pada kumpulan pengaturan antarmuka pengguna untuk aplikasi, termasuk area tampilan dan perilaku input, ditambah utas yang digunakannya untuk pemrosesan. Anda menentukan konfigurasi dan pengaturan yang Anda butuhkan saat membuat tampilan. Proses pengaturan tampilan aplikasi dijelaskan dalam struktur aplikasi Marble Maze. Di Direct3D, tampilan istilah memiliki beberapa arti. Tampilan sumber daya menentukan sub-sumber daya yang dapat diakses sumber daya. Misalnya, ketika objek tekstur dikaitkan dengan tampilan sumber daya shader, shader tersebut nantinya dapat mengakses tekstur. Salah satu keuntungan dari tampilan sumber daya adalah Anda dapat menginterpretasikan data dengan cara yang berbeda pada tahap yang berbeda dalam alur penyajian. Untuk informasi selengkapnya tentang tampilan sumber daya, lihat Tampilan Sumber Daya. Saat digunakan dalam konteks transformasi tampilan atau matriks transformasi tampilan, tampilan mengacu pada lokasi dan orientasi kamera. Transformasi tampilan merelokasi objek di dunia di sekitar posisi dan orientasi kamera. Untuk informasi selengkapnya tentang transformasi tampilan, lihat Lihat Transformasi (Direct3D 9). Cara Marble Maze menggunakan tampilan sumber daya dan matriks dijelaskan secara lebih rinci dalam topik ini.

 

Memuat sumber daya adegan

Marble Maze menggunakan kelas BasicLoader , yang dideklarasikan dalam BasicLoader.h, untuk memuat tekstur dan shader. Marble Maze menggunakan kelas SDKMesh untuk memuat jala 3D untuk labirin dan marmer.

Untuk memastikan aplikasi responsif, Marble Maze memuat sumber daya adegan secara asinkron, atau di latar belakang. Saat aset dimuat di latar belakang, game Anda dapat merespons peristiwa jendela. Proses ini dijelaskan secara lebih rinci dalam Memuat aset game di latar belakang dalam panduan ini.

Memuat overlay 2D dan antarmuka pengguna

Di Marble Maze, overlay adalah gambar yang muncul di bagian atas layar. Overlay selalu muncul di depan adegan. Di Marble Maze, overlay berisi logo Windows dan string teks DirectX Marble Maze sampel game. Manajemen overlay dilakukan oleh kelas SampleOverlay , yang ditentukan dalam SampleOverlay.h. Meskipun kami menggunakan overlay sebagai bagian dari sampel Direct3D, Anda dapat menyesuaikan kode ini untuk menampilkan gambar apa pun yang muncul di depan adegan Anda.

Salah satu aspek penting dari overlay adalah bahwa, karena kontennya tidak berubah, kelas SampleOverlay menarik, atau cache, kontennya ke objek ID2D1Bitmap1 selama inisialisasi. Pada waktu gambar, kelas SampleOverlay hanya perlu menggambar bitmap ke layar. Dengan cara ini, rutinitas mahal seperti gambar teks tidak harus dilakukan untuk setiap bingkai.

Antarmuka pengguna (UI) terdiri dari komponen 2D, seperti menu dan tampilan heads-up (HUD), yang muncul di depan adegan Anda. Marble Maze mendefinisikan elemen UI berikut:

  • Item menu yang memungkinkan pengguna memulai permainan atau melihat skor tinggi.
  • Timer yang menghitung mundur selama tiga detik sebelum pemutaran dimulai.
  • Timer yang melacak waktu bermain yang berlalu.
  • Tabel yang mencantumkan waktu selesai tercepat.
  • Teks bertuliskan Dijeda saat permainan dijeda.

Marble Maze mendefinisikan elemen UI khusus game di UserInterface.h. Marble Maze mendefinisikan kelas ElementBase sebagai jenis dasar untuk semua elemen UI. Kelas ElementBase mendefinisikan atribut seperti ukuran, posisi, perataan, dan visibilitas elemen UI. Ini juga mengontrol bagaimana elemen diperbarui dan dirender.

class ElementBase
{
public:
    virtual void Initialize() { }
    virtual void Update(float timeTotal, float timeDelta) { }
    virtual void Render() { }

    void SetAlignment(AlignType horizontal, AlignType vertical);
    virtual void SetContainer(const D2D1_RECT_F& container);
    void SetVisible(bool visible);

    D2D1_RECT_F GetBounds();

    bool IsVisible() const { return m_visible; }

protected:
    ElementBase();

    virtual void CalculateSize() { }

    Alignment       m_alignment;
    D2D1_RECT_F     m_container;
    D2D1_SIZE_F     m_size;
    bool            m_visible;
};

Dengan menyediakan kelas dasar umum untuk elemen UI, kelas UserInterface , yang mengelola antarmuka pengguna, hanya perlu menyimpan koleksi objek ElementBase , yang menyederhanakan manajemen UI dan menyediakan manajer antarmuka pengguna yang dapat digunakan kembali. Marble Maze mendefinisikan jenis yang berasal dari ElementBase yang mengimplementasikan perilaku khusus game. Misalnya, HighScoreTable menentukan perilaku untuk tabel skor tinggi. Untuk informasi selengkapnya tentang jenis ini, lihat kode sumber.

Catatan

Karena XAML memungkinkan Anda untuk lebih mudah membuat antarmuka pengguna yang kompleks, seperti yang ditemukan dalam simulasi dan game strategi, pertimbangkan apakah akan menggunakan XAML untuk menentukan UI Anda. Untuk informasi tentang cara mengembangkan antarmuka pengguna di XAML dalam game DirectX UWP, lihat Memperpanjang sampel game, yang mengacu pada sampel permainan menembak DirectX 3D.

 

Memuat shader

Marble Maze menggunakan metode BasicLoader::LoadShader untuk memuat shader dari file.

Shader adalah unit dasar pemrograman GPU dalam game hari ini. Hampir semua pemrosesan grafis 3D didorong melalui shader, baik itu transformasi model dan pencahayaan adegan, atau pemrosesan geometri yang lebih kompleks, dari pengulir karakter hingga tesselulasi. Untuk informasi selengkapnya tentang model pemrograman shader, lihat HLSL.

Marble Maze menggunakan vertex dan piksel shader. Shader vertex selalu beroperasi pada satu verteks input dan menghasilkan satu vertex sebagai output. Shader piksel mengambil nilai numerik, data tekstur, nilai per vertex terinterpolasi, dan data lainnya untuk menghasilkan warna piksel sebagai output. Karena shader mengubah satu elemen pada satu waktu, perangkat keras grafis yang menyediakan beberapa alur shader dapat memproses serangkaian elemen secara paralel. Jumlah alur paralel yang tersedia untuk GPU dapat jauh lebih besar dari jumlah yang tersedia untuk CPU. Oleh karena itu, bahkan shader dasar dapat sangat meningkatkan throughput.

Metode MarbleMazeMain::LoadDeferredResources memuat satu shader vertex dan satu shader piksel setelah memuat overlay. Versi waktu desain dari shader ini didefinisikan dalam BasicVertexShader.hlsl dan BasicPixelShader.hlsl, masing-masing. Marble Maze menerapkan shader ini untuk bola dan labirin selama fase penyajian.

Proyek Marble Maze mencakup versi .hlsl (format waktu desain) dan .cso (format run-time) dari file shader. Pada waktu build, Visual Studio menggunakan fxc.exe effect-compiler untuk mengkompilasi file sumber .hlsl Anda ke dalam shader biner .cso. Untuk informasi selengkapnya tentang alat pengkompilasi efek, lihat Alat Pengkompilasi Efek.

Shader vertex menggunakan matriks model, tampilan, dan proyeksi yang disediakan untuk mengubah geometri input. Posisikan data dari geometri input diubah dan dihasilkan dua kali: sekali di ruang layar, yang diperlukan untuk penyajian, dan sekali lagi di ruang dunia untuk memungkinkan pemecah piksel melakukan perhitungan pencahayaan. Vektor normal permukaan diubah ke ruang dunia, yang juga digunakan oleh shader piksel untuk pencahayaan. Koordinat tekstur diteruskan melalui tidak berubah ke shader piksel.

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

Shader piksel menerima output shader vertex sebagai input. Shader ini melakukan perhitungan pencahayaan untuk meniru sorotan bermata lembut yang mengarah ke atas labirin dan selaras dengan posisi marmer. Pencahayaan paling kuat untuk permukaan yang menunjuk langsung ke arah cahaya. Komponen difus merunci hingga nol karena permukaan normal menjadi tegak lurus terhadap cahaya, dan istilah sekitar berkurang saat titik normal menjauh dari cahaya. Titik yang lebih dekat ke marmer (dan karena itu lebih dekat ke pusat sorotan) dinyalakan lebih kuat. Namun, pencahayaan di modulasi untuk titik di bawah marmer untuk mensimulasikan bayangan lembut. Dalam lingkungan nyata, objek seperti marmer putih akan mencerminkan sorotan ke objek lain di adegan. Ini diperkirakan untuk permukaan yang melihat bagian cerah dari marmer. Faktor iluminasi tambahan berada dalam sudut relatif dan jarak ke marmer. Warna piksel yang dihasilkan adalah komposisi tekstur sampel dengan hasil perhitungan pencahayaan.

float4 main(sPSInput input) : SV_TARGET
{
    float3 lightDirection = float3(0, 0, -1);
    float3 ambientColor = float3(0.43, 0.31, 0.24);
    float3 lightColor = 1 - ambientColor;
    float spotRadius = 50;

    // Basic ambient (Ka) and diffuse (Kd) lighting from above.
    float3 N = normalize(input.norm);
    float NdotL = dot(N, lightDirection);
    float Ka = saturate(NdotL + 1);
    float Kd = saturate(NdotL);

    // Spotlight.
    float3 vec = input.worldPos - marblePosition;
    float dist2D = sqrt(dot(vec.xy, vec.xy));
    Kd = Kd * saturate(spotRadius / dist2D);

    // Shadowing from ball.
    if (input.worldPos.z > marblePosition.z)
        Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));

    // Diffuse reflection of light off ball.
    float dist3D = sqrt(dot(vec, vec));
    float3 V = normalize(vec);
    Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
        * saturate(marbleRadius / dist3D);

    // Final composite.
    float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
    float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
    return float4(color * lightStrength, diffuseTexture.a);
}

Peringatan

Shader piksel yang dikompilasi berisi 32 instruksi aritmatika dan 1 instruksi tekstur. Shader ini harus berkinerja baik pada komputer desktop atau tablet berkinerja lebih tinggi. Namun, beberapa komputer mungkin tidak dapat memproses shader ini dan masih menyediakan kecepatan bingkai interaktif. Pertimbangkan perangkat keras khas audiens target Anda dan desain shader Anda untuk memenuhi kemampuan perangkat keras tersebut.

 

Metode MarbleMazeMain::LoadDeferredResources menggunakan metode BasicLoader::LoadShader untuk memuat shader. Contoh berikut memuat shader vertex. Format run-time untuk shader ini adalah BasicVertexShader.cso. Variabel anggota m_vertexShader adalah objek ID3D11VertexShader .

BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());

D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above

Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
    vertexShaderName,
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );

Variabel anggota m_inputLayout adalah objek ID3D11InputLayout . Objek input-layout merangkum status input tahap perakit input (IA). Salah satu pekerjaan tahap IA adalah membuat shader lebih efisien dengan menggunakan nilai yang dihasilkan sistem, juga dikenal sebagai semantik, untuk memproses hanya primitif atau simpul yang belum diproses.

Gunakan metode ID3D11Device::CreateInputLayout untuk membuat tata letak input dari array deskripsi elemen input. Array berisi satu atau beberapa elemen input; setiap elemen input menjelaskan satu elemen vertex-data dari satu buffer vertex. Seluruh set deskripsi elemen input menjelaskan semua elemen vertex-data dari semua buffer vertex yang akan terikat ke tahap IA.

layoutDesc dalam cuplikan kode di atas menunjukkan deskripsi tata letak yang digunakan Marble Maze. Deskripsi tata letak menjelaskan buffer vertex yang berisi empat elemen vertex-data. Bagian penting dari setiap entri dalam array adalah nama semantik, format data, dan byte offset . Misalnya, elemen POSITION menentukan posisi vertex di ruang objek. Ini dimulai pada byte offset 0 dan berisi tiga komponen floating-point (untuk total 12 byte). Elemen NORMAL menentukan vektor normal. Ini dimulai pada byte offset 12 karena muncul langsung setelah POSISI dalam tata letak, yang membutuhkan 12 byte. Elemen NORMAL berisi empat-komponen, 32-bit unsigned-integer.

Bandingkan tata letak input dengan struktur sVSInput yang ditentukan oleh shader vertex, seperti yang ditunjukkan dalam contoh berikut. Struktur sVSInput mendefinisikan elemen POSITION, NORMAL, dan TEXCOORD0 . Runtime DirectX memetakan setiap elemen dalam tata letak ke struktur input yang ditentukan oleh shader.

struct sVSInput
{
    float3 pos : POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

struct sPSInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
};

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

Dokumen Semantik menjelaskan masing-masing semantik yang tersedia secara lebih rinci.

Catatan

Dalam tata letak, Anda dapat menentukan komponen tambahan yang tidak digunakan untuk mengaktifkan beberapa shader untuk berbagi tata letak yang sama. Misalnya, elemen TANGENT tidak digunakan oleh shader. Anda dapat menggunakan elemen TANGENT jika Anda ingin bereksperimen dengan teknik seperti pemetaan normal. Dengan menggunakan pemetaan normal, juga dikenal sebagai pemetaan benjolan, Anda dapat membuat efek benjolan pada permukaan objek. Untuk informasi selengkapnya tentang pemetaan benjolan, lihat Pemetaan Benjolan (Direct3D 9).

 

Untuk informasi selengkapnya tentang tahap perakitan input, lihat Tahap Input-Assembler dan Memulai Tahap Perakitan Input.

Proses penggunaan shader vertex dan piksel untuk merender adegan dijelaskan di bagian Merender adegan nanti dalam dokumen ini.

Membuat buffer konstanta

Buffer Direct3D mengelompokkan kumpulan data. Buffer konstan adalah semacam buffer yang dapat Anda gunakan untuk meneruskan data ke shader. Marble Maze menggunakan buffer konstan untuk menahan tampilan model (atau dunia), dan matriks proyeksi untuk objek adegan aktif.

Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::LoadDeferredResources membuat buffer konstan yang nantinya akan menyimpan data matriks. Contoh membuat struktur D3D11_BUFFER_DESC yang menggunakan bendera D3D11_BIND_CONSTANT_BUFFER untuk menentukan penggunaan sebagai buffer konstan. Contoh ini kemudian meneruskan struktur tersebut ke metode ID3D11Device::CreateBuffer . Variabel m_constantBuffer adalah objek ID3D11Buffer .

// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};

// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;

constantBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags      = 0;
constantBufferDesc.MiscFlags           = 0;

// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;

DX::ThrowIfFailed(
    m_deviceResources->GetD3DDevice()->CreateBuffer(
        &constantBufferDesc,
        nullptr,    // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

Metode MarbleMazeMain::Update kemudian memperbarui objek ConstantBuffer , satu untuk labirin dan satu untuk marmer. Metode MarbleMazeMain::Render kemudian mengikat setiap objek ConstantBuffer ke buffer konstan sebelum setiap objek dirender. Contoh berikut menunjukkan struktur ConstantBuffer , yang ada di MarbleMazeMain.h.

// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    XMFLOAT4X4 model;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;

    XMFLOAT3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

Untuk lebih memahami bagaimana buffer konstan memetakan ke kode shader, bandingkan struktur ConstantBuffer di MarbleMazeMain.h dengan buffer konstanta ConstantBuffer yang didefinisikan oleh shader vertex di BasicVertexShader.hlsl:

cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

Tata letak struktur ConstantBuffer cocok dengan objek cbuffer . Variabel cbuffer menentukan register b0, yang berarti bahwa data buffer konstanta disimpan dalam register 0. Metode MarbleMazeMain::Render menentukan register 0 saat mengaktifkan buffer konstanta. Proses ini dijelaskan secara lebih rinci nanti dalam dokumen ini.

Untuk informasi selengkapnya tentang buffer konstanta, lihat Pengantar Buffer di Direct3D 11. Untuk informasi selengkapnya tentang kata kunci register, lihat mendaftar.

Memuat jala

Marble Maze menggunakan SDK-Mesh sebagai format run-time karena format ini menyediakan cara dasar untuk memuat data jala untuk aplikasi sampel. Untuk penggunaan produksi, Anda harus menggunakan format jala yang memenuhi persyaratan spesifik game Anda.

Metode MarbleMazeMain::LoadDeferredResources memuat data jala setelah memuat puncak dan shader piksel. Jala adalah kumpulan data puncak yang sering mencakup informasi seperti posisi, data normal, warna, bahan, dan koordinat tekstur. Jala biasanya dibuat dalam perangkat lunak penulisan 3D dan dikelola dalam file yang terpisah dari kode aplikasi. Marmer dan labirin adalah dua contoh jala yang digunakan permainan.

Marble Maze menggunakan kelas SDKMesh untuk mengelola jala. Kelas ini dideklarasikan dalam SDKMesh.h. SDKMesh menyediakan metode untuk memuat, merender, dan menghancurkan data jala.

Penting

Marble Maze menggunakan format SDK-Mesh dan menyediakan kelas SDKMesh hanya untuk ilustrasi. Meskipun format SDK-Mesh berguna untuk pembelajaran, dan untuk membuat prototipe, ini adalah format yang sangat dasar yang mungkin tidak memenuhi persyaratan sebagian besar pengembangan game. Kami menyarankan agar Anda menggunakan format jala yang memenuhi persyaratan spesifik permainan Anda.

 

Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::LoadDeferredResources menggunakan metode SDKMesh::Create untuk memuat data jala untuk labirin dan untuk bola.

// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

Memuat data tabrakan

Meskipun bagian ini tidak berfokus pada bagaimana Marble Maze mengimplementasikan simulasi fisika antara marmer dan labirin, perhatikan bahwa geometri jala untuk sistem fisika dibaca ketika jala dimuat.

// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_walls",
        m_collision.m_wallTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_Floor",
        m_collision.m_groundTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_floorSides",
        m_collision.m_floorTriList
        )
    );

m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);

Cara Anda memuat data tabrakan sangat bergantung pada format run-time yang Anda gunakan. Untuk informasi selengkapnya tentang bagaimana Marble Maze memuat geometri tabrakan dari file SDK-Mesh, lihat metode MarbleMazeMain::ExtractTrianglesFromMesh dalam kode sumber.

Memperbarui status permainan

Marble Maze memisahkan logika permainan dari logika penyajian dengan terlebih dahulu memperbarui semua objek adegan sebelum merendernya.

Struktur aplikasi Marble Maze menggambarkan perulangan permainan utama. Memperbarui adegan, yang merupakan bagian dari perulangan game, terjadi setelah peristiwa dan input Windows diproses dan sebelum adegan dirender. Metode MarbleMazeMain::Update menangani pembaruan UI dan game.

Memperbarui antarmuka pengguna

Metode MarbleMazeMain::Update memanggil metode UserInterface::Update untuk memperbarui status UI.

UserInterface::GetInstance().Update(
    static_cast<float>(m_timer.GetTotalSeconds()), 
    static_cast<float>(m_timer.GetElapsedSeconds()));

Metode UserInterface::Update memperbarui setiap elemen dalam koleksi UI.

void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}

Kelas yang berasal dari ElementBase (ditentukan dalam UserInterface.h) menerapkan metode Pembaruan untuk melakukan perilaku tertentu. Misalnya, metode StopwatchTimer::Update memperbarui waktu yang berlalu dengan jumlah yang disediakan dan memperbarui teks yang ditampilkan nanti.

void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}

Memperbarui adegan

Metode MarbleMazeMain::Update memperbarui game berdasarkan status komputer status saat ini ( GameState, disimpan di m_gameState). Ketika permainan dalam keadaan aktif (GameState::InGameActive), Marble Maze memperbarui kamera untuk mengikuti marmer, memperbarui bagian matriks tampilan dari buffer konstan, dan memperbarui simulasi fisika.

Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::Update memperbarui posisi kamera. Marble Maze menggunakan variabel m_resetKamera untuk menandai bahwa kamera harus diatur ulang agar terletak tepat di atas marmer. Kamera diatur ulang ketika permainan dimulai atau marmer jatuh melalui labirin. Saat menu utama atau layar tampilan skor tinggi aktif, kamera diatur di lokasi konstan. Jika tidak, Marble Maze menggunakan parameter timeDelta untuk menginterpolasi posisi kamera antara posisinya saat ini dan target. Posisi target sedikit di atas dan di depan marmer. Menggunakan waktu bingkai yang berlalu memungkinkan kamera untuk mengikuti secara bertahap, atau mengejar, marmer.

static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);

// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
    &targetEyePosition, 
    XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));

if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&eyePosition) 
            + ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition)) 
                * min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
            )
    );
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));

    m_camera->SetViewParameters(
        eyePosition, 
        marblePosition, 
        XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}

Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::Update memperbarui buffer konstan untuk marmer dan labirin. Model labirin, atau dunia, matriks selalu tetap menjadi matriks identitas. Kecuali untuk diagonal utama, yang elemennya semuanya, matriks identitas adalah matriks persegi yang terdiri dari nol. Matriks model marmer didasarkan pada matriks posisinya kali matriks rotasinya.

// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());

XMStoreFloat4x4(
    &m_marbleConstantBufferData.model, 
    XMMatrixTranspose(
        XMMatrixMultiply(
            marbleRotationMatrix, 
            XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
        )
    )
);

// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;

Untuk informasi tentang bagaimana metode MarbleMazeMain::Update membaca input pengguna dan mensimulasikan gerakan marmer, lihat Menambahkan input dan interaktivitas ke sampel Marble Maze.

Merender adegan

Saat adegan dirender, langkah-langkah ini biasanya disertakan.

  1. Atur buffer stensil kedalaman target render saat ini.
  2. Bersihkan tampilan render dan stensil.
  3. Siapkan vertex dan shader piksel untuk menggambar.
  4. Render objek 3D di adegan.
  5. Render objek 2D apa pun yang ingin Anda munculkan di depan adegan.
  6. Sajikan gambar yang dirender ke monitor.

Metode MarbleMazeMain::Render mengikat target render dan tampilan stensil kedalaman, membersihkan tampilan tersebut, menggambar adegan, lalu menggambar overlay.

Menyiapkan target render

Sebelum merender adegan, Anda harus mengatur buffer stensil kedalaman target render saat ini. Jika adegan Anda tidak dijamin menggambar setiap piksel di layar, hapus juga tampilan render dan stensil. Marble Maze membersihkan tampilan render dan stensil pada setiap bingkai untuk memastikan bahwa tidak ada artefak yang terlihat dari bingkai sebelumnya.

Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::Render memanggil metode ID3D11DeviceContext::OMSetRenderTargets untuk mengatur target render dan buffer stensil kedalaman sebagai yang saat ini.

auto context = m_deviceResources->GetD3DDeviceContext();

// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);

// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] = 
    { m_deviceResources->GetBackBufferRenderTargetView() };

context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
    m_deviceResources->GetBackBufferRenderTargetView(), 
    DirectX::Colors::Black);

context->ClearDepthStencilView(
    m_deviceResources->GetDepthStencilView(), 
    D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 
    1.0f, 
    0);

Antarmuka ID3D11RenderTargetView dan ID3D11DepthStencilView mendukung mekanisme tampilan tekstur yang disediakan oleh Direct3D 10 dan yang lebih baru. Untuk informasi selengkapnya tentang tampilan tekstur, lihat Tampilan Tekstur (Direct3D 10). Metode OMSetRenderTargets menyiapkan tahap output-merger dari alur Direct3D. Untuk informasi selengkapnya tentang tahap output-merger, lihat Tahap Output-Merger.

Menyiapkan vertex dan shader piksel

Sebelum Anda merender objek adegan, lakukan langkah-langkah berikut untuk menyiapkan puncak dan shader piksel untuk menggambar:

  1. Atur tata letak input shader sebagai tata letak saat ini.
  2. Atur shader puncak dan piksel sebagai shader saat ini.
  3. Perbarui buffer konstan dengan data yang harus Anda teruskan ke shader.

Penting

Marble Maze menggunakan sepasang vertex dan piksel shader untuk semua objek 3D. Jika gim Anda menggunakan lebih dari satu sepasang shader, Anda harus melakukan langkah-langkah ini setiap kali Anda menggambar objek yang menggunakan shader yang berbeda. Untuk mengurangi overhead yang terkait dengan perubahan status shader, kami sarankan Anda mengelompokkan panggilan render untuk semua objek yang menggunakan shader yang sama.

 

Bagian Memuat shader dalam dokumen ini menjelaskan bagaimana tata letak input dibuat saat shader vertex dibuat. Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::Render menggunakan metode ID3D11DeviceContext::IASetInputLayout untuk mengatur tata letak ini sebagai tata letak saat ini.

m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());

Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::Render menggunakan metode ID3D11DeviceContext::VSSetShader dan ID3D11DeviceContext::P SSetShader untuk mengatur shader vertex dan piksel sebagai shader saat ini.

// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
    0,                          // starting at the first sampler slot
    1,                          // set one sampler binding
    m_sampler.GetAddressOf());  // to use this sampler

Setelah MarbleMazeMain::Render mengatur shader dan tata letak inputnya, ia menggunakan metode ID3D11DeviceContext::UpdateSubresource untuk memperbarui buffer konstan dengan matriks model, tampilan, dan proyeksi untuk labirin. Metode UpdateSubresource menyalin data matriks dari memori CPU ke memori GPU. Ingat bahwa model dan komponen tampilan struktur ConstantBuffer diperbarui dalam metode MarbleMazeMain::Update . Metode MarbleMazeMain::Render kemudian memanggil metode ID3D11DeviceContext::VSSetConstantBuffers dan ID3D11DeviceContext::P SSetConstantBuffers untuk mengatur buffer konstan ini sebagai yang saat ini.

// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0);

m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

Metode MarbleMazeMain::Render melakukan langkah serupa untuk menyiapkan marmer yang akan dirender.

Merender labirin dan marmer

Setelah mengaktifkan shader saat ini, Anda dapat menggambar objek adegan Anda. Metode MarbleMazeMain::Render memanggil metode SDKMesh::Render untuk merender jala labirin.

m_mazeMesh.Render(
    m_deviceResources->GetD3DDeviceContext(), 
    0, 
    INVALID_SAMPLER_SLOT, 
    INVALID_SAMPLER_SLOT);

Metode MarbleMazeMain::Render melakukan langkah serupa untuk merender marmer.

Seperti disebutkan sebelumnya dalam dokumen ini, kelas SDKMesh disediakan untuk tujuan demonstrasi, tetapi kami tidak merekomendasikannya untuk digunakan dalam game berkualitas produksi. Namun, perhatikan bahwa metode SDKMesh::RenderMesh , yang dipanggil oleh SDKMesh::Render, menggunakan metode ID3D11DeviceContext::IASetVertexBuffers dan ID3D11DeviceContext::IASetIndexBuffer untuk mengatur buffer vertex dan indeks saat ini yang menentukan jala, dan metode ID3D11DeviceContext::D rawIndexed untuk menggambar buffer. Untuk informasi selengkapnya tentang cara bekerja dengan buffer puncak dan indeks, lihat Pengenalan Buffer di Direct3D 11.

Menggambar antarmuka pengguna dan overlay

Setelah menggambar objek adegan 3D, Marble Maze menarik elemen UI 2D yang muncul di depan adegan.

Metode MarbleMazeMain::Render berakhir dengan menggambar antarmuka pengguna dan overlay.

// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());

m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();

Metode UserInterface::Render menggunakan objek ID2D1DeviceContext untuk menggambar elemen UI. Metode ini mengatur status gambar, menggambar semua elemen UI aktif, lalu memulihkan status gambar sebelumnya.

void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(orientation2D);

    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = m_d2dContext->EndDraw();
    if (hr != D2DERR_RECREATE_TARGET)
    {
        DX::ThrowIfFailed(hr);
    }

    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}

Metode SampleOverlay::Render menggunakan teknik serupa untuk menggambar bitmap overlay.

Menyajikan adegan

Setelah menggambar semua objek adegan 2D dan 3D, Marble Maze menyajikan gambar yang dirender ke monitor. Ini menyinkronkan gambar ke vertikal kosong untuk memastikan bahwa waktu tidak menghabiskan waktu menggambar bingkai yang tidak akan pernah benar-benar ditampilkan pada tampilan. Marble Maze juga menangani perubahan perangkat saat menyajikan adegan.

Setelah metode MarbleMazeMain::Render kembali, perulangan game memanggil metode DX::D eviceResources::P resent untuk mengirim gambar yang dirender ke monitor atau tampilan. Metode DX::D eviceResources::P resent memanggil IDXGISwapChain::P resent untuk melakukan operasi saat ini, seperti yang ditunjukkan dalam contoh berikut:

// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
HRESULT hr = m_swapChain->Present(1, 0);

Dalam contoh ini, m_swapChain adalah objek IDXGISwapChain1 . Inisialisasi objek ini dijelaskan di bagian Menginisialisasi Direct3D dan Direct2D dalam dokumen ini.

Parameter pertama ke IDXGISwapChain::P resent, SyncInterval, menentukan jumlah kosong vertikal untuk menunggu sebelum menyajikan bingkai. Labirin Marmer menentukan 1 sehingga menunggu hingga kosong vertikal berikutnya.

Metode IDXGISwapChain::P resent mengembalikan kode kesalahan yang menunjukkan bahwa perangkat dihapus atau gagal. Dalam hal ini, Marble Maze menginisialisasi ulang perangkat.

// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED)
{
    HandleDeviceLost();
}
else
{
    DX::ThrowIfFailed(hr);
}

Langkah berikutnya

Baca Menambahkan input dan interaktivitas ke sampel Marble Maze untuk informasi tentang beberapa praktik utama yang perlu diingat saat Anda bekerja dengan perangkat input. Dokumen ini membahas bagaimana Marble Maze mendukung sentuhan, akselerometer, pengontrol permainan, dan input mouse.