TRANSLASI BUKU "CORE TECHNIQUES & ALGORITHM IN GAME PROGRAMMING"
Animasi
Animasi, baik berbasis vertex atau kerangka, merupakan proses
yang memakan CPU. Jika kita masuk pada jalur vertex kita akan membutuhkan limpahan interpolasi untuk memastikan
pergerakannya halus dan kita dapat
memungkinkan untuk menyimpan beberapa frame. Pada sisi skeletal,
interpolasi quaternion hanyalah sebagian dari masalah yang ada. Masalah
sebenarnya muncul ketika kita harus melakukan penggandaan poin matriks,
kemungkinan pencampuran matriks yang berbeda per vertex untuk melakukan
beberapa pengaruh bone (tulang).
Jadi tidak mengherankan bahwa
membuat animasi suatu karakter adalah salah satu tugas bahasa shading yang
dikhususkan
Pada contoh ini, anda dapat
menerapkan interpolasi keyframe
menggunakan shader vertex. Interpolator inti bukanlah masalah besar.
Interpolasi linear dapat diimplementasikan dengan persamaan berikut ini
Position=positionA*(1-alpha) + positionB*alpha
Dengan posisi A dan posisi B merupakan 2 posisi titik akhir dan
alpha sebagai interpolator. Ini mengubah secara baik ke dalam bentuk kode Cg
dengan fungsi lerp, yang mengimplementasikan sebuah interpolasi linier
Position=lerp(positionA, positionB, alpha);
Satu-satunya masalah dengan pendekatan ini adalah cara untuk melewati
kedua posisi ke shader vertex, yang jika Anda ingat,harus bertindak seperti
filter: menerima satu set simpul dan mengubahnya. Saat ini, tidak ada cara
untuk melewati kedua mesh, jadi kita perlu beberapa trik supaya cara ini dapat
bekerja. Trik yang akan kita gunakan adalah untuk menyimpan mesh kedua sebagai
koordinat tekstur, sehingga kita dapat mengaksesnya dari dalam shader. Secara
khusus, kita akan melewati sebuah posisi, set float2 biasa dari koordinat
tekstur, satu warna (float4), dan set kedua tekstur koordinat (float3), yang
akan memegang posisi kedua untuk interpolasi. Perhatikan kode berikut:
void
keyframe (float3 positionA: POSITION,
float3
positionB: TEXCOORD1,
float4
color: COLOR,
float2
texcoord: TEXCOORD0,
out
float4 oPosition: POSITION,
out
float2 oTexCoord: TEXCOORD0,
out
float3 oColor: COLOR,
uniform
float alpha,
uniform
float4x4 modelviewProj)
{
float3
position=lerp(positionA, positionB, alpha);
oPosition=mul(modelviewProj,float4(position,1));
oTexCoord=texCoord;
oColor=color;
}
Itu hanya empat baris sebenarnya, dan menyelamatkan para
pengembang dari sakit kepala. Semua yang perlu kita lakukan adalah menghitung nilai
alpha berdasarkan jarak antara kedua
frame kunci. Perhatikan bahwa kode
sebelumnya dapat menangani baik reguler dan tombol spasi merata. Kita dapat
meningkatkan kode untuk melewatkan data tambahan dan mengendalikan kuadrat atau
interpolator kubik, tetapi untuk sebagian besar penggunaan, linier baik-baik
saja.
Pendekatan ini dapat diadaptasi dengan mudah untuk animasi wajah
menggunakan morphs. Setelah itu, kita dapat melakukan beberapa interpolasi ekspresi
wajah per titik, sehingga kode sebelumnya, dalam segala kemudahan, pasti layak
untuk dilakukan pengecekan. Tapi di lain sisi, masalah animasi lebih serius untuk
ke depannya. Bukankah lebih keren untuk menerapkan pendekatan kemiripan untuk animasi kerangka?
Ini merupakan kasus yang jauh lebih sulit karena algoritma ini adalah algoritma
yang lebih kompleks. Mengingat kembali bahwa animasi kerangka (Aku sedang
berbicara tentang pendekatan multibone) diselesaikan dalam dua tahap. Kami
pertama menyebarkan matriks dan menyimpannya, untuk setiap vertex, tulang, dan
dengan demikian matriks yang mempengaruhi itu. Kami juga menyimpan berat matriks,
sehingga kami dapat mencampurnya benar. Seperti yang dapat Anda bayangkan, ini
adalah tahap mudah karena tahap yang selanjutnya melakukan penerapan banyak
titik-matrix berganda dan mencampur hasilnya bersama-sama. Oleh karena itu, pada
tahap kedua apa yang kita perlu tingkatkan dengan coding kode pengulitan
sebenarnya menjadi shader. Sekarang kode animasi kita harus mendapatkannya,
bersama dengan titik yang kita telah proses, matriks yang mempengaruhi dan
bobot yang sesuai. Untuk mempermudah, kita akan membatasi jumlah tulang yang
mempengaruhi simpul menjadi empat, jadi kami harus melewati empat matriks:
void
skinning(float3 position : POSITION,
float3
normal : NORMAL,
float2
texCoord : TEXCOORD0,
float4
weight : TEXCOORD1,
float4
matrixIndex : TEXCOORD2,
out
float4 oPosition : POSITION,
out
float2 oTexCoord : TEXCOORD0,
out
float4 color : COLOR,
uniform
Light light,
uniform
float4 boneMatrix[72], // 24 matrices
uniform
float4x4 modelViewProj)
{
float3
netPosition = 0, netNormal = 0;
for
(int i=0; i<4; i++)
{
float
index = matrixIndex[i];
float3x4
model = float3x4(boneMatrix[index+0], boneMatrix[index+1],
boneMatrix[index+2]);
float3
bonePosition = mul(model, float4(position, 1));
float3x3
rotate = float3x3(model[0].xyz, model[1].xyz, model[2].xyz);
float3
boneNormal = mul(rotate, normal);
netPosition
+= weight[i] * bonePosition;
netNormal
+= weight[i] * boneNormal;
}
netNormal
= normalize(netNormal);
oPosition
= mul(modelViewProj, float4(netPosition, 1));
oTexCoord
= texCoord;
ocolor = Illuminate(light, netPosition, netNormal);
Kita kemudian melakukan loop
empat kali (satu per matriks), mengumpulkan setiap kontribusi matriks ke vertex
dan nilai normal. Perhatikan bagaimana panggilan untuk Illuminasi (yang tidak
termasuk dalam kesederhanaan) menghitung shading berdasarkan nilai-nilai ini.
Kemudian, untuk setiap matriks, TEXCOORD1 digunakan untuk menentukan beratnya,
dan TEXCOORD2 digunakan untuk melakukan indeks menjadi array bonematrix yang
lebih besar. Variabel Model memegang pengaruh matriks tulang saat ini, yang
kemudian diterapkan pada vertex dan akhirnya ke normal. Kemudian, posisi
kerangka ini (posisi titik menurut tulang ini) dan boneNormal (normal menurut
tulang yang sama) ditambahkan, mengintegrasikan berat badan pada persamaan.
Persamaan ini persis sama dengan yang kita digunakan dalam diskusi kita pada
kerangka animasi.
Tekstur
Procedural
Salah satu kegunaan utama untuk
shader dalam industri film adalah penciptaan tekstur prosedural. di sini bahan
yang tidak didefinisikan dalam hal bitmap tetapi dalam fungsi matematika yang,
ketika digabungkan, menghasilkan estetika. Sebagai contoh sederhana, bayangkan
sebuah pola papan, setiap ubin menjadi satu meter di seluruh. Berikut adalah
pseudocode untuk shader yang akan menghitung ini di GPU, sehingga menghemat
tekstur dan sumber CPU :
checkers
(point pos)
if
(trunc(pos.x) + trunc(pos.y)) is odd
color=black
else color=white
Seperti yang Anda lihat, tekstur
prosedural selalu dilaksanakan dengan pixel shader, dengan shader menerima titik ke Texturize dan menghitung
warna keluaran sebagai balasannya.
Sebuah perpustakaan yang baik
dari fungsi matematika sangat penting untuk tekstur prosedural untuk bekerja.
Kebanyakan efek dicapai dengan operator seperti fungsi trigonometri, logaritma,
dan terutama, fungsi kebisingan terkendali seperti Kebisingan Perlin (untuk
lebih lanjut tentang fungsi ini, lihat artikel Perlin tercantum dalam Lampiran
E). fungsi ini menghasilkan nilai pseudorandom melalui ruang 3D, sehingga
nilai-nilai dekat di ruang parameter dekat dalam nilai kebisingannya, dan parameter jauh menghasilkan output tidak
berkorelasi. Anggap saja sebagai band terbatas, terus menerus dan diturunkan
kebisingannya.
Menggunakan Kebisingan Perlin
dengan benar, Anda dapat melaksanakan sebagian besar bahan alamiah, seperti
kayu, marmer, atau awan. Sebagai contoh yang menunjukkan kemampuannya, lihatlah
Gambar 21,7, yang diambil dari shader oleh Pere Fort. Di sini kita menggunakan
Kebisingan Perlin untuk menghitung matahari animasi lengkap dengan Coronas, permukaan
ledakan, dan animasi. Matahari benar-benar sebuah quad, bertekstur menggunakan
pixel shader.
Gambar
21,7. piringan matahari terbakar yang diimplementasikan sebagai quad dengan
Perlin prosedural tekstur pada pixel shader.
Berikut adalah komentar kode
sumber untuk efek yang menarik, yang entah bagaimana menunjukkan bagaimana real-time shader benar-benar dapat
membuat beberapa efek yang tidak pernah dilihat sebelumnya dalam permainan
komputer. Kode sumber sangat panjang, tapi saya pikir itu memberikan visi yang
jelas tentang apa masa depan untuk bahasa shading:
struct
vp2fp
{
float4
Position : POSITION;
float4
TexCoord : TEXCOORD0;
float4
Param : TEXCOORD1;
float4
Color : COLOR0;
};
//
INPUTS
//
TexCoord : TEXCOORD0 = here we encode the actual coordinates plus the time
//
Param : TEXCOORD1 = here we encode in x and y the angles to rotate the sun
float4
main(vp2fp IN, const uniform sampler2D permTx, const uniform sampler1D gradTx
):
COLOR
{
//distance
vector from center
float2
d = IN.TexCoord.xy - float2(0.5f,0.5f);
//distance
scalar
float
dist = sqrt(dot(d,d)) * 2;
//
Sun's radius
float
radSun = 0.6f;
//
Halo's radius
float
radHalo = 1.0f;
float3
vCoord = IN.TexCoord.xyz;
float
freq;
if(dist
< radSun) // fragment inside the Sun
{
//
freq inside the Sun is higher than outside
freq
= 64;
//
texture coords displacement in 2D
vCoord.xy
-= IN.Param.xy;
//
sphere function, with 70% depth
vCoord.xy
-= d*sqrt(radSun*radSun-dist*dist)*0.7f;
}
else
//fragment outside the Sun
{
//
freq outside the Sun is half than inside
freq
= 32;
// a
little displacement (20%) of the halo while rotating the sphere
vCoord.xy
-= IN.Param.xy*0.2;
}
//
sum of two octaves of perlin noise, second with half the freq of the first
float4
total = 0f.xxxx;
total
+= (perlinNoise3_2D(vCoord*freq , permTx, gradTx) + 0.7).xxxx*0.33f;
total
+= (perlinNoise3_2D(vCoord*freq/2, permTx, gradTx) + 0.7).xxxx*0.66f;
//
after getting the result of the noise we can compute the radius of the
explosions
//
we can use one dimension from the result to get a single dimension perlin noise
float
radExpl = (1 + total.x) * radSun * 0.6f;
//
this generates a little halo between Sun and explosions, and avoids some
aliasing
if(radExpl
< radSun*1.01f) radExpl = radSun*1.01f;
//
filtering the colour of fragments
//
first smoothstep makes explosions darker as the radius increases
//
second smoothstep makes the halo darker as it becomes far from the sun
dist
= smoothstep(radExpl,radSun,dist)*0.5f + smoothstep(radHalo,radExpl,dist) *
0.5f;
//
transform black into red
//
blue to 0 means obtaining yellow
//
maintain the alpha input
total.rba
= float3(total.x*1.8f,0.0f,IN.Color.a);
//
ensure the ranges of the colors after all modifications
total.xyz
= clamp(total.xyz,0,1);
//
return all the modifications with the filter applied
return
total*dist;
}
Efek Spesial
Lampu, animasi, dan texturing
adalah bagian populer untuk penggunaan shader. Namun, shader kita miliki diperiksa
sejauh ini shader sangat konservatif yang hanya beberapa baris panjang dan
menerapkan efek sederhana. Ada beberapa efek yang menarik yang menunggu Anda
untuk dikodekan. Sebagai contoh, saya akan mengekspos kode shader untuk
menghitung shading Toon di GPU. Untuk lebih lanjut tentang shading Toon, lihat
Bab 17, yang mencakup teknik shading. Implementasi kami ketat akan mengikuti
algoritma yang ada di bab tersebut. Saya telah memilih versi yang berisi
pasangan simpul dan shader fragmen. Shader vertex menghitung koordinat teksturing,
dan versi fragmen melakukan pencampuran warna yang sebenarnya. Kedua shader kemudian
dihubungkan, sehingga output dari vertex shader akhirnya akan diproses oleh
shader fragmen. Berikut adalah kode cg untuk shader vertex:
void
toonShading_v(float4 position : POSITION,
float3
normal : NORMAL,
out
float4 oPosition : POSITION,
out
float diffuseLight : TEXCOORD0,
out
float specularLight : TEXCOORD1,
out
float edge : TEXCOORD2,
uniform
float3 lightPosition, // Light Pos (in obj space)
uniform
float3 eyePosition, // Eye Pos (in obj space)
uniform
float shininess,
uniform
float4x4 modelViewProj)
{
oPosition
= mul(modelViewProj, position);
//
Calculate diffuse lighting
float3
N = normalize(normal);
float3
L = normalize(lightPosition - position.xyz);
diffuseLight
= max(0, dot(L, N));
//
Calculate specular lighting
float3
V = normalize(eyePosition - position.xyz);
float3
H = normalize(L + V);
specularLight
= pow(max(0, dot(H, N)), shininess);
if
(diffuseLight<=0) specularLight = 0;
//
Perform edge detection
edge
= max(0, dot(V, N));
}
Bagian pertama dari kode
sebelumnya tidak benar-benar membutuhkan penjelasan. Kami menghitung posisi
yang telah di proyeksikan, dan kemudian memproses secara berurutan komponen
diffuse dan specular. Kami kemudian menghitung nilai tepi, yang nol jika V dot
N kurang dari atau sama dengan nol. Dengan demikian, kita mendeteksi, dengan
nilai nol, yang tempat di mana V dan N adalah tegak lurus, dan dengan demikian,
kita berada di siluet obyek yang akan diberikan. Kami harus memberikan nilai
yang mendorong shader fragmen kami, dan pada kode berikut, perhatikan bagaimana
shader vertex tidak menghitung warna per titik atau koordinat tekstur:
void
toonShading_f (float diffuseLight : TEXCOORD0,
float specularLight : TEXCOORD1,
float edge : TEXCOORD2,
out
float4 color : COLOR,
uniform
float4 Kd,
uniform
float4 Ks,
uniform
sampler1D diffuseRamp,
uniform
sampler1D specularRamp,
uniform
sampler1D edgeRamp)
{
//
Apply step functions
diffuseLight
= tex1D(diffuseRamp, diffuseLight).x;
specularLight
= tex1D(specularRamp, specularLight).x;
edge
= tex1D(edgeRamp, edge).x;
//
Compute the final color
color
= edge * (Kd * diffuseLight + Ks * specularLight);
}
Shader ini benar-benar sederhana.
Kita mulai dengan melakukan tabel lookup berdasarkan nilai pencahayaan diffuse dihitung
pada tahap puncak, dan kemudian menggunakan ini untuk mendorong lookup tekstur
1D. Tekstur ini akan menjadi peta khas terdiskritisasi, yang biasanya memiliki
dua atau tiga warna: satu untuk warna gelap, satu untuk pertengahan rentang,
dan satu untuk zona cukup terang. Baris kedua tidak persis hal yang sama untuk
tekstur specular, dan kemudian ujungnya diproses. Untuk tepi, tekstur
mengkodekan kisaran nilai yang kita inginkan untuk menetapkan ke garis hitam. Nilai-nilai yang
datang dari vertex shader berada di kisaran 0 .. 1, tapi kita perlu siluet kami
untuk menjadi sempurna dibagi menjadi zona yang putih murni dan zona yang hitam
sempurna. Dengan demikian, tekstur tepi lookup Peta kisaran kontinyu 0 .. 1
sampai kemungkinan dua nilai, 0 untuk nilai-nilai di bawah ambang batas tetap
(katakanlah, 0,2) dan 1 dalam hal lain.
Persamaan terakhir adalah di
mana semua bagian berada di tempatnya. Anggota di dalam kurung adalah hanya
persamaan pencahayaan biasa yang menggunakan nilai-nilai terdiskritisasi untuk
memodulasi difus (Kd) dan specular (Ks) warna. Kita kalikan ini dengan nilai
tepi, sehingga tepi yang sama dengan nol (dengan demikian, poin dalam siluet)
yang diarsir pada hitam, dan garis besar yang bagus diambil.
Ini hanya contoh sederhana dari
yang bisa dicapai dengan memanfaatkan kekuatan vertex dan fragmen shader, dan
terutama dengan cara menghubungkan keduanya sehingga mereka berdua bekerja
sebagai tahapan dari proses secara keseluruhan.
Dikutip dari
Buku "Core Techniques & Algorithm in Game Programming"
Dikutip dari
Buku "Core Techniques & Algorithm in Game Programming"
Tidak ada komentar:
Posting Komentar