Bagaimana cara menggunakan turunan layar-ruang untuk antialias bentuk parametrik dalam pixel shader?

8

Dalam kertas Alpha Tested Magnification milik Valve , disebutkan menggunakan "turunan ruang-layar per-pixel" untuk melakukan anti-aliasing. Pemahaman saya adalah bahwa ini adalah fungsi ddxdan ddyintrinsik di HLSL?

Saya mencoba menggambar bentuk parametrik (misalnya lingkaran: x² + y² <1 ) dalam shader, dan saya tidak tahu cara menggunakan teknik ini untuk benar-benar anti-alias tepi piksel bentuk saya. Adakah yang bisa memberikan contoh?

Untuk kelengkapan, berikut adalah contoh jenis pixel shader yang saya buat:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float dist = input.TexCoord.x * input.TexCoord.x
               + input.TexCoord.y * input.TexCoord.y;
    if(dist < 1)
        return float4(0, 0, 0, 1);
    else
        return float4(1, 1, 1, 1);
}
Andrew Russell
sumber

Jawaban:

10

Mengambil contoh Anda, Anda memiliki fungsi langkah jarak, yang menghasilkan tepi yang sangat keras (alias). Cara sederhana untuk antialias lingkaran adalah mengubahnya menjadi ambang lunak, seperti:

float distFromEdge = 1.0 - dist;  // positive when inside the circle
float thresholdWidth = 0.01;  // a constant you'd tune to get the right level of softness
float antialiasedCircle = saturate((distFromEdge / thresholdWidth) + 0.5);
return lerp(outsideColor, insideColor, antialiasedCircle);

Di sini saya menggunakan jalan linear dijepit untuk fungsi ambang lunak, tetapi Anda juga bisa menggunakan smoothstepatau sesuatu yang lain. The + 0.5adalah untuk pusat jalan di lokasi matematika dari tepi. Pokoknya, intinya adalah bahwa fungsi ini dengan lancar berubah dari outsideColorke insideColorbeberapa rentang jarak, jadi jika Anda memilih dengan thresholdWidthtepat, Anda akan mendapatkan tepi yang tampak antialiased.

Tetapi bagaimana Anda memilih thresholdWidth? Jika terlalu kecil, Anda akan mendapatkan aliasing lagi, dan jika terlalu besar ujungnya akan terlalu buram. Selain itu, umumnya akan tergantung pada posisi kamera: jika distdiukur dalam ruang dunia atau unit ruang tekstur, maka thresholdWidthyang bekerja untuk satu posisi kamera akan salah untuk yang lain.

Di sinilah turunan layar-ruang masuk (ya, mereka adalah ddxdan ddyberfungsi seperti yang Anda duga). Dengan menghitung panjang gradien distAnda bisa mendapatkan ide seberapa cepat itu berubah dalam ruang layar dan menggunakannya untuk memperkirakan thresholdWidth, seperti:

float derivX = ddx(distFromEdge);
float derivY = ddy(distFromEdge);
float gradientLength = length(float2(derivX, derivY));
float thresholdWidth = 2.0 * gradientLength;  // the 2.0 is a constant you can tune

Anda masih memiliki nilai yang dapat Anda sesuaikan untuk mendapatkan tingkat kelembutan yang diinginkan, tetapi sekarang Anda harus mendapatkan hasil yang konsisten terlepas dari posisi kamera.

Nathan Reed
sumber
Jawaban bagus - kode berfungsi dengan baik. +1 dan diterima. Tetapi bisakah saya menyulitkan Anda untuk memperluas jawaban Anda dan menjelaskan mengapa ini berhasil? Saya agak bingung tentang apa derivXdan derivYsebenarnya mewakili.
Andrew Russell
@AndrewRussell Apakah Anda terbiasa dengan derivatif, seperti dalam kalkulus? derivXdan derivYhanya (perkiraan) turunan parsial dari ekspresi apa pun yang Anda berikan ddxdan ddy, sehubungan dengan layar-ruang x dan y. Jika Anda belum mempelajari kalkulus, itu adalah topik yang lebih besar daripada yang bisa saya jelaskan di sini. :)
Nathan Reed
Kalkulus saya agak berkarat - saya harus pergi dan mempelajari kembali apa itu turunan parsial . Tapi dari sana saya pikir saya sudah menemukannya. Terima kasih :)
Andrew Russell
OpenGL tidak mendukung ddx / ddy sehingga dalam kasus-kasus tersebut Anda mungkin memerlukan http.developer.nvidia.com/GPUGems/gpugems_ch25.html yang pada dasarnya menggunakan tekstur yang dikomputasi untuk mengidentifikasi level mipmap yang digunakan, untuk memperkirakan ddx / ddy.
Runonthespot
2
@Runonthespot OpenGL memiliki turunannya; mereka hanya disebut sesuatu yang lain: dFdx/dFdy bukannya ddx/ ddy.
Nathan Reed