Pages

Selasa, 19 Juni 2012

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"

Tidak ada komentar:

Posting Komentar