2016-11-14 51 views
10

Próbuję zaimplementować oświetlenie Oren-Nayar w cieniującym cieniu, jak pokazano here.Oren-Nayar oświetlenie w OpenGL (jak obliczyć kierunek widoku w module cieniującym fragmentu)

Dostaję jednak dziwne efekty świetlne w terenie, jak pokazano poniżej.

Obecnie wysyłam moduł cieniujący w kierunku "kierunku widoku" jako wektor "przodu" kamery. Nie jestem pewien, czy to prawda, ponieważ poruszenie aparatem zmienia artefakty.

Pomnożenie wektora "przód" przez matrycę MVP daje lepszy wynik, ale artefakty są nadal bardzo zauważalne podczas oglądania terenu pod pewnymi kątami. Jest szczególnie zauważalny w ciemnych obszarach i wokół krawędzi ekranu.

Co może być przyczyną tego efektu?

Artifact przykład

enter image description here

Jak scena powinna wyglądać

enter image description here

Vertex Shader

#version 450 

layout(location = 0) in vec3 position; 
layout(location = 1) in vec3 normal; 

out VS_OUT { 
    vec3 normal; 
} vert_out; 

void main() { 
    vert_out.normal = normal; 
    gl_Position = vec4(position, 1.0); 
} 

tesselacji Kontrola Shader

#version 450 

layout(vertices = 3) out; 

in VS_OUT { 
    vec3 normal; 
} tesc_in[]; 

out TESC_OUT { 
    vec3 normal; 
} tesc_out[]; 

void main() { 
    if(gl_InvocationID == 0) { 
     gl_TessLevelInner[0] = 1.0; 
     gl_TessLevelInner[1] = 1.0; 

     gl_TessLevelOuter[0] = 1.0; 
     gl_TessLevelOuter[1] = 1.0; 
     gl_TessLevelOuter[2] = 1.0; 
     gl_TessLevelOuter[3] = 1.0; 
    } 

    tesc_out[gl_InvocationID].normal = tesc_in[gl_InvocationID].normal; 
    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; 
} 

tesselacji ocena Shader

#version 450 

layout(triangles, equal_spacing) in; 

in TESC_OUT { 
    vec3 normal; 
} tesc_in[]; 

out TESE_OUT { 
    vec3 normal; 
    float height; 
    vec4 shadow_position; 
} tesc_out; 

uniform mat4 model_view; 
uniform mat4 model_view_perspective; 
uniform mat3 normal_matrix; 
uniform mat4 depth_matrix; 

vec3 lerp(vec3 v0, vec3 v1, vec3 v2) { 
    return (
     (vec3(gl_TessCoord.x) * v0) + 
     (vec3(gl_TessCoord.y) * v1) + 
     (vec3(gl_TessCoord.z) * v2) 
    ); 
} 

vec4 lerp(vec4 v0, vec4 v1, vec4 v2) { 
    return (
     (vec4(gl_TessCoord.x) * v0) + 
     (vec4(gl_TessCoord.y) * v1) + 
     (vec4(gl_TessCoord.z) * v2) 
    ); 
} 

void main() { 
    gl_Position = lerp(
     gl_in[0].gl_Position, 
     gl_in[1].gl_Position, 
     gl_in[2].gl_Position 
    ); 

    tesc_out.normal = normal_matrix * lerp(
     tesc_in[0].normal, 
     tesc_in[1].normal, 
     tesc_in[2].normal 
    ); 

    tesc_out.height = gl_Position.y; 

    tesc_out.shadow_position = depth_matrix * gl_Position; 
    gl_Position = model_view_perspective * gl_Position; 
} 

Fragment Shader

#version 450 

in TESE_OUT { 
    vec3 normal; 
    float height; 
    vec4 shadow_position; 
} frag_in; 

out vec4 colour; 

uniform vec3 view_direction; 
uniform vec3 light_position; 

#define PI 3.141592653589793 

void main() { 
    const vec3 ambient = vec3(0.1, 0.1, 0.1); 
    const float roughness = 0.8; 

    const vec4 water = vec4(0.0, 0.0, 0.8, 1.0); 
    const vec4 sand = vec4(0.93, 0.87, 0.51, 1.0); 
    const vec4 grass = vec4(0.0, 0.8, 0.0, 1.0); 
    const vec4 ground = vec4(0.49, 0.27, 0.08, 1.0); 
    const vec4 snow = vec4(0.9, 0.9, 0.9, 1.0); 

    if(frag_in.height == 0.0) { 
     colour = water; 
    } else if(frag_in.height < 0.2) { 
     colour = sand; 
    } else if(frag_in.height < 0.575) { 
     colour = grass; 
    } else if(frag_in.height < 0.8) { 
     colour = ground; 
    } else { 
     colour = snow; 
    } 

    vec3 normal = normalize(frag_in.normal); 
    vec3 view_dir = normalize(view_direction); 
    vec3 light_dir = normalize(light_position); 

    float NdotL = dot(normal, light_dir); 
    float NdotV = dot(normal, view_dir); 

    float angleVN = acos(NdotV); 
    float angleLN = acos(NdotL); 

    float alpha = max(angleVN, angleLN); 
    float beta = min(angleVN, angleLN); 
    float gamma = dot(view_dir - normal * dot(view_dir, normal), light_dir - normal * dot(light_dir, normal)); 

    float roughnessSquared = roughness * roughness; 
    float roughnessSquared9 = (roughnessSquared/(roughnessSquared + 0.09)); 

    // calculate C1, C2 and C3 
    float C1 = 1.0 - 0.5 * (roughnessSquared/(roughnessSquared + 0.33)); 
    float C2 = 0.45 * roughnessSquared9; 

    if(gamma >= 0.0) { 
     C2 *= sin(alpha); 
    } else { 
     C2 *= (sin(alpha) - pow((2.0 * beta)/PI, 3.0)); 
    } 

    float powValue = (4.0 * alpha * beta)/(PI * PI); 
    float C3 = 0.125 * roughnessSquared9 * powValue * powValue; 

    // now calculate both main parts of the formula 
    float A = gamma * C2 * tan(beta); 
    float B = (1.0 - abs(gamma)) * C3 * tan((alpha + beta)/2.0); 

    // put it all together 
    float L1 = max(0.0, NdotL) * (C1 + A + B); 

    // also calculate interreflection 
    float twoBetaPi = 2.0 * beta/PI; 

    float L2 = 0.17 * max(0.0, NdotL) * (roughnessSquared/(roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi); 

    colour = vec4(colour.xyz * (L1 + L2), 1.0); 
} 
+0

Jeśli Oren-Nayar jest zbyt kosztowny, warto rozważyć użycie [wrap lighting] (http://http.developer.nvidia.com/GPUGems/gpugems_ch16.html). – BeyelerStudios

Odpowiedz

4

Fir Włączyłem twój shader fragmentów do mojego renderera z moimi widokowymi/normalnymi/lekkimi wektorami i działa idealnie. Problem musi być taki, jak obliczasz te wektory.

Następnie mówisz, że ustawiasz view_dir na przednim wektorze aparatu. Zakładam, że chodziło o "wektor przedni aparatu w przestrzeni świata", który byłby nieprawidłowy. Ponieważ wyliczasz produkty z kropkami za pomocą wektorów w obszarze kamery, kamera view_dir musi również znajdować się w obszarze aparatu. To jest vec3(0,0,1) będzie łatwym sposobem sprawdzenia tego. Jeśli to działa - znaleźliśmy twój problem.

Jednakże użycie (0,0,1) dla kierunku widoku nie jest ściśle poprawne podczas rzutowania perspektywicznego, ponieważ kierunek od fragmentu do kamery zależy od lokalizacji fragmentu na ekranie. Prawidłowa formuła byłaby wtedy view_dir = normalize(-pos) gdzie pos jest pozycją fragmentu w przestrzeni kamery (to znaczy z matrycą widoku modelu zastosowaną bez projekcji).Ponadto, ilość ta zależy teraz tylko od położenia fragmentu na ekranie, dzięki czemu można go obliczyć jako:

view_dir = normalize(vec3(-(gl_FragCoord.xy - frame_size/2)/(frame_width/2), flen)) 

flen jest ogniskowa aparatu, który można obliczyć jako flen = cot(fovx/2).

+0

Dzięki za odpowiedź, niedługo przyjrzę się temu. – Caw

+0

Próbowałem obliczać view_dir w shader fragmentu, jak pokazano na dole odpowiedzi. Czym jest flen? Wprowadzam wartość 1.0 dla flen i rozmiar okna dla frame_size, a artefakty nadal mogą być widoczne w obszarach z cieniem. Spód terenu wygląda szczególnie źle, jeśli światło jest nad nim. Jakieś pomysły? Dzięki za pomoc. – Caw

+0

Miałem formułę niepoprawną. Zobacz edycję, także dotyczącą 'flen'. Nie wiem, co widzisz na "spodniej stronie", ale prawdopodobnie powinieneś odwrócić normalne, kiedy renderujesz tę drugą stronę. Dlaczego jednak dbasz o "spód" terenu? Czy nie powinien zostać zabity? – ybungalobill