Masalah dengan cahaya specular pada sudut yang sangat miring dengan Blinn-Phong

Aug 17 2020

Saya mengalami masalah dengan perender Blinn-Phong dasar saya, saat melihat objek pada sudut yang sangat miring:

Saya tidak berpikir ini adalah masalah dengan kode saya, meskipun saya akan memposting GLSL fragmen saya di bawah ini. Sebaliknya, ini tampaknya merupakan konsekuensi yang diperlukan dari pemotongan komponen specular pencahayaan menjadi nol ketika titik (Normal, Cahaya) <= 0. (Yang setiap orang menyuruh Anda lakukan.) Tetapi melakukan itu berarti akan ada diskontinuitas ini di terminator. Menghapus penjepit menyebabkan masalah lain; tidak ada lagi jahitan yang terlihat, tapi sekarang sorotan specular berlanjut ke sisi gelap bola.

Apakah ada cara sederhana untuk mengatasi ini, atau apakah ini hanya kelemahan yang tidak dapat dihindari dari model Blinn-Phong?

EDIT

TL; DR: Sepertinya ini bukan hanya kelemahan dari model Blinn-Phong.

Saya melakukan beberapa penelitian lebih lanjut tentang BRDF dan menemukan makalah ini: Model BRDF Ward Baru dengan Bounded Albedo dan Fitting Reflectance Data untuk RADIANCE , yang membahas kekurangan model Ward, khususnya di sekitar sudut penggembalaan yang tinggi (tepatnya masalah saya!), Dan bagaimana caranya mereka mengubah model untuk memperbaikinya. Ward adalah model anisotropik, tetapi dapat disederhanakan menjadi isotropik, dan ketika Anda melakukannya pada formulir siap-implementasinya dari halaman 22, Anda mendapatkan:

Saya menghubungkan ini ke kode saya (diperbarui di bawah), aaaannnnnd ... tidak ada dadu. Ini terlihat cantik dalam kasus normal, tetapi menunjukkan mode kegagalan yang sama di edge, dan beberapa yang bahkan lebih spektakuler di luar edge (bahkan lebih buruk daripada Blinn-Phong):

Catatan: kedua model menggunakan parameter "mengkilap", tetapi artinya berbeda untuk setiap model. Tangkapan layar asli dengan shiny = 0,8, untuk Ward saya harus mengubahnya menjadi 0,1.

#version 150

#extension GL_ARB_conservative_depth : enable

in Frag {
    vec3 color;
    vec3 coord;
    vec3 center;
    float R;
};

out vec4 color_out;
layout (depth_greater) out float gl_FragDepth;

uniform mat4 VIEW;
uniform mat4 PROJ;

const vec3 gamma = vec3(1.0 / 2.2);
const float ambientPower = .15;
const float diffusePower = .75;

const bool PHONG = false;
const float specHardness = 60.0;
const float shiny = .1;

const bool WARD = true;

void main() {
    // Find intersection of ray (given by coord) with sphere
    vec3 eyeNormal = normalize(coord);
    float b = dot(center, eyeNormal);
    float c = b * b - (dot(center, center) - R * R);
    if (c < 0.0) {
        discard;  // Doesn't intersect sphere
    }
    vec3 point = (b - sqrt(c)) * eyeNormal;
    // Redo depth part of the projection matrix
    gl_FragDepth = (PROJ[2].z * point.z + PROJ[3].z) / -point.z;

    // Lighting begins here

    // The light dir is in world-space, unlike the others, so we have to project it to view space.
    // The direction (0, 1, 0) corresponds to the 2nd column. By the properties of the view matrix
    // (the 3x3 part is an orthogonal matrix), this is already normalized.
    vec3 lightNormal = VIEW[1].xyz;
    vec3 normal = normalize(point - center);
    float diffuse = dot(lightNormal, normal);

    float specular = 0.0;
    if (PHONG) {
        // Have to reverse sign for eyeNormal so it points out
        vec3 halfway = normalize(lightNormal - eyeNormal);
        specular = diffuse <= 0.0 ? 0.0 : pow(max(0.0, dot(halfway, normal)), specHardness);
    } else if (WARD) {
        const float PI = 3.14159265359;
        const float alpha = .15;
        const float invAlpha2 = 1 / (alpha * alpha);
        // Would move this computation to CPU and pass invAlpha2 as uniform if alpha were a parameter
        const float cFactor = invAlpha2 / PI;

        // Have to reverse sign for eyeNormal so it points out, note this is *unnormalized*
        vec3 halfway = lightNormal - eyeNormal;
        float dotP = dot(halfway, normal);
        float invDot2 = 1 / (dotP * dotP);
        float semiNormalizedInvDot = dot(halfway, halfway) * invDot2;
        // Note: You can't factor the exp(invAlpha2) part out as a constant term,
        // you'll blow out the floating-point range if you try.
        specular = cFactor * exp(invAlpha2-invAlpha2*semiNormalizedInvDot) * semiNormalizedInvDot * invDot2;
    }
    diffuse = max(0.0, diffuse);
    vec3 colorPre = (ambientPower + diffusePower * diffuse) * color
        + specular * shiny * vec3(1);

    color_out = vec4(pow(colorPre, gamma), 0);
}

Jawaban

4 D0SBoots Aug 20 2020 at 05:35

TL; DR: Kalikan nilai specular Anda dengan titik (normal, lightNormal). (Dan melakukan penjepit yang titik produk untuk minimal 0!)

Saya, dan saya curiga (dari melihat semua tutorial di luar sana) banyak orang lain, telah melakukan semua ini dengan salah.

Saya menggunakan Fungsi Distribusi Reflektansi Dua Arah (AKA BRDF) secara langsung untuk menghitung intensitas specular. Namun, BRDF sebenarnya mendefinisikan besaran diferensial yang dimaksudkan untuk dipasang ke integral radiometrik. Krusial, apa yang biasanya disebut dengan BRDF tidak memiliki jangka cos (θ) yang merupakan bagian dari integral keseluruhan. Jika Anda memiliki perut untuk matematika, itu dibahas lebih detail di sini:http://www.pbr-book.org/3ed-2018/Color_and_Radiometry/Surface_Reflection.html#TheBRDF

Saat kita hanya berurusan dengan sumber cahaya titik, kita tidak perlu mengevaluasi keseluruhan integral; batas integral sebagai sumber cahaya menjadi titik menuju integral. Tapi suku cos (θ) masih penting.

Dalam praktiknya, ini berarti shader membutuhkan sedikit penyesuaian. Hasil:

Di atas adalah Blinn-Phong, sudah diperbaiki. Ini dengan kilau yang sama seperti sebelumnya; karena Blinn-Phong tidak memiliki suku Fresnel, penetapan cos (θ) menyebabkan intensitas turun secara dramatis pada sudut penggembalaan. Tapi diskontinuitas hilang, setidaknya.

Ini diperbaiki Ward, dengan kilau yang sama seperti gambar ke-2 sebelumnya. Refleksi Fresnel (kira-kira, refleksi lebih tinggi pada sudut yang lebih miring) dimodelkan di Ward, jadi ini terlihat bagus. Dan tidak ada diskontinuitas!