Anda di halaman 1dari 165

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 fungsi-
fungsi, 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
Kepala
b

u
c

v
Ekor

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
koordinat dari kepala vector tersebut.

c
b
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+

j Z+

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 += ( CONST D3DXVECTOR3& );
D3DXVECTOR3& operator -= ( CONST D3DXVECTOR3& );
D3DXVECTOR3& operator *= ( FLOAT );
D3DXVECTOR3& operator /= ( FLOAT );

// 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 2
u = ux + uy + uz

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 :

u ⎛ 4 7 3 ⎞
û= = ⎜⎜ , , ⎟⎟
74 ⎝ 74 74 74 ⎠

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 masing-
masing vector untuk membentuk vector baru.

u + v = (ux + vx, uy + vy, uz + vz)

secara geometris dapat digambarkan sebagai berikut :

v
u+
u

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
u-
v
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,
terkecuali faktor perkaliannya negatif, maka arah vector akan berbalik
3u
u

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 :

u•v
cos θ =
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

Secara geometris, cross product dapat digambarkan sebagai berikut :


p

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 :

⎛ b11 b12 ⎞
⎛ m11 m12 m13 ⎞ ⎜ ⎟
⎜ ⎟ ⎛a a12 a13 ⎞ ⎜b b22 ⎟
M = ⎜ m21 m22 m23 ⎟ A = ⎜⎜ 11 ⎟ B = ⎜ 21
⎝ a 21 a 22 a 23 ⎟⎠ b b32 ⎟
⎜m
⎝ 31 m32 m33 ⎟⎠ ⎜ 31 ⎟
⎜b
⎝ 41 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 :

⎛ u11 ⎞
⎜ ⎟
v = (v11 v12 v13 v14 ) dan u = ⎜ u 21 ⎟
⎜u ⎟
⎝ 31 ⎠

Kesamaan, perkalian skalar, dan penambahan


Mari kita lihat matrix-matrix berikut ini :

⎛ 1 5 ⎞ ⎛ 6 − 1⎞ ⎛ 1 5 ⎞ ⎛ − 2 0 −1 3⎞
A = ⎜⎜ ⎟⎟ B = ⎜⎜ ⎟⎟ C = ⎜⎜ ⎟⎟ D = ⎜⎜ ⎟⎟
⎝ − 3 − 2⎠ ⎝− 2 3 ⎠ ⎝ − 3 − 2⎠ ⎝ 0 − 3 2 2⎠

• Dua matrix adalah sama jika mempunyai dimensi yang sama dan semua komponen matrix-
matrix 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) ⎞


kD = ⎜⎜ ⎟⎟ jika k = 3 maka
⎝ 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.

⎛ 1 5 ⎞ ⎛ 6 − 1⎞ ⎛ 1 + 6 5 −1 ⎞ ⎛ 7 4⎞
A + B = ⎜⎜ ⎟⎟ + ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟
⎝ − 3 − 2 ⎠ ⎝ − 2 3 ⎠ ⎝ − 3 + (−2) − 2 + 3 ⎠ ⎝ − 5 1 ⎠

• Sedangkan untuk mengurangkan dua matrix, operasi yang dilakukan adalah spt berikut :

⎛ 1 5 ⎞ ⎛ 6 − 1⎞ ⎛ 1 5 ⎞ ⎛− 6 1 ⎞ ⎛ 1 − 6 5 +1 ⎞ ⎛− 5 6 ⎞
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 a12 ⎞ ⎛b b12 b13 ⎞


A = ⎜⎜ 11 ⎟⎟ dan B = ⎜⎜ 11 ⎟
⎝ a 21 a 22 ⎠ ⎝ b21 b22 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 a1 • b2 ⎞ ⎛ 7 − 6 ⎞
AB = ⎜⎜ ⎟⎟⎜⎜ ⎟⎟ = ⎜⎜ ⎟=⎜ ⎟
⎝ 0 1 ⎠⎝ 4 − 2 ⎠ ⎝ a 2 • b1 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⎞ ⎜ ⎟
⎛1 0⎞ ⎜ ⎟ ⎜0 1 0 0⎟
⎜⎜ ⎟⎟ ⎜0 1 0⎟ ⎜0
⎝0 1⎠ ⎜0 0 1⎟ 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 :

⎛a b c⎞
⎛−1 8 9 ⎞ ⎜ ⎟
A = ⎜⎜ ⎟⎟ B = ⎜d e f⎟
⎝ 3 2 − 5⎠ ⎜g h i ⎟⎠

Maka transposenya adalah

⎛ − 1 3⎞ ⎛a d g⎞
⎜ ⎟ ⎜ ⎟
A = ⎜ 2 8⎟
T
B = ⎜b
T
e h⎟
⎜ − 5 9⎟ ⎜c f 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( CONST FLOAT* ) ;
D3DXMATRIX( CONST D3DMATRIX& ) ;
D3DXMATRIX( FLOAT _11, FLOAT _12, FLOAT _13, FLOAT _14,
FLOAT _21, FLOAT _22, FLOAT _23, FLOAT _24,
FLOAT _31, FLOAT _32, FLOAT _33, FLOAT _34,
FLOAT _41, FLOAT _42, FLOAT _43, FLOAT _44 ) ;
// access grants
FLOAT& operator ( ) ( UINT Row, UINT Col ) ;
FLOAT operator ( ) ( UINT Row, UINT Col ) const ;

// casting operators
operator FLOAT* ( );
operator CONST FLOAT* ( ) const;

// assignment operators
D3DXMATRIX& operator *= ( CONST D3DXMATRIX& ) ;
D3DXMATRIX& operator += ( CONST D3DXMATRIX& ) ;
D3DXMATRIX& operator -= ( CONST D3DXMATRIX& ) ;
D3DXMATRIX& operator *= ( FLOAT ) ;
D3DXMATRIX& operator /= ( FLOAT ) ;

// unary operators
D3DXMATRIX operator + ( ) const ;
D3DXMATRIX operator - ( ) const ;

// binary operators
D3DXMATRIX operator * ( CONST D3DXMATRIX& ) const ;
D3DXMATRIX operator + ( CONST D3DXMATRIX& ) const ;
D3DXMATRIX operator - ( CONST D3DXMATRIX& ) const ;
D3DXMATRIX operator * ( FLOAT ) const ;
D3DXMATRIX operator / ( FLOAT ) 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, _13, _14 ;
float _21, _22, _23, _24 ;
float _31, _32, _33, _34 ;
float _41, _42, _43, _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 :

⎛1 0 0 0⎞
⎜ ⎟
⎜0 1 0 0⎟
(
p = px py pz 1)⎜
0 0 1 1⎟
= (px py pz p z ) dengan pz ≠ 0 dan pz ≠ 1
⎜ ⎟
⎜0 0 0 0 ⎟⎠

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 ⎛x y z w⎞ ⎛ x y z ⎞
u = (x y z w) = ⎜ ⎟=⎜ 1⎟ , sehingga untuk kasus p diatas bisa
⎝w w w w⎠ ⎝ w w w ⎠
kita dapatkan

27
⎛p py ⎞ ⎛ px py ⎞
p' = ( p x p z ) = ⎜⎜ x
pz pz
py pz ⎟⎟ = ⎜⎜ 1 1⎟⎟
⎝ pz pz pz pz ⎠ ⎝ pz pz ⎠

Matrix Translasi
Perhatikan gambar berikut ini :

(-2, 5)

(-5, 2) Translasi

(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 0 0⎞
⎜ ⎟
⎜0 1 0 0⎟
T (d ) = ⎜
0 0 1 0⎟
⎜ ⎟
⎜d
⎝ x dy dz 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 0 0⎞
⎜ ⎟
⎜ 0 1 0 0⎟
T −1 (d ) = ⎜
0 0 1 0⎟
⎜ ⎟
⎜− d
⎝ x − dy − dz 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.

⎛1 0 0 0⎞
⎜ ⎟
⎜ 0 cos θ sin θ 0⎟
rotX (θ ) = ⎜
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 − sin θ 0⎞
⎜ ⎟
⎜ 0 1 0 0⎟
rotY (θ ) = ⎜
sin θ 0 cos θ 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 θ 0 0⎞
⎜ ⎟
⎜ − sin θ cos θ 0 0⎟
rotZ (θ ) = ⎜
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 0 0⎞
⎜ ⎟
⎜0 ky 0 0⎟
S (k ) = ⎜
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 ⎞
⎜ 0 0 0⎟
⎜ kx ⎟
⎜ 1 ⎟
−1 ⎛1⎞ ⎜ 0 0 0⎟
S (k ) = S ⎜ ⎟ = ky
⎝k⎠ ⎜ ⎟
1
⎜ 0 0 0⎟
⎜ kz ⎟
⎜ 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 : Matrix rotasi :

⎛1 ⎞
⎜ 0 0 0⎟
⎜3 ⎟ ⎛ 0.5 0 − 0.866 0⎞
1 ⎜ ⎟
⎛1 1 1⎞ ⎜0 0 0⎟ ⎛π ⎞ ⎜ 0 1 0 0⎟
S⎜ , , ⎟ = ⎜ 3 ⎟ Ry ⎜ ⎟ = ⎜
⎝ 3 3 3⎠ ⎜ 1 ⎟ ⎝ 3 ⎠ ⎜ 0.866 0 0.5 0⎟

⎜0 0
3
0⎟ ⎜ 0
⎝ 0 0 1 ⎟⎠
⎜ ⎟
⎝0 0 0 1⎠

Matrix translasi

⎛1 0 0 0⎞
⎜ ⎟
⎜0 1 0 0⎟
T (2,−1,4 ) = ⎜
0 0 1 0⎟
⎜ ⎟
⎜2 −1
⎝ 4 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 ⎞
⎜ 0 0 0⎟
⎜3 ⎟⎛⎜ 0.5 0 − 0.866 0 ⎞⎛ 1 0 00⎞
1 ⎟⎜ ⎟
⎜0 0 0 ⎟⎜ 0 1 0 0 ⎟⎜ 0 1 00⎟
SR y T = ⎜ 3 ⎟⎜
⎜ 1 ⎟⎜ 0.866 0 0 .5 0 ⎟⎜ 0 0 1 0⎟
⎟⎜ ⎟
⎜0 0 0 ⎟⎜ 0 0 0 1 ⎟⎠⎜⎝ 2 − 1 4 1 ⎟⎠
⎜ 3 ⎟⎝
⎝0 0 0 1⎠
⎛ 0.1665 0 − 0.288 0 ⎞⎛ 1 0 0 0 ⎞ ⎛ 0.1665 0 − 0.288 0⎞
⎜ ⎟⎜ ⎟ ⎜ ⎟
⎜ 0 0.333 0 0 ⎟⎜ 0 1 0 0⎟ ⎜ 0 0.333 0 0⎟
=⎜ =
0.288 0 0.1665 0 ⎟⎜ 0 0 1 0 ⎟ ⎜ 0.288 0 0.1665 0⎟
⎜ ⎟⎜ ⎟ ⎜ ⎟
⎜ 0
⎝ 0 0 1 ⎟⎠⎜⎝ 2 − 1 4 ⎟
1⎠ ⎝ 2⎜ −1 4 1 ⎟⎠

Sehingga kita dapatkan u' sebagai berikut :

⎛ 0.1665 0 − 0.288 0⎞
⎜ ⎟
G G ⎜ 0 0.333 0 0⎟
u ' = u SR y T = (3 6 0 1)⎜ = (2.5 1 3,136 1)
0.288 0 0.1665 0⎟
⎜ ⎟
⎜ 2
⎝ −1 4 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 dalam library D3DX yaitu D3DXVec3TransformCoord yang akan
mentransformasikan vector dengan anggapan elemen keempat bernilai 1 dan
D3DXVec3TransformNormal mentransformasikan vector dengan anggapan elemen keempat
bernilai 0. Prototype dan penggunaannya sebagai berikut

D3DXVECTOR3 *D3DXVec3TransformCoord(
D3DXVECTOR3* pOut, // Hasil.
CONST D3DXVECTOR3* pV, // Point yg ditransformasi.
CONST D3DXMATRIX* pM // 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, // vector yg ditransformasi.
CONST D3DXMATRIX *pM // 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.

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.

(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, // plane.
CONST D3DXVECTOR3 *pV // 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, // Hasil.
CONST D3DXVECTOR3* pV1, // Point 1 di plane.
CONST D3DXVECTOR3* pV2, // Point 2 di plane.
CONST D3DXVECTOR3* pV3 // 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)

⎛ G
1 G n d ⎞⎟
G (n , d ) = ⎜⎜ G , G
n ⎝ 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

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

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⎜ G G ⎟ = p0 + ⎜ G G ⎟u
⎝ 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 <cstdio>
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

Merupakan tipe data sebagai handle dari instance aplikasi. Instance adalah
jumlah aplikasi yang dijalankan dari file yang sama. Misal kita punya file
HINSTANCE 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.

Variabel ini merupakan peninggalan dari Windows 3.1. Pada Windows 3.1,
hPrevInstance variabel ini berisi instance sebelumnya dari aplikasi. Untuk Windows 95 ke
atas, variabel ini selalu akan berisi NULL.

lpCmdLine Variabel ini berisi string command line dari program yang dijalankan.

Menunjukkan bagaimana Window ditampilkan, isi dari variabel ini dilihat


nShowCmd
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
Fungsi
Variabel

Merupakan handle ke Window yang merupakan parent dari Message Box


tersebut. Jika berisi NULL, maka parent dari Message Box tersebut adalah
hWnd desktop.
Tipe dari variabel ini adalah HWND, yang berarti handle ke window.
Ini merupakan teks yang akan ditampilkan dalam message box
lpText Tipe dari variabel ini adalah LPCSTR, artinya constant ke string atau sama
dengan const char *.

lpCaption Ini merupakan teks yang ditampilkan dalam title bar message box

Ini merupakan variabel bertipe UINT (unsigned int) yang menunjukkan tombol
uType 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 Messages Window Data


Procedure

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

Merupakan ukuran structure WNDCLASSEX, diisi dengan


cbSize
sizeof(WNDCLASSEX).
Merupakan suatu value unsigned integer yang bisa merupakan satu atau
kombinasi dari beberapa value. Silahkan lihat di dokumentasi MSDN untuk
style
melihat value yang mungkin dimasukkan dalam member ini. Untuk aplikasi
kita, kita selalu menggunakan CS_HREDRAW | CS_VREDRAW .

lpfnWndProc Merupakan pointer ke Window Procedure yang akan menangani message

Jumlah byte yang dialokasikan di belakang structure window class, system


cbClsExtra
menginisialisasinya dengan 0.
Jumlah byte yang dialokasikan setelah suatu instance window. System
cbWndExtra
menginisialisasninya dengan 0

hInstance Handle untuk instance aplikasi

Handle untuk icon yang digunakan dalam window, jika berisi NULL, akan
hIcon ditampilkan default icon.
Tipe dari variabel ini adalah HICON yang berarti handle dari icon aplikasi.
Handle dari cursor mouse yang digunakan dalam window, jika berisi NULL,
maka aplikasi harus secara eksplisit mengeset cursor yang digunakan ketika
hCursor mouse bergerak di dalam window.
Tipe variabel ini adalah HCURSOR yang berarti handle dari cursor mouse.
Warna brush yang digunakan dalam background window, bisa berupa
handle ke brush yang dibuat oleh user atau brush system.
hbrBackground
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
lpszMenuName
mempunyai menu maka nilainya adalah NULL.

lpszClassName Nama untuk identifikasi Window Class

Handle untuk icon berukuran kecil yang akan ditampilkan di window. Jika
hIconSm 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

Informasi tambahan tentang message, arti dari nilai yang tersimpan


wParam
tergantung dari jenis message.
Informasi tambahan tentang message, arti dari nilai yang tersimpan
lParam
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, // ExStyle dari window
LPCTSTR lpClassName, // Nama dari window class
LPCTSTR lpWindowName, // Judul Window
DWORD dwStyle, // Style Window
int x, // 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

Akan menjadi char bila UNICODE tidak terdefinisi dan menjadi wchar_t
TCHAR
jika UNICODE terdefinisi.
Unicode terdefinisi : LPWSTR atau wchar_t *
LPTSTR
Unicode tidak terdefinisi : LPSTR atau char *
Unicode terdefinisi : LPCWSTR atau const wchar_t *
LPCTSTR
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 <tchar.h>

/************************************************************************/
/* 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 variabel untuk menampung instance handle


HINSTANCE g_hInstance = NULL ;
// global variabel untuk menampung handle window
HWND 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
X+
Width (lebar)
[0,0] [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

Merupakan pixel format 24 bit yang dimulai dari bit paling kiri 8
D3DFMT_R8G8B8 bit untuk warna merah, 8 bit untuk warna hijau, dan 8 bit untuk
warna biru.
Merupakan pixel format 32 bit yang dimulai dari bit paling kiri, 8
D3DFMT_X8R8G8B8 bit tidak dipakai, 8 bit untuk warna merah, 8 bit untuk warna
hijau, dan 8 bit untuk warna biru.
Merupakan pixel format 32 bit yang dimulai dari bit paling kiri, 8
D3DFMT_A8R8G8B8 bit untuk alpha, 8 bit untuk warna merah, 8 bit untuk warna
hijau, dan 8 bit untuk warna biru.
Merupakan pixel format floating point 64 bit. Dimulai dari bit
D3DFMT_A16R16G16B16F paling kiri, 16 bit untuk alpha, 16 bit untuk warna merah, 16 bit
untuk warna hijau, dan 16 bit untuk warna biru.
Merupakan pixel format floating point 128 bit. Dimulai dari bit
D3DFMT_A32R32G32B32F 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

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
D3DPOOL_DEFAULT
dalam default pool harus di-release sebelum memanggil
IDirect3DDevice9::Reset. Dan harus diinisialisasi lagi setelah
pemanggilan reset tersebut.
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
D3DPOOL_MANAGED 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 Resource semuanya disimpan dalam system memory.

Resource disimpan dalam system memory, perbedaan dengan


D3DPOOL_SYSTEMMEM adalah resource tersebut bisa keluar dari batas
D3DPOOL_SCRATCH
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, Back Buffer, frame


merupakan surface yang sedang diproses
yang isinya sedang dirender dalam
ditampilkan di layar 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 Surface 2

Front Buffer 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 yang akan kita cari kapabilitasnya. Untuk adapter default kita
Adapter
gunakna D3DADAPTER_DEFAULT.
Device type yang digunakan hardware device (D3DDEVTYPE_HAL)
DeviceType
atau software (D3DDEVTYPE_REF)
Pointer ke structure D3DCAPS9 yang digunakan untuk mendapatkan
pCaps
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

Jumlah backbuffer yang digunakan, kita menggunakan 1


BackBufferCount
back buffer saja, sehingga nilai dari member ini adalah 1.
Tipe multisample. Lihat dokumentasi DirectX SDK untuk
MultisampleType
keterangan lebih lengkap
Kualitas multisample. Lihat dokumentasi DirectX SDK
MultisampleQuality
untuk keterangan lebih lengkap.
Salah satu anggota dari enumerasi D3DSWAPEFFECT yang
SwapEffect menyatakan bagaimana flipping chain berjalan
D3DSWAPEFFECT_DISCARD adalah yang paling efisien.
Merupakan window handle yang akan dihubungkan dengan
hDeviceWindow
device. Window ini tempat kita menggambar
TRUE jika berjalan dalam modus window dan FALSE jika
Windowed
full screen.
Set ke TRUE supaya Direct3D mengatur depth dan stencil
EnableAutoDepthStencil
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 di-
lock. 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.
Refresh rate, gunakan default refresh rate dengan
FullScreen_RefreshRateInHz
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 = 640;
d3dpp.BackBufferHeight = 480;
d3dpp.BackBufferFormat = D3DFMT A8R8G8B8; //pixel format
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = g_hWindow;
d3dpp.Windowed = TRUE; // windowed
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT D24S8; // depth format
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.PresentationInterval = 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 yang akan digunakan sebagai device, untuk


Adapter
primary display menggunakan D3DADAPTER_DEFAULT
Tipe Device yang akan dibuat, D3DDEVTYPE_HAL untuk
DeviceType
HAL dan D3DDEVTYPE_REF untuk REF.
Adalah handle ke window yang dikaitkan dengan device
yang akan dibuat, window yang akan digunakan untuk
hFocusWindow
menggambar scene 3D. Sama dengan hDeviceWindow
pada D3DPRESENT_PARAMETERS.
Flags yang menggambarkan bagaimana vertex diproses,
BehaviorFlags apakah D3DCREATE_HARDWARE_VERTEXPROCESSING atau
D3DCREATE_SOFTWARE_VERTEXPROCESSING.
Pointer ke structure D3DPRESENT_PARAMETERS yang
pPresentationParameters
sudah diinisialisasi karakteristiknya
Pointer ke pointer untuk interface IDirect3DDevice9
ppReturnedDeviceInterface
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

Jumlah rectangle yang akan di-clear, jika ini berisi 0, maka parameter
kedua harus NULL. Dengan begitu seluruh area akan di-clear yang
Count
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 :
D3DCLEAR_STENCIL
Flags
Membersihkan stencil buffer
D3DCLEAR_TARGET
Membersihkan render target (buffer yang berisi tampilan

64
scene)
D3DCLEAR_ZBUFFER
Membersihkan depth buffer.
Warna yang digunakan untuk membersihkan layar. Bisa menggunakan
Color
Macro D3DCOLOR_XRGB atau yang lain.

Z 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
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

Pointer ke RECT structure yang menyatakan rectangle asal yang


pSourceRect akan di-swap. Nilai dari parameter ini harus NULL jika ingin men-
swap keseluruhan backbuffer.
Pointer ke RECT structure yang menyatakan rectangle tujuan yang
pDestRect akan di swap. Nilai dari parameter ini harus NULL jika ingin men-
swap keseluruhan frontbuffer.
Window yang akan diguanakan sebagai area target. Jika ingin
hDestWindowOverride menggunakan window asal yang telah ditentukan, nilai parameter
ini harus NULL.
Detail dari region dari backbuffer yang harus diupdate. Sekali lagi
pDirtyRegion 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 <d3d9.h>

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 mem-
presentnya 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

Pointer ke MSG structure yang akan menampung message yang


lpMsg
berhasil didapatkan oleh fungsi PeekMessage.

hWnd Window yang akan diintip messagenya

Message minimal yang diproses. Gunakan WM_KEYFIRST untuk


message keyboard pertama, dan WM_MOUSEFIRST untuk message
wMsgFilterMin mouse pertama.
Jika wMsgFilterMin dan wMsgFilterMax isinya 0, maka semua
message akan diproses.
Message maksimal yang diproses. Gunakan WM_KEYLAST untuk
message keyboard pertama, dan WM_MOUSELAST untuk message
wMsgFilterMax mouse pertama.
Jika wMsgFilterMin dan wMsgFilterMax isinya 0, maka semua
message akan diproses.
Menyatakan bagaimana message akan dihandle oleh
PeekMessage.
PM_REMOVE
Message akan diambil dari queue setelah memanggil
wRemoveMsg
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<float>( 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<float>( 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 di-
close. 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, // segitiga 1


1, 2, 3 } ; // 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 Lighting

Clipping Projection Viewport Space Rasterization

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+ Z+

B
A B
A B

C
C

D
D C
D
X+ X+ X+

Translasi 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, // pointer untuk menerima matrix
CONST D3DXVECTOR3* pEye, // position kamera di world
CONST D3DXVECTOR3* pAt, // arah kamera 'melihat'
CONST D3DXVECTOR3* pUp // 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

v1 v2

v2 v1

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'

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+

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 0 0⎟
2
⎜ ⎟
⎜ height
0 − 0 0⎟
⎜ 2 ⎟
⎜ ⎟
⎜ 0 0 MaxZ − MinZ 0⎟
⎜ Width Height ⎟
⎜X + Y + MinZ 1⎟
⎝ 2 2 ⎠

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

Jumlah byte yang dialokasikan untuk buffer. Jika kita ingin sebuah
Length buffer yang dapat menyimpan 8 vertex, maka kita set parameter
ini menjadi 8 * sizeof(Vertex).
Properti tambahan yang menyatakan bagaimana buffer digunakan,
Usage 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.
Flexible Vertex Format, yaitu format vertex yang dimasukkan ke
FVF
buffer

Pool Memory pool di mana buffer akan ditempatkan

ppVertexBuffer Pointer untuk menyimpan hasil vertex buffer yang dibuat

pSharedHandle Tidak dipakai, isi dengan NULL.

Menyatakan format dari index. Gunakan D3DFMT_INDEX16 untuk


Format 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

Offset (dalam byte) tempat mulai buffer akan dilock, perhatikan


OffsetToLock
gambar berikut :

92
Buffer Memory

Size To Lock

Offset To Lock +
Offset To Lock
SizeToLock
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, 0.0f, 2.0f ) ; // menulis vertex ke buffer


pVertices[ 1 ] = Vertex( 0.0f, 1.0f, 2.0f ) ;
pVertices[ 2 ] = Vertex( 1.0f, 0.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

Nomor identifikasi stream source di mana kita menggunakan vertex


StreamNumber 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

Offset dari awal stream dalam byte, menunjukkan awal dari vertex
yang akan dimasukkan ke rendering pipeline. Untuk mengeset
OffsetInBytes
parameter di sini selain 0, maka cek
D3DDEVCAPS2_STREAMOFFSET flag di structure D3DDEVCAPS9.
Ukuran tiap elemen dalam vertex buffer yang akan dimasukkan ke
Stride
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

Tipe primitive, karena kita ingin menggambar segitiga, maka kita


akan memakai D3DPT_TRIANGLELIST, untuk daftar primitive
PrimitiveType
lengkap silahkan baca DirectX SDK, cari dengan kata kunci
D3DPRIMITIVETYPE.
Index dari awal vertex buffer yang akan digambar. Parameter ini
StartVertex 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

Tipe primitive, karena kita ingin menggambar segitiga, maka kita


akan memakai D3DPT_TRIANGLELIST, untuk daftar primitive
Type
lengkap silahkan baca DirectX SDK, cari dengan kata kunci
D3DPRIMITIVETYPE.
Nomor awal dari vertex yang nantinya akan digunakan sebagai
BaseVertexIndex
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
StartIndex
buffer yang akan dipergunakan.

PrimitiveCount Jumlah Primitive yang digambar.

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 Vertex Buffer Vertex Buffer


untuk Kubus untuk Sphere untuk Cylinder

Vertex untuk tiga object disimpan dalam satu vertex buffer global

Vertex Buffer Vertex Buffer Vertex Buffer


untuk Kubus untuk Sphere untuk Cylinder

Vertex offset
kubus
Vertex offset
sphere 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

File ini berisi header-header yang dipakai hampir di semua file.


Common.h
Seperti Windows.h, tchar.h, cstdio dan lain-lain.
File ini berisi extern variabel global yang mungkin didefinisikan di
Globals.h file lain, file ini mengizinkan suatu file untuk dapat mengakses
variabel global yang dideklarasikan dalam file lain
File berisi utilities untuk melakukan inisialisasi dan deiniisialisasi
D3Dutil.h/cpp
Direct3D

Util.h/cpp File berisi utilities umum seperti CenterWindow dll.

Berisi rutin yang berhubungan dengan merender scene ke layar,


Render.h/cpp
fungsi DoFrame berada di sini.
Program utama, WinMain, Message Loop, dan Window Procedure
WinMain.cpp
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 12
const Vertex pVertCube [ VERTICESNUMBER ] =
{
{ -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 WORD pIdxCube [ INDICESNUMBER ] =


{
0, 1, 2, 0, 2, 3, // depan
4, 6, 5, 4, 7, 6, // belakang
4, 5, 1, 4, 1, 0, // kiri
3, 2, 6, 3, 6, 7, // kanan
1, 5, 6, 1, 6, 2, // atas
4, 0, 3, 4, 3, 7, // 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<float>(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 c3 .k3 c4 .k4 ) .

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 g_cWhite ( D3DCOLOR_XRGB( 255, 255, 255 ) ) ;


D3DXCOLOR g_cBlack ( D3DCOLOR_XRGB( 0, 0, 0 ) ) ;
D3DXCOLOR g_cRed ( D3DCOLOR_XRGB( 255, 0, 0 ) ) ;
D3DXCOLOR g_cGreen ( D3DCOLOR_XRGB( 0, 255, 0 ) ) ;
D3DXCOLOR g_cBlue ( D3DCOLOR_XRGB( 0, 0, 255 ) ) ;
D3DXCOLOR g_cYellow ( D3DCOLOR_XRGB( 255, 255, 0 ) ) ;
D3DXCOLOR g_cCyan ( D3DCOLOR_XRGB( 0, 255, 255 ) ) ;
D3DXCOLOR g_cMagenta( D3DCOLOR_XRGB( 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

Jumlah persentase warna diffuse yang dipantulkan oleh permukaan


Diffuse
object.
Jumlah persentase warna ambient yang dipantulkan oleh permukaan
Ambient
object.
Jumlah persentase warna specular yang dipantulkan oleh permukaan
Specular
object.
Komponen ini digunakan untuk menambahkan value ke tiap komponen
Emissive dalam diffuse, ambient, dan specular, sehingga terlihat seperti object
menghasilkan cahaya
Ketajaman dari specular, semakin tinggi, maka specular semakin
Power
tajam.

Sebagai contoh, misalkan kita mempunyai kotak warna merah, kita mendefinisikan kotak untuk
menyerap semua warna kecuali merah.

D3DMATERIAL9 RedBoxMaterial ;

RedBoxMaterial.Diffuse = D3DXCOLOR( 1.0f, 0.0f, 0.0f, 1.0f ) ;


RedBoxMaterial.Ambient = D3DXCOLOR( 1.0f, 0.0f, 0.0f, 1.0f ) ;
RedBoxMaterial.Specular = D3DXCOLOR( 1.0f, 0.0f, 0.0f, 1.0f ) ;
RedBoxMaterial.Emissive = D3DXCOLOR( 0.0f, 0.0f, 0.0f, 1.0f ) ;
RedBoxMaterial.Power = 4.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 = InitMaterial( g_cRed, g_cRed,
g_cRed, g_cBlack, 8.0f ) ;
const D3DMATERIAL9 g_GreenMaterial = InitMaterial( g_cGreen, g_cGreen,
g_cGreen, g_cBlack, 8.0f ) ;
const D3DMATERIAL9 g_BlueMaterial = InitMaterial( g_cBlue, g_cBlue,
g_cBlue, g_cBlack, 8.0f ) ;
const D3DMATERIAL9 g_YellowMaterial = InitMaterial( g_cYellow, g_cYellow,
g_cYellow, g_cBlack, 8.0f ) ;

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 *pV0,


D3DXVECTOR3 *pV1,
D3DXVECTOR3 *pV2,
D3DXVECTOR3 *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 rata-
rata 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=
4
(
n0 + n1 + n2 + n3 )
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

Tipe dari light source yaitu D3DLIGHT_POINT, D3DLIGHT_SPOT atau


Type
D3DLIGHT_DIRECTIONAL.

Diffuse Nilai diffuse yang dikeluarkan light source

Specular Nilai specular yang dikeluarkan light source

Ambient Nilai ambient yang dikeluarkan light source

Vector menggambarkan world position dari light source, diabaikan


Position
untuk directional lights.

118
Vector yang menyatakan arah dari light source bersinar. Diabaikan
Direction
untuk point lights
Range maximum untuk cahaya sebelum cahaya mati. Tidak bisa lebih
Range besar dari FLT _ MAX dan tidak mempunyai efek dengan
directional lights.
Hanya digunakan untuk spotlight. Menyatakan bagaimana intensitas
Falloff cahaya melemah dari kerucut dalam ke kerucut luar. Biasanya diset
ke 1.0f.
Menyatakan bagaimana cahaya melemah seiring dengan jarak. Hanya
digunakan untuk point dan spot lights. Attenuation0 adalah falloff
constant, Attenuation1 adalah linear falloff, dan Attenuation2
Attenuation0, adalah quadratic falloff. Dengan rumus ini, jika D adalah jarak dari
Attenuation1, light source dan A0, A1, dan A2 adalah nilai attenuation. Maka
Attenuation2 1
Attenuation =
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 = D3DCOLOR_DIRECTIONAL ;
light.Ambient = color * 0.4f ;
light.Diffuse = color ;
light.Specular = color * 0.6f ;
light.Direction = 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 Buffer
#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 },
} ;

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.

Dengan filter ini Direct3D akan memilih mipmap level yang paling
D3DTEXF_POINT dekat dengan ukuran screen triangle, begitu dipilih, Direct3D akan
memfilter tergantung dari min dan mag-nya.
Dengan filter ini, Direct3D memilih dua buah mipmap yang paling
D3DTEXF_LINEAR
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, 0.0f, 1.0f },
{-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f },
{ 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f },
{ 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.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<float>(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 sub-
bab 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

Pixel yang sedang dihitung (pixel yang datang) yang akan di-blend
SourcePixel
dengan backbuffer nanti.
Nilai dalam interval [0, 1] yang menyatakan persentase source pixel
SourceBlendFactor
yang diblend.

DestPixel Pixel di backbuffer

Nilai dalam interval [0, 1] yang menyatakan persentase pixel


DestBlendFactor
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)

SourceBlend = (1- as,1- as,1- as,1-as ) dan estination manjadi


D3DBLEND_BOTHINVSRCALPHA
(as, as, as,as ) blend ini hanya untuk D3DRS_SRCBLEND

Nilai default untuk sourcefactor 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

Membuat depth buffer 24 bit, 4 bit tidak dipakai, dan 4 bit


D3DFMT_D24X4S4
sebagai stencil buffer.
Membuat depth buffer 15 bit, dengan 1 bit sebagai stencil
D3DFMT_D15S1
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

Tidak digunakan, value ini hanya memaksa supaya enumarsi


disimpan dalam bentuk DWORD (unsigned int ), nilai
D3DCMP_FORCE_DWORD
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

Menambah nilai stencil buffer. Jika nilai stencil buffer


D3DSTENCILOP_INCRSAT melebihi dari nilai yang diizinkan, maka nilai akan di-clamp
ke nilai maximum tersebut.
Mengurangi nilai stencil buffer. Jika nilai stencil buffer
D3DSTENCILOP_DECRSAT
kurang dari 0, maka nilai akan di-clamp ke 0.

D3DSTENCILOP_INVERT Membalik seluruh bit dalam stencil buffer

Menambah nilai stencil buffer. Jika nilai stencil buffer


D3DSTENCILOP_INCR
melebihi dari nilai yang diizinkan, maka nilai akan diset ke 0
Mengurangi nilai stencil buffer. Jika nilai stencil buffer
D3DSTENCILOP_DECR
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 me-
mask 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

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 − 2(nˆ • v + d )nˆ
= v − 2[(nˆ • v )nˆ + dn
ˆ]

Dengan begitu kita bisa mendapatkan matrix transformasi untuk refleksi R sebagai berikut :

⎛ −2nx nx + 1 −2ny nx −2nz nx 0⎞


⎜ ⎟
−2nx ny −2ny ny + 1 −2nz ny 0⎟
R=⎜
⎜ −2nx nz −2ny nz −2nz nz + 1 0⎟
⎜⎜ ⎟
⎝ −2nx d −2ny d −2nz d 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 :

⎛ −1 0 0 0⎞ ⎛1 0 0 0⎞ ⎛1 0 0 0⎞
⎜ ⎟ ⎜ ⎟ ⎜ ⎟
0 1 0 0⎟ 0 −1 0 0⎟ 0 1 0 0⎟
Ryz =⎜ R xz = ⎜ R xy = ⎜
⎜0 0 1 0⎟ ⎜0 0 1 0⎟ ⎜ 0 0 −1 0⎟
⎜ ⎟ ⎜ ⎟ ⎜ ⎟
⎝0 0 0 1⎠ ⎝0 0 0 1⎠ ⎝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( D3DRS_STENCILENABLE, TRUE ) ;
g_pDevice->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ) ;
g_pDevice->SetRenderState( D3DRS_STENCILREF, 0x1 ) ;
g_pDevice->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ) ;
g_pDevice->SetRenderState( D3DRS_STENCILWRITEMASK, 0xffffffff ) ;
g_pDevice->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ) ;
g_pDevice->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ) ;
g_pDevice->SetRenderState( 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 + 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
⇔n ˆ • p + t (n
ˆ • L ) = −d
G
⇔ t (nˆ • L ) = −d − nˆ • p
−d − nˆ • p
⇔t = G
(nˆ • L )

Sehingga

⎛ −d − nˆ • p ⎞
s =p+⎜ G ⎟
⎝ (nˆ • L ) ⎠

158
Shadow dari point light
Lihat gambar berikut :

nˆ •p + d = 0

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 nx Ly nx Lz nx Lw ⎞

ny Lx ny Ly + k ny Lz ny Lw ⎟⎟
S=⎜
⎜ ny Lx nz Ly nz Lz + k nz Lw ⎟
⎜⎜ ⎟
⎝ dLx 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( D3DRS_STENCILENABLE, TRUE ) ;
g_pDevice->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_EQUAL ) ;
g_pDevice->SetRenderState( D3DRS_STENCILREF, 0x0 ) ;
g_pDevice->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ) ;
g_pDevice->SetRenderState( D3DRS_STENCILWRITEMASK, 0xffffffff ) ;
g_pDevice->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ) ;
g_pDevice->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ) ;
g_pDevice->SetRenderState( 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

Anda mungkin juga menyukai