Banding specular dengan kekuatan specular tinggi

8

Kami mengalami beberapa masalah dengan penelusuran ray kami di DirectX, terutama dengan beberapa masalah pita serius dengan specular. Dengan daya spekulan tinggi (di atas 8) dimulai. Saya ingin tahu apakah ini masalah HDR / LDR atau dapat dikaitkan dengan hal lain, seperti normals atau vektor lainnya?

MEMPERBARUI

Lihat di bawah untuk pembaruan.

Inilah kode shader yang relevan untuk Blinn-Phong dalam sebuah lingkup:

float3 hitPoint = thisRay.origin + thisRay.direction * bestHit.hitT;
float3 normal = normalize(hitPoint - spheres[bestHit.hitID].center);
float3 toLight = pointLights[0].position.xyz - hitPoint;
float d = length(toLight);
toLight = normalize(toLight);

float diffuse = max(dot(normal, toLight), 0.0f);

float3 v = normalize(thisRay.origin - hitPoint);
float3 h = normalize(v + toLight);

float spec = 0;
if (diffuse > 0)
    spec = pow(max(dot(normal, h), 0.0f), specPower) * diffuse;

output[threadID.xy] = spheres[bestHit.hitID].colour * diffuse + spheres[bestHit.hitID].specColour * spec;

specPower sama dengan 8 di gambar ini specPower sama dengan 8 di gambar ini

specPower sama dengan 9 di gambar ini specPower sama dengan 9 di gambar ini

Apakah ini sesederhana masalah HDR / LDR atau apakah itu terkait dengan presisi normal? Saya percaya saya pernah mengalami masalah ini sebelumnya di penyaji ditangguhkan di mana normals dari presisi rendah dan tidak benar dikemas / dibongkar tetapi dalam hal ini normals dihasilkan dengan cepat dan semuanya diserahkan langsung ke backbuffer.

Perbarui 1

Saya ingin menambahkan di atas bahwa segitiga menderita artefak yang sama dan mereka memiliki normal saat ini dihasilkan dengan cara berikut:

float3 normal = normalize(cross(triangles[bestHit.hitID].vertices[1] - triangles[bestHit.hitID].vertices[0],
                                    triangles[bestHit.hitID].vertices[2] - triangles[bestHit.hitID].vertices[0]));

Saya akan mengatakan ini membuatnya semakin tidak mungkin bahwa masalah normal adalah permukaannya. Gambar berikut ini menunjukkan apa yang terjadi ketika specPower mencapai 2048.

masukkan deskripsi gambar di sini

Bentebent
sumber
1
tebak saja: coba float3 h = normalize( reflect(toLight,normal) );, danspec = pow(dot(v, h) * 0.5 + 0.5, specPower) * diffuse;
Raxvan
Maaf untuk mengatakan ini menghasilkan artefak yang sama.
Bentebent
4
Sepertinya masalah melimpah. Nilai Anda harus menjadi nol, tetapi sebaliknya kembali ke 1. Cobalah mengeluarkan nilai specsecara langsung, dan juga max(dot(normal, h), 0.0f). Cari pengembalian ini ke nilai 1 dalam perhitungan mana pun.
Gambar ini menunjukkan pada titik berapa spec berjalan di atas 0.9f sehingga sepertinya itu bisa menjadi penyebabnya. imgur.com/YbTS8m5
Bentebent
Sementara itu, maks (titik (normal, h), 0,0f) terlihat seperti ini ( imgur.com/YV22egw ) saat mengeluarkan nilai di atas 0,99f.
Bentebent

Jawaban:

1

Sepertinya ada masalah dengan implementasi fungsi pow pada GPU khusus ini.

Ketika mencurigai bug dalam fungsi matematika ganti fungsi matematika dengan yang setara, misalnya ganti:

spec = pow(max(dot(normal, h), 0.0f), specPower) * diffuse;

Dengan bug yang muncul di specPower = 9.0, buat hard-code-nya

float temp = max(dot(normal, h), 0.0f);
spec = pow(temp, 8.0) * temp * diffuse;

Atau

float temp = max(dot(normal, h), 0.0f);
spec = temp * temp * temp * temp * temp * temp * temp * temp * temp * diffuse;

Jika masalah hilang, ini berarti kemungkinan ada bug underflow dalam fungsi pow terkait eksponen pada GPU atau driver ini.

Apa yang dapat Anda lakukan sebagai solusi, dengan anggapan perhitungan shader fragmen dilakukan secara internal dengan float 16bit. Jika saya menghitung ini dengan benar, Anda harus melakukannya

pow(max(temp, (Nth root of (1.0/16384.0))), N);

Bisa jadi 32768 atau 8192: Saya mungkin mati sedikit atau GPU mungkin menggunakan presisi lebih atau kurang. untuk pow (x, 9.0) penjepitan akan menjadi pow (maks (temp, 0.3401975), 9.0) Nilai-nilai X di bawah ini akan melemahkan kisaran eksponen (+15 hingga -14) dari floating point IEEE 16bit (sekali lagi, saya dengan asumsi inilah yang digunakan GPU.)

dari kode rosetta ( http://rosettacode.org/wiki/Nth_root#C.2B.2B )

double NthRoot(double value, double degree)
{
    return pow(value, (double)(1 / degree));
};

my_shader->SetUniform("specular_minimum", NthRoot((1.0/16384.0), specPower));

Anda harus menghitung ini pada CPU dan memberi nilai pada seragam: GPU mungkin akan memiliki masalah presisi yang sama jika Anda menghitungnya di shader.

Jika Anda dapat mengonfirmasi ini, coba hubungi tim driver GPU dengan contoh kode sumber shader yang mereplikasi masalah memberi mereka versi driver yang tepat, revisi OS, dan model GPU, yang dapat dieksekusi mungkin membantu tetapi tidak mengirimkannya karena email Anda akan terjebak dalam pemeriksa virus / spam. Hanya membangun satu dan menyimpan salinan kalau-kalau mereka meminta sampel pra-dibangun dieksekusi. Sebagai rasa hormat, beri mereka kesempatan untuk memperbaikinya sebelum menyebutnya secara publik. Itu bisa saja berupa bug pengoptimalan dalam kompiler shader mereka yang hanya terjadi pada shader khusus Anda. Mungkin perlu waktu (bulan) untuk menghubungi mereka, orang-orang ini sering kekurangan staf.

Stephane Hockenhull
sumber