Dasar Pemrograman 3 Dimensi Menggunakan Microsoft DirectX
By LynxLuna
1
Daftar Isi Daftar Isi.......................................................................................... 2 Pendahuluan .................................................................................... 6 Asumsi ................................................................................................... 6 Development Tools .................................................................................... 7 Hardware................................................................................................ 7 Instalasi ................................................................................................. 7 Coding Convention .................................................................................... 8
Tentang DirectX .............................................................................. 10 Buzzword 'DirectX' ....................................................................................10 Komponen-komponen yang membangun .................................................................10
Kenapa harus DirectX ................................................................................11 Arsitektur DirectX ....................................................................................11 Component Object Model...................................................................................11 Arsitektur Global DirectX ...................................................................................12 Library D3DX ..................................................................................................12
Math before you code ....................................................................... 13 Vector 3 Dimensi......................................................................................13 Kesamaan Vector.............................................................................................16 Menghitung magnitude vector .............................................................................16 Normalize Vector ............................................................................................17 Penambahan vector..........................................................................................18 Pengurangan vector .........................................................................................18 Perkalian skalar ..............................................................................................19 Dot Products ..................................................................................................20 Cross Products ................................................................................................20
Matrix...................................................................................................21 Kesamaan, perkalian skalar, dan penambahan..........................................................22 Perkalian Matrix ..............................................................................................22 Matrix Identitas ..............................................................................................23 Inverse .........................................................................................................23 Transpose Matrix .............................................................................................24 Matrix D3DX ...................................................................................................24
Transformasi Dasar ...................................................................................27 Matrix Translasi ..............................................................................................28 Matrix Rotasi..................................................................................................29 Matrix Scaling.................................................................................................30 Kombinasi Transformasi.....................................................................................31 Fungsi-fungsi untuk menangani transformasi ............................................................32
Plane (Bidang).........................................................................................33 D3DXPLANE....................................................................................................34 Hubungan Point dan Plane..................................................................................34 Konstruksi .....................................................................................................35 Me-normalize plane..........................................................................................36 Transformasi Plane ..........................................................................................36 Point terdekat dalam plane terhadap point lain........................................................37
Rays .....................................................................................................37 Pengertian Ray ...............................................................................................37 Perpotongan Rays dan Plane ...............................................................................38
Program DirectX Pertama................................................................... 40 Dasar Pemrograman Windows ......................................................................40 Struktur Aplikasi Windows ..................................................................................40 Window dan Message. .......................................................................................46 Membuat dan menampilkan window ......................................................................46
Pengenalan Direct3D .................................................................................53 REF device ....................................................................................................54
2
D3DDEVTYPE ..................................................................................................54
Beberapa hal yang perlu diperhatikan ............................................................54 Surface.........................................................................................................54 Multisample ...................................................................................................55 Pixel Format ..................................................................................................56 Memory Pool ..................................................................................................57 Swap Chain dan Page Flipping .............................................................................57 Depth Buffer ..................................................................................................58 Vertex Processing ............................................................................................59 Device Capabilities ..........................................................................................59
Menambahkan Direct3D..............................................................................60 Membuat Direct3D Object ..................................................................................60 Checking Hardware Vertex Processing....................................................................60 Mengisi structure D3DPRESENT_PARAMETERS .........................................................61 Membuat object IDirect3DDevice9...................................................................63
Operasi Dasar Device.................................................................................64 Membersihkan layar .........................................................................................64 Menampilkan ke layar .......................................................................................65 Melepaskan object Direct3D................................................................................66
Update the code!! ....................................................................................66 Implementasi InitDirect3D.............................................................................67 Implementasi DoFrame .....................................................................................69 Implementasi Cleanup .....................................................................................69 Implementasi EnterMessageLoop ......................................................................70 Implementasi ErrorMessage.............................................................................71 Implementasi CenterWindow.............................................................................72 Perubahan pada InitWindow .............................................................................73 Perubahan pada WndProc..................................................................................74 Menambahkan library Direct3D dan WinMM kemudian mem-build aplikasi .........................75
Rendering Pipeline ........................................................................... 77 Representasi model ..................................................................................77 Vertex Format ................................................................................................78 Triangles.......................................................................................................79 Index ...........................................................................................................79
Virtual Camera ........................................................................................80 Rendering Pipeline ...................................................................................81 Local Space ...................................................................................................81 World Space...................................................................................................82 View Space ....................................................................................................83 BackFace Culling .............................................................................................84 Lighting ........................................................................................................85 Clipping ........................................................................................................85 Proyeksi........................................................................................................86 Viewport transform ..........................................................................................87 Rasterization..................................................................................................89
Menggambar di DirectX...................................................................... 90 Vertex Buffer dan Index Buffer.....................................................................90 Membuat Vertex Buffer dan Index Buffer ................................................................90 Mengakses Buffer Memory ..................................................................................92 Mendapatkan informasi Vertex Buffer dan Index Buffer...............................................94 Render States.................................................................................................94
Persiapan untuk menggambar ......................................................................95 Menggambar Vertex dan Index Buffer .............................................................95 IDirect3DDevice9::DrawPrimitive ...............................................................96 IDirect3DDevice9::DrawIndexedPrimitive ...................................................96 Begin dan End Scene ........................................................................................98
D3DX Geometry .......................................................................................98 Let's Code!! ............................................................................................99 Mengedit Skeleton Code .................................................................................. 100
Sample Code Screenshot .......................................................................... 105
3
Segitiga ...................................................................................................... 105 Kubus......................................................................................................... 105 Teapot ....................................................................................................... 106 Bentuk-bentuk.............................................................................................. 106
Warna ..........................................................................................107 Representasi warna ................................................................................ 107 Vertex Color ......................................................................................... 108 Shading ............................................................................................... 109 Code to The End! ................................................................................... 109 Sample Code Screenshot .......................................................................... 112 Segitiga ...................................................................................................... 112 Kubus......................................................................................................... 112
Lighting/Pencahayaan ......................................................................113 Komponen warna ................................................................................... 113 Material .............................................................................................. 113 Vertex Normal....................................................................................... 115 Light Sources ........................................................................................ 117 Code Your Lights.................................................................................... 120 Sample Code Screenshot .......................................................................... 123 Pyramid...................................................................................................... 123 Directional Lights .......................................................................................... 123 Point Lights ................................................................................................. 124 Spot Lights .................................................................................................. 124
Texturing......................................................................................125 Texture Coordinate ................................................................................ 125 Membuat dan mengaktifkan texture ............................................................ 126 Filters................................................................................................. 127 Mipmap ............................................................................................... 128 Mipmap filter ............................................................................................... 128 Menggunakan mipmap dalam Direct3D ................................................................. 129
Address Mode........................................................................................ 129 Let's Code The Textured Quad ................................................................... 131 Samples ScreenShot ................................................................................ 135 Quad Plain................................................................................................... 135 TexQuad ..................................................................................................... 135 CubeTex ..................................................................................................... 136
Multitexturing ....................................................................................... 137 ScreenShot Multitextured Quad.......................................................................... 138
Blending .......................................................................................139 Persamaan Blending................................................................................ 139 Blend Factor ......................................................................................... 141 Transparansi ......................................................................................... 141 Alpha Channels ............................................................................................. 142 Menentukan sumber alpha................................................................................ 142
Menggunakan DirectX Texture Tool untuk Alpha Channel ................................... 143 Code and Blend ..................................................................................... 144 Sample ScreenShot ................................................................................. 147 Material Blend .............................................................................................. 147 Texture Alpha Blend....................................................................................... 147
Stenciling......................................................................................148 Menggunakan Stencil Buffer ...................................................................... 149 Meminta Alokasi Stencil Buffer .......................................................................... 150 Stencil Test ................................................................................................. 150 Mengkontrol Stencil Test ................................................................................. 150 Mengupdate stencil buffer................................................................................ 152 Stencil Write Mask ......................................................................................... 152
Aplikasi I : Mirror ................................................................................... 153 Metode Implementasi Mirror ............................................................................. 154 Implementasi Mirror dalam Code........................................................................ 155
4
Aplikasi II : Planar Shadow ........................................................................ 157 Shadow dari parallel light ................................................................................ 158 Shadow dari point light ................................................................................... 159 Shadow Matrix .............................................................................................. 159 Menggunakan stencil buffer untuk mengatasi double blending..................................... 160 Implementasi Shadow dalam Code ...................................................................... 161
ScreenShot Sample Code .......................................................................... 163 Mirror ........................................................................................................ 163 Shadow ...................................................................................................... 163 Shadow Mirror .............................................................................................. 164
Penutup .......................................................................................165
5
Pendahuluan Pemrograman grafis merupakan pekerjaan yang unik, menyenangkan, sekaligus menantang dalam dunia komputer sekarang ini. Hanya programmer grafis yang bisa merealisasikan dunia maya yang mungkin hanya ada dalam mimpi ke dalam layar komputer Anda. Perkembangan teknologi grafis yang begitu pesat mulai awal milenium ini menuntut upgrade kemampuan dan pengetahuan. Dunia grafis yang dulu hanya berkutat pada dunia dua dimensi mulai berlari melesat begitu cepat masuk ke dalam dunia 3 Dimensi. Dunia 3 dimensi yang pada mulanya hanya dapat dinikmati oleh para ilmuwan di lab universitas atau di lab perusahaan besar kini dapat dinikmati oleh sebagian besar pemilik PC rumahan biasa. PC rumahan biasa kini bisa menampilkan visualisasi dan simulasi 3D, games 3D yang realistis berkat perkembangan teknologi hardware maupun software 3 Dimensi. Dimulai dengan diperkenalkannya hardware Graphic Accelerator yang memungkinkan ditampilkannya gambar 3 Dimensi pada PC rumahan, kemudian dengan sangat cepat, perkembangan teknologi 3 Dimensi ini menjalar, tidak hanya untuk keperluan pengetahuan, tp juga untuk hiburan, misalkan dalam game 3 Dimensi. Microsoft Windows, sebuah sistem operasi yang paling banyak dipakai untuk PC rumahan pada mulanya bukanlah suatu platform favorit untuk pengembangan aplikasi grafik. Sistem Operasi ini tidak mengizinkan akses langsung ke hardware, sehingga untuk menggambar ke dalam layar relatif lebih lambat dibandingkan dengan mengakses langsung hardware yang biasa dilakukan aplikasi grafik yang berjalan di atas DOS. Hal tersebut pelan2 mulai berganti dengan ditemukannya DirectX oleh Microsoft. DirectX mengizinkan programmer untuk mengakses hardware secara langsung dengan cara yang seragam. Sebelumnya, programmer harus melakukan perlakuan yg berbeda jika ingin mengakses hardware video secara langsung bila berbeda produsen. Dengan DirectX, programmer dapat mengaksesnya secara langsung dengan perintah yang sama karena operasi yang vendor-specific sudah ditangani oleh driver. Perkembangan Microsoft DirectX begitu cepat. Sampai saat ini DirectX sudah mencapai versinya yang ke-10. Akan tetapi, DirectX 10 ini hanya bisa diakses pada Windows Vista. Untuk tutorial dalam buku ini kesemuanya ditulis menggunakan DirectX 9 yang bisa berjalan di Windows 98 SE ke atas. DirectX 9 sudah memuat teknologi yang amat mumpuni untuk aplikasi 3D. DirectX 9 sudah mendukung vertex dan pixel shader yang memungkinkan programmer memrogram GPU untuk menghasilkan suatu efek yang diinginkan seperti per-pixel lighting, distortion, dan lain sebagainya.
Asumsi Penulis berasumsi bahwasanya pembaca sudah mengetahui dan menguasai bahasa yang akan digunakan dalam seluruh buku ini, yaitu C++. Pengetahuan Aljabar Linear akan menjadi keuntungan tersendiri, dan pengetahuan tentang Object-Oriented Programming akan semakin mempermudah pembaca dalam memahami isi buku ini.
6
Development Tools Development tools yang digunakan dalam buku ini adalah Microsoft Visual C++ 2005. Anda bisa menggunakan versi apa saja, boleh versi Express yang gratis, maupun professional. Untuk menggunakan Microsoft Visual C++ 2005 Express Edition, anda memerlukan Windows Platform SDK. Untuk edisi Standard dan Professional, Platform SDK sudah terinstall bersama-sama dengan Visual Studio. Selain itu, perlu juga Microsoft DirectX SDK yang terbaru, ketika buku ini ditulis, DXSDK terbaru dirilis pada bulan Februari 2007. Jadi, untuk mengkompilasi seluruh sample di buku ini Anda memerlukan : 1. Microsoft Visual C++ 2005 Express, Standard, maupun Professional 2. Microsoft Windows Platform SDK (jika menggunakan MSVC Express) 3. Microsoft DirectX SDK terbaru
Hardware Hardware yang diperlukan supaya contoh-contoh dalam buku ini dapat tampil semuanya adalah hardware yang mendukung Microsoft DirectX 9 secara penuh. Contohnya adalah GeForce FX 5200 ke atas atau ATI Radeon 9700 ke atas. Disarankan yang mendukung hardware Pixel Shader dan Vertex Shader 2.0.
Instalasi Setelah menginstalasi Visual C++, Anda perlu menginstalasi Microsoft DirectX SDK dan mensetting Visual C++ Anda supaya dapat menggunakan Microsoft DirectX SDK dalam program Anda. Untuk menginstall Microsoft DirectX SDK, cukup dengan mengeksekusi file SETUP.EXE. Maka secara default akan terinstall di c:\Program Files\Microsoft DirectX SDK (February 2007). Setelah Microsoft DirectX SDK terinstall, kita perlu mensetting MSVC supaya dapat mengakses library DirectX tersebut. Untuk mensettingnya, buka menu Tools > Options kemudian pilih Project and Solutions > VC++ Directories.
7
Pilih Include Files pada Show Directories For, kemudian click tombol
untuk menambahkan
directory Include pada DirectX SDK. Jika DXSDK Anda terinstall di c:\Program Files\Microsoft DirectX SDK (February 2007), maka tambahkan directory c:\Program Files\Microsoft DirectX SDK (February 2007)\Include untuk Include Files.
Kemudian pindahkan pilihan ke LIBRARY FILES pada Show Directories For. Klik tombol kemudian tambahkan directory C:\Program Files\Microsoft DirectX SDK (February 2007)\Lib\x86 jika DXSDK Anda terinstall di C:\Program Files\Microsoft DirectX SDK (February 2007)\.
Coding Convention Saya menggunakan coding convention yang sama dengan Microsoft yaitu menggunakan Hungarian Notation. Dengan prefix g_ untuk global variable, m_ untuk private member dalam class, dan p untuk pointer. Untuk integer, saya memakai prefix n jika menyatakan suatu total jumlah dan i jika menyatakan suatu index. Mari kita lihat coding convention yang saya pakai : class nMyClass { public : nMyClass ( Vector3D &v ) ; ~nMyClass ( void ) ; private : Vector3D m_Position ; } ; Kemudian untuk if dan for, saya menggunakan style spt ini for ( i = 0 ; i < m_nCount ; i ++ ) { . . . }
8
if ( x == y && i != 0 ) { . . . } Saya selalu menyisipkan satu spasi setiap ada tanda spt kurung, sama dengan dan sebagainya. Dan saya tidak pernah menggunakan style syntax if atau for one-liner spt ini : if ( x == y ) dosomething( ) ;
Hal ini semata-mata supaya code saya mudah dibaca. Semua code yang saya tulis dalam buku ini menekankan pada kejelasan bukan performa sehingga mungkin sebagian besar dari code yang saya tulis dalam sample buku ini tidak optimal.
9
Tentang DirectX Microsoft DirectX. Mungkin Anda sering mendengarnya dalam iklan graphics card atau dari teman Anda yg faham tentang hardware. Tp sebenarnya apa DirectX itu? DirectX sebenarnya adalah sebuah kumpulan API (Application Programming Interface) yang paling banyak digunakan dalam platform Microsoft Windows untuk membuat game atau aplikasi multimedia lainnya. Bicara tentang DirectX, tidak bisa dilepaskan dari game. Beberapa tahun sebelum adanya DirectX, para pembuat game repot mengatasi kompatibilitas antara hardware satu dengan lainnya. Satu game mungkin bisa dipakai dalam satu hardware tetapi tidak bisa dimainkan di hardware lainnya. Kemudian Microsoft menemukan DirectX yang menyediakan sebuah API yang seragam yang dijalankan pada PC dengen konfigurasi hardware yang berbeda-beda. Setelah DirectX direlease oleh Microsoft, jumlah game dan aplikasi multimedia yang diproduksi untuk Windows jumlahnya semakin banyak.
Buzzword 'DirectX' DirectX adalah sebuah kumpulan API dari Microsoft yang memungkinkan developer game untuk mengakses hardware secara langsung dan berjalan di atas platform Microsoft Windows. Sampai pada versinya yang ke-10 saat ini, DirectX API menyediakan akses ke beberapa bagian hardware seperti grafik, suara, networking, input, menggunakan satu interface yang standard. Dengan interface ini, developer dapat dengan mudah memrogram suatu aplikasi multimedia secara seragam, tidak peduli merek dan jenis hardware yang dipakai.
Komponen-komponen yang membangun DirectX API terbagi dalam beberapa komponen. Tiap komponen mewakili satu aspek dalam sistem. Tiap komponen dapat dipakai sendiri-sendiri. •
DirectX Graphics. Komponen ini adalah yang menangani output grafis baik 2D maupun 3D.
•
DirectInput. Semua input ditangani oleh API ini. DirectInput mendukung berbagai macam hardware dr keyboard, mouse, joystick, dan lain-lain.
•
DirectPlay. Support untuk networking menggunakan DirectPlay. DirectPlay menyediakan akses high-level untuk networking.
•
DirectSound. Jika Anda memerlukan suara sebagai background music atau sound effect, maka API inilah yang dipakai. DirectSound memberikan kontrol penuh terhadap suara yg dimainkan dalam bentuk Waveform.
•
DirectMusic. API yang digunakan untuk memainkan soundtrack dinamis dari MIDI atau synthesizer lain. Musik bisa dimainkan dengan mengubah pitch, tempo, dan lain sebagainya.
•
DirectShow. Jika Anda ingin menambahkan cutscene ke dalam aplikasi atau game Anda, DirectShow adalah API yang Anda gunakan. Komponen inilah yg merupakan komponen utama yang digunakan dalam media player spt Windows Media Player.
•
DirectSetup. Menambahkan kemampuan untuk melakukan instalasi versi terakhir DirectX ke komputer user.
10
Kenapa harus DirectX Sebelum Windows direlease, developer membuat game untuk DOS. Developer langsung mengakses hardware karena memang hal tersebut diizinkan oleh DOS. Keuntungannya adalah developer memiliki akses penuh ke dalam hardware sehingga dapat mengkontrol dan menggunakan kemampuan hardware secara penuh. Kekurangannya adalah setiap developer harus membuat driver untuk hardware tersebut, bahkan untuk VGA dan sound card, developer harus berjuang membuat gamenya untuk dapat berjalan di VGA dan sound card tertentu. Membuat aplikasi atau game yang mendukung resolusi di atas 320x240, memerlukan developer untuk mengakses video register dan menulis langsung ke video memory. Kesulitan ini ditambah dengan perbedaan antara hardware satu dengan lainnya. Windows 3.1. pun direlease oleh Microsoft. Windows berjalan di atas DOS, dan juga ada pembatasan dalam mengakses hardware. Satu-satunya cara menggambar di Windows saat itu adalah dengan menggunakan GDI (Generic Display Interface) yang jauh lebih lambat daripada menulis langsung ke hardware sehingga pada waktu itu, game untuk Windows umumnya adalah game yang tidak memerlukan 'gerak cepat' seperti game kartu sedangkan game action dan game-game yang memerlukan gerak cepat dan dinamis tetap dibuat dalam DOS. Pertama kali Microsoft me-release WinG, sebuah API untuk membuat game di Windows untuk mengatasi kekurangan Windows 3.1. tersebut. Ini adalah mbahnya DirectX yang kita kenal sekarang ini. Sampai saat ini, DirectX sudah mencapai versinya yang ke-10. Tiap versi mempunyai penambahan feature dan penyempurnaan fungsi. Untuk DirectX 10 ini, penambahan fungsi yang terlihat adalah penambahan Geometry Shader dan penghilangan Fixed Functionality dari DirectX. DirectX 10 hanya bisa diakses di Windows Vista. Untuk aplikasi dalam buku ini keseluruhannya dibuat dengan DirectX 9.
Arsitektur DirectX DirectX membuat hidup developer game dan multimedia menjadi lebih mudah. Developer mendapatkan kumpulan library dan support dari Microsoft. DirectX berevolusi dengan cepat, menambah feature dan kecepatan untuk hardware terbaru. Microsoft ingin supaya tiap release dari DirectX mempunyai backward compatibility sehingga aplikasi yang ditulis pada versi sebelumnya berjalan tanpa masalah di versi yang terbaru. Oleh karena itu, Microsoft membuat DirectX berjalan di atas teknologi COM (Component Object Model).
Component Object Model COM (Component Object Model) adalah suatu arsitektur berbasis objek. COM mengekspos kumpulan interface yang mengandung method yang digunakan developer untuk mengakses DirectX. COM Objects biasanya berupa DLL yang teregistrasi ke sistem. COM Object hampir sama dengan C++ Object, perbedaannya adalah untuk mengakses COM Object diperlukan interface. Arsitektur ini mempunyai keuntungan, yaitu, satu object bisa mempunyai banyak interface sehingga mengizinkan backward compatibility. Contoh, tiap release DirectX mempunyai satu interface Direct3D terbaru tetapi juga mempunyai interface ke versi yang lebih lama, sehingga aplikasi yang ditulis menggunakan DirectX 8 tetap berjalan tanpa masalah di DirectX 9.
11
Arsitektur Global DirectX Win32 Application Direct3D API HAL Layer Device Driver Interface (DDI) Hardware
Gambar 1.1. Arsitektur Integrasi DirectX DirectX dibangun dalam 2 layer, yaitu API layer dan HAL layer. API layer berkomunikasi dengan hardware melalui HAL (Hardware Abstraction Layer). HAL menyediakan interface standard ke DirectX dan juga berkomunikasi langsung dengan hardware melalui Device Driver. Karena HAL perlu mengetahui komunikasi dengan Device Driver, pembuat hardware lah yang membuat HAL untuk hardware yang diproduksinya. Developer mengakses HAL secara tidak langsung melalui API DirectX. Gambar 1.1. menggambarkan bagaimana HAL dalam arsitektur DirectX.
Library D3DX Sejak DirectX 7, Microsoft memasukkan D3DX (Direct3D eXtension) library. Library ini berisi fungsifungsi, class, dan interface yang memudahkan operasi 3D yang umum dilakukan seperti operasi matematik, texture, image, mesh, dan shader. Kita menggunakan D3DX dalam seluruh buku ini untuk memfokuskan kita pada material yg lebih menarik daripada sekedar mengimplementasikan hitungan matematis.
12
Math before you code Sebelum menyelam ke dalam code, ada baiknya kita membahas dahulu sedikit tentang matematika. Ya, matematika. Seorang programmer grafis tidak akan bisa mengcode tanpa mengetahui matematika. Game tak akan bisa dibangun tanpa matematika. Selain itu, semua sample di buku ini menggunakan dasar matematika. Oleh karena itulah, saya membahas matematika sebelum kita masuk ke dalam code.
Vector 3 Dimensi Dalam geometri, vector didefinisikan sebagai garis yang mempunyai arah seperti terlihat dalam gambar 1.2.
a
c
b
Kepala
Ekor
u v
Gambar 1.2. Vector yang didefinisikan pada sembarang sistem koordinat Lokasi bukanlah properti dari suatu vector, sehingga vector dengan arah dan panjang yang sama sehingga terlihat paralel satu sama lain, maka dua vector tersebut adalah sama. Contoh dalam gambar 1.2. di atas adalah vector u dan v. Selain itu, kita juga harus mengetahui tentang sistem koordinat dalam aplikasi 3D. Ada 2 sistem koordinat yaitu left handed system dan right handed system, perbedaan keduanya adalah dalam arah sumbu Z positif. Secara default, DirectX menggunakan Left-Handed System. Y+
Y+
Z+
X+
X+
Z+
Gambar 1.3. Right Handed System (Kiri) dan Left Handed System (Kanan)
13
Karena lokasi vector tidak mengubah propertinya, kita bisa menempatkan vector sehingga ekornya berimpit pada titik origin O (0,0) pada sistem koordinat. Posisi inilah yang disebut sebagai posisi standard. Dalam posisi standard ini, kita bisa mendeskripsikan vector dengan menyebutkan posisi
b
c
koordinat dari kepala vector tersebut.
u v=
Gambar 1.4. Vector dalam posisi standard. Catatan Khusus : Karena kita bisa mendeskripsikan vector dengan posisi koordinat kepala vector, sering terjadi kesalahan dalam membedakan vector dan point. Point menunjukkan suatu lokasi, sedangkan vector menunjukkan suatu arah dengan panjang tertentu. Kita menyatakan vector dengan huruf tebal, terkadang huruf kecil, terkadang huruf besar, tergantung kebutuhan. Kita tidak terpaku pada aturan notasi matematis di sini. Contoh untuk vector 2, 3, dan 4 dimensi : v = (vx, vy), N = (Nx, Ny, Nz), q = (qx, qy, qz, qw). Y+
Z+
j k 0
i
X+
Gambar 1.5. Unit Vector
14
Perhatikan gambar 1.5! Pada gambar tersebut saya akan memperkenalkan vector spesial, yaitu unit vector dan zero-vector. Zero vector adalah sebuah vector dengan seluruh komponennya adalah 0. Dinyatakan dengan 0 = (0, 0, 0). Tiga vector selanjutnya disebut unit vector untuk ℜ3 yaitu vector i, j, dan k. Vector-vector tersebut mempunyai panjang 1 unit. Vector i, j, dan k dinyatakan dengan i = (1, 0, 0) ; j = (0, 1, 0) ; dan k = (0, 0, 1). Dalam library D3DX vector didefinisikan sebagai berikut : typedef struct _D3DXVECTOR3 : public D3DVECTOR { public : D3DXVECTOR3( ) {}; D3DXVECTOR3( CONST FLOAT * ); D3DXVECTOR3( CONST D3DVECTOR& ); D3DXVECTOR3( FLOAT x, FLOAT y, FLOAT z ); // casting operator FLOAT* ( ); operator CONST FLOAT* ( ) const; // assignment operators D3DXVECTOR3& operator += D3DXVECTOR3& operator -= D3DXVECTOR3& operator *= D3DXVECTOR3& operator /=
( ( ( (
CONST CONST FLOAT FLOAT
D3DXVECTOR3& ); D3DXVECTOR3& ); ); );
// unary operators D3DXVECTOR3 operator + ( ) const; D3DXVECTOR3 operator - ( ) const; // binary operators D3DXVECTOR3 operator + ( CONST D3DXVECTOR3& ) const; D3DXVECTOR3 operator - ( CONST D3DXVECTOR3& ) const; D3DXVECTOR3 operator * ( FLOAT ) const; D3DXVECTOR3 operator / ( FLOAT ) const; friend D3DXVECTOR3 operator * ( FLOAT, CONST struct D3DXVECTOR3& ); BOOL operator == ( CONST D3DXVECTOR3& ) const; BOOL operator != ( CONST D3DXVECTOR3& ) const; } D3DXVECTOR3, *LPD3DXVECTOR3; Structure di atas merupakan turunan dari D3DVECTOR yang didefinisikan sebagai berikut : typedef struct _D3DVECTOR { FLOAT x ; FLOAT y ; FLOAT z ; } D3DVECTOR ; Seperti scalar, vector juga punya aritmetika khusus seperti yang terlihat dalam definisii D3DXVECTOR3. Implementasi dari perhitungan tersebut sudah diimplementasikan dalam library D3DX, kita tinggal memakainya.
15
Catatan Khusus : Walaupun kita paling sering menggunakan vector 3D, terkadang kita memakai vector 2D dan 4D (quarternions) dalam pemrograman grafis 3D. D3DX Library menyediakan class D3DXVECTOR2 dan D3DXVECTOR4 untuk menyatakan vector 2D dan 4D. Seluruh aritmetika untuk vector 3D dapat dipakai dalam vector lain, kecuali cross product.
Kesamaan Vector Secara geometri, dua vector dinyatakan sama jika mempunyai arah dan panjang yang sama. Secara aljabar, kita katakan dua vector adalah sama jika mempunyai dimensi yang sama dan semua komponennya sama. Contoh : (ux, uy, uz) = (vx, vy, vz) jika ux = vx, uy = vy, dan uz = vz. Dalam code kita mengetest apakah dua vector itu sama dengan menggunakan tanda kesamaan (==) yang sudah dioverload. D3DXVECTOR3 u( 1.0f, 0.0f, 1.0f ) ; D3DXVECTOR3 v( 0.0f, 1.0f, 0.0f ) ; if ( u == v ) { return true ; } Atau kita bisa mengetest apakah dua vector tidak sama dengen menggunakan tanda ketidaksamaan (!=) yang sudah dioverload. if ( u != v ) { return true ; }
Catatan Khusus : Ketika membandingkan dua buah floating point (bilangan pecahan), harus berhati-hati, karena dua buah bilangan float yang sama, bisa saja berbeda. Sehingga tidak benar-benar sama tetapi hampir sama. Oleh karena itu, kitat mendefinisikan EPSILON sebagai referensi, yaitu bilangan yang amat kecil. Kita bisa menganggap dua bilangan float sama jika selisihnya lebih kecil atau sama dengan EPSILON. Lihat contoh berikut : const float EPSILON = 0.0001f ; bool IsEquals( float a, float b ) { return ( fabs( a - b ) < EPSILON ) ; }
Menghitung magnitude vector Magnitude vector adalah panjang dari vector. Dengan mengetahui komponen dari vector tersebut, kita bisa menghitung magnitude vector dengan rumus : 2
2
u = ux + uy + uz
2
16
Tanda ∥u∥ artinya adalah panjang dari vector u. Contoh: cari magnitude dari vector u = (4, 7, 3) dan v = ( 2, 5 ) Penyelesaian : untuk u kita dapatkan :
u = 4 2 + 7 2 + 3 2 = 74 Dengan analogi yang sama, kita dapatkan
v = 2 2 + 5 2 = 29 Menggunakan D3DX Library, kita bisa mencari magnitude dari suatu vector dengan fungsi sbb : FLOAT D3DXVec3Length( CONST D3DXVECTOR3 *pV ) ; Contoh penggunaannya untuk pertanyaan di atas: D3DXVECTOR3 u( 4.0f, 7.0f, 3.0f ) ; float magnitude = D3DXVec3Length( &u ) ; // sqrt(74)
Normalize Vector Normalize Vector adalah membuat panjang vector menjadi 1. Kita bisa menormalize vector dengan membagi tiap komponen dengan panjangnya, rumusnya sbb :
û=
u ⎛⎜ u x u y u z ⎞⎟ = , , u ⎜⎝ u u u ⎟⎠
contoh normalize untuk soal di atas :
û=
⎛ 4 3 ⎞ 7 = ⎜⎜ ⎟⎟ , , 74 ⎝ 74 74 74 ⎠
u
menggunakan D3DX Library, kita bisa menghitung normalize vector dengan fungsi sbb : D3DXVECTOR3 *D3DXVec3Normalize( D3DXVECTOR3* pOut, // Result. CONST D3DXVECTOR3* pV // The vector to normalize. ); Menggunakannya adalah sbb :
D3DXVECTOR3 u( 4.0f, 7.0f, 3.0f ) ; // original vector D3DXVECTOR3 unorm ; // result normalized vector D3DXVec3Normalize( &unorm, &u ) ; // unorm --> normalized of u
17
Penambahan vector Kita dapat menambahkan dua vector atau lebih dengan menambahkan komponen pada masingmasing vector untuk membentuk vector baru. u + v = (ux + vx, uy + vy, uz + vz) secara geometris dapat digambarkan sebagai berikut :
u
u+
v
v
v
Gambar 1.6. Penambahan vector Gambar di atas menunjukkan penambahan vector u dan vector v. Vector v digeser sehingga ekornya berimpit dengan kepala vector v sehingga menghasilkan vector u + v seperti di atas. Dalam code, untuk menambahkan vector bisa menggunakan operator + yang sudah dioverload. D3DXVECTOR3 u( 2.0f, 1.0f, 3.0f ) ; D3DXVECTOR3 v( -1.0f, 5.0f, 0.0f ) ; D3DXVECTOR3 sum = u + v ; // sum = ( 1.0f, 6.0f, 3.0f )
Pengurangan vector Vector dapat dikurangkan dengan mengurangkan masing-masing komponen dalam vector tersebut. u – v = u + (-v) = ( ux - vx, uy - vy, uz – vz ) Secara geometris, pengurangan vector dapat digambarkan seperti gambar 1.7.
18
u
v u-
v u-
v
Gambar 1.7. Pengurangan 2 vector u - v Vector (u-v) dapat dinyatakan dengan menarik garis dari kepala vector v ke kepala vector u. Garis tersebut dapat digeser hingga vector dalam posisi standard. Jika kepala vector dianggap sebagai point, maka kita bisa mendapatkan vector antara dua point tersebut dengan mengurangi dua point tersebut. Dalam code, pengurangan bisa dilakukan dengan menggunakan operator - yang sudah dioverload. D3DXVECTOR3 u ( 2.0f, 1.0f, 3.0f ) ; D3DXVECTOR3 v ( -1.0f, 5.0f, 0.0f ) ; D3DXVECTOR3 sum = u - v ; // sum = ( 3.0f, -4.0f, 3.0f )
Perkalian skalar Perkalian skalar akan mengubah panjang vector. Arah dari vector akan tetap seperti semula,
u
3u
terkecuali faktor perkaliannya negatif, maka arah vector akan berbalik
Gambar 1.8. Perkalian vector dengan skalar
19
Perkalian tersebut dapat dirumuskan ku = ( kux, kuy, kuz ) dalam code, jika menggunakan D3DX, dapat menggunakan tanda * yang sudah dioverload. D3DXVECTOR3 u( 1.0f, 1.5f, 0.4f ) ; D3DXVECTOR3 uscale = 4 * u ; // uscale = ( 4.0f, 6.0f, 1.6f )
Dot Products Dot product adalah perkalian antara 2 vector yang menghasilkan sebuah scalar. u ● v = uxvx + uyvy + uzvz rumus di atas tidak menggambarkan suatu arti geometri khusus. Akan tetapi dengan menggunakan hukum kosinus, kita bisa mendapatkan :
cos θ =
u•v u v
Jadi dengan mengetahui dot product dan magnitude dari tiap vector, kita bisa menentukan sudut antara dua buah vector. Jika u dan v adalah unit vector ( panjang u dan v = 1 ), maka u ● v adalah cosinus dari dua vector tersebut. Sifat dot product. •
Jika u ● v = 0 maka u ⊥ v.
•
Jika u ● v > 0 maka sudut antara dua vector tersebut < 90°
•
Jika u ● v < 0 maka sudut antara dua vector tersebut > 90°
Dalam library D3DX, kita bisa menggunakan fungsi D3DXVec3Dot untuk mencari dot product antara dua vector. FLOAT D3DXVec3Dot( // Returns the result. CONST D3DXVECTOR3* pV1, // Left sided operand. CONST D3DXVECTOR3* pV2 // Right sided operand. );
D3DXVECTOR3 u ( 2.0f, 1.0f, 3.0f ) ; D3DXVECTOR3 v ( -1.0f, 5.0f, 0.0f ) ; float dotprod = D3DXVec3Dot( &u, &v ) ; // dotprod = 3
Cross Products Selain dot product, ada satu lagi jenis perkalian antara 2 vector yaitu cross product. Tidak seperti dot product yang menghasilkan scalar, cross product menghasilkan vector lain yang orthogonal terhadap 2 vector yang lain. p = u×v = [ ( uyvz – uzvy ), ( uzvx – uxvz ), ( uxvy – uyvx ) ] contoh : buktikan bahwa j = k×i = ( 0, 0, 1 ) × ( 1, 0, 0 ) dan j tegak lurus k maupun i.
20
Penyelesaian: jx = (0(0) – 1(0)) = 0 jy = (1(1) – 0(0)) = 1 jz = (0(0) – 0(1)) = 0 j = ( 0, 1, 0 ). j.i = 0(1) + 1(0) + 0(0) = 0 ; j.k = 0(0) + 1(0) + 0(1) = 0; dari dot product di atas dpt diketahui bahwasanya vector j tegak lurus i dan j tegak lurus k. Menggunakan D3DX kita bisa menggunakan fungsi D3DXVec3Cross untuk mencari cross product antara 2 vector. D3DXVECTOR3 *D3DXVec3Cross( D3DXVECTOR3* pOut, // Result. CONST D3DXVECTOR3* pV1, // Left sided operand. CONST D3DXVECTOR3* pV2 // Right sided operand. ); Seperti berikut ini D3DXVECTOR3 u ( 2.0f, 1.0f, 3.0f ) ; D3DXVECTOR3 v ( -1.0f, 5.0f, 0.0f ) ; D3DXVECTOR3 crosprod ; D3DXVec3Cross( &crosprod, &u, &v ) ; crossprod = - 15, -3, 11
p
Secara geometris, cross product dapat digambarkan sebagai berikut :
v
u Gambar 1.9. Cross product p = u × v
Matrix Selain berurusan dengan vector, kita juga akan berurusan dengan matrix. Matrix m×n adalah suatu baris bilangan berupa kotak dengan m baris dan n kolom. Kita menyatakan matrix dengan menggunakan dua buah index (subscript). Angka pertama menyatakan baris dan angka kedua menyatakan kolom. Contoh Matrix M 3×3, A 2×3 dan B 4×2 seperti di bawah ini :
⎛ m11 ⎜ M = ⎜ m21 ⎜m ⎝ 31
m12 m22 m32
m13 ⎞ ⎟ ⎛a m23 ⎟ A = ⎜⎜ 11 ⎝ a 21 m33 ⎟⎠
a12 a 22
a13 ⎞ ⎟ a 23 ⎟⎠
⎛ b11 ⎜ ⎜b B = ⎜ 21 b ⎜ 31 ⎜b ⎝ 41
b12 ⎞ ⎟ b22 ⎟ b32 ⎟ ⎟ b42 ⎟⎠
21
Kita biasanya menggunakan huruf besar untuk menyatakan matrix. Kadang-kadang matrix hanya berisi satu baris atau satu kolom. Untuk matrix ini kita menyebutkan row vector dan column vector. Contoh row vector dan column vector sebagai berikut :
v = (v11
v12
v13
v14 )
⎛ u11 ⎞ ⎜ ⎟ dan u = ⎜ u 21 ⎟ ⎜u ⎟ ⎝ 31 ⎠
Kesamaan, perkalian skalar, dan penambahan Mari kita lihat matrix-matrix berikut ini :
5 ⎞ 5 ⎞ ⎛ 1 ⎛ − 2 0 −1 3⎞ ⎛ 1 ⎛ 6 − 1⎞ ⎟⎟ D = ⎜⎜ ⎟⎟ ⎟⎟ B = ⎜⎜ ⎟⎟ C = ⎜⎜ A = ⎜⎜ ⎝ − 3 − 2⎠ ⎝ 0 − 3 2 2⎠ ⎝ − 3 − 2⎠ ⎝− 2 3 ⎠ •
Dua matrix adalah sama jika mempunyai dimensi yang sama dan semua komponen matrixmatrix tersebut sama. Contoh A = C karena dimensinya sama dan isinya juga sama. Sedangkan A ≠ B dan C ≠ D karena dimensinya serta isinya tidak sama.
•
Kita bisa mengalikan matrix dengan skalar contoh kitat kalikan matrix D dengan skalar k :
⎛ k ( −2) k (0) k (−1) k (3) ⎞ ⎟⎟ jika k = 3 maka kD = ⎜⎜ k ( 0 ) k ( − 3 ) k ( 2 ) k ( 2 ) ⎝ ⎠ ⎛ 3( −2) 3(0) 3( −1) 3(3) ⎞ ⎛ − 6 0 − 3 9 ⎞ ⎟⎟ = ⎜⎜ ⎟⎟ 3D = ⎜⎜ ⎝ 3(0) 3(−3) 3(2) 3(2) ⎠ ⎝ 0 − 9 6 6 ⎠ •
Untuk menambahkan dua matrix, keduanya harus mempunyai dimensi yang sama.
5 ⎞ ⎛ 6 − 1⎞ ⎛ 1 + 6 5 −1 ⎞ ⎛ 7 4⎞ ⎛ 1 ⎟⎟ + ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ A + B = ⎜⎜ ⎝ − 3 − 2 ⎠ ⎝ − 2 3 ⎠ ⎝ − 3 + (−2) − 2 + 3 ⎠ ⎝ − 5 1 ⎠ •
Sedangkan untuk mengurangkan dua matrix, operasi yang dilakukan adalah spt berikut :
5 ⎞ ⎛ 6 − 1⎞ ⎛ 1 5 ⎞ ⎛− 6 1 ⎞ ⎛ 1 − 6 5 +1 ⎞ ⎛− 5 6 ⎞ ⎛ 1 ⎟⎟ − ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ + ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ A − B = ⎜⎜ ⎝ − 3 − 2 ⎠ ⎝ − 2 3 ⎠ ⎝ − 3 − 2 ⎠ ⎝ 2 − 3⎠ ⎝ − 3 + 2 − 2 − 3⎠ ⎝ − 1 − 5 ⎠
Perkalian Matrix Perkalian matrix merupakan operasi yang penting dalam 3D computer graphics. Dengan menggunakan perkalian matrix, kita bisa mentransformasi dan mengkombinasikan beberapa transformasi sekaligus. Untuk membuat matrix AB, maka jumlah kolom matrix A harus sama dengan jumlah baris Matrix B. Contoh berikut matrix A berukuran 2x2 dan matrix B berukuran 2x3.
⎛a A = ⎜⎜ 11 ⎝ a 21
a12 ⎞ ⎛b ⎟⎟ dan B = ⎜⎜ 11 a 22 ⎠ ⎝ b21
b12 b22
b13 ⎞ ⎟ b23 ⎟⎠
22
Produk AB terdefinisi karena jumlah kolom matrix A sama dengan jumlah baris matrix B. Akan tetapi, produk BA tidak terdefinisi karena jumlah kolom B tidak sama dengan jumlah baris A. Dengan begitu hukum komutatif tidak berlaku untuk perkalian matrix. AB ≠ BA. Jika suatu matrix A berukuran m × n dan matrix B berukuran n × p maka hasil perkalian AB adalah matrix C dengan ukuran m × p dengan cij adalah dot product antara row vector A pada baris ke i dengan column vector B pada kolom ke j.
cij = ai ● bj Sebagai contoh. Mari kita kalikan 2 matrix berikut :
⎛ − 1 2 ⎞⎛ 1 2 ⎞ ⎛ a1 • b1 ⎟⎟⎜⎜ ⎟⎟ = ⎜⎜ AB = ⎜⎜ ⎝ 0 1 ⎠⎝ 4 − 2 ⎠ ⎝ a 2 • b1
a1 • b2 ⎞ ⎛ 7 − 6 ⎞ ⎟=⎜ ⎟ a 2 • b2 ⎟⎠ ⎜⎝ 4 − 2 ⎟⎠
Matrix Identitas Ada sebuah matrix khusus yang disebut sebagai Matrix Identitas (Identity Matrix). Matrix Identitas adalah sebuah matrix yang berisi 0 di semua elemennya kecuali diagonalnya yang berisi 1. contoh berikut adalah matrix identitas 2×2, 3×3, dan 4×4
⎛1 0 0⎞ ⎟ ⎜ ⎜0 1 0⎟ ⎜0 0 1⎟ ⎠ ⎝
⎛1 0⎞ ⎜⎜ ⎟⎟ ⎝0 1⎠
⎛1 ⎜ ⎜0 ⎜0 ⎜ ⎜0 ⎝
0 1 0 0
0 0 1 0
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Hukum MATRIX IDENTITAS :
IM = MI = M I = matrix identitas. Dengan begitu, mengalikan suatu matrix dengan matrix identitas, tidak mengganti nilai matrix tersebut.
Inverse Dalam aritmetika matrix, tidak ada analogi untuk pembagian matrix. Akan tetapi ada perkalian inverse. Hal-hal berikut yang perlu diperhatikan dalam inverse matrix : •
Hanya matrix persegi yang mempunyai inverse.
•
Inverse dari matrix M n × n adalah matrix n × n yang dinyatakan dengan M-1.
•
Tidak semua matrix persegi mempunyai inverse.
•
Mengalikan matrix dengan inversenya menghasilkan matrix identitas : MM-1 = M-1M = I. Perkalian matrix adalah komutatif jika dikalikan dengan inversenya.
Matrix inverse sangat berguna untuk mencari matrix lain dalam persamaan matrix. Sebagai contoh, sebuah persamaan p' = pR dan kita sudah mengetahui p' dan R, yang kita cari adalah p. Hal yang
23
pertama kali dilakukan adalah mencari R-1. Setelah R-1 diketahui, maka dengan mudah kita bisa mencari p. p'R-1= p (RR-1) p'R-1 = pI p'R-1 = p ada satu ciri matrix inverse yang penting yaitu:
(AB)-1 = B-1A-1 Dengan asumsi bahwa A dan B mempunyai inverse dan dimensi A dan B adalah sama.
Transpose Matrix Matrix mempunyai transpose dengan menukar baris menjadi kolom. Dengan begitu, transpose dari matrix m × n adalah matrix n × m. Kita nyatakan matrix transpose dari M dengan MT. Contoh : carilah transpose dari matrix berikut :
⎛−1 8 9 ⎞ ⎟⎟ A = ⎜⎜ ⎝ 3 2 − 5⎠
c⎞ ⎟ f⎟ i ⎟⎠
⎛a b ⎜ B = ⎜d e ⎜g h ⎝
Maka transposenya adalah
⎛ − 1 3⎞ ⎟ ⎜ A = ⎜ 2 8⎟ ⎜ − 5 9⎟ ⎠ ⎝ T
⎛a ⎜ B = ⎜b ⎜c ⎝ T
d e f
g⎞ ⎟ h⎟ i ⎟⎠
Matrix D3DX Ketika memrogram Direct3D, yang digunakan biasanya adalah matrix 4×4 dan row vector 1×4. Dengan menggunakan 2 matrix tersebut maka perkalian berikut terdefinisi : •
Perkalian Vector dan Matrix. Jika v adalah row vector 1×4 dan T adalah matrix 4×4 maka product vT terdefinisi yang menghasilkan row vector 1×4.
•
Perkalian Matrix dan Matrix. Jika T adalah matrix 4×4 dan R adalah matrix 4×4 maka product TR dan RT terdefinisi yang menghasilkan matrix 4×4. Karena perkalian matrix tidak komutatif maka TR ≠ RT.
Untuk menyatakan 1×4 row vector dengan D3DX, kita biasa menggunakan class D3DXVECTOR3 dan D3DXVECTOR4, tentu saja D3DXVECTOR3 hanya mempunyai 3 komponen, bukan 4. Akan tetapi, komponen keempat berisi satu atau 0. Untuk menyatakan matrix 4×4 class yang digunakan D3DXMATRIX didefinisikan sbb : typedef struct D3DXMATRIX : public D3DMATRIX { public:
24
D3DXMATRIX( D3DXMATRIX( D3DXMATRIX( D3DXMATRIX(
) { } ; CONST FLOAT* ) ; CONST D3DMATRIX& ) ; FLOAT _11, FLOAT _12, FLOAT _13, FLOAT FLOAT _21, FLOAT _22, FLOAT _23, FLOAT FLOAT _31, FLOAT _32, FLOAT _33, FLOAT FLOAT _41, FLOAT _42, FLOAT _43, FLOAT // access grants FLOAT& operator ( ) ( UINT Row, UINT Col ) ; FLOAT operator ( ) ( UINT Row, UINT Col ) const ;
_14, _24, _34, _44 ) ;
// casting operators operator FLOAT* ( ); operator CONST FLOAT* ( ) const; // assignment operators D3DXMATRIX& operator *= D3DXMATRIX& operator += D3DXMATRIX& operator -= D3DXMATRIX& operator *= D3DXMATRIX& operator /=
( ( ( ( (
CONST CONST CONST FLOAT FLOAT
D3DXMATRIX& ) ; D3DXMATRIX& ) ; D3DXMATRIX& ) ; ) ; ) ;
// unary operators D3DXMATRIX operator + ( ) const ; D3DXMATRIX operator - ( ) const ; // binary operators D3DXMATRIX operator D3DXMATRIX operator D3DXMATRIX operator D3DXMATRIX operator D3DXMATRIX operator
* + * /
( ( ( ( (
CONST CONST CONST FLOAT FLOAT
D3DXMATRIX& ) const ; D3DXMATRIX& ) const ; D3DXMATRIX& ) const ; ) const ; ) const ;
friend D3DXMATRIX operator * ( FLOAT, CONST D3DXMATRIX& ) ; BOOL operator == ( CONST D3DXMATRIX& ) const ; BOOL operator != ( CONST D3DXMATRIX& ) const ; } D3DXMATRIX, *LPD3DXMATRIX; Class D3DXMATRIX diturunkan dari structure D3DMATRIX sebagai berikut : typedef struct _D3DMATRIX { union { struct { float _11, _12, float _21, _22, float _31, _32, float _41, _42, } ;
_13, _23, _33, _43,
_14 _24 _34 _44
; ; ; ;
float m[ 4 ][ 4 ] ; } } D3DXMATRIX; Bisa dilihat bahwa D3DXMATRIX mempunyai banyak sekali operator yang berguna, seperti testing untuk kesamaan, penambahan dan pengurangan matrix, casting, dan lebih penting lagi perkalian
25
dua D3DXMATRIX atau lebih. Karena perkalian matrix sangat penting, saya akan memberikan contoh penggunaan D3DXMATRIX untuk perkalian. D3DXMATRIX A( . . . ) ; // inisialisasi A D3DXMATRIX B( . . . ) ; // inisialisasi B D3DXMATRIX C = A * B ; // C = A B Operator penting lainnya, adalah operator kurung ( ... ) yang sangat praktis digunakan untuk mengeset entry dari sebuah matrix. D3DXMATRIX mat ; mat(0, 0) = 4.0f ; // mengeset entry ij = 11 ke 4.0f Library D3DX juga menyediakan D3DXMATRIX untuk membuat matrix identitas, mencari transpose matrix dan mencari inverse D3DXMATRIX. D3DXMATRIX *D3DXMatrixIdentity( D3DXMATRIX *pOut ) ; // matrix diset menjadi matrix identitas Penggunaannya : D3DXMATRIX mat ; // deklarasi matrix D3DXMatrixIdentity( &mat ) ; // mengeset matrix menjadi matrix identity Mencari transpose D3DXMATRIX *D3DXMatrixTranspose( D3DXMATRIX *pOut, D3DXMATRIX *pM ) ; Penggunaannya : D3DXMATRIX C( ... ) ; // inisialisasi matrix D3DXMATRIX D ; D3DXMatrixTranspose( &D, &C ) ; // D = transpose C Mencari inverse : D3DXMATRIX *D3DXMatrixInverse( D3DXMATRIX *pOut, // hasil FLOAT *pDeterminant, // determinan, jika tidak ada, NULL D3DXMATRIX *pM // matrix yg dicari inversenya ) ; Penggunaannya : D3DXMATRIX M( ... ) ; // inisialisasi matrix D3DXMATRIX N ; D3DXMatrixInverse( &N, NULL, &M ) ; // inverse
26
Transformasi Dasar Ketika menggunakan Direct3D untuk matrix T 4 × 4 untuk menyatakan transformasi dan row vector v 1 × 4 untuk menyatakan koordinat dan juga vector. vT adalah vektor hasil transformasi v terhadap T yang bisa juga dinyatakan dengan v'. Jadi misalkan T menyatakan matrix translasi terhadap sumbu X sebanyak 7 unit untuk vektor v = [ 3, -4, 2, 1 ], maka vT = v' = [ 10, -4, 2, 1 ]. Kenapa kita menggunakan matrix 4 × 4 untuk ruang 3 dimensi? Karena matrix dengan ukuran 4 × 4 lah yang bisa menyatakan seluruh transformasi yang mungkin dilakukan dalam ruangan 3 dimensi. Mungkin matrix 3 × 3 terasa lebih sesuai untuk ruang 3 dimensi. Akan tetapi, ada beberapa transformasi yang tidak bisa dinyatakan dalam matrix 3 × 3 seperti translasi, proyeksi perspective, dan refleksi. Kita bekerja dengan product vector dan matrix, sehingga gerak kita terbatas pada perkalian matrix dan vector untuk menyatakan transformasi, menggunakan matrix 4 × 4 membuat kita menjadi mungkin untuk menyatakan lebih banyak transformasi.Karena kita menggunakan matrix 4 × 4, maka kita pun harus menggunakan row vector 1 × 4 supaya perkalian antara row vector dan matrix terdefinisi. Kemudian apa yang harus kita lakukan dengan elemen keempat? Untuk point kita mengeset elemen keempat dengan 1, sehingga dapat dilakukan translasi. Akan tetapi untuk vector kita mengeset elemen keempat dengan 0 karena posisi bukanlah properti vector, sehingga translasi tidak mempunyai arti khusus pada vector. Contoh : Untuk menyatakan posisi p = (px, py, pz), kita nyatakan p dalam row vector [ px, py, pz, 1 ]. Sedangkan untuk menyatakan vector v = (vx, vy, vz), kita nyatakan v dalam row vector [ vx, vy, vz, 0 ]. Vector 4D seperti di atas disebut homogenous vector. Homogenous vector bisa digunakan untuk menyatakan point dan vector. Untuk selanjutnya istilah vector dapat berupa point maupun vector itu sendiri. Terkadang transformasi mengubah elemen w (elemen keempat) sehingga w ≠ 0 dan w ≠ 1, seperti di bawah ini :
(
p = px
py
pz
⎛1 ⎜ ⎜0 1)⎜ 0 ⎜ ⎜0 ⎝
0 1 0 0
0⎞ ⎟ 0⎟ = (px 1⎟ ⎟ 0 ⎟⎠
0 0 1 0
py
pz
p z ) dengan pz ≠ 0 dan pz ≠ 1
terlihat bahwa w = pz. Ketika kita melihat vector di mana w≠0 dan w≠1, maka kita namai vector tersebut dalam homogenous space yang berbeda dengan vector dalam 3-D space. Jika kita menemukan homogenous space kemudian ingin mengubahnya ke 3-D space, yang kita lakukan adalah membagi tiap elemen dengan nilai w tersebut. Misalkan kita punya vector u = ( x, y, z, w) dan ingin mengubahnya menjadi 3-D space, maka masing-masing elemen kita bagi dengan w.
G u = (x
y
⎛x z w) = ⎜ ⎝w
y w
z w
w⎞ ⎛ x ⎟=⎜ w⎠ ⎝ w
y w
z ⎞ 1⎟ , sehingga untuk kasus p diatas bisa w ⎠
kita dapatkan
27
p' = ( p x
py
pz
⎛p p z ) = ⎜⎜ x ⎝ pz
py pz
pz pz
pz pz
⎞ ⎛ px ⎟⎟ = ⎜⎜ ⎠ ⎝ pz
py pz
⎞ 1 1⎟⎟ ⎠
Matrix Translasi Perhatikan gambar berikut ini :
(-2, 5)
Translasi
(-5, 2)
(5, -1)
(2, -4)
Gambar 1.10. Translasi 10 unit di sumbu x dan -6 unit di sumbu y Gambar di atas menunjukkan perpindahan (translasi) sebanyak 10 unit di sumbu x dan -6 unit di sumbu y. Kita bisa mentranslasi suatu vector (x, y, z, 1) sebanyak dx unit sepanjang sumbu x, dy unit sepanjang sumbu y dan dz unit sepanjang sumbu z dengan mengalikan vector tersebut dengan matrix berikut ini :
⎛1 ⎜ ⎜0 T (d ) = ⎜ 0 ⎜ ⎜d ⎝ x
0 1
0 0
0
1
dy
dz
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Dengan D3DX library, matrix translasi dapat dibuat dengan fungsi : D3DXMATRIX *D3DXMatrixTranslation ( D3DXMATRIX *pOut, // output FLOAT x, // jarak translasi di sumbu x FLOAT y, // jarak translasi di sumbu y FLOAT z // jarak translasi di sumbu z );
28
Untuk mencari Inverse dari matrix translasi tersebut cukup mudah yaitu tinggal menegatifkan elemen translasi matrix tersebut :
⎛ 1 ⎜ ⎜ 0 T −1 (d ) = ⎜ 0 ⎜ ⎜− d ⎝ x
0 1
0 0
0 − dy
1 − dz
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Matrix Rotasi
Rotasi
Gambar 1.11. Rotasi berlawanan arah jarum jam Kita bisa merotasi vektor sebanyak θ radian dengan sumbu x, y, dan z menggunakan matrix berikut. Sudut positif adalah searah dengan jarum jam.
0 ⎛1 ⎜ ⎜ 0 cos θ rotX (θ ) = ⎜ 0 − sin θ ⎜ ⎜0 0 ⎝
0 sin θ cos θ 0
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Fungsi dalam D3DX adalah D3DXMatrixRotationX sebagai berikut D3DXMATRIX *D3DXMatrixRotationX( D3DXMATRIX* pOut, // Result. FLOAT Angle // Sudut rotasi dalam radian ); Rotasi dengan as sumbu Y
⎛ cos θ ⎜ ⎜ 0 rotY (θ ) = ⎜ sin θ ⎜ ⎜ 0 ⎝
0 − sin θ 1 0 0 cos θ 0 0
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Fungsi dalam D3DX adalah D3DXMatrixRotationY sebagai berikut
29
D3DXMATRIX *D3DXMatrixRotationY( D3DXMATRIX* pOut, // Result. FLOAT Angle // Sudut rotasi dalam radian ); Rotasi dengan as sumbu Z
⎛ cos θ ⎜ ⎜ − sin θ rotZ (θ ) = ⎜ 0 ⎜ ⎜ 0 ⎝
sin θ cos θ 0 0
0 0 1 0
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Fungsi dalam D3DX adalah D3DXMatrixRotationY sebagai berikut D3DXMATRIX *D3DXMatrixRotationZ( D3DXMATRIX* pOut, // Result. FLOAT Angle // Sudut rotasi dalam radian ); Inverse dari matrix rotasi adalah transpose dari matrix tersebut. R-1 = RT.
Matrix Scaling
Scale
Gambar 1.12. Scaling 1,5 kali di sumbu X dan 2,5 kali di sumbu Y Jika kita ingin men-scale suatu vector dengan faktor kx untuk sumbu x, ky untuk sumbu y, dan kz untuk sumbu z dengan matrix berikut :
⎛kx ⎜ ⎜0 S (k ) = ⎜ 0 ⎜ ⎜0 ⎝
0 ky 0 0
0 0 kz 0
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Fungsi dalam D3DX adalah D3DXMatrixScaling sebagai berikut :
30
D3DXMATRIX *D3DXMatrixScaling( D3DXMATRIX* pOut, // Result. FLOAT kx, // Faktor perkalian untuk elemen x. FLOAT ky, // Faktor perkalian untuk elemen y. FLOAT kz // Faktor perkalian untuk elemen z. ); Inverse dari scaling matrix adalah matrix yang sama dengan faktor skala 1/k
⎛ 1 ⎜ ⎜ kx ⎜ ⎛1⎞ ⎜ 0 −1 S (k ) = S ⎜ ⎟ = ⎝k⎠ ⎜ ⎜ 0 ⎜ ⎜ 0 ⎝
0
0
1 ky
0
0 0
1 kz 0
⎞ 0⎟ ⎟ ⎟ 0⎟ ⎟ 0⎟ ⎟ 1 ⎟⎠
Kombinasi Transformasi Sering
kita
mengkombinasikan
beberapa
transformasi,
misalkan,
kita
menscale
vector,
merotasikannya kemudian mentranslasinya ke posisi yang kita inginkan. Contoh : Scale vector u = [ 3, 6, 0, 1 ] sepertiga pada semua sumbu, rotasikan sebanyak π/3 dengan sumbu y sebagai poros kemudian translasikan sebanyak 2 unit di sumbu x, -1 di sumbu y, dan 4 unit di sumbu z. Jawaban : kita tentukan dulu matrix-matrixnya : Matrix scaling :
⎛1 ⎜ ⎜3 ⎛1 1 1⎞ ⎜0 S⎜ , , ⎟ = ⎜ ⎝ 3 3 3⎠ ⎜ ⎜0 ⎜ ⎝0
Matrix rotasi :
0 1 3 0 0
⎞ 0 0⎟ ⎟ 0 0⎟ ⎟ ⎟ 1 0⎟ 3 ⎟ 0 1⎠
⎛ 0.5 ⎜ ⎛π ⎞ ⎜ 0 Ry ⎜ ⎟ = ⎜ ⎝ 3 ⎠ ⎜ 0.866 ⎜ 0 ⎝
0 − 0.866 1 0 0 0.5 0 0
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Matrix translasi
⎛1 0 ⎜ ⎜0 1 T (2,−1,4 ) = ⎜ 0 0 ⎜ ⎜2 −1 ⎝
0 0 1 4
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Kita misalkan hasil transformasi u adalah u' maka :
31
G G u ' = u SR y T Jadi pertama kali kita kombinasikan matrix SRyT sbb :
⎛1 ⎜ ⎜3 ⎜0 SR y T = ⎜ ⎜ ⎜0 ⎜ ⎝0
0
0
1 3
0
0 0
1 3 0
⎞ 0⎟ ⎟⎛⎜ 0.5 0 ⎟⎜ 0 ⎟⎜ ⎟⎜ 0.866 0 ⎟⎜ 0 ⎟⎝ 1⎠
0 − 0.288 ⎛ 0.1665 ⎜ 0.333 0 ⎜ 0 =⎜ 0.288 0 0.1665 ⎜ ⎜ 0 0 0 ⎝
0 1
− 0.866
0 0
0 .5 0
0 ⎞⎛ 1 ⎟⎜ 0 ⎟⎜ 0 0 ⎟⎜ 0 ⎟⎜ 1 ⎟⎠⎜⎝ 2
0
0 ⎞⎛ 1 0 ⎟⎜ 0 ⎟⎜ 0 1 0 ⎟⎜ 0 0 ⎟⎜ 1 ⎟⎠⎜⎝ 2 − 1
0 0 1 4
0⎞ ⎟ 0⎟ 0 1 0⎟ ⎟ − 1 4 1 ⎟⎠ 0 1
0 0
− 0.288 0 ⎞ ⎛ 0.1665 0 ⎟ ⎜ 0⎟ ⎜ 0 0.333 0 = 0 ⎟ ⎜ 0.288 0 0.1665 ⎟ ⎜ ⎜ ⎟ −1 1⎠ ⎝ 2 4
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Sehingga kita dapatkan u' sebagai berikut :
0 − 0.288 ⎛ 0.1665 ⎜ 0.333 0 G G ⎜ 0 u ' = u SR y T = (3 6 0 1)⎜ 0.288 0 0.1665 ⎜ ⎜ 2 4 −1 ⎝
0⎞ ⎟ 0⎟ = (2.5 1 3,136 1) 0⎟ ⎟ 1 ⎟⎠
Mungkin perkalian di atas sedikit berbeda dengan apa yang diajarkan di sekolah atau kampus. Hal ini terjadi karena matrix dan vector yang kita gunakan. Dalam matematika di buku-buku aljabar linear, transformasi dikalikan dengan urutan terbalik. u' = TRySu. Hal ini terjadi karena pada buku matematika yang digunakan adalah column vector, sedangkan kita menggunakan row vector. Jika Anda pernah menggunakan OpenGL, maka perhitungan matrix dan vector dalam OpenGL sama dengan buku-buku aljabar linear karena memakai column vector.
Fungsi-fungsi untuk menangani transformasi Ada
beberapa
fungsi
mentransformasikan
dalam vector
library dengan
D3DX
yaitu
anggapan
D3DXVec3TransformCoord elemen
keempat
yang
akan
1
dan
bernilai
D3DXVec3TransformNormal mentransformasikan vector dengan anggapan elemen keempat bernilai 0. Prototype dan penggunaannya sebagai berikut D3DXVECTOR3 *D3DXVec3TransformCoord( D3DXVECTOR3* pOut, CONST D3DXVECTOR3* pV, CONST D3DXMATRIX* pM );
// Hasil. // Point yg ditransformasi. // Matrix Transformasi.
D3DXMATRIX T(...); // inisialisasi matrix transformasi D3DXVECTOR3 p(...); // initialisasi point D3DXVec3TransformCoord( &p, &p, &T); // transformasi point D3DXVECTOR3 *WINAPI D3DXVec3TransformNormal( D3DXVECTOR3 *pOut, // Hasil.
32
);
CONST D3DXVECTOR3 *pV, CONST D3DXMATRIX *pM
// vector yg ditransformasi. // matrix transformasi.
D3DXMATRIX T(...); // inisialisasi matrix transformasi D3DXVECTOR3 v(...); // initialisasi vector D3DXVec3TransformNormal( &v, &v, &T); // transformasi vector
Plane (Bidang) Plane atau bidang bisa dinyatakan dalam vector n dan sebuah point dalam plane p0. Vector n adalah perpendicular (tegak lurus) dengan plane dan disebut sebagai normal vector.
n p0
Gambar 1.13. Plane dinyatakan dengan vector normal n dan point p0. Dari gambar 1.14 kita dapat mengetahui bahwa sebuah plane memuat semua titik p yang memenuhi persamaan.
n
(p-p0)
p0
p
Gambar 1.14. jika p0 adalah point dalam plane dan p juga merupakan point dalam plane jika vector yang terbentuk dari p0 dan p adalah orthogonal terhadap vector normal plane (n). Dengan begitu :
n ● (p-p0) = 0 Ketika menyatakan suatu plane, biasanya normal n diketahui dan p0 adalah konstan. Maka persamaan di atas bisa juga ditulis :
n ● p + d = 0, dengan d = -n ● p0
Catatan Khusus : Jika normal vector n adalah unit vector, d = -n ● p0 menghasilkan jarak terdekat dari titik origin ke plane.
33
D3DXPLANE Ketika menyatakan suatu plane terhadap code, normal vector n dan konstanta d sudah cukup. Sangat praktis jika kita berpikir itu adalah vector 4D ( n, d ). D3DX Library menggunakan structure di bawah ini untuk menyatakan plane : typedef struct D3DXPLANE { #ifdef __cplusplus public: D3DXPLANE() {} D3DXPLANE( CONST FLOAT* ); D3DXPLANE( CONST D3DXFLOAT16* ); D3DXPLANE( FLOAT a, FLOAT b, FLOAT c, FLOAT d ); // casting operator FLOAT* (); operator CONST FLOAT* () const; // unary operators D3DXPLANE operator + () const; D3DXPLANE operator - () const; // binary operators BOOL operator == ( CONST D3DXPLANE& ) const; BOOL operator != ( CONST D3DXPLANE& ) const; #endif //__cplusplus FLOAT a, b, c, d; } D3DXPLANE, *LPD3DXPLANE; Dengan a, b, dan c adalah elemen dari vector normal n dan d adalah konstanta d dari persamaan di atas.
Hubungan Point dan Plane Persamaan plane di atas sangat berguna untuk mengetes lokasi dari suatu point relatif terhadap plane. Misal diberikan plane (n, d) maka hubungan antara point p terhadap plane tersebut adalah : •
Jika n ● p + d = 0 maka p coplanar dengan plane.
•
Jika n ● p + d > 0 maka p berada di depan plane, dan di plane positive space.
•
Jika n ● p + d < 0 maka p berada di belakang plane, dan di plane negative space.
Catatan Khusus : Jika plane normal vector n adalah unit vector, maka n ● p + d menghasilkan jarak terdekat antara plane ke point. Fungsi D3DX di bawah ini mencari n ● p + d untuk suatu plane dan point. FLOAT D3DXPlaneDotCoord( CONST D3DXPLANE *pP, CONST D3DXVECTOR3 *pV );
// plane. // point.
// Test hubungan point dan plane D3DXPLANE p( 0.0f, 1.0f, 0.0f, 0.0f );
34
D3DXVECTOR3 v( 3.0f, 5.0f, 2.0f ); float x = D3DXPlaneDotCoord( &p, &v ); if( x mendekati 0.0f )// v coplanar dengan plane. if( x > 0 ) // v ada di positive space dari plane if( x < 0 ) // v ada di negative space dari plane
Catatan Khusus : Saya menggunakan istilah mendekati untuk mengakomodasi kesalahan dalam presisi floating point.
Fungsi
hampir
sama
D3DXPlaneDotCoord
adalah
D3DXPlaneDot
dan
D3DXPlaneDotNormal, lihat dokumentasi DirectX untuk lebih jelas.
Konstruksi Selain dengan menentukan normal(n) dan jarak (d) kita bisa mencari komponen tersebut dalam 2 cara. Dengan normal n dan sebuah point p0, kita bisa mencari d. n ● p0 + d = 0 n ● p0 = -d -n ● p0 = d Library D3DX menyediakan fungsi di bawah ini untuk melakukan kalkulasi tersebut : D3DXPLANE *D3DXPlaneFromPointNormal( D3DXPLANE* pOut, // Hasil. CONST D3DXVECTOR3* pPoint, // Point di plane. CONST D3DXVECTOR3* pNormal // Normal dari plane. ); Cara kedua adalah dengan mencari persamaan plane dari tiga titik kita bisa mencari 2 vector dalam plane, p0, p1, dan p2. u = p1 – p0 v = p2 – p0 Dari dua vector tersebut kita bisa mencari normal dari plane dengan mencari cross product dari dua vector tersebut : n=u×v kemudian –(n ● p0) = d. Fungsi dari library D3DX untuk mencari plane dari tiga point sebagai berikut: D3DXPLANE *D3DXPlaneFromPoints( D3DXPLANE* pOut, CONST D3DXVECTOR3* pV1, CONST D3DXVECTOR3* pV2, CONST D3DXVECTOR3* pV3
// // // //
Hasil. Point 1 di plane. Point 2 di plane. Point 3 di plane.
35
);
Me-normalize plane Terkadang kita mempunyai plane dan ingin menormalkan plane tersebut. Mungkin Anda berpikir bahwa kita tinggal menormalkan normal vector seperti vector lain. Akan tetapi mari kita lihat persamaan plane berikut : d = -n ● p0 di n ● p + d = 0. Terlihat bahwa panjang normal vector mempengaruhi constant d. Dengan begitu jika kita menormalkan normal vector, maka kita juga harus menghitung d.
G d n G = − G • p0 n n Dengan begitu kita mempunyai rumus berikut untuk menormalkan normal vector dari plane (n, d)
⎛ 1 G G (n , d ) = ⎜⎜ n ⎝
G n d ⎞⎟ G , G n n ⎟⎠
Kita bisa menggunakan fungsi D3DX berikut ini untuk menormalkan sebuah plane: D3DXPLANE *D3DXPlaneNormalize( D3DXPLANE *pOut, // Plane yang ternormalisasi CONST D3DXPLANE *pP // Input plane. );
Transformasi Plane Plan dapat ditransformasikan dengan menganggap plane (n, d) sebagai vector 4-D dan mengalikannya dengan inverse-transpose dari matrix transformasi. Normal dari plane harus dinormalkan dahulu sebelum ditransformasi. Dengan library D3DX kita bisa menggunakan fungsi berikut : D3DXPLANE *D3DXPlaneTransform( D3DXPLANE *pOut, // Hasil CONST D3DXPLANE *pP, // Input plane. CONST D3DXMATRIX *pM // Matrix Transformasi. ); Sample code : D3DXMATRIX T(...); // Init. T ke transformasi yg dikehendaki. D3DXMATRIX inverseOfT; D3DXMATRIX inverseTransposeOfT; D3DXMatrixInverse( &inverseOfT, 0, &T ); D3DXMatrixTranspose( &inverseTransposeOfT, &inverseOfT ); D3DXPLANE p(...); // Init. Plane. D3DXPlaneNormalize( &p, &p ); // plane normal harus dinormalkan dulu D3DXPlaneTransform( &p, &p, &inverseTransposeOfT );
36
Point terdekat dalam plane terhadap point lain Anggap kita mempunyai point p di suatu ruang dan akan mencari point q di plane (n, d) dengan asumsi normal vector adalah unit vector. (n, d)
q
n
-kn
kn
p
Gambar 1.15. point dalam plane (q) yg paling dekat dengan point lain (p) Dari gambar 1.15 kita dapatkan q = p + (-kn). Dengan k adalah jarak terdekat antara p dengan plane yang merupakan jarak terdekat antara p dan q. Anggap normal n adalah unit vector, maka n ● p + d memberikan jarak terdekat dari plane ke point p.
Rays Bayangkan seorang player dalam game mengarahkan pistolnya dan menembakkan ke musuh. Bagaimana kita menentukan apakah pelurunya mengenai target? Salah satu caranya adalah dengan menganggap peluru tersebut adalah ray dan musuh dengan bounding sphere atau bounding box. Bounding box/sphere adalah box/sphere terkecil yg bisa melingkupi suatu object. Kemudian kita bisa menentukan apakah ray akan apakah mengenai bounding box/sphere tersebut dan dimana tempat kenanya.
Pengertian Ray Ray bisa dinyatakan dengan sebuah origin (titik asal) dan direction(arah). Persamaan ray adalah sebagai berikut :
p(t) = p0 + tu
37
p0+3u p0+2u p0+u p0
u
Gambar 1.16. Sebuah ray dinyatakan dengan sebuah origin (p0) dan vector arah (u) kita bisa membuat point-point sepanjang ray dengan mengalikan vector arah dengan bilangan k yang lebih besar atau sama dengan 0. p0 adalah titik origin dari ray dan u adalah arah dari ray, dan t adalah parameternya. Dengan memberikan nilai yang berbeda terhadap t kita bisa mencari point-point sepanjang ray. Parameter t harus berada dalam interval [0, ∞). Nilai kurang dari 0 akan membuat point di belakang ray. Sehingga jika kita biarkan t ∈ (-∞,∞) kita mempunyai sebuah garis dalam ruang 3-D.
Perpotongan Rays dan Plane Misal kita mempunyai ray p(t) = p0 + tu, dan plane n ● p + d = 0 kita akan mengetahui bahwa sebuah ray berpotongan dengan plane pada suatu titik tertentu. Untuk mencari ini, kita masukkan ray ke dalam persamaan plane dan mencari parameter t sehingga memenuhi persamaan tersebut. Dengan memasukkan ray ke dalam persamaan plane kita dapatkan :
G n • p (t ) + d = 0 G G ⇔ n • ( p 0 + tu ) + d = 0 G G G ⇔ n • p 0 + n • tu + d = 0 G G G ⇔ n • tu = − d − ( n • p 0 ) G G G ⇔ t (n • u ) = −d − (n • p 0 ) G − d − (n • p 0 ) ⇔t= G G n •u Jika t tidak berada pada interval [0, ∞) maka ray tidak berpotongan dengan plane.
38
Jika t berada pada interval [0, ∞) maka kita bisa mencari titik perpotongan antara ray dan plane dengan memasukkan parameter t ke dalam persamaan ray
G G ⎛ − d − (n • p 0 ) ⎞ ⎛ − d − (n • p0 ) ⎞ G p(t ) = p⎜ ⎟ = p0 + ⎜ ⎟u G G G G n •u n •u ⎝ ⎠ ⎝ ⎠
Dengan begitu titik potong antara ray da nplane bisa dicari.
39
Program DirectX Pertama Setelah membahas matematika, saatnya membuat aplikasi sebenarnya. Saya akan mengajarkan langkah demi langkah dalam membuat aplikasi DirectX. Beberapa sample dari DirectX bergantung pada DXUT atau DirectX Sample Framework. Saya tidak akan menggunakan framework tersebut, tetapi akan memulai semuanya dari awal. Karena DirectX berjalan di platform Microsoft Windows, saya akan mengajarkan dasar pemrograman aplikasi Windows terlebih dahulu sebelum masuk ke dalam DirectX.
Dasar Pemrograman Windows Memrogram untuk Windows, berbeda dengan memrogram untuk program console. Karena kita tidak menggunakan RAD tools seperti Microsoft Visual Basic, .NET, atau Borland Delphi, maka kita memerlukan pengetahuan tentang Windows API untuk memrogram Windows. Windows API adalah sekumpulan fungsi-fungsi yang diperlukan untuk memrogram aplikasi untuk Microsoft Windows. Windows API mempunyai banyak sekali fungsi. Akan tetapi kita hanya akan menggunakan sedikit saja bagian dari Windows API untuk memrogram aplikasi DirectX kita.
Struktur Aplikasi Windows Pernahkan Anda menulis aplikasi console berbasis teks menggunakan C++? Untuk membuat console dengan C++ yaitu dengan membuat satu fungsi bernama main( ). Mari kita lihat aplikasi console Hello World yang ditulis menggunakan C++. #include int main( int argc, char ** argv ) { printf( "Hilo Wurld! LynxLuna Ganteng!" ) ; return 0 ; }
Program di atas akan menampilkan tulisan Hilo Wurld! LynxLuna Ganteng! di layar. Struktur aplikasi console cukup sederhana, minimal hanya satu fungsi bernama main( ). Secara global, struktur aplikasi Windows hampir sama dengan aplikasi console. Perbedaannya, aplikasi Windows tidak menggunakan fungsi main( ) sebagai entry point, tetapi WinMain( ) dengan parameter sebagai berikut : INT WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nShowCmd ) ; Sebelum membahas arti dari parameter fungsi WinMain( ) di atas. Pertama kali, kita buat project Windows dahulu. Pertama kali buka Visual Studio. Saya menggunakan edisi professional dari Microsoft Visual Studio. Layar New Project •
Klik Menu File > New > Project
40
•
Pilih Visual C++ > Win32
•
Pilih Win32 Project
•
Tentukan lokasi dan nama project.
•
Klik OK
Gambar 2.1. Tampilan Layar New Project Setelah diklik OK, selanjutnya adalah Layar Win32 Application Wizard. Untuk layar ini klik pada Application Settings dan kasih tanda check (centang) pada pilihan Empty Project kemudian click Finish.
Gambar 2.2. Tampilan Layar Win32 Application Wizard
41
Setelah diclick finish maka akan tercipta sebuah project kosong dengan tiga folder yaitu Header Files, Resource Files, dan Source Files seperti gambar di bawah ini :
. Gambar 2.3. Tampilan Source Explorer untuk project HiloWorld Untuk menambahkan file, klik kanan pada project, kemudian Add New Item.
Gambar 2.4. Menu untuk menambah item. Setelah ditambah New Item akan keluar layar Add New Item. Pilih Code > C++ File (.cpp) kemudian beri nama untuk file tersebut. Untuk kali ini saya memberi nama WinMain.cpp.
42
Gambar 2.5 Layar Add New Item Setelah ditambah New Item akan keluar layar Add New Item. Pilih C++ File (.cpp) kemudian beri nama untuk file tersebut. Untuk kali ini saya memberi nama WinMain.cpp. Microsoft Visual Studio 2005 secara default mengeset aplikasi ke modus Unicode. Untuk saat ini, kita tidak membutuhkan Unicode, oleh karena itu, kita ubah setting aplikasi supaya tidak Unicode. •
Klik kanan pada Project
•
Klik Properties
•
Pilih pada node General
•
Cari Item Character Set dan set ke modus Not Set untuk mematikan support Unicode.
Gambar 2.6 Layar Project Properties.
43
Setelah itu barulah kita menulis program HiloWorld untuk Window. Program kita amat sederhana, hanya menampilkan Message Box ke layar. #include <Windows.h> INT WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { MessageBox( NULL, "Hilo Wurld! LynxLuna Ganteng", "Hilo", MB_OK ) ; return 0 ; } Tulis code di atas pada window code. Untuk mengcompilenya menjadi program, klik Build > Build HiloWorld. Untuk mendebugnya, klik Debug > Start Debugging, maka akan keluar Message Box seperti di bawah ini :
Gambar 2.7 Hasil dari program di atas Mari kita lihat codenya satu-per satu. #include <Windows.h> Ini adalah header yang harus dimasukkan ke dalam setiap aplikasi Windows. INT WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) Fungsi yang merupakan entry point program. Fungsi tersebut memiliki kembalian bertipe INT (integer) dengan WINAPI sebagai calling conventionnya. Calling convention adalah suatu perintah kepada compiler bagaimana program memasukkan parameter ke dalam stack. Dalam program di atas kita menemui tipe data baru. Data tersebut akan diterangkan dalam tabel berikut : Tipe Data
Fungsi
HINSTANCE
Merupakan tipe data sebagai handle dari instance aplikasi. Instance adalah jumlah aplikasi yang dijalankan dari file yang sama. Misal kita punya file HiloWorld.exe dijalankan sekali maka aplikasi mempunyai 1 instance. Ketika dijalankan, maka hInstance ini akan berisi suatu nilai yang merupakan handle dari aplikasi tersebut.
LPSTR
Merupakan pointer ke string ANSI, sama dengan char * pada C++ biasa.
44
Arti dari parameter WinMain adalah sebagai berikut : Nama Variabel
Fungsi
hInstance
Berisi Handle terhadap instance aplikasi yang dijalankan.
hPrevInstance lpCmdLine nShowCmd
Variabel ini merupakan peninggalan dari Windows 3.1. Pada Windows 3.1, variabel ini berisi instance sebelumnya dari aplikasi. Untuk Windows 95 ke atas, variabel ini selalu akan berisi NULL. Variabel ini berisi string command line dari program yang dijalankan. Menunjukkan bagaimana Window ditampilkan, isi dari variabel ini dilihat dalam dokumentasi MSDN.
Baris selanjutnya : MessageBox( NULL, "Hilo Wurld! LynxLuna Ganteng", "Hilo", MB_OK ) ; Baris di atas akan menampilkan Message Box ke layar bertuliskan Hilo Wurld! LynxLuna Ganteng dengan judul window Hilo dengan satu tombol OK. Prototype dari fungsi di atas adalah sbb : MessageBox( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType ) ; Arti dari parameter fungsi di atas adalah sebagai berikut : Nama Variabel
hWnd
Fungsi Merupakan handle ke Window yang merupakan parent dari Message Box tersebut. Jika berisi NULL, maka parent dari Message Box tersebut adalah desktop. Tipe dari variabel ini adalah HWND, yang berarti handle ke window. Ini merupakan teks yang akan ditampilkan dalam message box
lpText
lpCaption uType
Tipe dari variabel ini adalah LPCSTR, artinya constant ke string atau sama dengan const char *. Ini merupakan teks yang ditampilkan dalam title bar message box Ini merupakan variabel bertipe UINT (unsigned int) yang menunjukkan tombol serta icon yang ditampilkan dalam message box, untuk nilai yang bisa digunakan silahkan membaca dokumentasi MessageBox di MSDN.
Tentunya aplikasi kita lebih kompleks daripada aplikasi di atas. Kita memerlukan sebuah window untuk menampung gambar yang ditampilkan dalam window, tidak hanya message box seperti di atas. Untuk aplikasi dengan Window, strukturnya agak lain. Akan saya bahas pada subbab berikut.
45
Window dan Message. Aplikasi yang memiliki window berjalan dalam suatu loop yang disebut message loop atau message pump. Message adalah suatu 'pesan' yang dikirimkan dari sistem atau ke sistem window. Message digenerate
jika
terjadi
'sesuatu'
terhadap
window
seperti
mouse
click,
key
press,
maximize/minimize dan lain sebagainya.
Aplikasi
Windows
Window Procedure
Messages
Window Data
Window Object
Gambar 2.8 Struktur program windows Tiap kali Windows mengirimkan message ke aplikasi kita, kita bisa melakukan sesuatu berdasarkan jenis message yang dikirimkan. Oleh karena itu, selain WinMain, yang kita perlukan adalah Window Procedure. Window Procedure adalah suatu fungsi yang digunakan untuk menangani message yang dirkirimkan dari Windows. Istilah window dalam pemrograman window tidak terbatas pada window yang mempunyai title bar dan menu. Seluruh objek visual yang berinteraksi dengan user adalah window. Button, CheckBox, Combo Box, TextBox, dan lain-lain kesemuanya adalah window. Jadi object button, Combo Box, CheckBox, TextBox dan lain-lain mempunyai tipe HWND yang berarti handle ke suatu window.
Membuat dan menampilkan window Setidaknya ada beberapa hal yang harus diperhatikan sebelum membuat dan menampilkan suatu window ke layar. Hal-hal tersebut antara lain. •
Window Class. Window Class merupakan sebuah structure yang menggambarkan style window yang akan dibuat.
•
Message Structure. Merupakan suatu structure yang digunakan untuk menampung message yang dikirimkan window ke aplikasi.
•
Window Procedure. Merupakan suatu procedure yang harus dibuat untuk menangani message yang dikirimkan ke window.
Window Class Dalam Windows SDK, Window Class dinyatakan dalam structure WNDCLASSEX sebagai berikut : typedef struct { UINT cbSize ;
46
UINT style ; WNDPROC lpfnWndProc ; int cbClsExtra ; int cbWndExtra ; HINSTANCE hInstance ; HICON hIcon ; HCURSOR hCursor ; HBRUSH hbrBackground ; LPCTSTR lpszMenuName ; LPCTSTR lpszClassName ; HICON hIconSm ; } WNDCLASSEX, *PWNDCLASSEX; Arti dari tiap member structure di atas adalah sebagai berikut : Nama Variabel
Fungsi
cbSize
Merupakan ukuran sizeof(WNDCLASSEX).
style
Merupakan suatu value unsigned integer yang bisa merupakan satu atau kombinasi dari beberapa value. Silahkan lihat di dokumentasi MSDN untuk melihat value yang mungkin dimasukkan dalam member ini. Untuk aplikasi kita, kita selalu menggunakan CS_HREDRAW | CS_VREDRAW .
lpfnWndProc
structure
WNDCLASSEX,
diisi
dengan
Merupakan pointer ke Window Procedure yang akan menangani message
cbClsExtra
Jumlah byte yang dialokasikan di belakang structure window class, system menginisialisasinya dengan 0.
cbWndExtra
Jumlah byte yang dialokasikan setelah suatu instance window. System menginisialisasninya dengan 0
hInstance
Handle untuk instance aplikasi
hIcon
Handle untuk icon yang digunakan dalam window, jika berisi NULL, akan ditampilkan default icon. Tipe dari variabel ini adalah HICON yang berarti handle dari icon aplikasi.
hCursor
Handle dari cursor mouse yang digunakan dalam window, jika berisi NULL, maka aplikasi harus secara eksplisit mengeset cursor yang digunakan ketika mouse bergerak di dalam window. Tipe variabel ini adalah HCURSOR yang berarti handle dari cursor mouse.
hbrBackground
lpszMenuName lpszClassName hIconSm
Warna brush yang digunakan dalam background window, bisa berupa handle ke brush yang dibuat oleh user atau brush system. Tipe variabel ini adalah HBRUSH yang berarti handle ke warna brush. Untuk dokumentasi mengenai brush bisa dilihat di MSDN. Nama dari resource menu yang digunakan dalam window, jika window tidak mempunyai menu maka nilainya adalah NULL. Nama untuk identifikasi Window Class Handle untuk icon berukuran kecil yang akan ditampilkan di window. Jika isinya adalah NULL, maka system akan mencari icon yang digunakan dalam member hIcon dan mencari ukuran yang pas untuk icon yang kecil.
47
Message Structure Message structure adalah suatu structure yang digunakan untuk menampung message yang dikirim ke window. Message structure didefinisikan sebagai berikut : typedef struct { HWND hwnd ; UINT message ; WPARAM wParam ; LPARAM lParam ; DWORD time ; POINT pt ; } MSG, *PMSG; Arti dari masing-masing member adalah sebagai berikut Nama Variabel
Fungsi
hwnd
Handle ke window yang menerima message
message
Suatu nilai yang menyatakan jenis message yang dikirim
wParam
Informasi tambahan tentang message, arti dari nilai yang tersimpan tergantung dari jenis message.
lParam
Informasi tambahan tentang message, arti dari nilai yang tersimpan tergantung dari jenis message.
time
Waktu ketika message dikiriim.
pt
Posisi kursor dalam koordinat layar ketika message dikirim
Window Procedure Window Procedure adalah suatu fungsi yang digunakan untuk menangani message yang dikirim ke window. Dalam window procedure ini kita bisa menentukan apa yang harus dilakukan ketika suatu message dikirim ke window yang bersangkutan. Nama dari window procedure adalah terserah dari user, tetapi harus memenuhi prototype sebagai berikut: LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) ; Membuat Window Untuk membuat window, kita menggunakan fungsi dari Win32API yaitu CreateWindowEx sebagai berikut, untuk dokumentasi lengkap dari CreateWindowEx bisa dilihat di MSDN, untuk fungsi ini saya hanya akan menampilkan keterangan dalam comment. HWND CreateWindowEx( DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x,
// // // // //
ExStyle dari window Nama dari window class Judul Window Style Window Posisi window di layar (x)
48
int y, // Posisi window di layar (y) int nWidth, // Lebar window dalam pixel int nHeight, // Panjang window dalam pixel HWND hWndParent, // Parent dari window HMENU hMenu, // Handle ke menu, NULL jika tidak memakai menu HINSTANCE hInstance, // handle ke instance aplikasi LPVOID lpParam // parameter tambahan ); Baiklah, sekarang saatnya kita masuk ke code untuk membuat aplikasi dengan window. Pertama kali buat project kosong seperti di atas dengan satu file WinMain.cpp. Program kita kali ini akan terbagi dalam 3 fungsi yaitu WinMain sebagai fungsi utama, InitWindow sebagai fungsi untuk membuat window, dan WndProc sebagai Window Procedure. Kita akan menulis InitWindow dan WndProc di bawah fungsi WinMain. Oleh karena itu dalam program kita kali ini kita akan menulis prototype InitWindow dan WndProc di atas WinMain. Untuk aplikasi kita yang sekarang, kita tidak akan mengubah character set. Kita akan menggunakan header tchar.h yang memungkinkan kita menulis code yang sama baik untuk aplikasi UNICODE maupun ANSI. Yang perlu diingat adalah selalu meletakkan macro TEXT() untuk setiap string literal dan juga memperhatikan tipe-tipe data berikut ini : Tipe
Keterangan
TCHAR
Akan menjadi char bila UNICODE tidak terdefinisi dan menjadi wchar_t jika UNICODE terdefinisi.
LPTSTR
LPCTSTR
Unicode terdefinisi : LPWSTR atau wchar_t * Unicode tidak terdefinisi : LPSTR atau char * Unicode terdefinisi : LPCWSTR atau const wchar_t * Unicode tidak terdefinisi : LPCSTR atau const char *
Fungsi WinMain untuk ANSI adalah sebagai berikut : INT WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) ; Sedangkan untuk UNICODE adalah sebagai berikut : INT WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd ) Untungnya tchar.h sudah mendefiniskan macros untuk WinMain, sehingga dengan tchar.h kita hanya perlu menulis WinMain untuk kedua versi tersebut sebagai berikut : INT WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd ) ; Mari kita lihat kode lengkap per bagian : Variabel, Konstanta Global dan WinMain
49
Ini adalah entry point untuk program, bagian ini berisi deklarasi include header, variabel global dan kontanta global. #include <Windows.h> #include /************************************************************************/ /* Konstanta Global */ /************************************************************************/ // Nama Class Window LPCTSTR g_szClassName = TEXT( "Window Class" ) ; // Judul Window LPCTSTR g_szWindowTitle = TEXT( "LynxLuna - DirectX Window" ) ; const int g_nWindowWidth = 640 ; // lebar window const int g_nWindowHeight = 480 ; // panjang window /************************************************************************/ /* Global Variabel */ /************************************************************************/ // global HINSTANCE // global HWND
variabel untuk menampung instance handle g_hInstance = NULL ; variabel untuk menampung handle window g_hWindow = NULL ;
/************************************************************************/ /* Prototype WndProc dan InitWindow supaya bisa digunakan di WinMain */ /************************************************************************/ LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) ; BOOL InitWindow( HINSTANCE hInstance ) ; INT WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpcmdLine, int nShowCmd ) { MSG msg ; // message structure BOOL bMsgRet = FALSE ; // untuk menampung hasil GetMessage ZeroMemory( &msg, sizeof( MSG ) ) ; // Kosongkan isi msg dengan 0 g_hInstance = hInstance ; // Inisialisasi Window if ( !InitWindow( g_hInstance ) ) { return 1 ; } // Masuk ke Message Loop while ( ( bMsgRet = GetMessage(&msg, NULL, 0, 0) ) != 0 ) { if ( bMsgRet == -1 ) { MessageBox( NULL, TEXT( " Error in Message Loop" ), TEXT( "Error!" ), MB_ICONSTOP ) ; break ; } // Proses Message TranslateMessage( &msg ) ; DispatchMessage( &msg ) ; } DestroyWindow( g_hWindow ) ; UnregisterClass( g_szClassName, g_hInstance ) ; }
return (INT) msg.wParam ;
50
Bagian terpenting dari bagian ini adalah message loop bagian inilah yang menerima. Fungsi GetMessage mengecek dalam antrian message pada aplikasi. Jika ada message maka fungsi TranslateMessage dan DispatchMessage dijalankan untuk melempar kontrol ke WndProc. Setelah menulis WinMain seperti di atas, sekarang kita mengimplementasikan InitWindow, yaitu fungsi untuk membuat dan menampilkan window.
InitWindow Sebelum window dibuat, aplikasi harus meregister class. Setelah class teregister, aplikasi membuat window yang diperlukan. Code berikut ini membuat dan menampilkan window. /************************************************************************/ /* Fungsi InitWindow( HINSTANCE hInstance ) */ /* Digunakan untuk membuat dan menampilkan window */ /* Return value: TRUE jika berhasil dan FALSE jika gagal membuat window */ /************************************************************************/ BOOL InitWindow( HINSTANCE hInstance ) { // Deklarasi Window Class dan isi dengan 0 WNDCLASSEX wcex ; ZeroMemory( &wcex, sizeof( WNDCLASSEX ) ) ; // Isi member Window Class wcex.cbSize = sizeof( WNDCLASSEX ) ; wcex.style = CS_VREDRAW | CS_HREDRAW ; wcex.lpfnWndProc = WndProc ; wcex.cbClsExtra = 0 ; wcex.cbWndExtra = 0 ; wcex.hInstance = hInstance ; wcex.hIcon = LoadIcon( hInstance, IDI_APPLICATION ) ; wcex.hCursor = LoadCursor( hInstance, IDC_ARROW ) ; wcex.hbrBackground = ( HBRUSH ) COLOR_APPWORKSPACE ; wcex.lpszMenuName = NULL ; wcex.lpszClassName = g_szClassName ; wcex.hIconSm = NULL ; // Register Class if ( !RegisterClassEx( &wcex ) ) { return FALSE ; } // Buat Window g_hWindow = CreateWindowEx( WS_EX_APPWINDOW, g_szClassName, g_szWindowTitle, WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_SIZEBOX , CW_USEDEFAULT, CW_USEDEFAULT, g_nWindowWidth, g_nWindowHeight, NULL, NULL, hInstance, NULL); // Jika g_hWindow berisi NULL berarti gagal membuat window if ( g_hWindow == NULL ) { return FALSE ; } // Show and update the window
51
ShowWindow(g_hWindow, SW_SHOWNORMAL); UpdateWindow(g_hWindow) ; return TRUE ; }
Setiap aplikasi yang akan membuat window, pertama kali harus meregister window classnya dahulu, window class mendeskripsikan karakteristik dari window yang akan dibuat. Seperti yang saya terangkan sebelumnya window class ditampung dalam structure WNDCLASSEX. Pada Window di atas saya menambahkan & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX dengan tujuan supaya window tidak bisa di-resize dan dimaximize. Setelah membuat window, saatnya mengimplementasikan WndProc.
WndProc (Main Window Procedure) Fungsi WndProc digunakan untuk menentukan apa yang akan dilakukan aplikasi ketika suatu message dikirimkan dari sistem ke aplikasi.
/************************************************************************/ /* Fungsi WndProc */ /* Befungsi untuk menangani message yg dikirim dr system */ /************************************************************************/ LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch( uMsg ) { case WM_DESTROY : PostQuitMessage( 0 ) ; break ; default : return DefWindowProc( hWnd, uMsg, wParam, lParam ) ; } return FALSE ; }
Ini adalah bagian terakhir yang diperlukan dalam suatu program window. Dalam fungsi ini kita menggunakan statement switch untuk menentukan response terhadap suatu message. Dalam program di atas yang kita tangani adalah message WM_DESTROY yang akan mengirimkan message WM_QUIT dengan PostQuitMessage. Seluruh message akan menghasilkan nilai nonzero ketika diambil oleh GetMessage kecuali WM_QUIT sehingga ketika WM_QUIT dikirimkan, message loop yang mengandung GetMessage dalam fungsi WinMain di atas akan keluar dan akan mengakhiri aplikasi. Setelah dicompile, dan dijalankan, akan keluar window kosong seperti di bawah ini :
52
Gambar 2.9 Window Kosong hasil dari program di atas Hasil dari code di atas adalah sebuah window kosong dengan warna warna background Application Workspace seperti yang diset di Control Panel. Window tersebut mempunyai icon default berupa icon untuk aplikasi, dengan tombol maximize terdisable dan window tidak bisa diresize, cursor yang digunakan adalah berupa panah biasa.
Pengenalan Direct3D Dalam DirectX 9, untuk memrogram 3D kita menggunakan Direct3D. Direct3D adalah suatu graphic API bersifat low-level yang menjadikan programmer mampu merender dunia 3D ke dalam layar komputer. Direct3D bisa diumpamakan seperti penghubung antara graphic hardware dan aplikasi. Gambar berikut ini menunjukkan hubungan aplikasi, Direct3D, dan hardware
Aplikasi
Direct3D
HAL
Graphic Device
Gambar 2.10 Hubungan Aplikasi, D3D, dan Graphic Device. Direct3D menyediakan interface yang berguna untuk melakukan fungsi-fungsi tertentu terhadap graphic device, contoh, bila programmer ingin melakukan clearing (pembersihan) pada layar, kita menggunakan method IDirect3Ddevice9::Clear. Pada gambar 2.10, terlihat setelah Direct3D ada HAL (Hardware Abstraction Layer). Direct3D tidak bisa berinteraksi secara langsung dengan hardware karena ada banyak jenis hardware di pasaran yang mempunyai kemampuan dan implementasi sendiri-sendiri, misal mungkin satu card dengan card lain berbeda dalam implementasi clear screen. HAL adalah sebuah set fungsi-fungsi yang tergantung pada produsen hardware untuk implementasinya.
53
Pembuat hardware memrogram seluruh kemampuan hardware ke dalam HAL, feature yang didukung Direct3D tetapi tidak didukung hardware tidak diimplementasikan dalam HAL. Memanggil fungsi Direct3D yang tidak didukung oleh HAL menghasilkan kegagalan. Kecuali vertex processing, yang bisa diemulasi oleh Direct3D di dalam software, jika menggunakan feature yang hanya didukung beberapa card di pasaran, pastikan sudah memverifikasi dukungan feature tersebut pada hardware.
REF device Mungkin Anda ingin menulis code yang didukung oleh Direct3D tetapi tidak didukung oleh hardware Anda, selain HAL, tersedia juga device REF yang mengemulasi seluruh Direct3D API secara software. REF device hanya untuk keperluan development, REF device sangat lamban dibandingkan dengan HAL device.
D3DDEVTYPE Dalam code, HAL dinyatakan dengan konstanta D3DDEVTYPE_HAL, yang merupakan anggota dari enumerasi D3DDEVTYPE. Untuk REF, yang digunakan adalah konstanta D3DDEVTYPE_REF. Konstanta ini penting karena akan digunakan ketika kita membuat Direct3D Device.
Beberapa hal yang perlu diperhatikan Sebelum kita masuk ke dalam inisialisasi dan pemrograman Direct3D, ada beberapa hal yang perlu diperhatikan dan dipelajari seperti surface, pixel formats, memory pools, dan swap chain.
Surface Surface adalah barisan pixel yang digunakan Direct3D untuk menyimpan data image 2D yang ditampilkan di layar. Walaupun data ditulis dalam bentuk matrix, dalam memory pixel disimpan dalam barisan array. Image element (pixel) Pitch Width (lebar) [0,0]
X+
[0,1]
[1,0] Height (panjang)
Pitch Area
Y+ Gambar 2.10 Sebuah Surface
54
Panjang dan lebar surface diukur dalam pixel. Pitch diukur dalam bytes. Lebih dari itu, pitch mungkin saja lebih lebar daripada lebar, tergantung dari implementasi hardware. Jadi tidak bisa kita menentukan bahwa pitch = width * sizeof(pixelformat). Dalam code, kita mengakses surface melalui interface IDirect3DSurface9. Interface ini menyediakan beberapa method untuk menulis dan membaca dari surface secara langsung. Method yang paling penting dari IDirect3DSurface9 antara lain : •
LockRect, digunakan untuk mendapatkan pointer ke memory surface sehingga kita membaca dan menulis pixel dari surface.
•
UnlockRect, setelah kita selesai dengan LockRect, kita sudah selesai mengakses memory dari surface yang bersangkutan, kita harus meng-unlock surface tersebut.
•
GetDesc, method ini digunakan untuk mendapatkan deskripsi surface dengan mengisi structure D3DSURFACE_DESC.
Contoh penggunaan interface ini ada dalam snippet code di bawah ini, yaitu dengan mengisi semua surface dengan warna pixel biru. // anggap pSurface adalah pointer ke IDirect3DSurface9 // asumsi pixel format yang digunakan adalah 32 bit per pixel D3DSURFACE_DESC SurfDesc ; pSurface->GetDesc( &SurfDesc ) ; // Lock the rect D3DLOCKED_RECT LockedRect ; pSurface->LockRect( &LockedRect, 0, 0 ) ; //iterasi dari surface dan set semua surface ke biru DWORD *pImageData = ( DWORD* ) LockedRect.pBits ; int i = 0, j = 0 ; int index = 0 ; for ( i = 0 ; i < SurfDesc.Height; i ++ ) { for ( j = 0 ; j < SurfDesc.Width; j ++ ) { index = i * LockedRect.Pitch / 4 + j ; pImageData[ index ] = 0xff0000ff ; }
}
pSurface->UnlockRect( ) ;
structure D3DLOCKED_RECT didefinisikan sebagai berikut : typedef struct _D3DLOCKED RECT { INT Pitch; // surface pitch void *pBits; // pointer to surface memory } D3DLOCKED_RECT;
Multisample Multisample adalah suatu teknik untuk menghaluskan image yang kasar dan blocky. Satu teknik yang menggunakan multisampling adalah Full Screen Anti Aliasing (FSAA).
55
Gambar 2.11 Garis biasa (kiri) dan garis yang telah dianti-aliasing (kanan) Enumerasi D3DMULTISAMPLE_TYPE berisi nilai-nilai yang bisa digunakan untuk menentukan multisample dalam surface yaitu : •
D3DMULTISAMPLE_NONE – tanpa multisample
•
D3DMULTISAMPLE_1_SAMPLE sampai D3DMULTISAMPLE_16_SAMPLE – menunjukkan level multisample dari 1 sampai 16.
Selain itu ada juga nilai DWORD yang digunakan untuk menentukan kualitas multisample. Untuk program-program dalam buku ini, kita sama sekali tidak menggunakna multisample karena akan menjadikan aplikasi amat lambat. Jika ingin menggunakan, jangan lupa untuk mengeceknya dulu dengan method IDirect3D9::CheckDeviceMultisampleType untuk mengecek apakah device yang digunakan mendukung multisample dan juga kualitas multisample.
Pixel Format Kita akan sering sekali menggunakan pixel format ketika membuat surface atau texture. Format dari pixel ditentukan dengan nilai dari enumerasi D3DFORMAT. Beberapa format antara lain sebagai berikut : Nilai
Keterangan
D3DFMT_R8G8B8
Merupakan pixel format 24 bit yang dimulai dari bit paling kiri 8 bit untuk warna merah, 8 bit untuk warna hijau, dan 8 bit untuk warna biru.
D3DFMT_X8R8G8B8
Merupakan pixel format 32 bit yang dimulai dari bit paling kiri, 8 bit tidak dipakai, 8 bit untuk warna merah, 8 bit untuk warna hijau, dan 8 bit untuk warna biru.
D3DFMT_A8R8G8B8
Merupakan pixel format 32 bit yang dimulai dari bit paling kiri, 8 bit untuk alpha, 8 bit untuk warna merah, 8 bit untuk warna hijau, dan 8 bit untuk warna biru.
D3DFMT_A16R16G16B16F
Merupakan pixel format floating point 64 bit. Dimulai dari bit paling kiri, 16 bit untuk alpha, 16 bit untuk warna merah, 16 bit untuk warna hijau, dan 16 bit untuk warna biru.
D3DFMT_A32R32G32B32F
Merupakan pixel format floating point 128 bit. Dimulai dari bit paling kiri, 32 bit untuk alpha, 32 bit untuk warna merah, 32 bit untuk warna hijau, dan 32 bit untuk warna biru.
Untuk daftar Pixel Format yang lengkap bisa dilihat dalam dokumentasi DirectX SDK. Yang paling sering digunakan adalah D3DFMT_R8G8B8, D3DFMT_X8R8G8B8,dan D3DFMT_A8R8G8B8. Ketiga format tersebut didukung hampir di semua hardware. Sedangkan format lainnya tidak. Jika ingin
56
menggunakan pixel format yang tidak didukung di semua hardware pastikan mengeceknya dulu sebelum memakai.
Memory Pool Surface dan resource Direct3D lainnya dapat disimpan dalam banyak memory pool. Memory pool ditentukan dengan salah satu nilai dari enumerasi D3DPOOL. Memory pool yang tersedia antara lain : Nilai
Keterangan
D3DPOOL_DEFAULT
Default memory pool, menyebabkan Direct3D menentukan memory pool yang paling sesuai untuk tipe resource tertentu. Memory tersebut bisa video memory, AGP memory, atau System memory. Resource dalam default pool harus di-release sebelum memanggil IDirect3DDevice9::Reset. Dan harus diinisialisasi lagi setelah pemanggilan reset tersebut.
D3DPOOL_MANAGED
Resource yang dimasukan dalam managed pool dimanage oleh Direct3D (sehingga perpindahan memory dari AGP ke video memory atau sebaliknya menjadi otomatis). Sebagai tambahan, sebuah backup disimpan di dalam system memory. Ketika aplikasi mengakses resource, aplikasi mengubah apa yang ada di system memory kemudian Direct3D akan memindahkannya ke video memory jika diperlukan.
D3DPOOL_SYSTEMMEM
D3DPOOL_SCRATCH
Resource semuanya disimpan dalam system memory. Resource disimpan dalam system memory, perbedaan dengan D3DPOOL_SYSTEMMEM adalah resource tersebut bisa keluar dari batas hardware sehingga tidak bisa diakses oleh device. Tetapi resource bisa dicopy dari satu ke yang lain.
Swap Chain dan Page Flipping Direct3D mengatur beberapa surface, biasanya dua atau tiga, yang disebut swap chain yang dapat diakses dengan interface IDirect3DSwapChain9. Kita hanya akan menerangkan dasarnya saja karena swap chain diatur secara otomatis oleh Direct3D dan kita jarang memanipulasinya. Swap chain, yang digunakan dalam page flipping adalah suatu teknik untuk menampilkan animasi yang halus. Perhatikan gambar berikut yang menggambarkan swap chain dengan 2 surface. Surface 1
Surface 2
Front Buffer, merupakan surface yang isinya sedang ditampilkan di layar
Back Buffer, frame yang sedang diproses dirender dalam buffer ini
Gambar 2.12 Swap chain dengan 2 surface Surface dalam front buffer berisi citra yang sedang ditampilkan di layar. Monitor tidak menampilkan front buffer secara instan akan tetapi tergantung dari refresh rate monitor yang bersangkutan, dan frame rate biasanya jauh lebih cepat daripada monitor refresh rate. Sebagai contoh, jika monitor 80 Hz, maka monitor memerlukan waktu 1/80 second untuk menampilkan citra
57
ke layar monitor. Akan tetapi, kita tidak ingin mengupdate front buffer dengan frame selanjutnya sebelum monitor selesai menampilkan frame yang sekarang sedangkan kita juga tidak ingin menghentikan rendering ketika menunggu monitor selesai menampilkan frame. Itulah gunanya back buffer yang menampung hasil rendering, ketika front buffer sudah selesai dirender maka surface tersebut dipindah di belakang swap chain, dan back buffer dipindah menjadi front buffer. Hal ini disebut presenting. Surface 1
Swap
Front Buffer
Surface 2 Back Buffer
Surface 2
Swap
Surface 1
Front Buffer
Back Buffer
Surface 1
Surface 2
Front Buffer
Back Buffer
Gambar 2.13 Present dua kali Gambar 2.13. menggambarkan bagaimana proses presenting dalam swap chain. Dari gambar tersebut bisa kita dapatkan bahwa presenting adalah menukar dua surface.
Depth Buffer Depth buffer adalah sebuah surface yang tidak berisi data citra, tetapi berisi informasi dari kedalaman (depth) dari tiap pixel. Jika kita membuat buffer dengan ukuran 800x600 maka akan ada depth buffer dengan ukuran 800x600 pula.
Gambar 2.14 beberapa object yang menutupi object lain Pada gambar 2.14 terlihat object saling menutupi satu sama lain. Agar Direct3D menentukan pixel mana yang menutupi pixel tertentu, maka diperlukan teknik depth buffering atau z buffering.
58
Depth buffering dilakukan dengan mencari nilai depth dari tiap pixel dan melakukan depth test. Depth test secara ringkas adalah mengetest mana pixel yang menutupi pixel lain. Jika pixel lebih dekat dengan kamera, maka pixel tersebut digambar, jika tidak maka tidak digambar. Format depth buffer menentukan ketepatan depth test. Nilai
Keterangan
D3DFMT_D32
Depth buffer 32 bit
D3DFMT_D24S8
Depth buffer 24 bit dengan 8 bit sebagai stencil buffer
D3DFMT_D24X8
Depth buffer 24 bit dengan 8 bit tidak dipakai
D3DFMT_D24X4S4
Depth buffer 24 bit dengan 4 bit sebagai stencil buffer
D3DFMT_D16
Depth buffer 16 bit
Vertex Processing Vertex adalah data dasar yang digunakan untuk membuat object 3D dan dapat diproses dengan beberapa cara baik software maupun hardware. Software vertex processing selalu bisa digunakan tetapi hardware vertex processing hanya bisa dilakukan oleh hardware yang mendukung. Hardware vertex processing lebih diutamakan daripada software vertex processing. Dengan memindah vertex processing ke hardware maka kerja CPU menjadi lebih sedikit, selain itu hardware vertex processing lebih cepat karena dilakukan dalam satu hardware tersendiri yaitu VGA.
Device Capabilities Setiap feature yang ada di Direct3D mempunyai member data yang berkaitan pada structure D3DCAPS9. Guna dari structure ini adalah untuk menentukan feature apa saja yang didukung oleh hardware kita. Kita bisa menentukan apakah suatu feature didukung dengan mengecek member dari D3DCAPS9. Snippet di bawah ini menunjukkan bagaimana cara menentukan apakah suatu feature hardware transform and lighting didukung atau tidak. Feature ini berguna untuk menentukan apakah hardware mendukung hardware vertex processing atau tidak. // asumsi bahwa g_pD3D adalah pointer ke object Direct3D D3DCAPS9 Caps ; bool isHardwareTNLSupported = false ; ZeroMemory( &Caps, sizeof( D3DCAPS9 ) ) ; g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &Caps ) ; isHardwareTNLSupported = ( Caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) ;
59
Menambahkan Direct3D Untuk menambahkan Direct3D ke dalam aplikasi kita langkah-langkah yang perlu dilakukan adalah sebagai berikut : 1. Dapatkan pointer ke interface IDirect3D9. Interface ini digunakan untuk mendapatkan informasi tentang hardware dan juga digunakan untuk membuat pointer ke interface IDirect3DDevice9 yang merupakan object untuk menampilkan grafik 3D ke layar. 2. Cek capabilitas device (D3DCAPS9) untuk mengecek apakah Primary Display Adapter mendukung hardware vertex processing atau tidak. Kita memerlukan informasi ini untuk membuat object IDirect3DDevice9. 3. Inisialisasi
member
dari
structure
D3DPRESENT_PARAMETERS
untuk
menentukan
karakteristik device yang akan kita buat nanti. 4. Buat object IDirect3DDevice9 berdasarkan structure D3DPRESENT_PARAMETERS.
Membuat Direct3D Object Direct3D object menyediakan interface ke fungsi yang digunakan untuk mengenumerasi dan menentukan capabilitas device Direct3D. Untuk membuat Direct3D object kita memanggil fungsi berikut : IDirect3D9* Direct3DCreate9( D3D_SDK_VERSION ) ;
Catatan Khusus : D3D_SDK_VERSION
adalah
satu-satunya
parameter
yang
valid
untuk
fungsi
Direct3DCreate9. Fungsi ini akan mengembalikan pointer ke interface IDirect3D9. Jika nilai yang dikembalikan adalah NULL, berarti fungsi tersebut gagal dipanggil. Kita bisa mengenumerasi dan meng-query kemampuan dari semua Direct3D device yang ada dalam komputer kita (walau umumnya hanya 1). Untuk menentukan jumlah device adapter Direct3D yang ada dalam komputer kita, kita cukup memanggil method GetAdapterCount setelah interface IDirect3D9 didapatkan. Prototype dari method ini adalah : UINT IDirect3D9::GetAdapterCount( VOID ) ;
Fungsi ini tidak memerlukan parameter apapun dan akan mengembalikan nilai UINT (unsigned int) yang merupakan jumlah adapter Direct3D yang ada dalam komputer kita. Untuk buku ini kita tidak akan melakukan enumerasi apapun, asumsi kita adalah bahwa kita bekerja pada adapter default yaitu adapter yang sedang digunakan sekarang, adapter yang menampilkan desktop Anda.
Checking Hardware Vertex Processing Ketika kita membuat object IDirect3DDevice9, kita memerlukan informasi bagaimana vertex diproses, apakah dengan hardware atau software? Untuk itu sebelum membuat object device
60
tersebut, pertama kali yang kita lakukan adalah mencari tahu apakah primary display adapter kita mendukung hardware vertex processing, jika iya digunakan, jika tidak maka kita menggunakan software vertex processing. Untuk itu kita mengakses fungsi GetDeviceCaps untuk mendapatkan informasi ini. Prototype dari method GetDeviceCaps adalah sebagai berikut : HRESULT IDirect3D9::GetDeviceCaps( UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9 *pCaps );
Parameter
Keterangan
Adapter
Adapter yang akan kita cari kapabilitasnya. Untuk adapter default kita gunakna D3DADAPTER_DEFAULT.
DeviceType
Device type yang digunakan hardware device (D3DDEVTYPE_HAL) atau software (D3DDEVTYPE_REF)
pCaps
Pointer ke structure D3DCAPS9 yang digunakan untuk mendapatkan hasil dari method GetDeviceCaps.
Mari kita lihat code berikut ini : // asumsi g_pD3D adalah object Direct3D yang sudah dibuat D3DCAPS9 Caps ; // deklarasi structure DWORD dwVertexProcessingFlags = 0 ; // deklarasi VP Flags ZeroMemory( &Caps, sizeof( D3DCAPS9 ) ) ; // kosongkan structure g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &Caps ) ; dwVertexProcessingFlags = ( Caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) ? D3DCREATE_HARDWARE_VERTEXPROCESSING : D3DCREATE_SOFTWARE_VERTEXPROCESSING ;
Mengisi structure D3DPRESENT_PARAMETERS Setelah itu, langkah selanjutnya adalah mengisi structure D3DPRESENT_PARAMETERS yang digunakan untuk menentukan karakteristik object IDirect3DDevice9 yang akan kita buat nanti. Structure dari D3DPRESENT_PARAMETERS dideklarasikan sebagai berikut : typedef struct _D3DPRESENT_PARAMETERS_ { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS;
Arti dari member structure ini adalah sebagai berikut :
61
Parameter
Keterangan
BackBufferWidth
Lebar dari surface yang digunakan sebagai backbuffer
BackBufferHeight
Panjang dari surface yang digunakan sebagai backbuffer
BackBufferFormat
Pixel Format dari backbuffer
BackBufferCount
Jumlah backbuffer yang digunakan, kita menggunakan 1 back buffer saja, sehingga nilai dari member ini adalah 1.
MultisampleType
Tipe multisample. Lihat dokumentasi DirectX SDK untuk keterangan lebih lengkap
MultisampleQuality
Kualitas multisample. Lihat dokumentasi DirectX SDK untuk keterangan lebih lengkap.
SwapEffect
Salah satu anggota dari enumerasi D3DSWAPEFFECT yang menyatakan bagaimana flipping chain berjalan D3DSWAPEFFECT_DISCARD adalah yang paling efisien.
hDeviceWindow
Merupakan window handle yang akan dihubungkan dengan device. Window ini tempat kita menggambar
Windowed
TRUE jika berjalan dalam modus window dan FALSE jika full screen.
EnableAutoDepthStencil
Set ke TRUE supaya Direct3D mengatur depth dan stencil buffer secara otomatis.
AutoDepthStencilFormat
Format dari Depth dan Stencil Buffer Karakteristik tambahan. Gunakan 0 atau flag tambahan. Yang paling sering digunakan adalah : D3DPRESENTFLAG_LOCKABLE_BACKBUFFER menyatakan bahwa bahwa Backbuffer bisa dilock. Menggunakan lockable backbuffer menurunkan performa.
Flags
D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL Menyatakan bahwa depth dan stencil buffer akan diabaikan setelah backbuffer di-present. Dengan mendiscard, kita menyatakan bahwa depth dan stencil buffer menjadi tidak valid setelah backbuffer ditampilkan, sehingga menaikkan performa.
FullScreen_RefreshRateInHz
Refresh rate, gunakan default refresh rate dengan D3DPRESENT_RATED_DEFAULT Salah satu anggota enumerasi D3DPRESENT lihat dokumentasi untuk semua interval yang valid. Yang paling sering digunakan adalah D3DPRESENT_INTERVAL_IMMEDIATE
PresentationInterval
Present secepatnya D3DPRESENT_INTERVAL_DEFAULT Direct3D akan menentukan present rate, biasanya sama dengan refresh rate.
62
Contoh pengisian dari structure di atas adalah sebagai berikut : D3DPRESENT_PARAMETERS d3dpp; d3dpp.BackBufferWidth d3dpp.BackBufferHeight d3dpp.BackBufferFormat d3dpp.BackBufferCount d3dpp.MultiSampleType d3dpp.MultiSampleQuality d3dpp.SwapEffect d3dpp.hDeviceWindow d3dpp.Windowed d3dpp.EnableAutoDepthStencil d3dpp.AutoDepthStencilFormat d3dpp.Flags d3dpp.FullScreen_RefreshRateInHz d3dpp.PresentationInterval
= = = = = = = = = = = = = =
640; 480; D3DFMT A8R8G8B8; //pixel format 1; D3DMULTISAMPLE NONE; 0; D3DSWAPEFFECT_DISCARD; g_hWindow; TRUE; // windowed TRUE; D3DFMT D24S8; // depth format 0; D3DPRESENT_RATE_DEFAULT; D3DPRESENT_INTERVAL_IMMEDIATE;
Membuat object IDirect3DDevice9 Setelah mengisi structure D3DPRESENT_PARAMETERS, kita siap membuat object device yang akan kita gunakan untuk menampilkan object 3D ke layar nanti. Kita menggunakan method CreateDevice dari interface IDirect3D9, prototypenya sebagai berikut : HRESULT IDirect3D9::CreateDevice ( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9 **ppReturnedDeviceInterface ) ;
Arti dari parameter dari fungsi di atas adalah sbb Parameter
Keterangan
Adapter
Adapter yang akan digunakan sebagai device, untuk primary display menggunakan D3DADAPTER_DEFAULT
DeviceType
Tipe Device yang akan dibuat, D3DDEVTYPE_HAL untuk HAL dan D3DDEVTYPE_REF untuk REF.
hFocusWindow
Adalah handle ke window yang dikaitkan dengan device yang akan dibuat, window yang akan digunakan untuk menggambar scene 3D. Sama dengan hDeviceWindow pada D3DPRESENT_PARAMETERS.
BehaviorFlags
Flags yang menggambarkan bagaimana vertex diproses, apakah D3DCREATE_HARDWARE_VERTEXPROCESSING atau D3DCREATE_SOFTWARE_VERTEXPROCESSING.
pPresentationParameters
Pointer ke structure D3DPRESENT_PARAMETERS yang sudah diinisialisasi karakteristiknya
ppReturnedDeviceInterface
Pointer ke pointer untuk interface IDirect3DDevice9 sebagai penampung dari device yang dibuat.
63
Contoh penggunaan : // asumsi g_pD3D adalah object Direct3D yang sudah dibuat IDirect3DDevice9 *g_pDevice = NULL ; HRESULT hr = 0 ; hr = g_pD3D->CreateDevice ( D3DADAPTER_DEFAULT, // primary adapter D3DDEVTYPE_HAL, // device type g_hWindow, // focus window D3DCREATE_HARDWARE_VERTEXPROCESSING, // vertex processing &d3dpp, // present parameters &g_pDevice // device object ) ; // jika gagal if( FAILED( hr ) ) { MessageBox( NULL, TEXT( "CreateDevice() - FAILED" ), TEXT( "Error" ), MB_OK | MB_ICONEXCLAMATION ) ; return FALSE ; }
Operasi Dasar Device Ada beberapa operasi dasar terhadap object device yang kita buat. Operasi ini yang akan kita gunakan untuk aplikasi pertama kita.
Membersihkan layar Setelah device berhasil dibuat, maka kita bisa merender ke layar. Sebelum menggunakan polygon dan texture, yang pertama kali kita lakukan adalah membersihkan layar. Untuk membersihkan layar, kita menggunakan method dari IDirect3DDevice9 sebagai berikut : HRESULT IDirect3DDevice9::Clear( DWORD Count, CONST D3DRECT * pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil );
Arti dari parameter dari fungsi di atas adalah sbb Parameter
Keterangan
Count
Jumlah rectangle yang akan di-clear, jika ini berisi 0, maka parameter kedua harus NULL. Dengan begitu seluruh area akan di-clear yang merupakan behavior yang umum. Jika nilainya lebih dari 0, maka pRects harus berisi array dari Rectangle yang akan diclear.
pRects
Array rectangle yang akan diclear. NULL jika parameter Count = 0 . Flags menunjukkan buffer apa saja yang akan di-clear, bisa salah satu atau kombinasi dari nilai D3DCLEAR berikut ini :
Flags
D3DCLEAR_STENCIL Membersihkan stencil buffer D3DCLEAR_TARGET Membersihkan render target (buffer yang berisi tampilan
64
scene) D3DCLEAR_ZBUFFER Membersihkan depth buffer. Color Z Stencil
Warna yang digunakan untuk membersihkan layar. Bisa menggunakan Macro D3DCOLOR_XRGB atau yang lain. Reset depth buffer ke nilai ini antara 0.0 dan 1.0 Reset stencil buffer ke nilai antara 0 dan 2n-1 (n = jumlah bit stencil buffer)
Contoh penggunaan untuk membersihkan layar menjadi warna biru. g_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 0, 255 ), 0.0f, 0 ) ;
Menampilkan ke layar Setelah frame di-clear, saatnya menampilkannya ke layar. Direct3D menggunakan method Present dari device object untuk melakukan ini. Semua operasi menggambar seperti clear dan lain-lain disimpan dalam backbuffer sebelum ditampilkan ke layar seperti yang saya terangkan pada swap chain di atas. method Present digunakan untuk melakukan swap/page flipping. Prototype dari method ini adalah sebagai berikut : HRESULT IDirect3DDevice9::Present( CONST RECT * pSourceRect, CONST RECT * pDestRect, HWND hDestWindowOverride, CONST RGNDATA * pDirtyRegion ) ;
Arti dari parameter di atas adalah : Parameter
Keterangan
pSourceRect
Pointer ke RECT structure yang menyatakan rectangle asal yang akan di-swap. Nilai dari parameter ini harus NULL jika ingin menswap keseluruhan backbuffer.
pDestRect
Pointer ke RECT structure yang menyatakan rectangle tujuan yang akan di swap. Nilai dari parameter ini harus NULL jika ingin menswap keseluruhan frontbuffer.
hDestWindowOverride
Window yang akan diguanakan sebagai area target. Jika ingin menggunakan window asal yang telah ditentukan, nilai parameter ini harus NULL.
pDirtyRegion
Detail dari region dari backbuffer yang harus diupdate. Sekali lagi nilai parameter ini harus NULL jika ingin mengupdate keseluruhan backbuffer.
Contoh penggunaan : // Asumsi g_pDevice adalah object Direct3D Device // yang sudah dibuat sebelumnya // menampilkan seluruh backbuffer ke layar g_pDevice->Present( NULL, NULL, NULL, NULL ) ;
65
Melepaskan object Direct3D Dalam semua software DirectX, hal terakhir yang harus dilakukan adalah membersihkan dan melepaskan (release) object DirectX. Ketika aplikasi berakhir, kita harus me-release semua object yang kita pakai. Untuk itu kita harus memanggil method Release pada semua object yang kita buat. if ( g_pDevice ) { g_pDevice->Release( ) ; g_pDevice = NULL ; } if ( g_pD3D ) { g_pD3D->Release( ) ; g_pD3D = NULL ; }
Urutan dalam me-release object adalah berbalik dari urutan waktu meng-create object. Satu hal yang penting lagi, selalu mengecek apakah object NULL atau tidak. Mencoba memanggil Release pada object NULL menyebabkan aplikasi crash.
Update the code!! Setelah kita mengetahui bagaimana menginisialisasi Direct3D, saatnya kita mengubah code kita supaya mendukung DirectX. Pertama kali yang harus dilakukan adalah menambahkan header untuk Direct3D. #include
Setelah itu kita deklarasi dua variabel global untuk menampung object Direct3D dan object Device. LPDIRECT3D9 g_pD3D = NULL ; LPDIRECT3DDEVICE9 g_pDevice = NULL ;
Supaya terstruktur, kita melakukan inisialisasi Direct3D pada fungsi tersendiri, kita namai InitDirect3D. Kita tulis dulu prototypenya sebelum WinMain. BOOL InitDirect3D( HWND hWindow ) ;
Kemudian kita panggil setelah window dibuat ( setelah InitWindow ). if ( !InitDirect3D( g_hWindow ) ) { return 1 ; }
Selain InitDirect3D, kita mendeklarasikan juga fungsi DoFrame untuk melakukan operasi frame dan Cleanup untuk melakukan operasi pembersihan terhadap Direct3D. Kita tulis prototypenya sebelum WinMain. BOOL CALLBACK DoFrame( float DeltaTime ) ; void Cleanup( void ) ;
66
Selain itu, kita pisahkan juga message loop ke dalam fungsi tersendiri, sehingga kita tulis prototypenya juga sebelum WinMain. Variabel DisplayCB adalah CALLBACK function yang merupakan pointer ke fungsi yg digunakan untuk render. WPARAM EnterMessageLoop( BOOL ( CALLBACK *lpfnDisplayCB )( float ) ) ;
Sebagai fungsi utilitas, kita membuat fungsi juga untuk menampilkan error bila terjadi kesalahan dan untuk menengahkan window di layar. Prototypenya sebagai berikut : void ErrorMessage( LPTSTR szMessage ) ; void CenterWindow( HWND hWindow ) ;
Jadi WinMain akan jadi seperti ini : INT WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpcmdLine, int nShowCmd ) { int iExitCode = 0 ; g_hInstance = hInstance ; // Inisialisasi Window if ( !InitWindow( g_hInstance ) ) { ErrorMessage( TEXT( "InitWindow( ) - Error!" ) ) ; return 1 ; } // Inisialsasi Direct3D if ( !InitDirect3D( g_hWindow ) ) { ErrorMessage( TEXT( "InitDirect3D( ) - Error!" ) ) ; return 1 ; } // Masuk ke message loop iExitCode = (INT) EnterMessageLoop( DoFrame ) ; // Bersih-bersih Cleanup( ) ; DestroyWindow( g_hWindow ) ; UnregisterClass( g_szClassName, g_hInstance ) ; return iExitCode ; }
Implementasi InitDirect3D InitDirect3D diimplementasikan dengan inisialisasi pointer g_pD3D dan g_pDevice sehingga bisa dipakai untuk menggambar scene ke layar. /************************************************************************/ /* Fungsi InitDirect3D */ /* Digunakan untuk inisialisasi direct3d ke window */ /************************************************************************/ BOOL InitDirect3D( HWND hWindow ) { // Deklarasi variabel dan structure yang diperlukan D3DCAPS9 Caps ; D3DPRESENT_PARAMETERS d3dpp ; DWORD dwVertexProcessing = 0 ;
67
HRESULT hr = 0 ; // Bersihkan structure ZeroMemory( &d3dpp, sizeof ( D3DPRESENT_PARAMETERS ) ) ; // Buat object D3D g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ; // Cek bila terjadi kesalahan if ( !g_pD3D ) { ErrorMessage( TEXT( "Error Creating D3D COM Object" ) ) ; return FALSE ; } // Dapatkan device caps if ( FAILED( g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &Caps ) ) ) { ErrorMessage( TEXT( "Error Getting Device Capabilities" ) ) ; return FALSE ; } // tentukan vertex processing dwVertexProcessing = ( Caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) ? D3DCREATE_HARDWARE_VERTEXPROCESSING : D3DCREATE_SOFTWARE_VERTEXPROCESSING ; // isi structure D3DPRESENT_PARAMETERS d3dpp.BackBufferWidth = g_nWindowWidth ; d3dpp.BackBufferHeight = g_nWindowHeight ; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8 ; d3dpp.BackBufferCount = 1 ; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE ; d3dpp.MultiSampleQuality= 0 ; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD ; d3dpp.hDeviceWindow = hWindow ; d3dpp.Windowed = TRUE ; d3dpp.EnableAutoDepthStencil = TRUE ; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8 ; d3dpp.Flags = 0 ; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT ; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE ; // Buat Device hr = g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWindow, dwVertexProcessing, &d3dpp, &g_pDevice ) ; // Cek error if ( FAILED ( hr ) || ( g_pDevice == NULL ) ) { if ( g_pD3D ) { g_pD3D->Release( ) ; g_pD3D = NULL ; } ErrorMessage( TEXT( "Error Creating D3D Device - Clearing & Quitting" ) ) ; return FALSE ; } // Bila sampai di sini berarti sukses return TRUE ; }
Fungsi di atas akan membuat object direct3d, mengisi D3DPRESENT_PARAMETERS dan mebuat device object.
68
Implementasi DoFrame DoFrame digunakan untuk menggambar scene ke layar. Parameter DeltaTime digunakan untuk mengukur kecepatan animasi yang bergantung pada jarak waktu render antar frame. Untuk saat ini yang perlu dilakukan hanyalah membersihkan backbuffer dengan warna terentu dan mempresentnya ke layar. /************************************************************************/ /* Fungsi Callback DoFrame */ /* Menggambar ke window, untuk saat ini yang dilakukan hanya membersih- */ /* kan layar ke warna biru */ /************************************************************************/ BOOL CALLBACK DoFrame( float DeltaTime ) { // cek apakah device dan direct3d object tidak NULL if ( !g_pD3D || !g_pDevice ) { return FALSE ; } // bersihkan render target, depth buffer, dan stencil buffer g_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB( 0x00, 0x00, 0xff ), 1.0f, 0 ) ; // present backbuffer ke layar. g_pDevice->Present( NULL, NULL, NULL, NULL ) ; return TRUE ; }
Kode di atas akan meng-clear render target, depth buffer, dan stencil buffer. Render target akan di-clear dengan warna putih, untuk nilai depth buffer dan stencil buffer akan diterangkan dalam bab selanjutnya. Setelah itu backbuffer di-present ke layar.
Implementasi Cleanup Cleanup digunakan untuk melepaskan object device dan object Direct3D setelah window diclose. /************************************************************************/ /* Fungsi Cleanup */ /* Membersihkan object Direct3D dari memory */ /************************************************************************/ void Cleanup( void ) { // Release Device Object if ( g_pDevice ) { g_pDevice->Release( ) ; g_pDevice = NULL ; } // Release Direct3D object ; if ( g_pD3D ) { g_pD3D->Release( ) ; g_pD3D = NULL ; } }
Sebelum device atau Direct3D di release, pointernya dicek dulu, jika NULL, maka tidak dilakukan apa2.
69
Implementasi EnterMessageLoop EnterMessageLoop digunakan sebagai pengganti message loop yang ada dalam code sebelumnya yang menggunakan GetMessage. GetMessage akan menunggu message yang ada sebelum keluar fungsi, supaya tidak saling menunggu, kita menggunakan fungsi lain, yaitu PeekMessage untuk mendapatkan message, jika tidak ada message maka kita merender scene kita ke layar dengan memanggil callback yang dimasukkan dalam parameter. PeekMessage mempunyai prototype sebagai berikut : BOOL PeekMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg );
Parameter
Keterangan
lpMsg
Pointer ke MSG structure yang akan menampung message yang berhasil didapatkan oleh fungsi PeekMessage.
hWnd
Window yang akan diintip messagenya
wMsgFilterMin
Message minimal yang diproses. Gunakan WM_KEYFIRST untuk message keyboard pertama, dan WM_MOUSEFIRST untuk message mouse pertama. Jika wMsgFilterMin dan wMsgFilterMax isinya 0, maka semua message akan diproses.
wMsgFilterMax
Message maksimal yang diproses. Gunakan WM_KEYLAST untuk message keyboard pertama, dan WM_MOUSELAST untuk message mouse pertama. Jika wMsgFilterMin dan wMsgFilterMax isinya 0, maka semua message akan diproses. Menyatakan bagaimana PeekMessage.
message
akan
dihandle
oleh
PM_REMOVE wRemoveMsg
Message akan diambil dari queue setelah memanggil PeekMessage. PM_NOREMOVE Message tidak akan diambil dari queue setelah memanggil PeekMessage.
Perhatikan implementasinya sebagai berikut : /************************************************************************/ /* Fungsi EnterMessageLoop */ /* Sebagai fungsi untuk melakukan loop message */ /************************************************************************/ WPARAM EnterMessageLoop( BOOL ( CALLBACK *lpfnDisplayCB ) ( float ) ) { MSG msg ; // message structure // apakah loop sudah selesai BOOL bDone = FALSE ;
70
// time sekarang dan selisih (delta time) float CurrentTime = 0.0f ; float DeltaTime = 0.0f ; // time terakhir static float LastTime = static_cast( timeGetTime( ) ) ; // Kosongkan isi msg ZeroMemory( &msg, sizeof( MSG ) ) ; // selama bDone == FALSE while ( !bDone ) { // 'intip' message if ( PeekMessage( &msg, g_hWindow, 0, 0, PM_REMOVE ) ) { // jika message adalah WM_QUIT maka keluar loop if ( msg.message == WM_QUIT ) { bDone = TRUE ; break ; } // Translate dan dispatch message seperti biasa TranslateMessage( &msg ) ; DispatchMessage( &msg ) ; } else // jika tidak ada message render scene { // jika lpfnDisplayCB tidak sama dengan NULL if ( lpfnDisplayCB ) { // dapatkan waktu sekarang dan hitung selisih waktu CurrentTime = static_cast( timeGetTime( ) ) ; DeltaTime = ( CurrentTime - LastTime ) * 0.001f ; // panggil fungsi untuk merender lpfnDisplayCB( DeltaTime ) ; // set waktu terakhir = waktu sekarang LastTime = CurrentTime ; } } } return msg.wParam ; }
Fungsi ini pertama kali adalah mengecek apakah ada message atau tidak, jika ada diproses, jika tidak maka render scene, jika message = WM_QUIT maka akan keluar dari loop. Untuk merender scene, perlu parameter DeltaTime. Untuk itu kita perlu menghitungnya. Untuk mendapatkan system time, yaitu waktu yang berjalan sejak windows dijalankan (dalam ms) kita hanya perlu memanggil fungsi timeGetTime. Fungsi ini tidak memerlukan parameter apapun dan akan menghasilkan nilai system time. Selisih time dicari dengan mengurangkan waktu sekarang dan waktu yang terakhir didapatkan ketika merender frame.
Implementasi ErrorMessage ErrorMessage adalah fungsi sederhana yang menampilkan error message. Berguna jika terjadi error. /************************************************************************/ /* Fungsi ErrorMessage */ /* Menampilkan pesan error */ /************************************************************************/
71
void ErrorMessage( LPTSTR lpMessage ) { if ( !lpMessage ) { return ; } // tampilkan pesan error MessageBox( NULL, lpMessage, TEXT( "Error!" ), MB_ICONEXCLAMATION | MB_OK ) ; }
Implementasi CenterWindow CenterWindow digunakan untuk menengahkan window di tengah layar. Kita memakai rumus berikut untuk menengahkan window.
PosWinX = ( Lebar Screen – Lebar Window ) / 2 PosWinY = ( Panjang Screen – Panjang Window ) / 2 Untuk mendapatkan lebar window, pertama kali kita mencari RECT dari window tersebut, kemudian mecari lebarnya dari rect tersebut. Untuk mendapatkan RECT dari window, kita mencarinya dengan fungsi GetWindowRect. BOOL GetWindowRect( HWND hWnd, LPRECT lpRect );
Parameter pertama adalah handle ke window yang akan dicari RECT-nya kemudian parameter kedua menampung hasil RECT-nya. Misalkan variabel RECT kita simpan dalam rcWindow. Maka kita dapatkan rumus sbb: PosWinX = ( Lebar Screen – ( rcWindow.right – rcWindow.left ) ) / 2 sehingga, PosWinX = ( Lebar Screen – rcWindow.right + rcWindow.left ) / 2 PosWinY = ( Panjang Screen – ( rcWindow.bottom – rcWindow.top ) ) / 2 PosWinY = ( Panjang Screen – rcWindow.bottom + rcWindow.top ) / 2 Sehingga codenya menjadi sebagai berikut /************************************************************************/ /* Fungsi Center Window */ /* Menengahkan Window */ /************************************************************************/ void CenterWindow( HWND hWindow ) { POINT wndPos ; RECT rcWindow ; if ( !hWindow ) { return ; } // dapatkan rectangle window GetWindowRect( hWindow, &rcWindow ) ;
72
// hitung posisi window terhadap layar wndPos.x = ( GetSystemMetrics( SM_CXSCREEN ) rcWindow.right + rcWindow.left ) / 2 ; wndPos.y = ( GetSystemMetrics( SM_CYSCREEN) rcWindow.bottom + rcWindow.top ) / 2 ; // pindah window ke tengah SetWindowPos( hWindow, NULL, wndPos.x, wndPos.y, 0, 0, SWP_NOSIZE ) ; }
Fungsi SetWindowPos pada listing di atas adalah untuk memindahkan letak window ke suatu tempat, flag SWP_NOSIZE, berguna untuk menyatakan bahwa fungsi tersebut hanya menentukan lokasi window, tidak mengubah ukuran lebar dan panjang window.
Perubahan pada InitWindow Area yang akan kita gambar adalah client area, perhatikan gambar berikut ini :
Gambar 2.15 Window dan client area Client area adalah tempat kita meletakkan scene, ketika kita menentukan panjang dan lebar window dalam CreateWindowEx, sebenarnya kita mengeset panjang dan lebar window untuk keseluruhan window, termasuk caption dan border. Sehingga misalkan kita menentukan bahwa window berukuran 640x480. maka itu adalah ukuran window, ukuran client area lebih kecil daripada itu. Jika kita ingin bahwa 640x480 adalah ukuran client area, maka kita harus menentukan ukuran window adalah ukuran client area ditambah ukuran border dan ukuran caption bar. Untuk mendapatkan ukuran border dan caption bar, kita menggunakan fungsi GetSystemMetrics : int GetSystemMetrics( int nIndex );
parameter nIndex adalah index dari ukuran yang akan diambil, untuk daftar ukuran yang lengkap bisa dilihat di dokumentasi MSDN. Jadi bila kita membuat window seperti di atas dan menginginkan ukuran client area adalah 640x480, rumusnya adalah :
73
Lebar Window = Lebar Client Area + 2 × lebar border (kanan dan kiri) Panjang Window = Panjang Client Area + Panjang Title Bar + Panjang border (bottom) Setelah window dibuat, maka kita panggil fungsi CenterWindow dengan memasukkan parameter handle window yang sudah dibuat tadi untuk menengahkan window di layar. Sehingga kodenya menjadi : . . . // Buat Window g_hWindow = CreateWindowEx( WS_EX_APPWINDOW, g_szClassName, g_szWindowTitle, WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_SIZEBOX , CW_USEDEFAULT, CW_USEDEFAULT, g_nWindowWidth + 2 * GetSystemMetrics( SM_CXBORDER ), g_nWindowHeight + GetSystemMetrics( SM_CYBORDER ) + GetSystemMetrics( SM_CYCAPTION ), NULL, NULL, hInstance, NULL); if ( g_hWindow == NULL ) { return FALSE ; } CenterWindow( g_hWindow ) ; . . .
SM_CYBORDER adalah untuk mencari tinggi border, SM_CXBORDER adalah untuk mencari lebar border, SM_CYCAPTION adalah untuk mencari tinggi caption bar. Untuk listing lengkap bisa dilihat dalam CD yang diikutkan dalam buku ini.
Perubahan pada WndProc Window procedure sedikit berubah dalam aplikasi ini, kita tidak menangani WM_DESTROY lagi, tetapi WM_CLOSE yang berarti window diclose dan juga WM_KEYUP untuk escape. Pada saat WM_CLOSE, kita mengirimkan message WM_QUIT sehingga main keluar dari loop, saat WM_KEYUP, kita mengecek apakah tombol yang ditekan adalah Escape maka kita perintahkan supaya window diclose. Sehingga Window Procedure menjadi sebagai berikut : /************************************************************************/ /* Fungsi WndProc */ /* Befungsi untuk menangani message yg dikirim dr system */ /************************************************************************/ LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch( uMsg ) { case WM_CLOSE : PostMessage( hWnd, WM_QUIT, 0, 0 ) ; break ;
74
// handle jika tombol keyboard dilepaskan case WM_KEYUP : // jika tombolnya adalah [ESC] // perintahkan window untuk menutup dirinya if ( wParam == VK_ESCAPE ) { CloseWindow( hWnd ) ; } break ; default : return DefWindowProc( hWnd, uMsg, wParam, lParam ) ; } return FALSE ; }
Menambahkan library Direct3D dan WinMM kemudian mem-build aplikasi Setelah melakukan perubahan seperti di atas, kita harus mengatur aplikasi supaya terkait dengan library d3d9.dll dan winmm.dll dengan melakukan linking terhadap export library-nya yaitu d3d9.lib dan winmm.lib. Kenapa kita harus melink aplikasi kita dengan d3d9.lib dan winmm.lib? Ini disebabkan kita memakai fungsi Direct3DCreate9 dari library d3d9.dll dan timeGetTime dari library winmm.dll. Jika tidak dilink dengan export librarynya, maka aplikasi tidak bisa di-build, karena ada missing link untuk fungsi Direct3DCreate9 dan timeGetTime. Untuk menambahkan kedua export library tersebut, kita melakukan langkah-langkah sebagai berikut: •
mengakses menu Project > [nama project] Properties.
•
Klik pada node Linker > Input pada treeview dan set Configuration ke All Configuration. Supaya setting diterapkan baik untuk Debug maupun Release build.
•
Masukkan file d3d9.lib dan winmm.lib ke field Additional dependencies.
•
Klik OK
Perhatikan gambar di bawah ini supaya lebih jelas.
75
Gambar 2.16 Penambahan additional dependencies Setelah selesai melakukan perubahan seperti di atas pada code window tadi, saatnya mem-build aplikasi. Jika Anda mengikuti petunjuk saya, maka tidak akan ada error dalam building aplikasi. Jika aplikasi di-run atau di-debug maka akan keluar sebuah window kosong dengan background warna biru. Artinya kita berhasil memerintahkan Direct3D untuk meng-clear render target, dan kita siap dengan langkah selanjutnya.
Gambar 2.17 Gambar window yang sudah mendukung Direct3D.
76
Rendering Pipeline Rendering pipeline bertanggung jawab menampilkan citra 2D dari koordinat 3D dan kamera maya yang menentukan bagaimana suatu object 3D terlihat.
Gambar 3.1. gambar sebelah kiri menunjukkan bagaimana object 3D diletakkan dalam koordinat 3D, sedangkan gambar sebelah kanan menunjukkan apa yang terlihat dari kamera
Representasi model Sebuah Scene adalah suatu kumpulan object atau model. Object ditampilkan sebagai triangle mesh. Triangle (segitiga) adalah dasar untuk membuat bentuk-bentuk geometri 3D. Seperti terlihat pada gambar berikut :
Gambar 3.2. sebuah object terrain dibuat dengan triangle. Point yang merupakan perpotongan dua edge dari polygon disebut vertex. Untuk menyatakan segitiga, kita bisa menentukan 3 vertex yang berupa tiga lokasi.
77
Vertex
Edge Gambar 3.3. Vertex dan edge pada triangle
Vertex Format Definisi vertex di atas secara matematis tidak ada masalah, tetapi untuk Direct3D, definisi tersebut masih kurang lengkap. Karena selain posisi, vertex bisa mempunyai properti lain seperti warna atau normal. Kita bisa dengan bebas menentukan bagaimana vertex format kita. Untuk membuat vertex format, pertama kali kita membuat structure yang menampung vertex data yang kita inginkan , contoh : struct VertexColor { float x, y, z ; // posisi DWORD Color ; } ; struct VertexNormalTex { float x, y, z ; // posisi float nx, ny, nz ; // normal float tu, tv ; // koordinat texture } ;
Structure yang pertama berisi posisi dan warna dari vertex. Structure yang kedua berisi posisi, normal, dan koordinat texture dari vertex. Setelah kita mendefinisikan structure tersebut, kita harus menentukan bagaimana format vertex tersebut dengan menggunakan kombinasi flag dari Flexible Vertex Format (FVF). Untuk structure yang pertama di atas, kita memakai vertex format #define FVF_COLOR ( D3DFVF_XYZ | D3DFVF_DIFFUSE )
Artinya vertex mempunyai properti posisi (xyz) dan warna diffuse. Sedangkan untuk yang kedua kita menggunakan vertex format berikut #define FVF_NORMAL_TEX ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 )
Artinya vertex mempunyai properti posisi(xyz), vector normal, dan koordinat texture. Yang harus diperhatikan adalah bahwa urutan Vertex Format harus sama dengan urutan structure yang dibuat. Silahkan lihat pada D3DFVF di dokumentasi DirectX SDK.
78
Triangles Triangle adalah building block untuk object 3D. Untuk membuat suatu object, kita membuat sebuah triangle list yang membentuk suatu object. Triangle list berisi setiap data dari triangle yang akan kita gambar di layar. Contoh, jika ingin membuat kotak(rectangle), kita bagi rectangle tersebut dalam dua buah triangle.
v1
v2
v0
v3
Gambar 3.4. Kotak yang terdiri dari 4 vertex dan 2 triangle. Jika dinyatakan dalam array sebagai berikut : Vertex rectVerts[ 6 ] = { v0, v1, v3, v1, v2, v3 } ;
Urutan dari vertex tersebut disebut winding. Keterangan lebih lanjut tentang winding bisa dilihat pada subbab BackFace Culling.
Index Seringkali untuk membuat satu object 3D banyak vertex yang sama yang dipakai oleh dua polygon. Seperti pada gambar 3.4. di atas v1 dan v3 dipakai oleh dua buah segitiga. Mungkin karena masih sedikit bisa kita abaikan, akan tetapi jika bentuk makin kompleks, maka akan semakin banyak vertex yang dipakai bersama.
Gambar 3.5. Kubus yang dibuat dengan triangle mempunyai banyak bertex yang dipakai bersama Untuk mengatasi ini, kita bisa menggunakan index. Kita membuat vertex list dan index list, vertex list berisi semua vertex, dan index list berisi value yang merupakan index dari vertex list untuk menyatakan urutan vertex.
79
Contoh dalam kasus kotak di atas : Vertex rectVerts[ 4 ] = { v0, v1, v2, v3 } ; WORD rectIndices[ 6 ] = { 0, 1, 3, 1, 2, 3 } ;
// segitiga 1 // segitiga 2
Arti dari array index di atas adalah, membuat segitiga pertama dengan rectVerts [ 0 ], kemudian rectVerts[ 1 ], kemudian rectVerts[ 3 ], lalu segitiga kedua dengan rectVerts [ 1 ], kemudian rectVerts[ 2 ], kemudian rectVerts[ 3 ].
Virtual Camera Camera memperlihatkan bagaimana kita melihat bagian dari dunia 3D di mana kita harus membuat citra 2D. Camera ditempatkan dan diarahkan di world dan menampilkan volume space yang terlihat. Perhatikan gambar berikut : Far Clip Plane
Near Clip Plane
Projection window, Direct3D mendefinisikan dengan plane Z=1
Vertical Field of View (FOV) Horizontal Field of View (FOV) Center of Projection
Gambar 3.6. Frustum dari kamera Volume di atas disebut dengan frustum yang didefinisikan dengan sudut penglihatan (field of view) dan near plane serta far plane. Penggunaan frustum dikarenakan bentuk monitor yang kotak. Object yang ada di dalam frustum ditampilkan dan yang di luar frustum akan diabaikan, teknik ini disebut dengan clipping. Projection Window adalah area 2D di mana geometri 3D yang berada di dalam frustum diproyeksikan ke dalam citra 2D. Untuk menyederhanakan, kita mengasumsikan dan membuat bahwa near plane dan projection window berimpit dan Direct3D mendefinisikan projection plane sebagai z = 1.
80
Rendering Pipeline Jika kita telah merepresentasikan model kita dalam 3D, kita mempunyai tugas untuk menampilkannya dalam citra 2D yang bisa dilihat di layar. Proses menampilkan dari data 3D ke citra 2D dinamakan Rendering Pipeline. Gambar di bawah ini menggambarkan bagaimana rendering pipeline itu. Local Space
World Space
View Space
BackFace Culling
Clipping
Projection
Viewport Space
Rasterization
Lighting
Gambar 3.7. Rendering Pipeline Beberapa tahapan dalam pipeline mentranformasi geometri dari satu sistem koordinat ke koordinat lain yang hanya bisa dilakukan dengan matrix. Direct3D akan melakukan transformasi sesuai dengan matrix yang kita masukkan. Supaya Direct3D melakukan transformasi, yang harus kita lakukan hanyalah memberikan matrix yang diperlukan untuk melakukan transformasi melalui method IDirect3DDevice9::SetTransform. Method ini mempunyai 2 parameter, parameter pertama adalah jenis transformasinya, parameter kedua adalah matrix transformasinya. g_pDevice->SetTransform( D3DTS_WORLD, &matWorld ) ;
Mari kita bahas satu persatu tahapan dalam rendering pipeline
Local Space Local space atau modelling space adalah sistem koordinat yang menentukan triangle list dari suatu object. Membuat model dalam space localnya lebih mudah daripada membuat model langsung ke world. Memodel dengan local space menjadikan kita dengan mudah menyatakan model tanpa mengetahui posisinya, ukurannya, atau orientasinya relatif terhadap object lain di world.
Gambar 3.8. Local Space dari teapot
81
World Space Jika kita sudah membuat banyak model yang berada pada local space masing-masing, kita harus menggambarnya semua dalam global scene (world). Object dari local space ditransformasi ke world space dengan proses yang disebut world transform yang biasa mengandung translasi, rotasi, dan scaling yang mengeset posisi, orientasi, dan ukuran dari model di world. World transformation menampilkan letak object-object di dalam world dan hubungannya dengan object lain.
Gambar 3.9. World Space Transformasi
world
dinyatakan
dengan
matrix
oleh
Direct3D
menggunakan
IDirect3DDevice9::SetTransform dengan D3DTS_WORLD sebagai parameter pertama dan matrix transformasi sebagai parameter kedua. Misal kita ingin membuat kubus pada posisi ( -3, 2, 1 ) dan sebuah bola (sphere) pada posisi (-1, 0, 5 ) maka kita akan menulis seperti ini :
// tentukan dan buat matrix transformasi untuk cube dan sphere D3DXMATRIX matWorldCube ; D3DXMATRIX matWorldSphere ; // tentukan posisi dengan translasi D3DXMatrixTranslation( &matWorldCube, -3.0f, 2.0f, 1.0f ) ; D3DXMatrixTranslation( &matWorldSphere, -1.0f, 0.0f, 5.0f ) ; // transformasi dan gambar kubus g_pDevice->SetTransform( D3DTS_WORLD, &matWorldCube ) ; DrawCube( ) ; // karena sphere mempunyai matrix yang berbeda dengan kubus, // maka kita harus mengganti transformasi ke sphere // jika tidak maka sphere akan mengikuti transformasi sebelumnya g_pDevice->SetTransform( D3DTS_WORLD, &matWorldSphere ) ; DrawSphere( ) ;
Contoh di atas adalah contoh yang disederhanakan. Hanya untuk menunjukkan bagaimana cara dan penggunaan world transformation dalam aplikasi.
82
View Space Pada world space, kamera dibuat relatif terhadap world. Akan tetapi hal ini menjadi tidak praktis dan kurang efisien untuk perhitungan matematis jika kamera berada dalam posisi dan orientasi tertentu dalam world. Untuk itulah kita mentransformasi kamera berimpit dengan origin kemudian memutarnya sehingga kamera menghadap ke z+. Semua object dalam world ditransformasi bersamaan dengan kamera dengan transformasi yang sama sehingga kita melihat hal yang sama. Z+
Z+ B
Z+ A
B B C
A
A
D
C C
D
D X+
X+ Translasi
X+ Rotasi
Gambar 3.10. Transformasi World Space ke View Space pertama kamera diimpitkan dengan origin kemudian dirotasikan supaya menghadap ke Z+ Transformasi untuk view space ini bisa dicari dengan menggunakan fungsi D3DX berikut : D3DXMATRIX *D3DXMatrixLookAtLH( D3DXMATRIX* pOut, // CONST D3DXVECTOR3* pEye, // CONST D3DXVECTOR3* pAt, // CONST D3DXVECTOR3* pUp // );
pointer untuk menerima matrix position kamera di world arah kamera 'melihat' arah 'atas' dr kamera
Parameter pEye merupakan posisi kamera di world, pAt adalah sasaran ke mana kamera melihat, dan pUp adalah vector untuk arah atas biasanya adalah arah Y positif (0, 1, 0) . Kita memakai LH karena Direct3D memakai Left Handed System. Contoh : kita akan membuat scene dengan kamera berada di posisi (2, -3, 1 ) dengan target adalah (1, 0, -1 ) kita bisa membuat matrix proyeksi sebagai berikut : D3DXMATRIX matProj ; D3DXVECTOR3 camOrigin( 2.0f, -3.0f, 1.0f ) ; D3DXVECTOR3 camTarget( 1.0f, 0.0f, -1.0f ) ; D3DXVECTOR3 camUp( 0.0f, 1.0f, 0.0f ) ; D3DXMATRIX matView ; D3DXMatrixLookAtLH( &matView, &camOrigin, &camTarget, &camUp ) ;
View
matrix
diset
dengan
menggunakan
IDirect3DDevice9::SetTransform
dengan
D3DTS_VIEW sebagai tipe transformasinya. g_pDevice->SetTransform( D3DTS_VIEW, &matProj ) ;
83
BackFace Culling Polygon mempunyai dua sisi yaitu sisi yang menghadap kamera sering disebug front face dan sisi yang membelakangi kamera yang disebut backface. Backface dari polygon tidak pernah terlihat karena kebanyakan object adalah solid sehingga kamera tidak mungkin 'masuk' ke dalam object tersebut dan melihat backfacenya. Lihat gambar berikut ini :
Back Face
Front Face
ViewPoint
Gambar 3.11. Transformasi World Space ke View Space pertama kamera diimpitkan dengan origin kemudian dirotasikan supaya menghadap ke Z+ Backface tidak pernah terlihat, oleh karena itu Direct3D bisa memanfaatkan ini dengan teknik culling yaitu mengabaikan backface sehingga tidak diproses lebih lanjut. Di kamera, object akan terlihat sama, karena backface memang tidak pernah terlihat.
ViewPoint Gambar 3.12. Front Face dirender tanpa backface Direct3D perlu mengetahui polygon mana yang frontface, mana yang backface dengan menggunakan winding order. Secara default Direct3D menganggap polygon yang vertexnya digambar urut searah jarum jam pada view space adalah sebagai frontface, dan yang berlawanan dengan arah jarum jam adalah backface.
84
v0
v0
v2
v1
v1
v2
Gambar 3.13. Winding order dalam pembuatan polygon. Searah jarum jam (kiri) dan berlawanan arah jarum jam (kanan). Catatan Khusus : Penekanan winding order dalam view space adalah karena mungkin satu polygon yang winding ordernya dalam world space searah dengan jarum jam, pada view space akan berlawanan. Misalnya jika suatu polygon dirotasi 180 derajat, maka polygon tersebut akan berbalik arah winding ordernya. Jika karena suatu alasan, kita perlu mengubah winding order, kita hanya perlu mengakses fungsi IDirect3DDevice9::SetRenderState dengan parameter pertama adalah D3DRS_CULLMODE dan parameter kedua adalah salah satu dari nilai berikut : Nilai
Keterangan
D3DCULL_NONE
Menonaktifkan backface culling.
D3DCULL_CW
Segitiga yang digambar searah jarum jam yang di-cull.
D3DCULL_CCW
Segitiga yang digambar berlawanan arah jarum jam yang di-cull.
Lighting Sumber cahaya ditentukan dalam world space dan ditransformasikan ke view space. Pada view space, sumber cahaya ini diterapkan untuk memberikan efek pencahayaan pada object. Untuk lighting akan dibahas pada bab tersendiri nanti.
Clipping Pada tahap ini kita meng-cull geometri yang ada di luar frustum. Proses ini disebut clipping. Ada 3 kemungkinan yang terjadi : •
Ada di luar frustum sama sekali, jika suatu segitiga ada di luar frustum maka segitiga tersebut diabaikan
•
Ada di dalam frustum seluruhnya, jika suatu segitiga ada di dalam frustum seluruhnya, maka segitiga tersebut diproses semuanya.
85
•
Ada sebagian di dalam frustum, sebagian di luar frustum, jika begini keadaannya, maka segitiga akan dibagi menjadi dua, yang ada di dalam frustum akan diproses yang di luar frustum akan diabaikan.
Pada tahap ini kita meng-cull geometri yang ada di luar frustum. Proses ini disebut clipping. Ada 3 kemungkinan yang terjadi : Z+
Di Dalam Di Luar Sebagian di luar/dalam X+
Gambar 3.14. clipping geometry yang ada di luar frustum
Proyeksi Pada view space, kita harus mengubah representasi 3D menjadi citra 2D di layar yang disebut dengan projection (proyeksi). Ada banyak sekali proyeksi, tetapi saya hanya akan bahas perspective projection. Proyeksi ini menggambarkan seperti keadaan asli, objek yang jauh keliatan kecil, sedang yang dekat akan keliatan besar.
q
q'
Center of Projection
Gambar 3.15. Projection point q ke q' di projection window (z=1)
86
Projection transformation mendefinisikan frustum kita dan bertanggung jawab memproyeksikan geometri ke dalam window. Membuat projection matrix sangat kompleks, sehingga kita hindari dulu pembuatan matrix projection secara manual. Kita pake saja fungsi dari D3DX di bawah ini : D3DXMATRIX *D3DXMatrixPerspectiveFovLH( D3DXMATRIX* pOut, // projection matrix FLOAT fovY, // fov vertikal FLOAT Aspect, // aspect ratio = width / height FLOAT zn, // jarak z untuk near plane FLOAT zf // jarak z untuk far plane );
Sebelum saya terangkan silahkan perhatikan gambar berikut :
Z+
f fov
n X+
Gambar 3.16. properti dari projection Aspect ratio diperlukan untuk mengkoreksi distorsi transformasi dari projection window ke layar. Aspect ratio adalah perbandingan antara lebar dan panjang layar. AspectRatio = ScreenWidth/ScreenHeight Sama dengan transformasi yang lain, untuk projection transformation kita menggunakan IDirect3DDevice9::SetTransform untuk menerapkan transformasi ke layar dengan parameter pertama adalah D3DTS_PROJECTION. D3DMATRIX matProj ; D3DXMatrixPerspectiveFovLH ( &matProj, D3DX_PI * 0.25f, // fovy = 1/4 pi rad = 45 deg (float) g_nWindowWidth / (float) g_nWindowHeight, 1.0f, // plane near = 1.0f 10000.0f // plane far = 10000.0f ) ; g_pDevice->SetTransform( D3DTS_PROJECTION, &matProj ) ;
Viewport transform Viewport transform bertanggung jawab atas transformasi koordinat di projection window ke rectangle dalam screen yang kita sebut viewport. Untuk games biasanya viewport adalah
87
keseluruhan window, tapi kita bisa mengaturnya agar hanya window tertentu. Viewport dinyatakan secara relatif terhadap window
Gambar 3.17. Window dan viewport Viewport dinyatakan dengan mengisi structure D3DVIEWPORT9 seperti berikut : typedef struct _D3DVIEWPORT9 { DWORD X; DWORD Y; DWORD Width; DWORD Height; DWORD MinZ; DWORD MaxZ; } D3DVIEWPORT9;
MinZ dan MaxZ adalah nilai depth yang kita spesifikasikan, Direct3D secara default menggunakan depth buffer 0 sampai 1, biarkan seperti itu kecuali jika menginginkan efek khusus. Setelah kita menspesfikasikan viewport, kita bisa mengeset viewport sebagai berikut : D3DVIEWPORT9 Viewport( 0, 0, 640, 480, 0, 1 ) ; g_pDevice->SetViewport( &Viewport ) ;
Direct3D menangani viewport secara otomatis. Sebagai refernsi, matrix yang digunakan dalam transformasi viewport adalah sebagai berikut :
⎛ width 0 ⎜ 2 ⎜ height ⎜ 0 − ⎜ 2 ⎜ 0 0 ⎜ ⎜ Width Height Y + ⎜X + 2 2 ⎝
0 0
MaxZ − MinZ MinZ
⎞ 0⎟ ⎟ 0⎟ ⎟ ⎟ 0⎟ ⎟ 1⎟ ⎠
88
Rasterization Rasterization adalah proses setelah vertex ditransform ke koordinat layar 2D, tahap rasterisasi ini diproses dengan menghitung warna dari tiap pixel pada tiap triangle. Proses rasterisasi adalah proses yang paling process-intensive, sehingga hanya dilakukan oleh hardware grafik.
Gambar 3.18. Triangle yang melalui proses rasterisasi Setelah dirasterisasi, scene siap ditampilkan di layar. Yaitu dengan page flipping yang sudah diterangkan di bab sebelumnya.
89
Menggambar di DirectX Bab ini akan membahas bagaimana kita menggambar scene di Direct3D bermodalkan pengetahuan yang sudah kita bahas di bab-bab sebelumnya. Method-method dan interface yang digunakan dalam buku ini merupakan method dan interface yang penting, karena dipakai di bab-bab selanjutnya.
Vertex Buffer dan Index Buffer Vertex buffer dan index buffer merupakan interface yang hampir sama, untuk itu kita bahas saja secara bersamaan. Vertex buffer secara ringkas adalah sebuah bagian dari memory yang berisi data vertex. Sedangkan index buffer adalah sebuah bagian memory yang berisi data integer yang merupakan index. Kita menggunaan vertex buffer dan index buffer untuk menyimpan data kita daripada menggunakan array karena vertex buffer dan index buffer disimpan dalam video memory. Merender data pada video memory lebih cepat daripada dari system memory. Vertex Buffer dinyatakan dalam interface IDirect3DVertexBuffer9 dan index buffer dinyatakan dalam interface IDirect3DindexBuffer9.
Membuat Vertex Buffer dan Index Buffer Vertex Buffer dan Index Buffer dapat dibuat dengan cara memanggil method-method sebagai berikut : HRESULT IDirect3DDevice9::CreateVertexBuffer( UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer HANDLE *pSharedHandle ) ; HRESULT IDirect3DDevice9::CreateIndexBuffer( UINT Length, DWORD Usage, DWORD Format, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppIndexBuffer HANDLE *pSharedHandle ) ;
Karena kedua method tersebut mempunyai parameter yang hampir sama, mari kita bahas bersamaan : Parameter
Keterangan
Length
Jumlah byte yang dialokasikan untuk buffer. Jika kita ingin sebuah buffer yang dapat menyimpan 8 vertex, maka kita set parameter ini menjadi 8 * sizeof(Vertex).
Usage
Properti tambahan yang menyatakan bagaimana buffer digunakan, nilainya bisa 0 yang berarti tidak ada properti baru atau kombinasi dari flag-flag berikut :
90
D3DUSAGE_DYNAMIC Menyetting flag ini menyebabkan buffer menjadi dinamis. Lihat catatan khusus. D3DUSAGE_POINTS Digunakan jika buffer berisi point primitives, biasanya untuk membuat particle system. D3DUSAGE_SOFTWAREPROCESSING Vertex Processing dilakukan di software D3DUSAGE_WRITEONLY Menunjukkan bahwa aplikasi hanya akan menulis ke buffer sehingga buffer ditempatkan di memory yang paling tepat digunakan untuk operasi tulis. Membaca dari buffer dengan flag ini akan menyebabkan error. FVF
Flexible Vertex Format, yaitu format vertex yang dimasukkan ke buffer
Pool
Memory pool di mana buffer akan ditempatkan
ppVertexBuffer
Pointer untuk menyimpan hasil vertex buffer yang dibuat
pSharedHandle
Tidak dipakai, isi dengan NULL.
Format
Menyatakan format dari index. Gunakan D3DFMT_INDEX16 untuk index 16 bit dan D3DFMAT_INDEX32 untuk index 32 bit. Tidak semua device mendukung format index 32 bit, cek dulu di capabilities.
ppIndexBuffer
Pointer untuk menyimpan hasil index buffer yang dibuat.
Catatan Khusus : Buffer yang dibuat tanpa D3DUSAGE_DYNAMIC disebut static buffer. Static buffer biasa disimpan dalam video memory. Membaca dari static buffer sangat lambat, karena mengakses video memory itu lambat. Untuk itu, kita hanya menggunakan static buffer untuk menyimpan static data yaitu data yang tidak akan berubah sangat cepat, contoh yang cocok dipakai sebagai static buffer adalah bangunan dan terrain. Buffer yang dibuat dengan D3DUSAGE_DYNAMIC disebut dynamic buffer. Dynamic buffer biasa disimpan dalam memory AGP untuk pemrosesan yang cepat. Dynamic buffer tidak diproses seefisien static buffer, tetapi dapat diakses dengan cepat. Contoh yang cocok dipakai sebagai dynamic buffer adalah particle system, karena berubah sangat sering. Sebenarnya ada cara paling efisien dalam menggunakan kedua buffer tersebut yaitu dengan menyimpan copy data di system memory, kemudian dicopy ke buffer yang bersangkutan jika diperlukan. Karena membaca dari system memory oleh CPU jauh lebih cepat daripada membaca dari video memory atau AGP memory.
91
Snippet di bawah ini adalah contoh penggunaan pembuatan vertex buffer yang menyimpan 8 vertex. UINT VertexTotalSize = 8 * sizeof( Vertex ) ; IDirect3DVertexBuffer9 *pVB = NULL ; g_pDevice->CreateVertexBuffer ( VertexTotalSize, 0, D3DFVF_XYZ, D3DPOOL_MANAGED, &pVB, NULL ) ;
Contoh di bawah ini adalah penggunaan pembuatan index buffer sebanyak 36 dengan dynamic buffer dan hanya digunakan untuk penulisan (write only) : UINT IndexTotalSize = 36 * sizeof( WORD ) ; IDirect3DIndexBuffer9 *pIB = NULL ; g_pDevice->CreateIndexBuffer ( IndexTotalSize, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &pIB, NULL ) ;
Mengakses Buffer Memory Untuk mengakses buffer memory kita memerlukan pointer ke memory internal dari buffer. Kita mendapatkan pointer ini dengan memanggil method Lock dari interface buffer tersebut. HRESULT IDirect3DVertexBuffer9::Lock( UINT OffsetToLock, UINT SizeToLock, BYTE** ppbData, DWORD Flags ); HRESULT IDirect3DIndexBuffer9::Lock( UINT OffsetToLock, UINT SizeToLock, BYTE** ppbData, DWORD Flags );
Parameter
Keterangan
OffsetToLock
Offset (dalam byte) tempat mulai buffer akan dilock, perhatikan gambar berikut :
92
Buffer Memory
Size To Lock
Offset To Lock + SizeToLock
Offset To Lock
Gambar 4.1.OffsetTo Lock dan SizeToLock OffsetToLock dan SizeToLock menyatakan memory yang akan di-lock, jika keduanya 0, maka seluruh buffer akan di-lock. SizeToLock
Ukuran Buffer yang akan dilock dalam bytes.
ppbData
Pointer ke awal dari memory yang dilock Flags yang menyatakan bagaimana buffer dilock, dapat berupa 0 atau kombinasi dari flag berikut : D3DLOCK_DISCARD Flag hanya digunakan untuk Dynamic Buffers. Memerintahkan hardware untuk mengabaikan buffer dan mengembalikan pointer ke buffer baru. Flag ini sangat berguna karena mencegah render stall ketika buffer yang sedang dirender di-lock. D3DLOCK_NOOVERWRITE
Flags
Flag hanya digunnakan untuk Dynamic Buffers, menunjukkan bahwa kita ingin menambahkan data ke buffer, dengan begitu hardware bisa merender data di buffer sedangkan kita menambahkan data tanpa mengganggu proses rendering. D3DLOCK_READONLY Flag yang menunjukkan bahwa kita hanya ingin membaca buffer, mencoba menulis buffer akan gagal. Dengan begitu Direct3D akan melakukan optimasi untuk pembacaan buffer.
Flag D3DLOCK_DISCARD dan D3DLOCK_NOOVERWRITE menunjukkan suatu situasi dimana memory bisa saja sedang dalam proses rendering ketika kita mengaksesnya sehingga mencegah rendering stall ketika locking. Contoh di bawah ini menunjukkan cara locking vertex buffer dan mengisi isinya : Vertex *pVertices = NULL ; pVB->Lock( 0, 0, ( void** ) &pVertices, 0 ) ; // lock seluruh buffer pVertices[ 0 ] = Vertex( -1.0f, pVertices[ 1 ] = Vertex( 0.0f, pVertices[ 2 ] = Vertex( 1.0f,
0.0f, 1.0f, 0.0f,
2.0f ) ; // menulis vertex ke buffer 2.0f ) ; 2.0f ) ;
pVB->Unlock( ) ;
93
Mendapatkan informasi Vertex Buffer dan Index Buffer Kadang-kadang kita membutuhkan informasi tentang vertex buffer atau index buffer yang kita telah buat,
bisa
dilakukan
dengan
memanggil
method
GetDesc
dan
disimpan
ke
structure
D3DVERTEXBUFFER_DESC atau D3DINDEXBUFFER_DESC. Snippet di bawah ini menunjukkan cara menggunakannya : D3DVERTEXBUFFER_DESC VBDesc ; D3DINDEXBUFFER_DESC IBDesc ; pVB->GetDesc( &VBDesc ) ; pIB->GetDesc( &IBDesc ) ;
definisi dari structure di atas adalah sebagai berikut : typedef struct _D3DVERTEXBUFFER DESC { D3DFORMAT Format; D3DRESOURCETYPE Type; DWORD Usage; D3DPOOL Pool; UINT Size; DWORD FVF; } D3DVERTEXBUFFER_DESC; typedef struct _D3DINDEXBUFFER_DESC { D3DFORMAT Format; D3DRESOURCETYPE Type; DWORD Usage; D3DPOOL Pool; UINT Size; } D3DINDEXBUFFER_DESC;
Arti dari member structure tersebut sama dengan arti parameter ketika membuat buffer.
Render States Direct3D menyediakan banyak render state yang memungkinkan kita memodifikasinya yang akan mempengaruhi bagaimana scene dirender. Render state mempunyai default values, sehingga kita hanya menggantinya jika diperlukan. Nilai dari render state akan tetap dan akan mempengaruhi proses render selanjutnya sebelum kita menggantinya dengan nilai baru. Method yang dipakai adalah IDirect3DDevice9::SetRenderState. HRESULT IDirect3DDevice9::SetRenderState( D3DRENDERSTATETYPE State, // state yang akan diganti DWORD Value // nilai state baru );
Contoh penggunaan : g_pDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME ) ;
untuk dokumentasi lengkap render state silahkan baca di DirectX SDK dengan keyword D3DRENDERSTATETYPE.
94
Persiapan untuk menggambar Sekali kita membuat vertex buffer dan (opsional) index buffer, saatnya merender si dari buffer tersebut ke layar. Tapi ada beberapa hal yang harus dilakukan dahulu 1. Mengeset stream source. Mengeset stream source mengkaitkan vertex buffer ke stream yang akan mengalirkan data geometri ke rendering pipeline. Method di bawah ini digunakan untuk mengeset stram source HRESULT IDirect3DDevice9::SetStreamSource( UINT StreamNumber, IDirect3DVertexBuffer9* pStreamData, UINT OffsetInBytes, UINT Stride );
Parameter
Keterangan
StreamNumber
Nomor identifikasi stream source di mana kita menggunakan vertex buffer. Karena kita hanya menggunakan satu stream dalam buku ini, kita selalu memakai stream 0.
pStreamData
Pointer ke vertex buffer yang akan dikaitkan dengan stream
OffsetInBytes
Offset dari awal stream dalam byte, menunjukkan awal dari vertex yang akan dimasukkan ke rendering pipeline. Untuk mengeset parameter di sini selain 0, maka cek D3DDEVCAPS2_STREAMOFFSET flag di structure D3DDEVCAPS9.
Stride
Ukuran tiap elemen dalam vertex buffer yang akan dimasukkan ke stream.
Contoh misal pVB adalah vertex buffer yang sudah diisi dengan vertex dengan tipe Vertex. g_pDevice->SetStreamSource( 0, pVB, 0, sizeof( Vertex ) ) ;
2. Set Vertex Format. Di sinilah kita mengeset vertex format dari vertex yang akan digunakan untuk perintah-perintah rendering di bawahnya. g_pDevice->SetFVF( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 ) ;
3. Jika kita menggambar menggunakan index buffer, maka perlu mengeset index buffer. Index buffer ini akan dipakai pada perintah drawing di bawahnya. Hanya bisa satu index buffer aktif dalam satu waktu, jika ingin memakai index buffer lain harus diset lagi. Misalkan index buffer adalah pIB, maka untuk mengesetnya perlu dilakukan perintah : g_pDevice->SetIndices( pIB ) ;
Menggambar Vertex dan Index Buffer Setelah kita membuat vertex dan index buffer dan melakukan seluruh persiapan, saatnya menggambar geometri ke layar yang mengirim geometri ke rendering pipeline dengan
95
DrawPrimitive atau DrawIndexedPrimitive. Method ini mengambil vertex info dari vertex stream dan index buffer yang sudah diset sebelumnya
IDirect3DDevice9::DrawPrimitive Method ini digunakan untuk menggambar primitive tanpa index buffer. HRESULT IDirect3DDevice9::DrawPrimitive( D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount );
Parameter
Keterangan
PrimitiveType
Tipe primitive, karena kita ingin menggambar segitiga, maka kita akan memakai D3DPT_TRIANGLELIST, untuk daftar primitive lengkap silahkan baca DirectX SDK, cari dengan kata kunci D3DPRIMITIVETYPE.
StartVertex
Index dari awal vertex buffer yang akan digambar. Parameter ini memberikan kebebasan kita untuk men-draw sebagian saja dari vertex di vertex buffer.
PrimitiveCount
Jumlah primitive yang akan digambar.
Contoh : menggambar 4 segitiga (triangles) g_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 4 ) ;
IDirect3DDevice9::DrawIndexedPrimitive Digunakan untuk menggambar primitive menggunakan index info. HRESULT IDirect3DDevice9::DrawIndexedPrimitive( D3DPRIMITIVETYPE Type, INT BaseVertexIndex, UINT MinIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount ) ;
Parameter
Keterangan
Type
Tipe primitive, karena kita ingin menggambar segitiga, maka kita akan memakai D3DPT_TRIANGLELIST, untuk daftar primitive lengkap silahkan baca DirectX SDK, cari dengan kata kunci D3DPRIMITIVETYPE.
BaseVertexIndex
Nomor awal dari vertex yang nantinya akan digunakan sebagai offset dari index. Silahkan lihat di catatan khusus
MinIndex
Index minimum
NumVertices
Jumlah vertex
96
Index dari element di index buffer yang menandai awal index buffer yang akan dipergunakan.
StartIndex
Jumlah Primitive yang digambar.
PrimitiveCount
Contoh : g_pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 8, 0, 12 ) ;
Catatan Khusus : Parameter BaseVertexIndex perlu penjelasan khusus. Perhatikan gambar berikut ini : Vertex untuk tiga object disimpan dalam vertex buffer yang berbeda
Vertex Buffer untuk Kubus
Vertex Buffer untuk Sphere
Vertex Buffer untuk Cylinder
Vertex untuk tiga object disimpan dalam satu vertex buffer global
Vertex Buffer untuk Kubus
Vertex offset kubus
Vertex Buffer untuk Sphere
Vertex offset sphere
Vertex Buffer untuk Cylinder
Vertex offset cylinder
Gambar 4.2.Tiga vertex buffer digabung dalam satu global vertex buffer Jika membuat tiga buah vertex buffer untuk masing-masing benda, kita dengan mudah mengeset VertexBaseIndex dengan 0, tetapi bayangkan jika kita menggunakan satu vertex buffer global untuk ketiga benda tersebut, maka kita harus mengeset VertexBaseIndex untuk masing-masing benda. Jadi kita tidak perlu menghitung kembali nilai index jika kita menggabungkan ketiga buffer tersebut. Nilai index tetap sama dengan jika satu-satu tetapi menggunakan VertexBaseIndex sehingga Direct3D menghitung kembali nilai index sebenarnya dalam vertex buffer.
97
Begin dan End Scene Menggambar primitive harus dilakukan di dalam pasangan IDirect3DDevice9::BeginScene dan IDirect3DDevice9::EndScene. if ( SUCCEEDED( g_pDevice->BeginScene( ) ) ) { g_pDevice->DrawPrimitive( ... ) ; g_pDevice->EndScene( ) ; }
D3DX Geometry Membuat geometri 3D dengan membuat triangle list sendiri amat melelahkan. Untungya D3DX library menyediakan method untuk men-generate mesh data dari object object 3D sederhana untuk kita. •
D3DXCreateBox
•
D3DXCreateSphere
•
D3DXCreateCylinder
•
D3DXCreateTeapot
•
D3DXCreatePolygon
•
D3DXCreateTorus
Membuat geometri 3D dengan membuat triangle list sendiri amat melelahkan. Untungya D3DX library menyediakan method untuk men-generate mesh data dari object object 3D sederhana untuk kita.
Gambar 4.3.Geometri D3DX Semua fungsi tersebut menggunakan D3DX mesh data structure yang disimpan dalam interface ID3DXMesh. HRESULT D3DXCreateTeapot( LPDIRECT3DDEVICE9 pDevice, // device mesh LPD3DXMESH* ppMesh, // pointer untuk menyimpan mesh LPD3DXBUFFER* ppAdjacency // 0 untuk sekarang );
98
Contoh penggunaan untuk membuat teapot : D3DXCreateTeapot( g_pDevice, &pMesh, 0 ) ;
Setelah mesh data dibuat, kita bisa menggambarnya dengan ID3DXMesh::DrawSubset. Method ini hanya memakai satu parameter yang merupakan subset dari mesh yang bersangkutan. Mesh yang dibuat dengan fungsi D3DXCreate* di atas hanya mempunyai satu subset. Contoh dalam penggunaannya : if ( SUCCEEDED( g_pDevice->BeginScene( ) ) ) { pMesh->DrawSubset( 0 ) ; g_pDevice->EndScene( ) ; }
Seperti object COM lainnya, setelah dipakai maka mesh harus direlease jika sudah selesai dipakai : if ( pMesh ) { pMesh->Release( ) ; pMesh = NULL ; }
Let's Code!! Pada code kali ini ada perombakan yang lumayan besar dalam struktur project kita. Kita akan mulai menggunakan multiple files supaya project lebih terstruktur. Saya susun file sebagai berikut : File
Keterangan
Common.h
File ini berisi header-header yang dipakai hampir di semua file. Seperti Windows.h, tchar.h, cstdio dan lain-lain.
Globals.h
File ini berisi extern variabel global yang mungkin didefinisikan di file lain, file ini mengizinkan suatu file untuk dapat mengakses variabel global yang dideklarasikan dalam file lain
D3Dutil.h/cpp
File berisi utilities untuk melakukan inisialisasi dan deiniisialisasi Direct3D
Util.h/cpp
File berisi utilities umum seperti CenterWindow dll.
Render.h/cpp
Berisi rutin yang berhubungan dengan merender scene ke layar, fungsi DoFrame berada di sini.
WinMain.cpp
Program utama, WinMain, Message Loop, dan Window Procedure diletakkan dalam file ini.
Kode yang dipakai sama dengan kode untuk membuat window direct3d biru pada bab sebelumnya. Hanya satu fungsi yang saya ubah namanya yaitu Cleanup, menjadi CleanupDirect3D. Supaya lebih jelas bagaimana saya merubah struktur project, silahkan buka project skeleton pada directory Code Samples\Chapter4\Skeleton. Bila Anda melihat dengan seksama, ada beberapa perubahan dalam kode di skeleton ini antara lain :
99
•
Clear color diganti dengan warna putih, supaya object wireframe yang digambar dengan warna hitam bisa terlihat dengan jelas.
•
Icon dan cursor yang dipakai bukan icon dan cursor default melainkan icon dan cursor saya sendiri.
•
Handling untuk WM_KEYUP untuk tombol ESC tidak meng-close window tetapi langsung keluar aplikasi dengan mengirim WM_QUIT ke window yang bersangkutan.
•
Mempunyai menu.
•
Tombol [C] berguna untuk menghidupkan dan mematikan culling.
Perubahan-perubahan di atas bisa langsung dilihat di dalam code. Saya tidak akan menerangkan lebih jauh karena hampir setiap baris dalam sample code sudah dicomment sehingga saya akan fokus pada Direct3D saja. Jika ingin lebih jelas tentang pemrograman window bisa baca buku Charles Petzold tentang Windows Programming.
Mengedit Skeleton Code Skeleton Code yang saya gunakan adalah suatu framework sederhana yang akan digunakan untuk membuat semua sample code dalam buku ini. Untuk bab ini, saya membuat 4 buah sample yaitu : •
Triangle, merupakan sample sederhana yang menggambarkan segitiga di layar.
•
Cube, sedikit lebih kompleks daripada sample triangle, aplikasi ini menampilkan kubus yang berputar. Pada aplikasi ini saya memakai index buffer.
•
Teapot, aplikasi yang menggunakan D3DXCreateTeapot untuk membuat teapot yang berputar.
•
Objects, aplikasi yang merender banyak object dalam satu scene yang dibuat menggunakan D3DXCreate *.
Untuk lebih jelas, mari kita bahas sample aplikasi yang membuat kubus yang mengajarkan cara membuat vertex buffer dan index buffer secara lengkap. Untuk menggambar object ini, saya menambahkan dua fungsi yaitu Setup untuk mempersiapkan seluruh resource yang dibutuhkan dan cleanup untuk membersihkan resource. Kita hanya memodifikasi pada file Render.h dan Render.cpp. Kita mendeklarasikan dua fungsi di atas dalam Render.h. // Setup Resource untuk render BOOL Setup( ) ; // fungsi untuk menggambar frame BOOL CALLBACK DoFrame( float DeltaTime ) ; // Cleanup Resource untuk render BOOL Cleanup( ) ;
Pada Render.cpp kita deklarasikan vertex buffer, index buffer, structure Vertex , dan vertex format yang diperlukan. Semua variabel ini adalah static, supaya hanya bisa diakses pada module yang bersangkutan saja, tidak pada module yang lain :
100
// Vertex Buffer static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL ; // Index Buffer static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL ; // Structure dari vertex buffer struct Vertex { float x, y, z ; }; // Vertex Format #define VERTEXFORMAT ( D3DFVF_XYZ )
Setelah itu, kita mendefinisikan sebuah array yang berisi vertex dan index dari kubus yang bersangkutan. Sebenarnya kita bisa langsung menulis di vertex dan index buffer, tetapi resikonya adalah jika kita memodifikasi kedua vertex tersebut akan sangat lambat sehingga kita buat dulu sebuah array di system memory, kemudian akan dicopy-kan langsung nanti. #define VERTICESNUMBER 8 #define INDICESNUMBER 36 #define PRIMITIVESNUMBER const Vertex pVertCube [ { { -1.0f, -1.0f, -1.0f { -1.0f, 1.0f, -1.0f { 1.0f, 1.0f, -1.0f { 1.0f, -1.0f, -1.0f { -1.0f, -1.0f, 1.0f { -1.0f, 1.0f, 1.0f { 1.0f, 1.0f, 1.0f { 1.0f, -1.0f, 1.0f } ; const { 0, 4, 4, 3, 1, 4, } ;
12 VERTICESNUMBER ] = }, }, }, }, }, }, }, },
WORD pIdxCube [ INDICESNUMBER ] = 1, 6, 5, 2, 5, 0,
2, 5, 1, 6, 6, 3,
0, 4, 4, 3, 1, 4,
2, 7, 1, 6, 6, 3,
3, 6, 0, 7, 2, 7,
// // // // // //
depan belakang kiri kanan atas bawah
// ukuran Vertex dan Index Buffer const UINT nTotalVertSize = VERTICESNUMBER * sizeof( Vertex ) ; const UINT nTotalIdxSize = INDICESNUMBER * sizeof( WORD ) ;
Fungsi Setup menyiapkan dan mengeset matrix view dan projection dan menyiapkan dan mengisi vertex buffer dan index buffer dengan data vertex dan index di atas, perhatikan codenya secara lengkap di bawah ini, hampir tiap code mempunyai comment BOOL Setup() { // Matrix D3DXMATRIX matView ; D3DXMATRIX matProj ; // Vector untuk transformasi view D3DXVECTOR3 vPos( 0.0f, 0.0f, -5.0f ) ;
101
D3DXVECTOR3 vTarget( 0.0f, 0.0f, 0.0f ) ; D3DXVECTOR3 vUp( 0.0f, 1.0f, 0.0f ) ; // menampung hasil eksekusi method Direct3D HRESULT hr = 0 ; // pointer untuk menampung index buffer dan vertex buffer Vertex *pVertices = NULL ; LPWORD pIndices = NULL ; if ( !g_pDevice ) { ErrorMessage( TEXT ( "Cannot Create Buffer from NULL Device" ) ) ; return FALSE ; } // Buat Vertex Buffer hr = g_pDevice->CreateVertexBuffer( nTotalVertSize, D3DUSAGE_WRITEONLY, VERTEXFORMAT , D3DPOOL_MANAGED, &g_pVB, NULL ); if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Creating Vertex Buffer" ) ) ; return FALSE ; } // Buat Index Buffer hr = g_pDevice->CreateIndexBuffer( nTotalIdxSize, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &g_pIB, NULL ) ; if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Creating Index Buffer" ) ) ; return FALSE ; } // Lock Vertex Buffer hr = g_pVB->Lock( 0, 0, ( void ** ) &pVertices, D3DLOCK_DISCARD ) ; if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Locking Vertex Buffer" ) ) ; return FALSE ; } // Copy data ke Vertex Buffer memcpy( pVertices, pVertCube, nTotalVertSize ) ; // Unlock Vertex Buffer hr = g_pVB->Unlock( ) ; if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Unlocking Vertex Buffer" ) ) ; return FALSE ; } // Lock Index Buffer hr = g_pIB->Lock( 0, 0, ( void ** ) &pIndices, D3DLOCK_DISCARD ) ; if ( FAILED( hr ) ) { ErrorMessage( TEXT ( "Error Locking Index Buffer" ) ) ; } // copy data ke index buffer memcpy( pIndices, pIdxCube, nTotalIdxSize ) ; // Unlock index buffer hr = g_pIB->Unlock( ) ;
102
if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Unlocking Index Buffer" ) ) ; return FALSE ; } // Set Matrix View dan Projection D3DXMatrixLookAtLH( &matView, &vPos, &vTarget, &vUp ) ; D3DXMatrixPerspectiveFovLH( &matProj, // projection D3DX_PI * 0.25f, // 45 derajat static_cast(g_nWindowWidth) / g_nWindowHeight, // Aspect 1.0f, 1000.0f ) ; // set transformasi View dan Projection g_pDevice->SetTransform( D3DTS_VIEW, &matView ) ; g_pDevice->SetTransform( D3DTS_PROJECTION, &matProj ) ; // gambar sebagai wireframe g_pDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME ) ; return TRUE ; }
Pada code yang saya beri warna lebih gelap di situlah proses locking, memcopy, dan unlocking untuk vertex dan index buffer terjadi. Kode di atas juga mengubah fill mode menjadi wireframe, artinya digambar kerangkanya saja, tanpa warna, dan tanpa shading. Untuk DoFrame, tugasnya adalah mengupdate dan menggambar scene ke layar. Kita ingin memutar kubusnya sehingga kita perlu mengeset world matrix untuk memutar kubus tersebut.Kubus akan diputar dengan cara menambah sudut perputaran setiap frame. /************************************************************************/ /* Fungsi Callback DoFrame */ /* Menggambar ke window, untuk saat ini yang dilakukan hanya membersih- */ /* kan layar ke warna putih */ /************************************************************************/ BOOL CALLBACK DoFrame( float DeltaTime ) { // matrix untuk rotasi D3DXMATRIX matRotX, matRotY, matRotAll ; static float rotY = 0.0f ; // cek apakah device dan direct3d object tidak NULL if ( !g_pD3D || !g_pDevice ) { return FALSE ; } // SetMatrix Rotasi untuk X D3DXMatrixRotationX( &matRotX, D3DX_PI * 0.3f ) ; // SetMatrix Rotasi untuk Y D3DXMatrixRotationY( &matRotY, rotY ) ; // tambah rotasi Y tiap frame rotY += DeltaTime ; // jika rotY > 360 derajat (2*PI) maka balik kurangi dengan // 2*PI untuk menjaga agar rotasi tetap pada // 0 - 360 derajat if ( rotY >= D3DX_PI * 2.0f ) { rotY -= D3DX_PI * 2.0f ; }
103
// kombinasikan kedua rotasi tersebut matRotAll = matRotX * matRotY ; // bersihkan render target, depth buffer, dan stencil buffer g_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB( 0xff, 0xff, 0xff ), 1.0f, 0 ) ; // set matrix rotasi untuk kubus g_pDevice->SetTransform( D3DTS_WORLD, &matRotAll ) ; // Begin Scene g_pDevice->BeginScene( ) ; // Kaitkan Stream Source, Index, dan FVF g_pDevice->SetStreamSource( 0, g_pVB, 0, sizeof( Vertex ) ) ; g_pDevice->SetIndices( g_pIB ) ; g_pDevice->SetFVF( VERTEXFORMAT ) ; // Gambar Cube Mesh g_pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, VERTICESNUMBER, 0, PRIMITIVESNUMBER ) ; // End Scene g_pDevice->EndScene( ) ; // present backbuffer ke layar. g_pDevice->Present( NULL, NULL, NULL, NULL ) ; return TRUE ; }
Setelah itu, barulah kita bersihkan index buffer dan vertex buffer di fungsi cleanup. BOOL Cleanup() { // Lepaskan Vertex Buffer if ( g_pVB ) { g_pVB->Release( ) ; g_pVB = NULL ; } // Lepaskan Index Buffer if ( g_pIB ) { g_pIB->Release( ) ; g_pIB = NULL ; } return TRUE ; }
Untuk screenshot dari masing-masing sample code bisa dilihat di halaman selanjutnya.
104
Sample Code Screenshot Segitiga
Gambar 4.4. Segitiga dengan Direct3D
Kubus
Gambar 4.5. Kubus dengan Direct3D
105
Teapot
Gambar 4.6. Teapot dengan Direct3D
Bentuk-bentuk
Gambar 4.7. Bentuk-bentuk yang diputar
106
Warna Pada bab sebelumnya kita belajar menggambar object ke layar. Pada bab ini kita akan belajar mewarnai object ke layar.
Representasi warna Dalam Direct3D, warna dinyatakan dalam triplet RGB. Kita menentukan nilai untuk warna merah, hijau, dan biru untuk warna yang bersangkutan. Ada dua tipe yang dipakai untuk menyimpan data RGB. Pertama adalah D3DCOLOR, yang sebenarnya merupakan nilai DWORD 32 bit yang dibagi dalam komponen warna masing-masing 1 byte (8 bit). 32 bit Alpha
Red
Green
Blue
Gambar 5.1. Pembagian struktur warna. Karena tiap komponen warna berukuran 1 byte maka, range dari tiap warna adalah 0-255. 0 adalah warna dengan intensitas paling rendah dan 255 adalah intensitas warna paling tinggi. Menspesifikasikan tiap komponen untuk membentuk warna yang diinginkan membutuhkan operasi bit. Direct3D menyediakan macro D3DCOLOR_ARGB( a, r, g, b ) untuk membuat nilai 32 bit dari warna yang bersangkutan. Tiap parameter harus 0-255, contoh : D3DCOLOR_ARGB( 255, 0, 0, 255 ) ; // warna biru D3DCOLOR_ARGB( 255, 255, 0, 255 ) ; // warna magenta
Mungkin kita tidak memakai komponen alpha, untuk itu kita selalu mengesetnya ke nilai 255. Jika kita yakin tidak akan memakai alpha, kita bisa memakai macro D3DCOLOR_XRGB( r, g, b ) untuk membentuk warna yang kita inginkan hanya dengan elemen merah, hijau, dan biru. Macro ini merupakan bentuk singkat dari D3DCOLOR_ARGB( 255, r, g, b ) sebab macro ini didefinisikan sebagai berikut : #define D3DCOLOR_XRGB( r, g, b ) D3DCOLOR_ARGB( 255, r, g, b )
Cara lain untuk merepresentasikan warna adalah dengan struct D3DCOLORVALUE dengan anggota adalah 4 bilangan float yang berkisar antara 0.0f – 1.0f dengan 1.0f adalah intensitas penuh. typedef struct _D3DCOLORVALUE { float r; // komponen merah, range 0.0-1.0 float g; // komponen hijau, range 0.0-1.0 float b; // komponen biru, range 0.0-1.0 float a; // komponen alpha, range 0.0-1.0 } D3DCOLORVALUE;
107
Ada juga structure D3DXCOLOR yang isinya sama dengan D3DCOLOR tetapi dengan operator sehingga untuk mengubahnya amat mudah. Karena mempunyai member yang sama, kita bisa meng-cast antara D3DCOLOR dan D3DXCOLOR. Kedua structure di atas mempunyai 4 komponen yaitu r, g, b, dan a. Sehingga analog seperti vector 4D. Vector warna dapat ditambahkan, dikurangkan, dan discaling seperti vector lain. Tetapi dot product dan cross product tidak berarti apa-apa untuk warna. Sehingga operasi perkalian dalam anggota class D3DXCOLOR melakukan perkalian komponen. Perkalian komponen mempunyai arti khusus dalam vector warna. Symbol ⊗ menunjukkan perkalian komponen dari warna sehingga
( c1
c2
c3
c4 ) ⊗ ( k1 k2
k3
k4 ) = ( c1.k1 c2 .k2
c4 .k4 ) .
c3 .k3
Mari kita ubah globals.h dan direct3dutil.cpp dengan konstan warna yang umum digunakan. Globals.h // deklarasi extern warna extern D3DXCOLOR g_cWhite ; extern D3DXCOLOR g_cBlack ; extern D3DXCOLOR g_cRed ; extern D3DXCOLOR g_cGreen ; extern D3DXCOLOR g_cBlue ; extern D3DXCOLOR g_cYellow ; extern D3DXCOLOR g_cCyan ; extern D3DXCOLOR g_cMagenta ;
Direct3Dutil.cpp D3DXCOLOR D3DXCOLOR D3DXCOLOR D3DXCOLOR D3DXCOLOR D3DXCOLOR D3DXCOLOR D3DXCOLOR
g_cWhite ( g_cBlack ( g_cRed ( g_cGreen ( g_cBlue ( g_cYellow ( g_cCyan ( g_cMagenta(
D3DCOLOR_XRGB( D3DCOLOR_XRGB( D3DCOLOR_XRGB( D3DCOLOR_XRGB( D3DCOLOR_XRGB( D3DCOLOR_XRGB( D3DCOLOR_XRGB( D3DCOLOR_XRGB(
255, 255, 255 0, 0, 0 ) ) ; 255, 0, 0 ) ) 0, 255, 0 ) ) 0, 0, 255 ) ) 255, 255, 0 ) 0, 255, 255 ) 255, 0, 255 )
) ) ; ; ; ; ) ; ) ; ) ;
Vertex Color Warna dihitung pervertex, sehingga kita memerlukan nilai warna dalam vertex structure kita. D3DXCOLORVALUE tidak bisa digunakan di sini karena Direct3D menggunakan nilai 32 bit untuk menentukan warna dari suatu vertex. ( Sebenarnya dengan vertex shader kita bisa mendapatkan warna dari 4D color vector dan dengan begitu mendapatkan warna 128 bit tetapi tidak akan di bahas pada bab ini ) typedef struct _Vertex { float x, y, z ; D3DCOLOR color ; } Vertex ;
Dan kita set vertex formatnya sebagai berikut : #define VERTEXFORMAT ( D3DFVF_XYZ | D3DFVF_DIFFUSE )
108
Shading Shading dijalankan saat rasterisasi, dan menunjukkan bagaimana vertex color digunakan untuk mencari warna semua pixel yang membentuk primitive. Ada dua buah shading mode yaitu flat shading dan gouroaud shading. Dengan flat shading, pixel dalam primitive diwarnai menurut pixel pertama dalam primitive tersebut, jadi misalkan ada segitiga dengan vertexnya sebagai berikut : Vertex g_pVertTriangles[ VERTEXCOUNT ] = { { -1.0f, 0.0f, 0.0f, D3DCOLOR_XRGB( 255, 0, 0 ) }, { 0.0f, 1.0f, 0.0f, D3DCOLOR_XRGB( 0, 255, 0 ) }, { 1.0f, 0.0f, 0.0f, D3DCOLOR_XRGB( 0, 0, 255 ) }, } ;
Dengan flat shading, segitiga di atas akan diwarnai dengan warna merah, karena itulah warna vertex pertama. Dalam gouraud shading, segitiga akan diwarnai dengan gradien untuk tiap vertex. Warna dari pixel2 di dalam primitive diinterpolasi dari warna vertex bersebut. Berikut ini adalah gambaran flat shading dan gouraud shading dalam aplikasi.
Gambar 5.2. Segitiga dengan flat shading dan gouraud shading.
Code to The End! Untuk menggambar segitiga ini, kita siapkan 1 vertex buffer untuk menampung segitiga dan melakukan editing pada vertex structure dan vertex format. Sehingga vertex structure dan vertex format menjadi seperti di bawah ini : // Vertex Structure dan FVF typedef struct _Vertex { float x, y, z ;
109
D3DCOLOR color ; } Vertex ; #define VERTEXFORMAT ( D3DFVF_XYZ | D3DFVF_DIFFUSE )
Kemudian kita deskripsikan segitiga dengan array vertex seperti di bawah ini : // Vertex array dari triangle #define VERTEXCOUNT 3 static const Vertex g_pVertTriangles[ VERTEXCOUNT ] { { -1.0f, 0.0f, 0.0f, D3DCOLOR_XRGB( 255, 0, 0 { 0.0f, 1.0f, 0.0f, D3DCOLOR_XRGB( 0, 255, 0 { 1.0f, 0.0f, 0.0f, D3DCOLOR_XRGB( 0, 0, 255 } ;
= ) }, ) }, ) },
static const UINT g_nTotalVertSize = VERTEXCOUNT * sizeof( Vertex ) ;
Fungsi Setup() masih seperti pada sample triangle sebelumnya yaitu mempersiapkan vertex buffer dan projection matrix : BOOL Setup() { if ( !g_pDevice || !g_pD3D ) { return FALSE ; } // Matrix transformasi D3DXMATRIX matView ; D3DXMATRIX matProj ; const D3DXVECTOR3 vEye( 0.0f, 0.0f, -4.5f ) ; const D3DXVECTOR3 vTarget( 0.0f, 0.0f, 0.0f ) ; const D3DXVECTOR3 vUp( 0.0f, 1.0f, 0.0f ) ; // vertex untuk VB locking Vertex *pVertices = NULL ; // buat VertexBuffer if ( FAILED( g_pDevice->CreateVertexBuffer( g_nTotalVertSize, D3DUSAGE_WRITEONLY, VERTEXFORMAT, D3DPOOL_MANAGED, &g_pVB, NULL ) ) ) { ErrorMessage( TEXT( "Kesalahan saat membuat Vertex Buffer" ) ) ; return FALSE ; } if ( FAILED( g_pVB->Lock( 0, 0, (void**)&pVertices, 0 ) ) ) { ErrorMessage( TEXT( "Error saat locking buffer" ) ) ; if ( g_pVB ) { g_pVB->Release( ) ; } return FALSE ; } memcpy( pVertices, g_pVertTriangles, g_nTotalVertSize ) ; if ( FAILED ( g_pVB->Unlock( ) ) ) { ErrorMessage( TEXT( "Error saat unlock buffer" ) ) ; if ( g_pVB ) { g_pVB->Release( ) ; }
110
return FALSE ; } // Set Matrix dan Transformasi D3DXMatrixLookAtLH( &matView, &vEye, &vTarget, &vUp ) ; D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI * 0.25f, (float) g_nWindowWidth / g_nWindowHeight, 1.0f, 1000.0f ) ; g_pDevice->SetTransform( D3DTS_VIEW, &matView ) ; g_pDevice->SetTransform( D3DTS_PROJECTION, &matProj ) ; // Matikan lighting // Jika tidak dimatikan, maka akan terlihat hitam // karena Direct3D mengira tidak ada cahaya g_pDevice->SetRenderState( D3DRS_LIGHTING, FALSE ) ; return TRUE ; }
Kemudian fungsi DoFrame() menggambar triangle dua kali pada posisi yang berbeda dengan shade mode yang berbeda. Posisi tiap triangle diatur oleh world matrix. BOOL CALLBACK DoFrame( float DeltaTime ) { D3DXMATRIX matWorld1, matWorld2 ; // cek apakah device dan direct3d object tidak NULL if ( !g_pD3D || !g_pDevice ) { return FALSE ; } // Set Matrix translasi untuk segitiga 1 & 2 D3DXMatrixTranslation( &matWorld1, -1.3f, 0.0f, 0.0f ) ; D3DXMatrixTranslation( &matWorld2, 1.3f, 0.0f, 0.0f ) ; // bersihkan render target, depth buffer, dan stencil buffer g_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB( 0xff, 0xff, 0xff ), 1.0f, 0 ) ; g_pDevice->BeginScene( ) ; // Set Stream Source dan FVF g_pDevice->SetStreamSource(0, g_pVB, 0, sizeof( Vertex ) ) ; g_pDevice->SetFVF( VERTEXFORMAT ) ; // Set Shading ke flat g_pDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT ) ; //Set Matrix dan draw g_pDevice->SetTransform( D3DTS_WORLD, &matWorld1 ) ; g_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 ) ; // Set Shading ke flat g_pDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_GOURAUD ) ; //Set Matrix dan draw g_pDevice->SetTransform( D3DTS_WORLD, &matWorld2 ) ; g_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 ) ; g_pDevice->EndScene( ) ; // present backbuffer ke layar. g_pDevice->Present( NULL, NULL, NULL, NULL ) ; return TRUE ; }
111
Sample Code Screenshot Segitiga
Gambar 5.3. Segitiga dengan flat shading dan gouraud shading.
Kubus Sample ini merupakan sample tambahan yang merupakan modifikasi dari sample kubus pada bab selanjutnya tetapi dirender menggunakan warna. Ada pilihan untuk mengganti shade mode dan fill mode. Untuk lebih jelasnya silahkan baca sample code yang disertakan.
Gambar 5.4. Kubus dengan goraud shading
112
Lighting/Pencahayaan Untuk meningkatkan realitas object 3D, tambahkan lighting. Lighting juga menunjukkan object terlihat pejal/tidak dan volume dari object. Ketika menggunakan lighting, kita tidak menentukan vertex color sendiri, tetapi membiarkan Direct3D menghitung vertex color berdasarkan light sources, material, dan normal dari polygon.
Komponen warna Dalam Direct3D, cahaya yang dikeluarkan dari light source (sumber cahaya) mempunyai tiga komponen penting. •
Ambient, komponen ini adalah cahaya yang dipantulkan oleh suatu permukaan sehingga menyebabkan warnanya mempengaruhi warna dari tiap scene.
•
Diffuse, komponen ini adalah cahaya yang mengenai suatu permukaan sehingga memantulkan warna. Cahaya ini dipantulkan ke arah mana saja secara sama. Oleh karena itu, diffuse tidak mempedulikan tempat dan posisi kamera.
•
Specular, komponen ini adalah cahaya yang datang dari sudut tertentu, ketika mengenai suatu permukaan, cahaya ini dipantulkan hanya ke satu arah. Oleh karena itu, hanya bisa dilihat di sisi tertentu sehingga posisi kamera penting dalam pencahayaan ini. Contoh cahaya ini adalah cahaya yang dipantulkan dari permukaan yang mengkilap.
Specular lighting memerlukan komputasi yang lebih berat daripada tipe pencahayaan yang lain. Secara default, specular dimatikan oleh Direct3D. Jika kita ingin menggunakannya, cukup dengan menghidupkannya dalam render state. g_pDevice->SetRenderState( D3DRS_SPECULARENABLE, TRUE ) ;
Keseluruhan nilai dari cahaya, dinyatakan dengan D3DCOLORVALUE atau D3DXCOLOR yang menentukan warna dari cahaya yang bersangkutan. D3DXCOLOR GreenAmbient( 0.0f, 1.0f, 0.0f, 1.0f ) ; D3DXCOLOR RedDiffuse( 1.0f, 0.0f, 0.0f, 1.0f ) ; D3DXCOLOR WhiteSpecular( 1.0f, 1.0f, 1.0f, 1.0f ) ;
Material Warna dari object yang kita lihat sehari-hari adalah warna yang dipantulkan oleh suatu object. Contoh kotak yang berwarna merah menyerap semua komponen cahaya kecuali komponen merah. Komponen merah dipantulkan ke mata sehingga kita bisa melihat bahwa kotak tersebut berwarna merah. Direct3D mendeskripsikan warna ini sebagai material. Dengan material, kita bisa menentukan persentase dari warna yang dipantulkan oleh suatu permukaan. Material dinyatakan dalam structure D3DMATERIAL9. typedef struct _D3DMATERIAL9 { D3DCOLORVALUE Diffuse, Ambient, Specular, Emissive ; float Power ; } D3DMATERIAL9 ;
113
Parameter
Keterangan
Diffuse
Jumlah persentase warna diffuse yang dipantulkan oleh permukaan object.
Ambient
Jumlah persentase warna ambient yang dipantulkan oleh permukaan object.
Specular
Jumlah persentase warna specular yang dipantulkan oleh permukaan object.
Emissive
Komponen ini digunakan untuk menambahkan value ke tiap komponen dalam diffuse, ambient, dan specular, sehingga terlihat seperti object menghasilkan cahaya
Power
Ketajaman dari specular, semakin tinggi, maka specular semakin tajam.
Sebagai contoh, misalkan kita mempunyai kotak warna merah, kita mendefinisikan kotak untuk menyerap semua warna kecuali merah. D3DMATERIAL9 RedBoxMaterial ; RedBoxMaterial.Diffuse RedBoxMaterial.Ambient RedBoxMaterial.Specular RedBoxMaterial.Emissive RedBoxMaterial.Power
= = = = =
D3DXCOLOR( D3DXCOLOR( D3DXCOLOR( D3DXCOLOR( 4.0f ;
1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
1.0f 1.0f 1.0f 1.0f
) ) ) )
; ; ; ;
Kita mengeset nilai komponen merah menjadi 1 yang berarti object memantulkan 100% warna merah. Kemudian warna biru dan hijau masing-masing 0 yang berarti object tidak memantulkan warna biru dan hijau karena nilainya 0%. Sebagai catatan, jika kita mendefinisikan light source yang hanya mengeluarkan cahaya biru, lighting akan gagal karena object sama sekali tidak memantulkan cahaya biru, semua cahaya biru akan terserap dan dia memantulkan cahaya merah yang bernilai 0, sehingga tidak ada cahaya yang memantul, dan object terlihat hitam. Object akan terlihat putih jika memantulkan 100% warna merah, hijau, dan biru. Karena mengisi structure material amat merepotkan maka kita membuat fungsi di D3DUtil.h untuk membantu kita menginisialisasi structure ini : D3DMATERIAL9 &InitMaterial( D3DXCOLOR &ambient, D3DXCOLOR &diffuse, D3DXCOLOR &specular, D3DXCOLOR &emissive, D3DXCOLOR &power ) { D3DMATERIAL9 Material ; Material.Ambient = ambient ; Material.Diffuse = diffuse ; Material.Specular = specular ; Material.Emissive = emissive ; Material.Power = power ; return Material ; }
Kemudian kita deklarasikan material yang mempunyai warna umum di fungsi Setup() : const D3DMATERIAL9 g_WhiteMaterial
= InitMaterial( g_cWhite, g_cWhite,
114
g_cWhite, g_cBlack, 8.0f ) ; const D3DMATERIAL9 g_RedMaterial g_cRed, g_cBlack, 8.0f ) ; const D3DMATERIAL9 g_GreenMaterial g_cGreen, g_cBlack, 8.0f ) ; const D3DMATERIAL9 g_BlueMaterial g_cBlue, g_cBlack, 8.0f ) ; const D3DMATERIAL9 g_YellowMaterial g_cYellow, g_cBlack, 8.0f ) ;
= InitMaterial( g_cRed, g_cRed, = InitMaterial( g_cGreen, g_cGreen, = InitMaterial( g_cBlue, g_cBlue, = InitMaterial( g_cYellow, g_cYellow,
Vertex structure tidak mengandung material sehingga material harus diset untuk keseluruhan object. Saat material diset maka object yang dirender mengikuti material yang telah diset tersebut. // Gambar sphere dengan warna biru g_pDevice->SetMaterial( &g_BlueMaterial ) ; DrawSphere( ) ; // Gambar torus dengan warna merah g_pDevice->SetMatrial( &g_RedMaterial ) ; DrawTorus( ) ;
Vertex Normal Face Normal adalah vector yang menyatakan ke arah mana suatu polygon menghadap.
Gambar 6.1. Face Normal Vertex Normal hampir sama dengan face normal, akan tetapi berlaku untuk tiap vertex, sedangkan face normal adalah untuk tiap polygon.
Gambar 6.2. Vertex Normal Direct3D perlu mengetahui vertex normal, sehingga bisa menghitung sudut dari mana sebuah cahaya memantul pada suatu permukaan. Karena lighting dilakukan per vertex, Direct3D memerlukan informasi vertex normal. Vertex normal tidak harus sama dengan face normal. Pada penggambaran polygon untuk bola(sphere) dan lingkaran (circle) adalah contoh di mana vertex normal tidak sama denga face normal.
115
Gambar 6.3. Contoh bentuk yang berbeda vertex normalnya dan face normalnya, yang digambar dengan warna abu-abu adalah face normal, sedangkan yang warna hitam adalah vertex normal Untuk mendeskripsikan normal pada vertex, kita harus mengubah vertex structure kita menjadi : typedef struct _Vertex { float x, y, z ; float nx, ny, nz ; } Vertex ;
Dan vertex format sebagai berikut : #define VERTEXFORMAT ( D3DFVF_XYZ | D3DFVF_NORMAL )
Kita menghapus Vertex color karena warna akan dihitung oleh Direct3D dengan lighting. Untuk object sederhana seperti kubus dan sphere, kita bisa mencari vertex normal dengan melihatnya saja. Sedangkan untuk object yang lebih kompleks, kita perlu cara yang lebih kompleks pula. Misal kita punya segitiga dengan 3 vertex p0, p1, dan p2. Dan kita perlu menghitung vertex normal untuk tiap vertex n0, n1, dan n2. Cara yang paling mudah adalah dengan mencari face normal untuk tiap vertex, dan menggunakan face normal tersebut sebagai vertex normal. Pertama kita mencari dua vector yang ada di segitiga tersebut.
G p1 − p0 = u G p2 − p0 = v Kemudian kita cari face normalnya :
G G G n = u×v Karena kita menganggap tiap vertex sama dengan face normalnya, maka
n1 = n2 = n3 = n
116
Di bawah ini adalah fungsi dalam bahasa C untuk mencari face normal dari triangle. Fungsi ini berasumsi bahwa vertex yang dimasukkan diurutkan searah jarum jam, jika tidak, maka normalnya akan mengarah ke arah yang berlawanan. void CalculateNormal(
{
D3DXVECTOR3 D3DXVECTOR3 D3DXVECTOR3 D3DXVECTOR3
*pV0, *pV1, *pV2, *pOut )
D3DXVECTOR3 u = ( *pV1 ) - ( *pV0 ) ; D3DXVECTOR3 v = ( *pV2 ) - ( *pV0 ) ; D3DXVec3Cross( pOut, &u, &v ) ; D3DXVec3Normalize( pOut, pOut ) ;
}
Menggunakan face normal sebagai vertex normal tidak menghasilkan pencahayaan yang halus untuk permukaan melengkung. Cara yang lebih baik adalah menggunakan normal averaging, mencari ratarata normal dari segitiga yang menggunakan vertex itu bersama-sama. Jadi kita mencari dulu face normal dari segitiga-segitiga yang menggunakan vertex yang sama, kemudian membuat rata-rata normal dari tiap vertex tersebut Sebagai contoh, misalkan ada 4 segitiga menggunakan vertex yang sama dengan n0, n1, n2, dan n3 adalah normal dari ketiga vertex tersebut, maka rumusnya menjadi :
G 1 JJG JJG JJG JJG n= n0 + n1 + n2 + n3 4
(
)
Saat transformasi bisa saja vertex menjadi tidak normal. Untuk itu kita membiarkan Direct3D untuk merenormalize normal vector kita dengan menghidupkan render state D3DRS_NORMALIZENORMAL. g_pDevice->SetRenderState( D3DRS_NORMALIZENORMALS, TRUE ) ;
Light Sources Direct3D mendukung beberapa tipe light sources sebagai berikut : •
Point Lights, light source ini merupakan light dengan source yang berasa dari satu titik dan menyebar ke segala arah
Gambar 6.4. Point Lights •
Directional Lights, light source ini tidak mempunyai posisi. Cahayanya paralel.
117
Gambar 6.5. Directional Light •
Spot Lights, sama dengan senter, dengan dua kerucut dengan sudut θ dan φ. θ merupakan sudut pada kerucut dalam dan φ adalah sudut pada kerucut luar.
Gambar 6.6. Spot Light. Di dalam code, light source dinyatakan dalam structure D3DLIGHT9 : typedef struct D3DLIGHT9 { D3DLIGHTTYPE Type; D3DCOLORVALUE Diffuse; D3DCOLORVALUE Specular; D3DCOLORVALUE Ambient; D3DVECTOR Position; D3DVECTOR Direction; float Range; float Falloff; float Attenuation0; float Attenuation1; float Attenuation2; float Theta; float Phi; } D3DLIGHT9;
Member
Keterangan
Type
Tipe dari light source yaitu D3DLIGHT_POINT, D3DLIGHT_SPOT atau D3DLIGHT_DIRECTIONAL.
Diffuse
Nilai diffuse yang dikeluarkan light source
Specular
Nilai specular yang dikeluarkan light source
Ambient
Nilai ambient yang dikeluarkan light source
Position
Vector menggambarkan world position dari light source, diabaikan untuk directional lights.
118
Vector yang menyatakan arah dari light source bersinar. Diabaikan untuk point lights
Direction
Range maximum untuk cahaya sebelum cahaya mati. Tidak bisa lebih
FLT _ MAX dan besar dari directional lights.
Range
tidak
mempunyai
efek
dengan
Hanya digunakan untuk spotlight. Menyatakan bagaimana intensitas cahaya melemah dari kerucut dalam ke kerucut luar. Biasanya diset ke 1.0f.
Falloff
Menyatakan bagaimana cahaya melemah seiring dengan jarak. Hanya digunakan untuk point dan spot lights. Attenuation0 adalah falloff constant, Attenuation1 adalah linear falloff, dan Attenuation2 adalah quadratic falloff. Dengan rumus ini, jika D adalah jarak dari light source dan A0, A1, dan A2 adalah nilai attenuation. Maka
Attenuation0, Attenuation1, Attenuation2
Attenuation =
1 A0 + A1.D + A2 .D 2
Theta
Sudut untuk kerucut dalam dalam Spot light
Phi
Sudut untuk kerucut luar dalam spot light
Seperti inisialisasi material, menginisialisasi structure D3DLIGHT9 akan sangat merepotkan jika kita hanya ingin pencahayaan sederhana, untuk itu kita buat fungsi untuk menginisialisasinya. D3DLIGHT9 InitDirectionalLight( D3DXVECTOR3& direction, D3DXCOLOR& color ) ; D3DLIGHT9 InitPointLight( D3DXVECTOR3& position, D3DXCOLOR& color ) ; D3DLIGHT9 InitSpotLight( D3DXVECTOR3& position, D3DXVECTOR3& direction, D3DXCOLOR &color ) ;
Implementasinya cukup mudah, saya hanya akan memperlihatkan untuk implementasi directional light. D3DLIGHT9 InitDirectionalLight( D3DXVECTOR3& direction, D3DXCOLOR& color ) { D3DLIGHT9 light ; ZeroMemory( &light, sizeof( D3DLIGHT9 ) ) ; light.Type light.Ambient light.Diffuse light.Specular light.Direction
= = = = =
D3DCOLOR_DIRECTIONAL ; color * 0.4f ; color ; color * 0.6f ; direction ;
return light ; }
Sehingga jika kita ingin membuat directional light yang paralel dengan sumbu x positif, maka kita akan memanggil fungsinya sebagai berikut : D3DLIGHT9 light = InitDirectionalLight( D3DXVECTOR3( 1.0f, 0.0f, 0.0f, 0.0f ), // vector X positif g_cWhite ) ;
119
Setelah kita menginisialisasi instance D3DLIGHT9, kita daftarkan ke Direct3D. g_pDevice->SetLight( 0, // elemen yang diset yaitu light nomor 0 &light // alamat dari D3DLIGHT9 structure ) ;
Setelah diregister, kita aktifkan lighting untuk light nomor 0 g_pDevice->LightEnable( 0, // elemen yang diaktifkan TRUE // TRUE = aktif, FALSE = tidak aktif ) ;
Code Your Lights Pada bab ini saya menyertakan sample untuk ketiga jenis light (directional, point light, dan spot light ) dengan bentuk-bentuk dari D3DXCreate*. Dan juga satu sample berupa piramid yang diberi lighting. Yang akan saya terangkan pada bab ini hanya yang piramid, sedangkan untuk tiga sample yang lain, silahkan baca codenya karena saya kira commentnya cukup jelas.
Gambar 6.7. Pyramid yang diberi cahaya Untuk mengaktifkan lighting, step yang harus dijalankan adalah : 1. Hidupkan Lighting 2. Buat material untuk setiap object dan set material sebelum object dirender 3. Buat satu atau lebih light sources, set mereka, dan hidupkan 4. Tambahkan state untuk lighting, seperti specular states Seperti biasa, kita menyimpan vertex dari pyramid ke vertex buffer g_pVB. Lighting dienable secara default oleh Direct3D, akan tetapi untuk lebih memastikan kita tambahkan method untuk mengaktifkan lighting sebagai berikut pada fungsi Setup() : g_pDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
kemudian kita deklarasikan vertex posisi dan normal dari piramid pada variabel global
120
// Structure dari vertex buffer struct Vertex { float x, y, z ; float nx, ny, nz ; }; // Vertex Format #define VERTEXFORMAT ( D3DFVF_XYZ | D3DFVF_NORMAL ) // Vertex array yang membangun pyramid mesh // Saya memakai saran yang disarankan yaitu // Membuat vertex array di system memory // kemudian ditransfer ke Direct3D Vertex/Index #define VERTICESNUMBER 12 #define PRIMITIVESNUMBER 4 const Vertex pVertPyramid [ VERTICESNUMBER ] = { { -1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f { 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, -0.707f { 1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f { -1.0f, 0.0f, 1.0f, -0.707f, 0.707f, 0.0f { 0.0f, 1.0f, 0.0f, -0.707f, 0.707f, 0.0f { -1.0f, 0.0f, -1.0f, -0.707f, 0.707f, 0.0f { 1.0f, 0.0f, -1.0f, 0.707f, 0.707f, 0.0f { 0.0f, 1.0f, 0.0f, 0.707f, 0.707f, 0.0f { 1.0f, 0.0f, 1.0f, 0.707f, 0.707f, 0.0f { 1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f { 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, 0.707f { -1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f } ;
Buffer
}, }, }, }, }, }, }, }, }, }, }, },
Baru kita membuat vertex buffer dan mengcopy datanya ke vertex buffer // Buat Vertex Buffer hr = g_pDevice->CreateVertexBuffer( nTotalVertSize, D3DUSAGE_WRITEONLY, VERTEXFORMAT , D3DPOOL_MANAGED, &g_pVB, NULL ); if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Creating Vertex Buffer" ) ) ; return FALSE ; } // Lock Vertex Buffer hr = g_pVB->Lock( 0, 0, ( void ** ) &pVertices, D3DLOCK_DISCARD ) ; if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Locking Vertex Buffer" ) ) ; return FALSE ; } // Copy data ke Vertex Buffer memcpy( pVertices, pVertPyramid, nTotalVertSize ) ; // Unlock Vertex Buffer hr = g_pVB->Unlock( ) ; if ( FAILED ( hr ) ) { ErrorMessage( TEXT ( "Error Unlocking Vertex Buffer" ) ) ; return FALSE ; }
Kemudian kita set material ke putih (material putih sudah kita deklarasikan) : // set material g_pDevice->SetMaterial( &g_WhiteMaterial ) ;
121
dan langkah terakhir adalah mengeset dan mengaktifkan directional light : // directional light lightDir = InitDirectionalLight( D3DXVECTOR3( 1.0f, 0.0f, 0.0f ), g_cWhite ) ; // hidupkan lighting g_pDevice->SetRenderState( D3DRS_LIGHTING, TRUE ) ; g_pDevice->SetLight( 0, &lightDir ) ; g_pDevice->LightEnable( 0, TRUE ) ;
Atur state yang berhubungan dengan lighting : // hidupkan specular light dan renormalize normals g_pDevice->SetRenderState( D3DRS_NORMALIZENORMALS, TRUE ) ; g_pDevice->SetRenderState( D3DRS_SPECULARENABLE, TRUE ) ;
Untuk fungsi DoFrame() isinya hanya menggambar vertex buffer ke layar. Yang berubah hanyalah background saja menjadi hitam supaya efek lighting terlihat lebih jelas. Untuk sample yang lain ada penambahan pada fungsi DoFrame() yaitu penanganan keyboard. Kamera bisa diputar dengan tombol panah dengan mendeklarasikan sudut dan tinggi kemudian mengubahnya ketika tombol dipencet lalu diterapkan dalam view matrix. Seperti ini : // variabel untuk menghitung posisi dan tinggi kamera static float Angle = 1.5f * D3DX_PI ; static float Height = 5.0f ; int i = 0 ; if ( !g_pDevice || !g_pD3D ) { return FALSE ; } // Checking Keys secara asynchronous if ( GetAsyncKeyState( VK_LEFT ) & 0x8000f ) { Angle -= 0.5f * DeltaTime ; } else if ( GetAsyncKeyState( VK_RIGHT ) & 0x8000f ) { Angle += 0.5f * DeltaTime ; } else if ( GetAsyncKeyState( VK_UP ) & 0x8000f ) { Height += 5.0f * DeltaTime ; } else if ( GetAsyncKeyState( VK_DOWN ) & 0x8000f ) { Height -= 5.0f * DeltaTime ; } // // Terapkan dalam view matrix // D3DXVECTOR3 vPos( cosf( Angle ) * 7.0f, Height, sinf( Angle ) * 7.0f ) ; D3DXVECTOR3 vTarget( 0.0f, 0.0f, 0.0f ) ; D3DXVECTOR3 vUp( 0.0f, 1.0f, 0.0f ) ; D3DXMATRIX matView ; D3DXMatrixLookAtLH( &matView, &vPos, &vTarget, &vUp ) ; g_pDevice->SetTransform( D3DTS_VIEW, &matView ) ;
122
Sample Code Screenshot Pyramid
Gambar 6.8. Segitiga dengan Direct3D
Directional Lights
Gambar 6.9. Directional Lights
123
Point Lights
Gambar 6.10. Point Lights
Spot Lights
Gambar 6.11. Spot Lights
124
Texturing Texture mapping adalah teknik yang menempelkan image ke segitiga untuk meningkatkan scene supaya lebih realistis.Contoh nyata adalah pada kotak beton sebagai berikut :
Gambar 7.1. Kotak Beton
Texture Coordinate Texture coordinate dalam Direct3D dinyatakan dengan vector u horizontal dan v yang vertikal. u positif ke kanan, dan v positif ke bawah :
Gambar 7.2. Texture Coordinates Interval antara 0.0f dan 1.0f menjadikan Direct3D dapat bekerja pada texture dengan ukuran bermacam-macam. Mari kita lihat cara mapping suatu texture ke dalam suatu triangle.
125
Gambar 7.3. Texture Mapping, segitiga 3 dimensi di-map ke dalam texture. Untuk itu, kita modifikasi vertex structure menjadi sebagai berikut : // Structure dari vertex buffer struct Vertex { float x, y, z ; float nx, ny, nz ; float u, v ; };
Dan FVF menjadi // Vertex Format #define VERTEXFORMAT ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 )
Kita menambahkan D3DFVF_TEX1 yang berarti structure vertex kita mempunyai sepasang texture coordinate.
Membuat dan mengaktifkan texture Data texture biasanya dibaca dari suatu file image, dan diload ke IDirect3DTexture9. Untuk melakukan itu, bisa dengan cara manual, atau dengan menggunakan fungsi D3DX berikut ini : HRESULT D3DXCreateTextureFromFile( LPDIRECT3DDEVICE9 pDevice, LPCTSTR pSrcFile, LPDIRECT3DTEXTURE9 * ppTexture );
Fungsi di atas bisa me-load file BMP, DDS, DIB, JPG, PNG, dan TGA. Contoh, untuk me-load file Metal.dds sebagai texture : IDirect3DTexture9 *g_pMetalTex = NULL ; D3DXCreateTextureFromFile( g_pDevice, TEXT( "Metal.dds" ), &g_pMetalTex ) ;
Untuk mengeset texture menggunakan method IDirect3DTexture9::SetTexture sebagai berikut :
126
HRESULT IDirect3DDevice9::SetTexture( DWORD Stage, // nilai antara 0-7 sebagai identifikasi texture stage IDirect3DBaseTexture9* pTexture // pointer ke texture ) ;
Contoh : g_pDevice->SetTexture( 0, g_pMetalTex ) ;
Untuk mematikan texture pada texturing stage, tinggal kita set ke NULL : g_pDevice->SetTexture( 0, NULL ) ; RenderObjectTanpaTexture( ) ;
Jika kita ingin men-texture triangle dengan texture yang berbeda-beda kita tinggal mengganti texture sebelum menggambar object : g_pDevice->SetTexture( 0, g_pMetalTex ) ; RenderTrianglesWithMetals( ) ; g_pDevice->SetTexture( 0, g_pGrassTex ) ; RenderTrianglesWithGrass( ) ;
Filters Texture di-mapping pada screen space, sehingga ukuran texture triangle tentu tidak akan sama dengan screen triangle. Jika texture triangle lebih kecil texture dimagnified supaya pas. Jika texture triangle lebih besar, texture diminified supaya pas. Pada bebarapa kasus, akan terjadi distorsi. Untuk mengatasi distorsi, Direct3D menggunakan teknik filtering. Ada 3 buah jenis filtering yang disediakan oleh Direct3D, kualitas selalu berbanding terbalik dengan performa, semakin bagus kualitas, semakin lambatlah performa aplikasinya, sehingga perlu dipikirkan keseimbangan antara kualitas dan performa. Texture filters diset menggunakan method IDirect3DDevice9::SetSamplerState. •
Nearest-point sampling, merupakan filtering paling jelek dan merupakan default dari Direct3D akan tetapi paling cepat dihitung oleh hardware. Kode di bawah ini mengeset filter ke nearest point untunk magnification dan minification.
g_pDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT ) ; g_pDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT ) ;
•
Linear Filtering, filtering dengan hasil lumayan bagus, dan bisa dilakukan dengan cepat pada hardware zaman sekarang. Disarankan minimal menggunakan linear fitering. Kode di bawah ini mengeset ke linear filtering untuk magnification dan minification.
g_pDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ) ; g_pDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ) ;
•
Anisotropic Filtering, filtering dengan hasil paling bagus, tetapi juga palin berat komputasinya. Kode di bawah ini mengeset ke anisotropic filtering untuk magnification dan minification.
127
g_pDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC ) ; g_pDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC ) ;
Jikamenggunakan anisotropic harus mengeset anisotropic level. Untuk mengetahui maksimum anisotropic level yang didukung bisa lihat di structure D3DCAPS9. Contoh di bawah ini mengeset anisotropic ke 4 : g_pDevice->SetSamplerState( 0, D3DSAMP_MAXANISOTROPY, 4 ) ;
Mipmap Seperti yang telah dibahas bahwa screen triangle beda ukuran dengan texture triangle. Untuk membuat supaya beda ukuran tidak begitu besar, kita harus membuat mipmap. Mipmap adalah texture dengan ukuran yang berbeda-beda (biasanya square) pada tiap levelnya, sehingga kita bisa menerapkan filtering pada tiap level.
Gambar 7.4. Contoh mipmapping, perhatikan ukuran dari tiap mip level adalah separuh dari ukuran level sebelumnya.
Mipmap filter Mipmap filter digunakan untuk mengkontrol Direct3D untuk membuat mipmap. Cukup menggunakan SetSamplerState dengan D3DSAMP_MIPFILTER sebagai state yang diset. Contoh jika menggunakan nearest point sebagai filter mipmapnya : g_pDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_POINT ) ;
ada 3 buah filter mipmap yang didukung : Nilai
Keterangan
D3DTEXF_NONE
Menonaktifkan mipmap.
D3DTEXF_POINT
Dengan filter ini Direct3D akan memilih mipmap level yang paling dekat dengan ukuran screen triangle, begitu dipilih, Direct3D akan memfilter tergantung dari min dan mag-nya.
D3DTEXF_LINEAR
Dengan filter ini, Direct3D memilih dua buah mipmap yang paling dekat, memfilternya kemudian mengkombinasikan keduanya
128
Menggunakan mipmap dalam Direct3D Menggunakan
mipmap
dalam
Direct3D
sangat
mudah.
Dengan
memanggil
D3DXCreateTextureFromFile akan membuat mipmap secara otomatis IDirect3DTexture9 akan memilih texture level yang pas secara otomatis.
Address Mode Pada bab di atas kita mengeset koordinat texture antara 0 sampai dengan 1. Tidak selamanya begitu, koordinat texture bisa lebih dari itu. Hasil dari koordinat yang lebih dari 1.0f tersebut diatur oleh Direct3D dalam Address Mode. Ada 4 buah address mode ditampilkan dalam screenshot sebagai berikut :
Gambar 7.5. Wrap Address Mode
Gambar 7.6. Border Color Address Mode
129
Gambar 7.7. Clamp Address Mode
Gambar 7.8. Mirror Address Mode Untuk beralih dari satu address mode ke address mode lain cukup mudah, yaitu dengan memanggil SetSamplerState, dengan mengubah state untuk D3DSAMP_ADDRESSU untuk addressing mode horizontal, dan D3DSAMP_ADDRESSV untuk addressing mode vertikal. Contoh : g_pDevice->SetSamplerState(0, D3DSAMP ADDRESSU, D3DTADDRESS BORDER); g_pDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS _BORDER); g_pDevice->SetSamplerState(0, D3DSAMP BORDERCOLOR, 0x000000ff);
130
Let's Code The Textured Quad Ada banyak hal yang berubah dalam program, terutama pada fungsi tambahan GetExePath yang berguna untuk mendapatkan path dari file .exe untuk mengambil texture agar texture diletakkan satu directory dengan file .exe yang dikompilasi. Untuk listing penuh, silahkan baca di GetExePath. Untuk mendapatkan Exe path ini, kita memerlukan satu dependensi lagi yaitu shlwapi.lib. tambahkan di bagian input linker. Textured quad terdiri dari 2 segitiga dan 4 vertex. Oleh karena itu, untuk menghemat tempat, saya memakai vertex dan index buffer, sehingga tidak memerlukan buffer vertex yang besar. Seperti biasa, jumlah primitif, fvf, dan ukuran-ukuran saya simpan di konstanta atau macros : // Vertex Buffer static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL ; static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL ; static LPDIRECT3DTEXTURE9 g_pTex = NULL ; static TCHAR g_szExePath[ MAX_PATH ] ; // Structure dari vertex buffer struct Vertex { float x, y, z ; float nx, ny, nz ; float u, v ; }; // Vertex Format #define VERTEXFORMAT ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ) // Vertex array yang membangun mesh // Saya memakai saran yang disarankan yaitu // Membuat vertex array di system memory // kemudian ditransfer ke Direct3D Vertex/Index Buffer #define VERTICESNUMBER 4 #define INDICESNUMBER 6 #define PRIMITIVESNUMBER 2 Vertex g_pQuadVerts[ VERTICESNUMBER ] = { {-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, { 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, } ;
-1.0f, -1.0f, -1.0f, -1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f 0.0f 0.0f 1.0f
}, }, }, },
WORD g_pQuadIndices[ INDICESNUMBER ] = { 0, 1, 2, 0, 2, 3, } ; // ukuran Vertex dan Index Buffer const UINT nTotalVertSize = VERTICESNUMBER * sizeof( Vertex ) ; const UINT nTotalIdxSize = INDICESNUMBER * sizeof( WORD ) ;
Perhatikan vertex structure, normal semuanya mengarah ke Z negatif (keluar layar), dan sepasang angka terakhir adalah texture coordinates. Setelah disiapkan seperti di atas, saatnya membuat vertex dan index buffer kemudian menampung seluruh mesh quad dan juga sekalian mengeset matrix view dan projection di fungsi Setup( ). Fungsi ini selain menyiapkan mesh, juga menyiapkan texture yang akan dipakai. Me-load, sekaligus menerapkan texture yang dipakai. Karena hanya satu texture dan tidak berganti-ganti, saya meletakkan fungsi pembuatan texture di sini.
131
BOOL Setup() { // // Set Matrix View dan Projection // D3DXMATRIX matView, matProj ; const D3DXVECTOR3 vEye ( 0.0f, 0.0f, -3.0f ) ; const D3DXVECTOR3 vUp ( 0.0f, 1.0f, 0.0f ) ; const D3DXVECTOR3 vTarget( 0.0f, 0.0f, 0.0f ) ; D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI * .25f, static_cast(g_nWindowWidth)/g_nWindowHeight, 1.0f, 1000.0f ) ; D3DXMatrixLookAtLH( &matView, &vEye, &vTarget, &vUp ) ; g_pDevice->SetTransform( D3DTS_VIEW, &matView ) ; g_pDevice->SetTransform( D3DTS_PROJECTION, &matProj ) ; g_pDevice->SetRenderState( D3DRS_LIGHTING, FALSE ) ; Vertex *pVerts = NULL ; WORD *pIndex = NULL ; if ( FAILED( g_pDevice->CreateVertexBuffer( nTotalVertSize, D3DUSAGE_WRITEONLY, VERTEXFORMAT, D3DPOOL_MANAGED, &g_pVB, NULL ))) { ErrorMessage( TEXT( "Error Creating Vertex Buffer " ) ) ; return FALSE ; } if ( FAILED( g_pVB->Lock( 0, 0, (LPVOID *) &pVerts, D3DLOCK_DISCARD ) ) ) { ErrorMessage( TEXT( "Error Locking Vertex Buffer " ) ) ; return FALSE ; } memcpy( pVerts, g_pQuadVerts, nTotalVertSize ) ; g_pVB->Unlock( ) ; if ( FAILED( g_pDevice->CreateIndexBuffer( nTotalIdxSize, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &g_pIB, NULL ) ) ) { ErrorMessage( TEXT( "Error Creating Index Buffer " ) ) ; return FALSE ; } if ( FAILED( g_pIB->Lock( 0, 0, (LPVOID *) &pIndex, D3DLOCK_DISCARD ) ) ) { ErrorMessage( TEXT( "Error Locking Index Buffer " ) ) ; return FALSE ; } memcpy( pIndex, g_pQuadIndices, nTotalIdxSize ) ; g_pIB->Unlock( ) ; ZeroMemory( g_szExePath, MAX_PATH * sizeof( TCHAR ) ) ; GetExePath( g_szExePath ) ; _tcscat( g_szExePath, TEXT( "Kyoko.dds" ) ) ; // // Make Texture for first time // if ( FAILED( D3DXCreateTextureFromFile( g_pDevice, g_szExePath, &g_pTex ) ) ) { return FALSE ; }
132
g_pDevice->SetTexture( 0, g_pTex ) ; g_pDevice->SetIndices( g_pIB ) ; return TRUE ; }
Jangan lupa mematikan lighting. Kita hanya ingin melihat kotak ditexture saja, tanpa lighting. Untuk itu, kita matikan lighting. Untuk fungsi DoFrame(), hanya berisi fungsi untuk membersihkan dan menampilkan quad. BOOL CALLBACK DoFrame( float DeltaTime ) { if ( !g_pDevice || !g_pD3D ) { return FALSE ; } D3DXMATRIX matWorld ; D3DXMatrixIdentity( &matWorld ) ; g_pDevice->SetTransform( D3DTS_WORLD, &matWorld ) ; g_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB( 0xff, 0xff, 0xff ), 1.0f, 0 ) ; if ( FAILED( g_pDevice->BeginScene( ) ) ) { return FALSE ; } g_pDevice->SetStreamSource( 0, g_pVB, 0, sizeof( Vertex ) ) ; g_pDevice->SetFVF( VERTEXFORMAT ) ; g_pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, VERTICESNUMBER, 0, PRIMITIVESNUMBER ) ; g_pDevice->EndScene( ) ; g_pDevice->Present( NULL, NULL, NULL, NULL ) ; return TRUE ; }
Untuk fungsi cleanup, hanya menambahkan pemanggilan method Release() untuk g_pTex. BOOL Cleanup() { //lepaskan index if ( g_pIB ) { g_pIB->Release( ) ; g_pIB = NULL ; } // Lepaskan Vertex Buffer if ( g_pVB ) { g_pVB->Release( ) ; g_pVB = NULL ; } // Lepaskan texture if ( g_pTex ) { g_pTex->Release( ) ; g_pTex = NULL ;
133
} return TRUE ; }
134
Samples ScreenShot Quad Plain Plain Quad.. dengan texture saja.
Gambar 7.9. Quad Plain
TexQuad Textured perspective quad dengan pilihan texturing :
Gambar 7.10. TexQuad dengan berbagai pilihan texturing
135
CubeTex Textured perspective cube
Gambar 7.11. Kubus dengan texture
136
Multitexturing Multitexturing adalah kemampuan untuk menerapkan lebih dari 1 texture pada satu triangle. Tidak semua hardware mendukung fasilitas ini. Untuk itulah perlu mengecek structure D3DCAPS9 pada saat inisialisasi Direct3D. Berapa maximum texture yang bisa dipakai bersamaan, dan berapa maximum blending yang bisa dipakai. Umumnya pada hardware modern support hingga 8 texture sekaligus. Yang perlu diperhatikan dalam rendering multitexture adalah color operation. Pada contoh di subbab ini saya memakai 3 buah texture : 1. Texture Fukada Kyouko 深田恭子 tanpa alpha channel 2. Texture serat dengan alpha channel masing-masing 50% dimodulasi dengan texture 1. 3. Texture yang hanya berupa alpha channel saja untuk dimodulasi dengan texture 1 dan 2. Untuk membuat dan mengaktifkan ketiga texture tersebut, kita masukkan masing-masing dalam stage 0, 1, dan 2. // // Create First Texture if ( FAILED( D3DXCreateTextureFromFile( g_pDevice, g_szTexName, &g_pTex ) ) ) { ErrorMessage( TEXT( "Error Creating Texture! " ) ) ; return FALSE ; } // // Create Second Texture if ( FAILED( D3DXCreateTextureFromFile( g_pDevice, g_szTexDetailName, &g_pTexDetail ) ) ) { ErrorMessage( TEXT( "Error Creating Detail Texture " ) ) ; return FALSE ; } // // Create Third Texture if ( FAILED( D3DXCreateTextureFromFile( g_pDevice, g_szTexAlphaName, &g_pTexAlpha ) ) ) { ErrorMessage( TEXT( "Error Creating Alpha Channel! " ) ) ; return FALSE ; } g_pDevice->SetTexture( 0, g_pTex ) ; g_pDevice->SetTexture( 1, g_pTexDetail ) ; g_pDevice->SetTexture( 2, g_pTexAlpha ) ;
Kemudian supaya device melakukan operasi modulasi, maka kita perlu menghidupkan Stage Statesnya dengan memanggil IDirect3DDevice9::SetTextureStageState : g_pDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ) ; g_pDevice->SetTextureStageState( 2, D3DTSS_COLOROP,D3DTOP_BLENDTEXTUREALPHA ) ;
Dengan begitu maka stage 1 yang berisi detail akan dimodulasi dengan texture 0, kemudian hasil tersebut akan di-blend dengan alpha dari texture 2.
137
Untuk Argumen dari SetTextureStageState dapat dilihat di dokumentasi DirectX SDK Anda, termasuk rumus-rumus modulasi tersebut. Kode lengkap bisa dilihat di sample.
ScreenShot Multitextured Quad MultiTextured Quad
Gambar 7.12. Multitextured Quad
138
Blending Pada bab ini, kita akan membahas tentang blending yaitu mengkombinasikan pixel yang sudah diraster dengan pixel yang datang. Dengan blending, membuat kita bisa mengaplikasikan efek misalnya transparansi.
Persamaan Blending Mari kita lihat dua buah gambar, yang satu adalah torus digambar di atas background quad dengan texture kyoko :
Gambar 8.1. Torus Opaque
Gambar 8.2. Torus Transparent
139
Bagaimana cara mendapatkan hasil tersebut? Kita meraster torus setelah menggambar background. Kita mengkombinasikan warna dari torus dengan warna background sehingga warna background dapat terlihat tembus pada torus. Kombinasi dari source pixel (pixel yang sedang dihitung) dengan destination pixel (pixel yang sudah ada di back buffer) disebut blending. Blending tidak hanya dengan transparansi seperti di atas, tetapi masih banyak lagi. Yang terpenting adalah mengingat bahwa triangle yang sedang diraster di-blend dengan pixel yang sudah ada di backbuffer. Pada gambar contoh, gambar kyoko digambar dulu, baru kemudian torus. Jadi aturan di bawah ini berlaku jika ingin menggambar dengan blending : Gambar object yang tidak menggunakan blending dahulu, kemudian urutkan penggambaran dari yang terjauh dari camera. Dapat dengan mudah dilakukan di view space, jadi tinggal mengurutkan menurut komponen Z. Kemudian gambar object yang memerlukan blending dari belakang ke depan. Rumus di bawah ini digunakan dalam persamaan blending :
OutputPixel = SourcePixel ⊗ SourceBlendFactor + DestPixel ⊗ DestBlendFactor Tiap pixel di atas dinyatakan dalam vector 4D (r, g, b, a ) dan simbol ⊗ adalah simbol perkalian tiap komponen. Variabel
Keterangan
OutputPixel
Hasil akhir pixel yang telah di-blend
SourcePixel
Pixel yang sedang dihitung (pixel yang datang) yang akan di-blend dengan backbuffer nanti.
SourceBlendFactor
Nilai dalam interval [0, 1] yang menyatakan persentase source pixel yang diblend.
DestPixel DestBlendFactor
Pixel di backbuffer Nilai dalam interval [0, 1] yang menyatakan persentase pixel destination yang diblend.
Dengan memodifikasi SourceBlendFactor dan DestBlendFactor dengan suatu nilai tertentu, maka kita dapat memperoleh banyak efek. Blending adalah mati secara default, sehingga perlu dihidupkan dengan SetRenderState : g_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ) ;
Catatan Khusus : Blending merupakan suatu operasi yang cukup memakan performa, oleh karena itu, hidupkanlah hanya untuk geometri yang membutuhkan, jadikan satu segitiga-segitiga yang membutuhkan blending, sehingga tidak menghidupkan dan mematikan blending berkali-kali dalam satu frame.
140
Blend Factor Dengan menyetting kombinasi yang berbeda antara source dan destination factor, kita bisa mendapatkan berbagai macam efek blending. Cukup dengan memanggil SetRenderState sbb : g_pDevice->SetRenderState(D3DRS_SRCBLEND, SourceFactor); g_pDevice->SetRenderState(D3DRS_DESTBLEND, DestinationFactor);
nilai dari SourceFactor dan Destination Factor adalah sebagai berikut : Factor
Keterangan
D3DBLEND_ZERO
Blendfactor = ( 0, 0, 0, 0 )
D3DBLEND_ONE
Blendfactor = ( 1, 1, 1, 1 )
D3DBLEND_SRCCOLOR
BlendFactor = ( rs, gs, bs, as )
D3DBLEND_INVSRCCOLOR
BlendFactor = ( 1-rs,1- gs,1- bs,1- as )
D3DBLEND_SRCALPHA
BlendFactor = (as, as, as,as )
D3DBLEND_INVSRCALPHA
BlendFactor = (1- as,1- as,1- as,1-as )
D3DBLEND_DESTALPHA
BlendFactor = ( ad, ad, ad, ad)
D3DBLEND_INVDESTALPHA
BlendFactor = (1- ad,1- ad,1- ad,1-ad )
D3DBLEND_DESTCOLOR
BlendFactor = (rd, gd, bd, ad)
D3DBLEND_INVDESTCOLOR
BlendFactor = (1- rd,1- gd,1- bd,1- ad)
D3DBLEND_ALPHASAT
BlendFactor = (f, f, f, 1 ) dengan f = min( as, 1 – ad)
D3DBLEND_BOTHINVSRCALPHA
Nilai
default
untuk
sourcefactor
SourceBlend = (1- as,1- as,1- as,1-as ) dan estination manjadi (as, as, as,as ) blend ini hanya untuk D3DRS_SRCBLEND
dan
destfactor
adalah
D3DBLEND_SRCALPHA
dan
D3DBLEND_INVSRCALPHA.
Transparansi Pada bab-bab sebelumnya kita mengabaikan komponen alpha (alpha channel). Komponen alpha ini paling banyak digunakan untuk blending. Akan tetapi komponen alpha ini juga diinterpolasi saat rasterisasi seperti warna. Komponen alpha paling banyak digunakan untuk menyatakan transparansi suatu pixel. Misal kita mengalokasikan 8 bit untuk alpha, maka nilai yang valid adalah pada interval [0, 255]. Dengan [0, 255] adalah ke-opaque-an [0%,100%]. Jadi pixel dengan nilai alpha 0 adalah 100% transparan, dan dengan nilai nilai 255 adalah 100% opaque.
141
Supaya komponen alpha menjadi nilai yang menyatakan transparansi dari suatu pixel, kita mengeset source blend ke D3DBLEND_SRCALPHA dan destination blend ke D3DBLEND_INVSRCALPHA. Kedua value tersebut adalah default blend factor.
Alpha Channels Daripada menggunakan nilai alpha yang diinterpolasi, kita bisa mendapatkan alpha dari texture alpha channels. Alpha channels adalah sebuah bit yang dimasukkan di tiap pixel yang menyimpan komponen alpha. Ketika texture di-mapping, maka alpha juga dimap pada pixel yang bersangkutan.
Gambar 8.3. Contoh alpha channel 8 bit. Gambar di bawah ini menunjukkan hasil rendering RGB Channel ditambah alpha channel.
Gambar 8.4. Hasil rendering RGB Channel ditambah alpha channel
Menentukan sumber alpha Secara default, jika texture yang sedang diset mempunyai alpha channel, maka alpha diambil dari alpha channel. Jika tidak memiliki alpha channel, maka akan diambil dari vertex color. Akan tetapi, kita bisa menentukan secara manual dengan mengeset texture stage state sbb : // menggunakan alpha dari warna diffuse g_pDevice->SetTextureStageState( 0, D3DTSS ALPHAARG1, D3DTA DIFFUSE ) ; g_pDevice->SetTextureStageState( 0, D3DTSS ALPHAOP, D3DTOP SELECTARG1 ) ; // menggunakan alpha dari texture alpha channel g_pDevice->SetTextureStageState( 0, D3DTSS ALPHAARG1, D3DTA TEXTURE ) ; g_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ) ;
142
Menggunakan DirectX Texture Tool untuk Alpha Channel Format image yang umum biasanya tidak menyimpan informasi alpha. Pada seksi ini akan diterangkan cara menggunakan alpha channel pada file DDS. File DDS adalah singkatan dari DirectDraw Surface dan biasa digunakan untuk texture dalam aplikasi DirectX, file DDS ini bisa diload dengan D3DXLoadTextureFromFile. Buka DirectX Texture Tool dan buka texture kyoko.jpg. Kemudian ubah formatnya dari X8R8G8B8 menjadi A8R8G8B8 dengan memilih menu Format > Change Surface Format, pilih Unsigned 32bit : A8R8G8B8 kemudian klik OK.
Gambar 8.5. Mengganti format texture. Ini akan membuat image dengan 8 bit alpha 8 bit red 8 bit green dan 8 bit blue. Simpan dengan nama kyoko.dds. Untuk menerapkan alpha channel, pilih menu File > Open Onto Alpha Channel of This Surface. Kemudian pilih image hitam putih tadi yang merupakan alpha channel kita. Setelah di-save, kemudian dibuka lagi maka texture akan berubah menjadi seperti di bawah ini :
143
Gambar 8.6. texture dengan alpha channel
Code and Blend Aplikasi contoh ada dua buah yaitu yang menggunakan material alpha dan texture alpha. Untuk yang saya terangkan di sini adalah yang menggunakan material alpha, yaitu menggambar torus transparan di atas background. Dengan menekan tombol [A] atau [S] maka nilai alpha dapat ditambah dan dikurangi. Kode yang kurang relevan dengan pembahasan seperti mengeset texture, lighting, dsb, tidak ditampilkan. Untuk code yang komplit silahkan lihat di sample. Pertama-tama kita siapkan vertex dan index buffer untuk background dan mesh buffer untuk torus. // Vertex Buffer static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL ; static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL ; static LPDIRECT3DTEXTURE9 g_pTex = NULL ; static LPD3DXMESH g_pMeshTorus = NULL ; static D3DMATERIAL9 g_MaterialTorus = g_BlueMaterial ; static D3DMATERIAL9 g_MaterialBack = g_WhiteMaterial ;
Pada fungsi Setup(), kita buat torus dan mengeset diffuse color sebagai asal alpha : BOOL Setup() { // -- setting projection, view, lighting, texture, // -- vertex dan index buffer untuk background dihilangkan // // Creating Torus // if ( FAILED( D3DXCreateTorus( g_pDevice, 0.5f, 1.0f,
144
32,32, &g_pMeshTorus, NULL ) ) ) { return FALSE ; } g_MaterialTorus.Diffuse.a = 0.5f ; // // Setting Torus Blending Mode (using diffuse alpha as argument) // g_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ) ; g_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ) ; g_pDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ) ; g_pDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ) ; return TRUE ; }
Pada fungsi menggambar, kita menggambar background dahulu dengan matrix world identitas, kemudian menggambar torus dengan memutarnya. BOOL CALLBACK DoFrame( float DeltaTime ) { if ( !g_pDevice || !g_pD3D ) { return FALSE ; } // // Cek key to increase alpha [A] or decrease alpha [S] // if ( GetAsyncKeyState( 'A' ) & 0x8000 ) { g_MaterialTorus.Diffuse.a += 0.01f ; } if ( GetAsyncKeyState( 'S' ) & 0x8000 ) { g_MaterialTorus.Diffuse.a -= 0.01f ; } if ( g_MaterialTorus.Diffuse.a > 1.0f ) { g_MaterialTorus.Diffuse.a = 1.0f ; } if ( g_MaterialTorus.Diffuse.a < 0.0f ) { g_MaterialTorus.Diffuse.a = 0.0f ; } // // World Transformation Matrices // D3DXMATRIX matWorld, matRotX, matRotY ; static float rotY = 0.0f ; rotY += DeltaTime ; if ( rotY > D3DX_PI * 2 ) { rotY -= D3DX_PI * 2 ; } D3DXMatrixIdentity( &matWorld ) ; g_pDevice->SetTransform( D3DTS_WORLD, &matWorld ) ;
145
// // Clear Target, depth, and stencil // g_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB( 0xff, 0xff, 0xff ), 1.0f, 0 ) ; if ( FAILED( g_pDevice->BeginScene( ) ) ) { return FALSE ; } // // Draw The KYOKO Background // g_pDevice->SetStreamSource( 0, g_pVB, 0, sizeof( Vertex ) ) ; g_pDevice->SetIndices( g_pIB ) ; g_pDevice->SetFVF( VERTEXFORMAT ) ; g_pDevice->SetTexture( 0, g_pTex ) ; g_pDevice->SetMaterial( &g_MaterialBack ) ; g_pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, VERTICESNUMBER, 0, PRIMITIVESNUMBER ) ; // // Draw and rotate The Transparent Torus // D3DXMatrixRotationX( &matRotX, D3DX_PI * 0.15f ) ; D3DXMatrixRotationY( &matRotY, rotY ) ; matWorld = matRotX * matRotY ; g_pDevice->SetTransform( D3DTS_WORLD, &matWorld ) ; g_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ) ; g_pDevice->SetMaterial( &g_MaterialTorus ) ; g_pDevice->SetTexture( 0, NULL ) ; g_pMeshTorus->DrawSubset( 0 ) ; g_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ) ; g_pDevice->EndScene( ) ; g_pDevice->Present( NULL, NULL, NULL, NULL ) ; return TRUE ; }
146
Sample ScreenShot Material Blend Mengeblend backdrop dengan torus transparan.
Gambar 8.7. Blending material antara backdrop dan torus.
Texture Alpha Blend Blending antara backdrop dengan texture yang sudah diberi nilai alpha, banyak perubahan dalam project ini, di antaranya backdrop dan kubus dibuat class dan diimplementasikan dengan metode interface-implementation, selengkapnya lihat code
Gambar 8.7. Blending backdrop dengan texture yang sudah diberi alpha channel
147
Stenciling Stencil buffer adalah sebuah off-screen buffer seperti depth buffer yang digunakan untuk mendapatkan efek tertentu. Stencil buffer mempunyai resolusi yang sama dengan back buffer sehingga pixel ij di dalam stencil buffer sama dengan pixel ij di back buffer. Seperti namanya, stencil buffer bekerja layaknya stencil yaitu memblok rendering pada area tertentu dalam back buffer. Contoh, misal kita ingin mengimplementasikan kaca/mirror. Kita hanya memerlukan refleksi pada kaca saja, bukan pada bagian lain. Kita bisa menggunakan stencil buffer untuk memblok rendering refleksi benda kecuali ke kaca. Untuk lebih jelasnya silahkan lihat pada perbedaan dua gambar berikut ini, yang satu menggambar refleksi tanpa stencil, yang kedual menggambar refleksi dengan stencil, sehingga hanya yang di kaca yang digambar.
Gambar 9.1. Mirror dengan Stencil Dimatikan
148
Gambar 9.2. Mirror dengan Stencil Dihidupkan Stencil buffer adalah seperti depth buffer, dapat dikontrol dengan interface tertentu. Seperti blending, interface tersebut menawarkan kemampuan yang fleksibel dan sangat powerful. Menggunakan stencil buffer paling gampang adalah dengan mempelajari aplikasi yang sudah ada. Oleh karena itu, saya menekankan pada contoh dua implementasi stencil buffer yaitu mirror (refleksi) dan bayangan (shadow).
Menggunakan Stencil Buffer Untuk
menggunakan
stencil
buffer,
pertama
kita
meminta
kepada
Direct3D
kemudian
mengaktifkannya. Untuk mengaktifkan stencil buffer, kita memberikan nilai TRUE pada render state D3DRS_STENCILENABLE. Untuk menonaktifkan, cukup dengan memberikan nilai FALSE pada render state D3DRS_STENCILENABLE. g_pDevice->SetRenderState( D3DRS_STENCILENABLE, TRUE ) ; ...// stencil work g_pDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ) ;
Kita bisa membersihkan stencil buffer dengan memanggil method IDirect3DDevice9::Clear seperti pada membersihkan render target dan depth buffer. g_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0xff000000, 1.0f, 0U ) ;
149
Kita memasukkan D3DCLEAR_STENCIL dan untuk argumen keenam artinya kita bersihkan stencil buffer ke nilai 0.
Meminta Alokasi Stencil Buffer Stencil buffer dibuat seperti kita membuat depth buffer. Kita menentukan format stencil buffer dan depth buffer dalam waktu yang sama. Di memory, depth buffer dan stencil buffer menempati lokasi yang sama, akan tetapi berbeda segmen, seperti pada warna, komponen alpha dan RGB menempati tempat yang berurutan. Format
Keterangan
D3DFMT_D24S8
Membuat depth buffer 24 bit dan stencil buffer 8 bit
D3DFMT_D24X4S4
Membuat depth buffer 24 bit, 4 bit tidak dipakai, dan 4 bit sebagai stencil buffer.
D3DFMT_D15S1
Membuat depth buffer 15 bit, dengan 1 bit sebagai stencil buffer.
Stencil Test Seperti yang dibahas sebelumnya, kita bisa menggunakan stencil buffer untuk mencegah rendering pada area tertentu. Caranya adalah dengan stencil test. Yang dinyatakan dengan : ( ref AND mask ) [operator] ( value AND mask ) Stencil test dilakukan di setiap pixel. Dengan asumsi stencil diaktifkan, dengan dua operands : •
Nilai di sebelah kiri ( LHS ) ditentukan dengan meng-AND-kan stencil reference value yang kita tentukan dengan mask yang kita tentukan.
•
Nilai di sebelah kanan ( RHS ) ditentukan dengan meng-AND-kan nilai di stencil buffer dengan mask yang kita tentukan.
Stencil test akan membandingkan LHS dan RHS dengan suatu comparation operation. Ekspresi di atas akan menghasilkan nilai true atau false. Kita menggambar pixel di backbuffer jika hasilnya adalah true (stencil pass) dan tidak menggambar pixel di backbuffer jika hasilnya adalah false (stencil fail). Karena pixel tidak ditulis di backbuffer, maka tidak ditulis pula di depth buffer.
Mengkontrol Stencil Test Untuk memberikan kita fleksibilitas, Direct3D mengizinkan kita mengkontrol variabel yang digunakan di stencil test. Dengan kata lain, kita harus menentukan stencil reference value, mask value, dan juga comparison operation dari stencil test. Stencil Reference Value Secara default stencil reference value diset ke 0. akan tetapi kita bisa menggantinya dengan mengubah render state D3DRS_STENCILREF. g_pDevice->SetRenderState( D3DRS_STENCILREF, 0x1 ) ;
kita memakai bilangan hexadecimal supaya mudah dalam melihatkan bit alignment dan hasil and.
150
Stencil Mask Stencil masking value adalah suatu nilai yang digunakan untuk me-masking ref dan value. Mask default adalah 0xffffffff yang berarti tidak me-mask bit manapun. Kita bisa menggantinya dengan mengganti render state D3DRS_STENCILMASK. Contoh di bawah ini me-mask 16 bit pertama. g_pDevice->SetRenderState( D3DRS_STENCILMASK, 0x0000ffff ) ;
Stencil Value Stencil value adalah nilai yang ada dalam stencil buffer yang akan kita test. Contoh, jika kita melakukan testing pada pixel ij, maka value pada ekspresi di atas adalah nilai stencil buffer untuk pixel ij. Kita tidak bisa mengeset nilai stencil satu persatu, akan tetapi kita bisa meng-clear stencil buffer seluruhnya ke nilai tertentu. Comparison Operation Kita
bisa
mengeset
comparison
operation
dari
stencil
test
dengan
mengubah
D3DRS_STENCILFUNC. Nilainya bisa salah satu dari enumerasi D3DCMPFUNC berikut ini : typedef enum _D3DCMPFUNC { D3DCMP_NEVER = 1, D3DCMP_LESS = 2, D3DCMP_EQUAL = 3, D3DCMP_LESSEQUAL = 4, D3DCMP_GREATER = 5, D3DCMP_NOTEQUAL = 6, D3DCMP_GREATEREQUAL = 7, D3DCMP_ALWAYS = 8, D3DCMP_FORCE_DWORD = 0x7fffffff } D3DCMPFUNC;
Nilai
Keterangan
D3DCMP_NEVER
Stencil test selalu fail
D3DCMP_LESS
Stencil test Sukses jika LHS < RHS
D3DCMP_EQUAL
Stencil test Sukses Jika LHS = RHS
D3DCMP_LESSEQUAL
Stencil test Sukses jika LHS ≤ RHS
D3DCMP_GREATER
Stencil test Sukses Jika LHS > RHS
D3DCMP_NOTEQUAL
Stencil test Sukses jika LHS ≠ RHS
D3DCMP_GREATEREQUAL
Stencil test Sukses Jika LHS ≥ RHS
D3DCMP_ALWAYS
Stencil test selalu sukses
D3DCMP_FORCE_DWORD
Tidak digunakan, value ini hanya memaksa supaya enumarsi disimpan dalam bentuk DWORD (unsigned int ), nilai 0x7fffffff merupakan nilai tertinggi dalam bilangan 32 bit dengan seluruh bitnya bernilai 1.
151
Mengupdate stencil buffer Selain menentukan apakah akan menggambar dalam pixel tertentu atau tidak, kita bisa menentukan bagaimana nilai dalam stencil buffer diupdate dengan mengeset tiga buah kemungkinan •
Jika stencil test fail di pixel ij. Kita bisa menentukan bagaimana nilai stencil buffer diubah dengan mengeset nilai D3DRS_STENCILFAIL.
g_pDevice->SetRenderState( D3DRS_STENCILFAIL, StencilOperation ) ;
•
Jika depth test fail di pixel ij. Kita bisa menentukan bagaimana nilai stencil buffer diubah dengan mengeset nilai D3DRS_STENCILZFAIL.
g_pDevice->SetRenderState( D3DRS_STENCILZFAIL, StencilOperation ) ;
•
Jika depth test dan stencil test fail di pixel ij, kita bisa menentukan bagaimana nilai stencil buffer diubah dengan mengeset nilai D3DRS_STENCILPASS
g_pDevice->SetRenderState( D3DRS_STENCILPASS, StencilOperation ) ;
dengan StencilOperation adalah salah satu dari nilai konstanta di bawah ini Nilai
Keterangan
D3DSTENCILOP_KEEP
Tidak mengganti nilai di stencil buffer
D3DSTENCILOP_ZERO
Mengganti nilai stencil buffer menjadi 0
D3DSTENCILOP_REPLACE
Mengganti nilai stencil buffer menjadi nilai ref
D3DSTENCILOP_INCRSAT
Menambah nilai stencil buffer. Jika nilai stencil buffer melebihi dari nilai yang diizinkan, maka nilai akan di-clamp ke nilai maximum tersebut.
D3DSTENCILOP_DECRSAT
Mengurangi nilai stencil buffer. Jika nilai stencil buffer kurang dari 0, maka nilai akan di-clamp ke 0.
D3DSTENCILOP_INVERT
Membalik seluruh bit dalam stencil buffer
D3DSTENCILOP_INCR
Menambah nilai stencil buffer. Jika nilai stencil buffer melebihi dari nilai yang diizinkan, maka nilai akan diset ke 0
D3DSTENCILOP_DECR
Mengurangi nilai stencil buffer. Jika nilai stencil buffer kurang dari 0, maka nilai akan diset ke nilai maximum.
Stencil Write Mask Selain render state di atas ada satu render state lagi yang berkaitan dengan stencil operation yaitu D3DRS_STENCILWRITEMASK yang akan me-mask nilai yang kita tulis ke stencil buffer, secara default nilainya adalah 0xffffffff yang tidak me-mask bit manapun, contoh di bawah ini memask 16 bit pertama g_pDevice->SetRenderState( D3DRS_STENCILWRITEMASK, 0x0000ffff ) ;
152
Aplikasi I : Mirror Banyak permukaan di dunia nyata yang bisa memantulkan bayangan benda. Akan tetapi kita di sini hanya membahas permukaan yang merupakan bidang yang memantulkan bayangan benda, bukan permukaan semacam mobil atau kendaraan yang mengkilap yang memantulkan bayangan. Oleh karena itu, aplikasi kita disebut planar reflection, karena refleksi terjadi pada bidang (plane). Sekarang kita akan menunjukkan bagaimana cara merefleksikan point v' = ( v'x, v'y, v'z ) dari v = ( vx,
ˆ •p + d = 0 vy, vz ) terhadap bidang n V’ -2kn
n q
kn V
G
nˆ • p + d
ˆ • p + d = 0 menghasilkan v' Gambar 9.3. Refleksi v terhadap bidang n G
ˆ dengan k adalah jarak terdekat antara v Dari materi bidang di bab I, kita dapatkan q = v -kn
(
)
ˆ, d didapat dengan persamaan berikut ini : dengan bidang. Dengan begitu, refleksi v pada bidang n
ˆ v ' = v − 2kn ˆ • v + d )nˆ = v − 2(n ˆ] = v − 2[(nˆ • v )nˆ + dn Dengan begitu kita bisa mendapatkan matrix transformasi untuk refleksi R sebagai berikut :
−2ny nx −2nz nx ⎛ −2nx nx + 1 ⎜ −2nx ny −2ny ny + 1 −2nz ny R=⎜ ⎜ −2nx nz −2ny nz −2nz nz + 1 ⎜⎜ −2ny d −2nz d ⎝ −2nx d
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1 ⎟⎠
Library D3DX menyediakan fungsi berikut untuk memproduksi matrix refleksi R seperti di atas : D3DXMATRIX *D3DXMatrixReflect( D3DXMATRIX *pOut, // Matrix hasil CONST D3DXPLANE *pPlane // Bidang untuk merefleksikan );
153
Berikut ini adalah matrix refleksi spesial yang merefleksikan pada bidang yz, xz, dan xy dengan matrixnya seperti di bawah ini :
Ryz
⎛ −1 ⎜ 0 =⎜ ⎜0 ⎜ ⎝0
0 0 0⎞ ⎛1 0 ⎜ ⎟ 1 0 0⎟ 0 −1 R xz = ⎜ ⎜0 0 0 1 0⎟ ⎜ ⎟ 0 0 1⎠ ⎝0 0
0 0⎞ ⎛1 0 0 ⎟ ⎜ 0 0⎟ 0 1 0 R xy = ⎜ ⎜ 0 0 −1 1 0⎟ ⎟ ⎜ 0 1⎠ ⎝0 0 0
0⎞ ⎟ 0⎟ 0⎟ ⎟ 1⎠
Metode Implementasi Mirror Ketika mengimplementasikan mirror, sebuah object hanya direfleksikan jika berada di depan mirror. Akan tetapi, kita tidak akan mengetest satu persatu pixel apakah berada di depan mirror atau tidak karena akan sangat kompleks. Untuk itu, supaya mudah, kita selalu merefleksikan object dan merendernya di manapun dia berada. Akan tetapi ini memproduksi masalah baru yaitu refleksi object digambar di permukaan yang bukan kaca seperti yang terjadi pada gambar di atas. Kita bisa mengatasinya dengan menggunakan stencil buffer untuk memblok area tertentu dalam backbuffer. Dengan begitu, kita bisa menggunakan stencil buffer untuk memblok render dari bayangan object di permukaan yang bukan mirror. Berikut ini adalah caranya langkah per langkah : 1. Render scene normal, seluruhnya, kecuali bayangan object. Langkah ini tidak mengubah stencil buffer. 2. Hapus stencil buffer ke 0. Gambar di bawah ini menggambarkan keadaan backbuffer dan stencil buffer :
Back Buffer
Stencil Buffer
Gambar 9.4. Keadaan back buffer dan stencil buffer setelah stencil buffer diclear menjadi 0 3. Gambar primitives yang merupakan mirror hanya ke stencil buffer. Set stencil test supaya selalu sukses dan set isi stencil buffer (replace) menjadi nilai ref yaitu 1 jika stencil pass. Karena kita hanya menggambar mirror saja, maka seluruh isi stencil buffer berisi 0 kecuali stencil buffer sehingga kira-kira menjadi sebagai berikut :
Back Buffer
Stencil Buffer
Gambar 9.5. Keadaan back buffer dan stencil buffer setelah render mirror ke stencil
154
4. Sekarang kita gambar refleksi dari object ke back buffer dan stencil buffer. Akan tetapi, kita hanya akan merender ke backbuffer jika stencil pass. Sekarang kita set supaya
Implementasi Mirror dalam Code Code yang relevan dengan fungsi adalah RenderMirror. Yang pertama kali menggambar mirror primitives ke stencil kemudian merender object refleksi ke mirror. Berikut diterangkan per bagian untuk memudahkan pemahaman. Bagian 1 Bagian ini adalah mengaktifkan stencil buffer dan render state yang dibutuhkan : void RenderMirror( void ) { g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState(
D3DRS_STENCILENABLE, TRUE ) ; D3DRS_STENCILFUNC, D3DCMP_ALWAYS ) ; D3DRS_STENCILREF, 0x1 ) ; D3DRS_STENCILMASK, 0xffffffff ) ; D3DRS_STENCILWRITEMASK, 0xffffffff ) ; D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ) ; D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ) ; D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ) ;
penjelasannya cukup sederhana, kita aktifkan Stencil Buffer, yang berarti stencil test selalu pass. Jika depth fail kita keep nilai value, karena kita tidak ingin merender pixel yang tertutup karena z fail. Kita juga menggunakan D3DSTENCILOP_KEEP untuk stencil fail. State ini tidak pernah terjadi karena kita mengeset D3DCMP_ALWAYS untuk stencil function. Jika depth dan stencil pass kita gunakan D3DSTENCILOP_REPLACE untuk mengganti nilai stencil ke nilai reference yaitu 0x1. Bagian 2 Bagian selanjutnya merender mirror, tetapi hanya ke stencil buffer. Kita hentikan menulis ke depth buffer dengan mengeset D3DRS_ZWRITENABLE ke FALSE. Kita bisa menghentikan update ke backbuffer dengan mengeset source blend ke D3DBLEND_ZERO dan destination ke D3DBLEND_ONE. Dengan menggunakan factor ke blending kita dapatkan persamaan berikut :
FinalPixel = SourcePixel ⊗ ( 0 0 0 0 ) + DestPixel ⊗ ( 1 1 1 1) = ( 0 0 0 0 ) + DestPixel = DestPixel // Matikan menulis ke depth dan back buffer g_pDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ) ; g_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ) ; g_pDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ) ; g_pDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ) ; // Gambar Mirror ke Stencil Buffer g_pDevice->SetStreamSource( 0, g_pVB, 0, sizeof( Vertex ) ) ; g_pDevice->SetFVF( VERTEXFORMAT ) ; g_pDevice->SetMaterial( &g_MirrorMaterial ) ; g_pDevice->SetTexture(0, g_pTextures ) ; g_pDevice->SetIndices( g_pIB ) ;
155
D3DXMATRIX matIdentity ; D3DXMatrixIdentity( &matIdentity ) ; g_pDevice->SetTransform( D3DTS_WORLD, &matIdentity ) ; g_pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 12, 0, 4, 0, 2 ) ; // Hidupkan lagi Zwrite untuk menulis ke depth buffer g_pDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE )
Bagian 3 Pada bagian ini, pixel dalam stencil buffer yang merupakan pixel yang terlihat diset ke 0x1 sehingga menandai itu adalah area tempat mirror berada. Sekarang kita merender bayangan refleksi dari object teapot, kita bisa melakukannya dengan mudah dengan bantuan stencil buffer. g_pDevice->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_EQUAL ) ; g_pDevice->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ) ;
dengan begitu kita mempunyai persamaan dan operasi yang baru sebagai berikut : (ref & mask) == ( value & mask ) ( 0x1 & 0xffffffff ) == ( value & 0xffffffff ) (0x1) == ( value & 0xffffffff ) Ini menunjukkan bahwa stencil akan pass jika value = 0x1 karena nilai 0x1 hanya berada pada area di mana mirror dirender, maka test hanya akan sukses di area itu. Dengan begitu, pada rendering pass berikut kita tidak mengganti nilai stencil buffer. Bagian 4 Bagian selanjutnya dari RenderMirror adalah menghitung matrix posisi dan refleksi dari object teapot : D3DXMATRIX matWorld, matTrans, matReflect ; D3DXPLANE XYPlane( 0.0f, 0.0f, 1.0f, 0.0f ) ; D3DXMatrixReflect( &matReflect, &XYPlane ) ; D3DXMatrixTranslation( &matTrans, g_vTeapotPosition.x, g_vTeapotPosition.y, g_vTeapotPosition.z ) ; matWorld = matTrans * matReflect ;
perhatikan, bahwa kita pertama kali mentranslasi teapot menjadi teapot normal, kemudian baru kita me-reflect terhadap bidang XY. Urutan transformasi ini harus sama dengan urutan perkalian. Bagian 5 Pada bagian ini kita sudah siap merender refleksi dari teapot. Akan tetapi jika kita render sekarang, tidak akan ditampilkan. Kenapa? Karena teapot refleksi mempunyai depth lebih besar daripada mirror, sehingga akan tertutup oleh kaca. Untuk itu, kita clear dulu depth buffer. g_pDevice->Clear( 0, NULL, D3DCLEAR_ZBUFFER, 0, 1.0f, 0 ) ;
156
tidak semua masalah teratasi, jika kita hanya membersihkan depth buffer, maka refleksi teapot akan digambar di depan mirror, dan tidak terlihat 'benar'. Supaya object teapot terlihat berada 'di dalam' mirror, maka kita blend teapot dengan mirror dengan persamaan berikut :
FinalPixel = SourcePixel ⊗ DestPixel + DestPixel ⊗ ( 0 0 0 0 ) Dalam code menjadi : g_pDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR ) ; g_pDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ) ;
kemudian kita gambar bayangan refleksi teapot : g_pDevice->SetTransform( D3DTS_WORLD, &matWorld ) ; g_pDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW ) ; g_pDevice->SetTexture( 0, NULL ) ; g_pTeapot->DrawSubset( 0 ) ;
Kita mengganti cull mode karena ketika suatu object di-reflect maka arah windingnya pun terbalik, jadi backface terbalik menjadi frontface, kita ingin melihat backface, sehingga yang kita cull adalah front-facenya. Sesudah itu kita matikan stencil dan blending dan mengembalikan cull mode g_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ) ; g_pDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ) ; g_pDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ) ; } // selesai render mirror
Aplikasi II : Planar Shadow Shadow( bayangan ) membantu persepsi kita tentang dari mana cahaya berasal dan menambah realistis scene kita. Bagian ini akan membahas shadow yang ada di bidang (planar shadow).
Gambar 9.6. Screenshot dari aplikasi sample StencilShadowMirror
157
Sebagai catatan, shadow di sini adalah sebuah cara cepat membuat shadow. Tidak sehebat dan sebagus jika menggunakan shadow volume. Shadow volume tidak dibahas dalam buku perkenalan seperti ini. Akan tetapi, DirectX SDK menyediakan sample untuk shadow volume. Untuk mengimplementasikan planar shadow, kita harus mencari di mana shadow yang dihasilkan oleh object di plane dan merendernya. Bisa dicari dengan sedikit 3D math. Kemudian kita render polygonnya dengan material hitam dengan transparansi 50%. Merender shadow sering menimbulkan artefak beruba "double blending" untuk itu kita memerlukan stencil buffer untuk mencegah double blending.
Shadow dari parallel light Lihat gambar berikut :
p
L
ˆ •p + d = 0 n s
Gambar 9.7. shadow yang dihasilkan paralel light Gambar di atas memperlihatkan shadow yang dicast oleh parallel light. Light ray dari light source, dengan arah L, melewati sembarang vertex p dengan r(t) = p + tL. Perpotongan antara ray r(t)
ˆ •p + d = 0 menghasilkan vertex s. Perpotongan dapat dengan mudah dicari dengan plane n dengan ray/plane intersection test.
G G ˆ • p + d = 0 dan r (t ) = p + tL . Kita masukkan r(t) ke persamaan bidang : n G ˆ • (p + tL ) + d = 0 n G ˆ • p + t (n ˆ • L ) = −d ⇔n G ˆ • L ) = −d − nˆ • p ⇔ t (n −d − nˆ • p G ⇔t = (nˆ • L ) Sehingga
⎛ −d − nˆ • p ⎞ G ⎟ s =p+⎜ ⎝ (nˆ • L ) ⎠
158
Shadow dari point light Lihat gambar berikut : L
p
nˆ •p + d = 0 s
Gambar 9.8. shadow yang dihasilkan point light Gambar di atas memperlihatkan shadow yang dicast oleh point light. L adalah posisi light ray datang
G
dari point light melalui sebarang titik p dinyatakan dengan persamaan r (t ) = p + t (p − L ) .
ˆ •p + d = 0 menghasilkan vertex s. Vertex-vertex Perpotongan antara ray r(t) dengan bidang n perpotongan antara ray/plane dari tiap polygon adalah bayangan dari object yang bersangkutan.
Shadow Matrix Dari gambar 9.7 dapat kita simpulkan bahwa shadow adalah parallel projection dari object ke
ˆ •p + d = 0 menurut arah paralel light. Sedangkan gambar 9.8. memperlihatkan bahwa bidang n ˆ •p + d = 0 dari viewpoint adalah shadow adalah perspective projection dari object ke bidang n light source.
ˆ •p + d = 0 dengan Kita bisa menyatakan transformasi vertex p ke proyeksinya s di bidang n matrix. Selain itu, kita bisa merepresentasikan orthogonal projection dan perspective projection dengan matrix dengan ingenuitas. Misal (nx, ny, nz, d) adalah 4D vector yang menyatakan koefisien dari bidang di mana kita ingin menempatkan shadow. Kemudian L = ( Lx, Ly, Lz, Lw ) merupakan 4D vector yang menyatakan arah parallel light maupun posisi point light. Kita menggunakan koordinat W yang menyatakan : •
W = 0, L adalah arah dari parallel light.
•
W = 1, L adalah posisi point light.
Dengan asumsi bahwa normal dari bidang telah dinormalisasi, maka k = (nx, ny, nz, d) ● ( Lx, Ly, Lz, Lw ) = nxLx + nyLy + nzLz + dLw. Kemudian kita nyatakan transformasi vertex p ke s dengan matrix berikut :
159
⎛ nx Lx + k ⎜ ny Lx S=⎜ ⎜ ny Lx ⎜⎜ ⎝ dLx
nx Ly nx Lz nx Lw ⎞ ny Ly + k ny Lz ny Lw ⎟⎟ nz Ly nz Lz + k nz Lw ⎟ ⎟ dLy dLz dLw + k ⎟⎠
Karena sudah dilakukan di suatu tempat, kita tidak perlu mengetahui dari mana matrix ini diturunkan. Bagi yang tertarik silahkan baca buku Jim Binn's Corner : A Trip Down to Graphics Pipeline bagian Me and My (Fake) Shadow. Library D3DX memberikan fungsi berikut ini untuk membuat shadow matrix dengan memberikan bidang dan vector yang menyatakan parallel light ( w = 0 ) atau point light ( w = 1 ) D3DXMATRIX *D3DXMatrixShadow( D3DXMATRIX *pOut, CONST D3DXVECTOR4 *pLight, // L CONST D3DXPLANE *pPlane // plane tempat shadow berada );
Menggunakan stencil buffer untuk mengatasi double blending Ketika kita 'meratakan' geometry ke shadow, maka triangle yang bertumpuk akan terlihat saling bertumpukan sehingga terlihat lebih gelap.
Gambar 9.9. Shadow dengan double blending, terlihat segitiga yang overlap saling bertumpukan
160
Gambar 9.10. shadow dengan double blending dimatikan Untuk mengatasinya kita bisa meggunakan stencil buffer, dengan memerintahkan supaya hanya merender pixel yang pertama, pixel selanjutnya tidak digambar. Ketika pertama kali merender di backbuffer, kita tandai di stencil buffer bahwa kita sudah menggambar pixel tersebut. Jika ada pixel dirender di tempat yang sama maka akan fail dalam stencil test.
Implementasi Shadow dalam Code Diambil dari fungsi RenderShadow di sample code. Kita mulai dengan mengeset render state untuk stencil dengan mengeset stencil comparison ke D3DCMP_EQUAL dan D3DSTENCIL_REF ke 0x0. Stencil sudah diset ke zero (0x0) maka pada saat pertama kali digambar akan selalu bernilai true, dan pixel digambar. Akan tetapi kita mengeset stencil operation untuk D3DRS_STENCILPASS ke D3DSTENCILOP_INCR, sehingga setelah pass, nilainya menjadi 0x1 sehingga stencil test akan fail jika kita mencoba merender pixel di tempat yang sama sehingga mencegah double blending. void RenderShadow( void ) { g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState( g_pDevice->SetRenderState(
D3DRS_STENCILENABLE, TRUE ) ; D3DRS_STENCILFUNC, D3DCMP_EQUAL ) ; D3DRS_STENCILREF, 0x0 ) ; D3DRS_STENCILMASK, 0xffffffff ) ; D3DRS_STENCILWRITEMASK, 0xffffffff ) ; D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ) ; D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ) ; D3DRS_STENCILPASS, D3DSTENCILOP_INCR ) ;
kemudian kita cari shadow transformation dan mentranslate shadow ke tempat yang benar di scene.
161
D3DXVECTOR4 LightDirection( 0.707f, -0.707f, 0.707f, 0.0f ) ; D3DXPLANE GroundPlane( 0.0f, -1.0f, 0.0f, 0.0f ) ; D3DXMATRIX matShadow ; D3DXMatrixShadow( &matShadow, &LightDirection, &GroundPlane ) ; D3DXMATRIX matTrans ; D3DXMatrixTranslation( &matTrans, g_vTeapotPosition.x, g_vTeapotPosition.y, g_vTeapotPosition.z ) ; D3DXMATRIX matWorld = matTrans * matShadow ; g_pDevice->SetTransform( D3DTS_WORLD, &matWorld ) ;
Terakhir, kita set material shadow ke transparansi 50%, mendisable depth test, render shadownya, kemudian meng-enable lagi depth buffer dan mematikan alpha blending dan stencil test. Kita mematikan z-buffer untuk mencegah Z-Fighting yaitu flicker yang terjadi ketika suatu scene mempunyai nilai depth buffer yang sama sehingga depth buffer tidak tahu harus menggambar yang mana dahulu. Karena shadow berada pada tempat yang sama dengan bidang, maka z-fighting akan terjadi. Dengan menggambar floornya dahulu kemudian menggambar shadownya kita menjamin bahwa shadow berada di atas floor. g_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ) ; g_pDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ) ; g_pDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ) ; D3DMATERIAL9 Material = InitMaterial( g_cBlack, g_cBlack, g_cBlack, g_cBlack, 0.0f ); Material.Diffuse.a = 0.5f ; g_pDevice->SetRenderState( D3DRS_ZENABLE, FALSE ) ; g_pDevice->SetMaterial( &Material ) ; g_pDevice->SetTexture( 0, NULL ) ; g_pTeapot->DrawSubset( 0 ) ; g_pDevice->SetRenderState( D3DRS_ZENABLE, TRUE ) ; g_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ) ; g_pDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ) ; } // Selesai RenderShadow
162
ScreenShot Sample Code Mirror Mirror dengan stencil buffer :
Gambar 9.11. Stencil Mirror Sample Code
Shadow Shadow dengan stencil buffer :
Gambar 9.12. Stencil Shadow Sample Code
163
Shadow Mirror Gabungan dua sample di atas :
164
Penutup Demikian sekelumit tutorial pemrograman 3D yang amat dasar. Seluruh sample code ada dalam paket buku ini. Diharapkan dengan tutorial yang hanya sekelumit ini, pembaca dapat meningkatkan atau belajar kemampuan pemrograman 3D. Pada akhirnya tidak ada gading yang tak retak, penulis mengucapkan maaf jika ada kalimat yang salah. Dan tentunya penulis sangat terbuka akan kritik membangun terhadap buku ini. Terima kasih.
165