Menulis HLSL Shaders di Direct3D 9

Dasar-Dasar Vertex-Shader

Saat beroperasi, shader vertex yang dapat diprogram menggantikan pemrosesan vertex yang dilakukan oleh alur grafis Microsoft Direct3D. Saat menggunakan shader vertex, informasi status mengenai transformasi dan operasi pencahayaan diabaikan oleh alur fungsi tetap. Ketika shader vertex dinonaktifkan dan pemrosesan fungsi tetap dikembalikan, semua pengaturan status saat ini berlaku.

Tessellation primitif urutan tinggi harus dilakukan sebelum shader vertex dijalankan. Implementasi yang melakukan tesselasi permukaan setelah pemrosesan shader harus melakukannya dengan cara yang tidak jelas untuk aplikasi dan kode shader.

Minimal, shader vertex harus menghasilkan posisi vertex di ruang klip homogen. Secara opsional, shader vertex dapat menghasilkan koordinat tekstur, warna puncak, pencahayaan vertex, faktor kabut, dan sebagainya.

Dasar-Dasar Pixel-Shader

Pemrosesan piksel dilakukan oleh shader piksel pada piksel individual. Shader Pixel bekerja dalam konser dengan shader vertex; output shader vertex menyediakan input untuk shader piksel. Operasi piksel lainnya (penpaduan kabut, operasi stensil, dan perpaduan target render) terjadi setelah eksekusi shader.

Tahap Tekstur dan Status Sampler

Shader piksel sepenuhnya menggantikan fungsionalitas penpaduan piksel yang ditentukan oleh blender multi-tekstur termasuk operasi yang sebelumnya ditentukan oleh status tahap tekstur. Operasi pengambilan sampel dan pemfilteran tekstur yang dikontrol oleh status tahap tekstur standar untuk minifikasi, pembesaran, pemfilteran mip, dan mode alamat pembungkus, dapat diinisialisasi dalam shader. Aplikasi ini bebas untuk mengubah status ini tanpa memerlukan regenerasi shader yang saat ini terikat. Status pengaturan dapat dibuat lebih mudah jika shader Anda dirancang dalam efek.

Input Pixel Shader

Untuk versi shader piksel ps_1_1 - ps_2_0, warna difus dan spekular jenuh (dijepit) dalam rentang 0 hingga 1 sebelum digunakan oleh shader.

Nilai warna yang dimasukkan ke shader piksel diasumsikan sebagai perspektif yang benar, tetapi ini tidak dijamin (untuk semua perangkat keras). Warna yang diambil sampelnya dari koordinat tekstur diulang secara perspektif dengan cara yang benar, dan dijepit ke rentang 0 hingga 1 selama perulangan.

Output Pixel Shader

Untuk versi piksel shader ps_1_1 - ps_1_4, hasil yang dipancarkan oleh shader piksel adalah konten register r0. Apa pun isinya ketika shader selesai diproses dikirim ke tahap kabut dan blender render-target.

Untuk versi piksel shader ps_2_0 ke atas, warna output dipancarkan dari oC0 - oC4.

Input Shader dan Variabel Shader

Mendeklarasikan Variabel Shader

Deklarasi variabel paling sederhana mencakup jenis dan nama variabel, seperti deklarasi floating-point ini:

float fVar;

Anda dapat menginisialisasi variabel dalam pernyataan yang sama.

float fVar = 3.1f;

Array variabel dapat dideklarasikan,

int iVar[3];

atau dinyatakan dan diinisialisasi dalam pernyataan yang sama.

int iVar[3] = {1,2,3};

Berikut adalah beberapa deklarasi yang menunjukkan banyak karakteristik variabel bahasa shader tingkat tinggi (HLSL):

float4 color;
uniform float4 position : POSITION; 
const float4 lightDirection = {0,0,1};

Deklarasi data dapat menggunakan jenis yang valid termasuk:

Shader dapat memiliki variabel, argumen, dan fungsi tingkat atas.

// top-level variable
float globalShaderVariable; 

// top-level function
void function(
in float4 position: POSITION0 // top-level argument
              )
{
  float localShaderVariable; // local variable
  function2(...)
}

void function2()
{
  ...
}

Variabel tingkat atas dideklarasikan di luar semua fungsi. Argumen tingkat atas adalah parameter ke fungsi tingkat atas. Fungsi tingkat atas adalah fungsi apa pun yang dipanggil oleh aplikasi (dibandingkan dengan fungsi yang dipanggil oleh fungsi lain).

Input Shader Seragam

Shader vertex dan pixel menerima dua jenis data input: bervariasi dan seragam. Input yang bervariasi adalah data yang unik untuk setiap eksekusi shader. Untuk shader vertex, data yang bervariasi (misalnya: posisi, normal, dll.) berasal dari aliran vertex. Data seragam (misalnya: warna material, transformasi dunia, dll.) konstan untuk beberapa eksekusi shader. Bagi mereka yang terbiasa dengan model shader rakitan, data seragam ditentukan oleh register konstanta dan data yang bervariasi oleh v dan t register.

Data seragam dapat ditentukan oleh dua metode. Metode yang paling umum adalah mendeklarasikan variabel global dan menggunakannya dalam shader. Setiap penggunaan variabel global dalam shader akan mengakibatkan penambahan variabel tersebut ke daftar variabel seragam yang diperlukan oleh shader tersebut. Metode kedua adalah menandai parameter input dari fungsi shader tingkat atas sebagai seragam. Penandaan ini menentukan bahwa variabel yang diberikan harus ditambahkan ke daftar variabel seragam.

Variabel seragam yang digunakan oleh shader dikomunikasikan kembali ke aplikasi melalui tabel konstanta. Tabel konstanta adalah nama untuk tabel simbol yang menentukan bagaimana variabel seragam yang digunakan oleh shader cocok dengan register konstanta. Parameter fungsi seragam muncul dalam tabel konstanta yang ditambahkan dengan tanda dolar ($), tidak seperti variabel global. Tanda dolar diperlukan untuk menghindari tabrakan nama antara input seragam lokal dan variabel global dengan nama yang sama.

Tabel konstanta berisi lokasi register konstan dari semua variabel seragam yang digunakan oleh shader. Tabel juga menyertakan informasi jenis dan nilai default, jika ditentukan.

Berbagai Input dan Semantik Shader

Berbagai parameter input (dari fungsi shader tingkat atas) harus ditandai dengan kata kunci semantik atau seragam yang menunjukkan nilainya konstan untuk eksekusi shader. Jika input shader tingkat atas tidak ditandai dengan kata kunci semantik atau seragam, shader akan gagal dikompilasi.

Semantik input adalah nama yang digunakan untuk menautkan input yang diberikan ke output dari bagian sebelumnya dari alur grafis. Misalnya, semantik input POSITION0 digunakan oleh shader vertex untuk menentukan di mana data posisi dari buffer vertex harus ditautkan.

Shader pixel dan vertex memiliki set semantik input yang berbeda karena berbagai bagian dari alur grafis yang masuk ke setiap unit shader. Semantik input shader verteks menjelaskan informasi per vertex (misalnya: posisi, normal, koordinat tekstur, warna, tangen, binormal, dll.) untuk dimuat dari buffer vertex ke dalam bentuk yang dapat dikonsumsi oleh shader vertex. Semantik input langsung dipetakan ke penggunaan deklarasi puncak dan indeks penggunaan.

Semantik input shader piksel menjelaskan informasi yang disediakan per piksel oleh unit rasterisasi. Data dihasilkan dengan menginterpolasi antara output shader vertex untuk setiap puncak primitif saat ini. Semantik input shader piksel dasar menautkan warna output dan informasi koordinat tekstur ke parameter input.

Semantik input dapat ditetapkan ke input shader dengan dua metode:

  • Menambahkan titik dua dan nama semantik ke deklarasi parameter.
  • Menentukan struktur input dengan semantik input yang ditetapkan untuk setiap anggota struktur.

Bayangan verteks dan piksel menyediakan data output ke tahap alur grafis berikutnya. Semantik output digunakan untuk menentukan bagaimana data yang dihasilkan oleh shader harus ditautkan ke input tahap berikutnya. Misalnya, semantik output untuk shader vertex digunakan untuk menautkan output interpolator di rasterizer untuk menghasilkan data input untuk shader piksel. Output shader piksel adalah nilai yang disediakan untuk unit penpaduan alfa untuk setiap target render atau nilai kedalaman yang ditulis ke buffer kedalaman.

Semantik output shader vertex digunakan untuk menautkan shader baik ke shader piksel maupun ke tahap rasterizer. Shader vertex yang dikonsumsi oleh rasterizer dan tidak terekspos ke shader piksel harus menghasilkan data posisi minimal. Shader vertex yang menghasilkan koordinat tekstur dan data warna menyediakan data tersebut ke shader piksel setelah interpolasi selesai.

Semantik output shader piksel mengikat warna output shader piksel dengan target render yang benar. Warna output shader piksel ditautkan ke tahap campuran alfa, yang menentukan bagaimana target render tujuan dimodifikasi. Output kedalaman shader piksel dapat digunakan untuk mengubah nilai kedalaman tujuan di lokasi raster saat ini. Output kedalaman dan beberapa target render hanya didukung dengan beberapa model shader.

Sintaks untuk semantik output identik dengan sintaks untuk menentukan semantik input. Semantik dapat ditentukan langsung pada parameter yang dinyatakan sebagai parameter "out" atau ditetapkan selama definisi struktur yang dikembalikan sebagai parameter "out" atau nilai pengembalian fungsi.

Semantik mengidentifikasi dari mana data berasal. Semantik adalah pengidentifikasi opsional yang mengidentifikasi input dan output shader. Semantik muncul di salah satu dari tiga tempat:

  • Setelah anggota struktur.
  • Setelah argumen dalam daftar argumen input fungsi.
  • Setelah daftar argumen input fungsi.

Contoh ini menggunakan struktur untuk menyediakan satu atau beberapa input shader vertex, dan struktur lain untuk menyediakan satu atau beberapa output shader vertex. Masing-masing anggota struktur menggunakan semantik.

vector vClr;

struct VS_INPUT
{
    float4 vPosition : POSITION;
    float3 vNormal : NORMAL;
    float4 vBlendWeights : BLENDWEIGHT;
};

struct VS_OUTPUT
{
    float4  vPosition : POSITION;
    float4  vDiffuse : COLOR;

};

float4x4 mWld1;
float4x4 mWld2;
float4x4 mWld3;
float4x4 mWld4;

float Len;
float4 vLight;

float4x4 mTot;

VS_OUTPUT VS_Skinning_Example(const VS_INPUT v, uniform float len=100)
{
    VS_OUTPUT out;

    // Skin position (to world space)
    float3 vPosition = 
        mul(v.vPosition, (float4x3) mWld1) * v.vBlendWeights.x +
        mul(v.vPosition, (float4x3) mWld2) * v.vBlendWeights.y +
        mul(v.vPosition, (float4x3) mWld3) * v.vBlendWeights.z +
        mul(v.vPosition, (float4x3) mWld4) * v.vBlendWeights.w;
    // Skin normal (to world space)
    float3 vNormal =
        mul(v.vNormal, (float3x3) mWld1) * v.vBlendWeights.x + 
        mul(v.vNormal, (float3x3) mWld2) * v.vBlendWeights.y + 
        mul(v.vNormal, (float3x3) mWld3) * v.vBlendWeights.z + 
        mul(v.vNormal, (float3x3) mWld4) * v.vBlendWeights.w;
    
    // Output stuff
    out.vPosition    = mul(float4(vPosition + vNormal * Len, 1), mTot);
    out.vDiffuse  = dot(vLight,vNormal);

    return out;
}

Struktur input mengidentifikasi data dari buffer vertex yang akan memberikan input shader. Shader ini memetakan data dari elemen posisi, normal, dan blendweight dari buffer vertex ke dalam daftar shader vertex. Jenis data input tidak harus sama persis dengan jenis data deklarasi puncak. Jika tidak sama persis, data puncak akan secara otomatis dikonversi menjadi jenis data HLSL ketika ditulis ke dalam daftar shader. Misalnya, jika data normal didefinisikan berjenis UINT oleh aplikasi, data tersebut akan dikonversi menjadi float3 saat dibaca oleh shader.

Jika data dalam aliran vertex berisi lebih sedikit komponen daripada jenis data shader yang sesuai, komponen yang hilang akan diinisialisasi ke 0 (kecuali w, yang diinisialisasi menjadi 1).

Semantik input mirip dengan nilai dalam D3DDECLUSAGE.

Struktur output mengidentifikasi parameter output shader vertex posisi dan warna. Output ini akan digunakan oleh alur untuk rasterisasi segitiga (dalam pemrosesan primitif). Output yang ditandai sebagai data posisi menunjukkan posisi puncak dalam ruang homogen. Minimal, shader vertex harus menghasilkan data posisi. Posisi ruang layar dihitung setelah shader puncak selesai dengan membagi koordinat (x, y, z) dengan w. Dalam ruang layar, -1 dan 1 adalah nilai minimum dan maksimum x dan y dari batas viewport, sementara z digunakan untuk pengujian z-buffer.

Semantik output juga mirip dengan nilai dalam D3DDECLUSAGE. Secara umum, struktur output untuk shader vertex juga dapat digunakan sebagai struktur input untuk shader piksel, asalkan shader piksel tidak membaca dari variabel apa pun yang ditandai dengan posisi, ukuran titik, atau semantik kabut. Semantik ini dikaitkan dengan nilai skalar per vertex yang tidak digunakan oleh shader piksel. Jika nilai-nilai ini diperlukan untuk shader piksel, nilai tersebut dapat disalin ke variabel output lain yang menggunakan semantik shader piksel.

Variabel global ditetapkan untuk mendaftar secara otomatis oleh pengkompilasi. Variabel global juga disebut parameter seragam karena konten variabel sama untuk semua piksel yang diproses setiap kali shader dipanggil. Register terkandung dalam tabel konstanta, yang dapat dibaca menggunakan antarmuka ID3DXConstantTable .

Semantik input untuk nilai peta shader piksel ke dalam daftar perangkat keras tertentu untuk transportasi antara shader vertex dan shader piksel. Setiap jenis register memiliki properti tertentu. Karena saat ini hanya ada dua semantik untuk koordinat warna dan tekstur, biasanya sebagian besar data ditandai sebagai koordinat tekstur bahkan ketika tidak.

Perhatikan bahwa struktur output shader vertex menggunakan input dengan data posisi, yang tidak digunakan oleh shader piksel. HLSL memungkinkan data output yang valid dari shader vertex yang bukan data input yang valid untuk shader piksel, asalkan tidak dirujuk dalam shader piksel.

Argumen input juga dapat berupa array. Semantik secara otomatis bertambah bertahap oleh pengkompilasi untuk setiap elemen array. Misalnya, pertimbangkan deklarasi eksplisit berikut:

struct VS_OUTPUT
{
    float4 Position   : POSITION;
    float3 Diffuse    : COLOR0;
    float3 Specular   : COLOR1;               
    float3 HalfVector : TEXCOORD3;
    float3 Fresnel    : TEXCOORD2;               
    float3 Reflection : TEXCOORD0;               
    float3 NoiseCoord : TEXCOORD1;               
};

float4 Sparkle(VS_OUTPUT In) : COLOR

Deklarasi eksplisit yang diberikan di atas setara dengan deklarasi berikut yang akan memiliki semantik yang secara otomatis bertambah bertahap oleh kompilator:

float4 Sparkle(float4 Position : POSITION,
                 float3 Col[2] : COLOR0,
                 float3 Tex[4] : TEXCOORD0) : COLOR0
{
   // shader statements
   ...

Sama seperti semantik input, semantik output mengidentifikasi penggunaan data untuk data output shader piksel. Banyak shader piksel hanya menulis ke satu warna output. Shader pixel juga dapat menuliskan nilai kedalaman ke dalam satu atau beberapa target render secara bersamaan (hingga empat). Seperti shader vertex, shader piksel menggunakan struktur untuk mengembalikan lebih dari satu output. Shader ini menulis 0 ke komponen warna, serta komponen kedalaman.

struct PS_OUTPUT
{
    float4 Color[4] : COLOR0;
    float  Depth  : DEPTH;
};

PS_OUTPUT main(void)
{
    PS_OUTPUT out;

   // Shader statements
   ...

  // Write up to four pixel shader output colors
  out.Color[0] =  ...
  out.Color[1] =  ...
  out.Color[2] =  ...
  out.Color[3] =  ...

  // Write pixel depth 
  out.Depth =  ...

    return out;
}

Warna output shader piksel harus berjenis float4. Saat menulis beberapa warna, semua warna output harus digunakan secara berdamai. Dengan kata lain, COLOR1 tidak dapat menjadi output kecuali COLOR0 telah ditulis. Output kedalaman shader piksel harus berjenis float1.

Sampler dan Objek Tekstur

Sampler berisi status sampler. Status sampler menentukan tekstur yang akan diambil sampelnya, dan mengontrol pemfilteran yang dilakukan selama pengambilan sampel. Tiga hal diperlukan untuk mengambil sampel tekstur:

  • Tekstur
  • Sampler (dengan status sampler)
  • Instruksi pengambilan sampel

Sampler dapat diinisialisasi dengan tekstur dan status sampler seperti yang ditunjukkan di sini:

sampler s = sampler_state 
{ 
  texture = NULL; 
  mipfilter = LINEAR; 
};

Berikut adalah contoh kode untuk mengambil sampel tekstur 2D:

texture tex0;
sampler2D s_2D;

float2 sample_2D(float2 tex : TEXCOORD0) : COLOR
{
  return tex2D(s_2D, tex);
}

Tekstur dideklarasikan dengan variabel tekstur tex0.

Dalam contoh ini, variabel sampler bernama s_2D dinyatakan. Sampler berisi status sampler di dalam kurung kurawal. Ini termasuk tekstur yang akan diambil sampelnya dan, secara opsional, status filter (yaitu, mode pembungkusan, mode filter, dll.). Jika status sampler dihilangkan, status sampler default diterapkan yang menentukan pemfilteran linier dan mode pembungkusan untuk koordinat tekstur. Fungsi sampler mengambil koordinat tekstur floating-point dua komponen, dan mengembalikan warna dua komponen. Ini diwakili dengan jenis pengembalian float2 dan mewakili data dalam komponen merah dan hijau.

Empat jenis sampler ditentukan (lihat Kata Kunci) dan pencarian tekstur dilakukan oleh fungsi intrinsik: tex1D(s, t) (DirectX HLSL), tex2D(s, t) (DirectX HLSL), tex3D(s, t) (DirectX HLSL), texCUBE(s, t) (DirectX HLSL). Berikut adalah contoh pengambilan sampel 3D:

texture tex0;
sampler3D s_3D;

float3 sample_3D(float3 tex : TEXCOORD0) : COLOR
{
  return tex3D(s_3D, tex);
}

Deklarasi sampler ini menggunakan status sampler default untuk pengaturan filter dan mode alamat.

Berikut adalah contoh pengambilan sampel kubus yang sesuai:

texture tex0;
samplerCUBE s_CUBE;

float3 sample_CUBE(float3 tex : TEXCOORD0) : COLOR
{
  return texCUBE(s_CUBE, tex);
}

Dan akhirnya, berikut adalah contoh pengambilan sampel 1D:

texture tex0;
sampler1D s_1D;

float sample_1D(float tex : TEXCOORD0) : COLOR
{
  return tex1D(s_1D, tex);
}

Karena runtime tidak mendukung tekstur 1D, pengkompilasi akan menggunakan tekstur 2D dengan pengetahuan bahwa koordinat y tidak penting. Karena tex1D(s, t) (DirectX HLSL) diimplementasikan sebagai pencarian tekstur 2D, pengkompilasi bebas memilih komponen y dengan cara yang efisien. Dalam beberapa skenario langka, kompilator tidak dapat memilih komponen y yang efisien, dalam hal ini akan mengeluarkan peringatan.

texture tex0;
sampler s_1D_float;

float4 main(float texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float, texCoords);
}

Contoh khusus ini tidak efisien karena kompilator harus memindahkan koordinat input ke register lain (karena pencarian 1D diimplementasikan sebagai pencarian 2D dan koordinat tekstur dinyatakan sebagai float1). Jika kode ditulis ulang menggunakan input float2 alih-alih float1, kompilator dapat menggunakan koordinat tekstur input karena tahu bahwa y diinisialisasi ke sesuatu.

texture tex0;
sampler s_1D_float2;

float4 main(float2 texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float2, texCoords);
}

Semua pencarian tekstur dapat ditambahkan dengan "bias" atau "proj" (yaitu, tex2Dbias (DirectX HLSL), texCUBEproj (DirectX HLSL)). Dengan akhiran "proj", koordinat tekstur dibagi dengan w-component. Dengan "bias," tingkat mip digeser oleh w-component. Dengan demikian, semua pencarian tekstur dengan akhiran selalu mengambil input float4. tex1D(s, t) (DirectX HLSL) dan tex2D(s, t) (DirectX HLSL) mengabaikan komponen yz dan z masing-masing.

Sampler juga dapat digunakan dalam array, meskipun saat ini tidak ada back end yang mendukung akses array dinamis sampler. Oleh karena itu, berikut ini valid karena dapat diselesaikan pada waktu kompilasi:

tex2D(s[0],tex)

Namun, contoh ini tidak valid.

tex2D(s[a],tex)

Akses dinamis sampler terutama berguna untuk menulis program dengan perulangan harfiah. Kode berikut mengilustrasikan akses array sampler:

sampler sm[4];

float4 main(float4 tex[4] : TEXCOORD) : COLOR
{
    float4 retColor = 1;

    for(int i = 0; i < 4;i++)
    {
        retColor *= tex2D(sm[i],tex[i]);
    }

    return retColor;
}

Catatan

Menggunakan runtime debug Microsoft Direct3D dapat membantu Anda menangkap ketidakcocokan antara jumlah komponen dalam tekstur dan sampler.

 

Fungsi Penulisan

Fungsi memecah tugas besar menjadi tugas yang lebih kecil. Tugas kecil lebih mudah di-debug dan dapat digunakan kembali, setelah terbukti. Fungsi dapat digunakan untuk menyembunyikan detail fungsi lain, yang membuat program yang terdiri dari fungsi lebih mudah diikuti.

Fungsi HLSL mirip dengan fungsi C dalam beberapa cara: Keduanya berisi definisi dan isi fungsi dan keduanya menyatakan jenis pengembalian dan daftar argumen. Seperti fungsi C, validasi HLSL melakukan pemeriksaan jenis pada argumen, jenis argumen, dan nilai pengembalian selama kompilasi shader.

Tidak seperti fungsi C, fungsi titik entri HLSL menggunakan semantik untuk mengikat argumen fungsi ke input dan output shader (fungsi HLSL yang disebut semantik yang diabaikan secara internal). Ini memudahkan untuk mengikat data buffer ke shader, dan mengikat output shader ke input shader.

Fungsi berisi deklarasi dan isi, dan deklarasi harus mendahului tubuh.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

Deklarasi fungsi mencakup semua yang ada di depan kurung kurawal:

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

Deklarasi fungsi berisi:

  • Jenis pengembalian
  • Nama fungsi
  • Daftar argumen (opsional)
  • Semantik output (opsional)
  • Anotasi (opsional)

Jenis pengembalian dapat berupa salah satu jenis data dasar HLSL seperti float4:

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
   ...
}

Jenis pengembalian dapat berupa struktur yang telah ditentukan:

struct VS_OUTPUT
{
    float4  vPosition        : POSITION;
    float4  vDiffuse         : COLOR;
}; 

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

Jika fungsi tidak mengembalikan nilai, kekosongan dapat digunakan sebagai jenis pengembalian.

void VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

Jenis pengembalian selalu muncul terlebih dahulu dalam deklarasi fungsi.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

Daftar argumen mendeklarasikan argumen input ke fungsi. Ini juga dapat mendeklarasikan nilai yang akan dikembalikan. Beberapa argumen adalah argumen input dan output. Berikut adalah contoh shader yang mengambil empat argumen input.

float4 Light(float3 LightDir : TEXCOORD1, 
             uniform float4 LightColor,  
             float2 texcrd : TEXCOORD0, 
             uniform sampler samp) : COLOR 
{
    float3 Normal = tex2D(samp,texcrd);

    return dot((Normal*2 - 1), LightDir)*LightColor;
}

Fungsi ini mengembalikan warna akhir, yang merupakan perpaduan dari sampel tekstur dan warna terang. Fungsi ini mengambil empat input. Dua input memiliki semantik: LightDir memiliki semantik TEXCOORD1 , dan texcrd memiliki semantik TEXCOORD0 . Semantik berarti bahwa data untuk variabel ini akan berasal dari buffer vertex. Meskipun variabel LightDir memiliki semantik TEXCOORD1 , parameternya mungkin bukan koordinat tekstur. Jenis semantik TEXCOORDn sering digunakan untuk memasok semantik untuk jenis yang tidak ditentukan sebelumnya (tidak ada semantik input shader verteks untuk arah cahaya).

Dua input lainnya LightColor dan samp diberi label dengan kata kunci yang seragam . Ini adalah konstanta seragam yang tidak akan berubah di antara panggilan gambar. Nilai untuk parameter ini berasal dari variabel global shader.

Argumen dapat dilabeli sebagai input dengan kata kunci dalam, dan argumen output dengan kata kunci keluar. Argumen tidak dapat diteruskan oleh referensi; namun, argumen dapat berupa input dan output jika dinyatakan dengan kata kunci masuk. Argumen yang diteruskan ke fungsi yang ditandai dengan kata kunci masuk dianggap sebagai salinan asli hingga fungsi kembali, dan disalin kembali. Berikut adalah contoh menggunakan inout:

void Increment_ByVal(inout float A, inout float B) 
{ 
    A++; B++;
}

Fungsi ini menaikkan nilai dalam A dan B dan mengembalikannya.

Isi fungsi adalah semua kode setelah deklarasi fungsi.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

Tubuh terdiri dari pernyataan yang dikelilingi oleh kurung kurawal. Isi fungsi mengimplementasikan semua fungsionalitas menggunakan variabel, harfiah, ekspresi, dan pernyataan.

Isi shader melakukan dua hal: ia melakukan perkalian matriks dan mengembalikan hasil float4. Perkalian matriks dicapai dengan fungsi mul (DirectX HLSL), yang melakukan perkalian matriks 4x4. mul (DirectX HLSL) disebut fungsi intrinsik karena sudah dibangun ke dalam pustaka fungsi HLSL. Fungsi intrinsik akan dibahas secara lebih rinci di bagian berikutnya.

Matriks mengalikan menggabungkan vektor input Pos dan matriks komposit WorldViewProj. Hasilnya adalah data posisi yang diubah menjadi ruang layar. Ini adalah pemrosesan shader puncak minimum yang dapat kita lakukan. Jika kita menggunakan alur fungsi tetap alih-alih shader vertex, data vertex dapat diambil setelah melakukan transformasi ini.

Pernyataan terakhir dalam isi fungsi adalah pernyataan pengembalian. Sama seperti C, pernyataan ini mengembalikan kontrol dari fungsi ke pernyataan yang memanggil fungsi .

Jenis pengembalian fungsi dapat berupa salah satu jenis data sederhana yang ditentukan dalam HLSL, termasuk bool, int half, float, dan double. Jenis pengembalian dapat menjadi salah satu jenis data kompleks seperti vektor dan matriks. Jenis HLSL yang merujuk ke objek tidak dapat digunakan sebagai jenis pengembalian. Ini termasuk pixelshader, vertexshader, tekstur, dan sampler.

Berikut adalah contoh fungsi yang menggunakan struktur untuk jenis pengembalian.

float4x4 WorldViewProj : WORLDVIEWPROJ;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VS_HLL_Example(float4 inPos : POSITION )
{
    VS_OUTPUT Out;

    Out.Pos = mul(inPos,  WorldViewProj );

    return Out;
};

Jenis pengembalian float4 telah diganti dengan struktur VS_OUTPUT, yang sekarang berisi satu anggota float4.

Pernyataan pengembalian menandakan akhir fungsi. Ini adalah pernyataan pengembalian yang paling sederhana. Ini mengembalikan kontrol dari fungsi ke program panggilan. Ini tidak mengembalikan nilai.

void main()
{
    return ;
}

Pernyataan pengembalian dapat mengembalikan satu atau beberapa nilai. Contoh ini mengembalikan nilai harfiah:

float main( float input : COLOR0) : COLOR0
{
    return 0;
}

Contoh ini mengembalikan hasil skalar ekspresi:

return  light.enabled;

Contoh ini mengembalikan float4 yang dibangun dari variabel lokal dan harfiah:

return  float4(color.rgb, 1) ;

Contoh ini mengembalikan float4 yang dibangun dari hasil yang dikembalikan dari fungsi intrinsik, dan beberapa nilai harfiah:

float4 func(float2 a: POSITION): COLOR
{
    return float4(sin(length(a) * 100.0) * 0.5 + 0.5, sin(a.y * 50.0), 0, 1);
}

Contoh ini mengembalikan struktur yang berisi satu atau beberapa anggota:

float4x4 WorldViewProj;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
    VS_OUTPUT out;
    out.Pos = mul(inPos, WorldViewProj );
    return out;
};

Kontrol Alur

Sebagian besar perangkat keras vertex dan pixel shader saat ini dirancang untuk menjalankan shader line by line, menjalankan setiap instruksi sekali. HLSL mendukung kontrol alur, yang mencakup percabangan statis, instruksi predikat, perulangan statis, percabangan dinamis, dan perulangan dinamis.

Sebelumnya, menggunakan pernyataan if menghasilkan kode shader bahasa rakitan yang mengimplementasikan sisi if dan sisi lain dari aliran kode. Berikut adalah contoh dalam kode HLSL yang dikompilasi untuk vs_1_1:

if (Value > 0)
    oPos = Value1; 
else
    oPos = Value2; 

Dan berikut adalah kode perakitan yang dihasilkan:

// Calculate linear interpolation value in r0.w
mov r1.w, c2.x               
slt r0.w, c3.x, r1.w         
// Linear interpolation between value1 and value2
mov r7, -c1                      
add r2, r7, c0                   
mad oPos, r0.w, r2, c1  

Beberapa perangkat keras memungkinkan perulangan statis atau dinamis, tetapi sebagian besar memerlukan eksekusi linier. Pada model yang tidak mendukung perulangan, semua perulangan harus tidak terdaftar. Contohnya adalah sampel DepthOfField Sample yang menggunakan perulangan yang tidak terdaftar bahkan untuk ps_1_1 shader.

HLSL sekarang menyertakan dukungan untuk masing-masing jenis kontrol alur ini:

  • percabangan statis
  • instruksi yang diprediksikan
  • perulangan statis
  • percabangan dinamis
  • perulangan dinamis

Percabangan statis memungkinkan blok kode shader diaktifkan atau dinonaktifkan berdasarkan konstanta shader Boolean. Ini adalah metode yang nyaman untuk mengaktifkan atau menonaktifkan jalur kode berdasarkan jenis objek yang saat ini sedang dirender. Di antara panggilan gambar, Anda dapat memutuskan fitur mana yang ingin Anda dukung dengan shader saat ini lalu mengatur bendera Boolean yang diperlukan untuk mendapatkan perilaku tersebut. Setiap pernyataan yang dinonaktifkan oleh konstanta Boolean dilewati selama eksekusi shader.

Dukungan percabangan yang paling akrab adalah percabangan dinamis. Dengan percabangan dinamis, kondisi perbandingan berada dalam variabel, yang berarti bahwa perbandingan dilakukan untuk setiap puncak atau setiap piksel pada waktu proses (dibandingkan dengan perbandingan yang terjadi pada waktu kompilasi, atau di antara dua panggilan gambar). Hit performa adalah biaya cabang ditambah biaya instruksi di sisi cabang yang diambil. Percabangan dinamis diimplementasikan dalam model shader 3 atau yang lebih tinggi. Mengoptimalkan shader yang berfungsi dengan model ini mirip dengan mengoptimalkan kode yang berjalan pada CPU.

Panduan Pemrograman untuk HLSL