ปัญหาเกี่ยวกับแสง specular ที่มุมเฉียงสูงกับ Blinn-Phong

Aug 17 2020

ฉันมีปัญหากับตัวแสดงผล Blinn-Phong พื้นฐานของฉันเมื่อมองไปที่วัตถุในมุมที่เอียงมาก:

ฉันไม่คิดว่านี่เป็นปัญหากับรหัสของฉันแม้ว่าฉันจะโพสต์ GLSL ของส่วนย่อยของฉันไว้ด้านล่าง แต่ดูเหมือนว่าจะเป็นผลที่จำเป็นในการตัดส่วนประกอบ specular ของแสงให้เหลือศูนย์เมื่อจุด (ปกติ, แสง) <= 0 (ซึ่งทุกคนบอกให้คุณทำ) แต่การทำเช่นนั้นหมายความว่าจะมีความไม่ต่อเนื่องนี้ที่ เทอร์มิเนเตอร์ การถอดที่หนีบนำไปสู่ปัญหาอื่น ๆ ไม่มีรอยต่อที่มองเห็นได้อีกต่อไป แต่ตอนนี้ไฮไลต์ที่โดดเด่นยังคงอยู่รอบด้านมืดของทรงกลม

มีวิธีง่ายๆในการแก้ปัญหานี้หรือเป็นเพียงข้อเสียที่หลีกเลี่ยงไม่ได้ของแบบจำลอง Blinn-Phong?

แก้ไข

TL; DR: ดูเหมือนว่านี่ไม่ใช่แค่ข้อเสียของแบบจำลอง Blinn-Phong

ฉันได้ทำการวิจัยเพิ่มเติมเกี่ยวกับ BRDFและพบเอกสารนี้: แบบจำลองใหม่ของ Ward BRDF ที่มี Bounded Albedo และข้อมูลการสะท้อนแสงที่เหมาะสมสำหรับการแผ่รังสีซึ่งกล่าวถึงข้อบกพร่องของแบบจำลอง Ward โดยเฉพาะเกี่ยวกับมุมเล็มหญ้าสูง (ตรงประเด็นของฉัน!) และวิธีการ พวกเขาปรับแต่งโมเดลเพื่อแก้ไข วอร์ดเป็นแบบจำลองแอนไอโซทรอปิก แต่สามารถทำให้เป็นไอโซทรอปิกได้ง่ายขึ้นและเมื่อคุณทำเช่นนั้นกับแบบฟอร์มที่พร้อมใช้งานจากหน้า 22 คุณจะได้รับ:

ฉันเสียบสิ่งนี้เข้ากับรหัสของฉัน (อัปเดตด้านล่าง) aaaannnnnd ... ไม่มีลูกเต๋า มันดูสวยในกรณีปกติ แต่มีโหมดความล้มเหลวแบบเดียวกันที่ขอบและบางโหมดที่งดงามยิ่งกว่าขอบ (แย่กว่า Blinn-Phong):

หมายเหตุ: ทั้งสองรุ่นใช้พารามิเตอร์ "เงา" แต่มีความหมายแตกต่างกันไปในแต่ละรุ่น ภาพหน้าจอดั้งเดิมเป็นเงา = .8 สำหรับ Ward ฉันต้องลดลงเป็น. 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);
}

คำตอบ

4 D0SBoots Aug 20 2020 at 05:35

TL; DR: คูณค่า specular ของคุณทีละจุด (ปกติ lightNormal) (และทำยึดสินค้าที่จุดต่ำสุดของ 0!)

ฉันและฉันสงสัย (จากการดูบทเรียนทั้งหมดที่นั่น) คนอื่น ๆ หลายคนทำสิ่งนี้ผิด

ฉันใช้ฟังก์ชันการกระจายการสะท้อนแสงแบบสองทิศทาง (AKA BRDF) โดยตรงเพื่อคำนวณความเข้มของสเปกตรัม อย่างไรก็ตาม BRDF กำหนดปริมาณที่แตกต่างซึ่งหมายถึงการต่อเข้ากับอินทิกรัลเรดิโอเมตริก ขับเคลื่อนสิ่งที่มักจะเรียกว่าBRDF ขาด cos ระยะ (θ) ที่เป็นส่วนหนึ่งของหนึ่งโดยรวม หากคุณมีอาการปวดท้องสำหรับคณิตศาสตร์จะมีการพูดคุยรายละเอียดเพิ่มเติมที่นี่:http://www.pbr-book.org/3ed-2018/Color_and_Radiometry/Surface_Reflection.html#TheBRDF

เมื่อเราจัดการกับแหล่งกำเนิดแสงแบบจุดเราไม่จำเป็นต้องประเมินอินทิกรัลทั้งหมด ขีด จำกัด ของอินทิกรัลเป็นแหล่งกำเนิดแสงกลายเป็นจุดที่ไปที่อินทิกรัล แต่คำว่า cos (θ) ยังคงมีความสำคัญ

ในทางปฏิบัติหมายความว่า shader ต้องการการปรับแต่งเล็กน้อย ผล:

ด้านบนคือบลินน์ - พงษ์คงที่ นี่คือกับเดียวกันสว่างไสวเหมือน แต่ก่อน เนื่องจาก Blinn-Phong ไม่มีคำว่า Fresnel การแก้ไข cos (θ) ทำให้ความเข้มลดลงอย่างมากที่มุมทุ่งเลี้ยงสัตว์ แต่ความไม่ต่อเนื่องก็หายไปอย่างน้อยที่สุด

สิ่งนี้ได้รับการแก้ไข Ward โดยมีความแวววาวเหมือนกับภาพที่ 2 ก่อนหน้านี้ การสะท้อนแบบเฟรส (โดยประมาณการสะท้อนที่สูงขึ้นในมุมที่เอียงมากขึ้น) ถูกจำลองขึ้นใน Ward ดังนั้นสิ่งนี้จึงดูดี และไม่ต่อเนื่อง!