Dark spot where light should be when attempting to code pbr shader - glsl

I keep having this bug where there's a black spot right where I would assume the model is supposed to be brightest. I pulled an all-nighter trying to get this to work, but no avail.
I've been following this tutuorial https://learnopengl.com/PBR/Lighting, and referencing this code as well https://github.com/Nadrin/PBR/blob/master/data/shaders/hlsl/pbr.hlsl
As far as I can tell, the math operations I'm doing are identical but they don't produce the intended results. Along with the dark spots, roughness seems to not effect the end result whatsoever, even though I use it in several places that effect the end result.
Here's the code I'm using, all inputs are in world chordinates:
vec3 gammaCorrect(vec3 color)
{
color = color / (color + vec3(1.0));
return pow(color, vec3(1.0/2.2));
}
vec3 shadeDiffuse(vec3 color, vec3 position, vec3 normal)
{
vec3 lightHue = vec3(0,0,0);
for(uint i = 0; i < plb.numLights; ++i)
{
float sqrdist = distance(plb.lights[i].position, position);
sqrdist *= sqrdist;
float b = max(0, dot(normalize(plb.lights[i].position - position), normal) * max(0, plb.lights[i].color.a * (1 / sqrdist)));
lightHue += plb.lights[i].color.xyz * b;
}
color *= lightHue;
return gammaCorrect(color);
}
#ifndef PI
const float PI = 3.14159265359;
#endif
float DistributionGGX(vec3 normal, vec3 viewVec, float roughness)
{
float a2 = pow(roughness, 4);
float NdotH = max(dot(normal, viewVec), 0.0);
float denom = (NdotH*NdotH * (a2 - 1.0) + 1.0);
return a2 / (PI * denom * denom);
}
float GeometrySchlickGGX(float dotp, float roughness)
{
return dotp / (dotp * (1.0 - roughness) + roughness);
}
float GeometrySmith(vec3 normal, vec3 viewVec, vec3 lightVec, float roughness)
{
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
return GeometrySchlickGGX(max(dot(normal, viewVec), 0.0), k) * GeometrySchlickGGX(max(dot(normal, lightVec), 0.0), k);
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
vec3 shadePBR(vec3 albedo, vec3 position, vec3 cameraPos, vec3 normal, float roughness, float metallic)
{
vec3 viewVec = normalize(cameraPos - position);
const vec3 F0 = mix(vec3(0.03), albedo, metallic);
vec3 lightHue = vec3(0);
for(uint i = 0; i < plb.numLights; ++i)
{
// radiance
vec3 lightVec = normalize(plb.lights[i].position - position);
vec3 halfVec = normalize(viewVec + lightVec);
float distance = length(plb.lights[i].position - position);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = plb.lights[i].color.xyz * attenuation * max(plb.lights[i].color.a, 0);
// brdf
float NDF = DistributionGGX(halfVec, normal, roughness);
float G = GeometrySmith(normal, viewVec, lightVec, roughness);
vec3 F = fresnelSchlick(max(dot(halfVec, viewVec), 0.0), F0);
vec3 kD = mix(vec3(1)-F, vec3(0), metallic);
float viewDot = max(dot(normal, viewVec), 0.0);
float lightDot = max(dot(normal, lightVec), 0.0);
vec3 specular = (NDF * G * F) / (4.0 * max(viewDot * lightDot, 0.000001));
// add to hue
lightHue += (kD * albedo / PI + specular) * radiance * lightDot;
}
//Add in ambient here later
vec3 color = lightHue;
return gammaCorrect(color);
}
I'm going to go sleep now, thanks for any help in advance.

So turns out I'm very stupid. Problem was that I was trying to grab the camera position from the render matrix, and as I have found out, you can't really grab a clean position from that without fully disassembling it, instead of just grabbing a few indexes from it. Passed camera position with a uniform instead and code immediately worked perfectly.

Related

Compute Normals After Vertex Deformation?

I am coding a vertex and a fragment shader trying to distort the surface of some water and then computing blinn-phong lighting on the surface. I am able to successfully compute the deformed matrices with a simple noise function, but how can I find the distorted normals? Since it isn't a linear transformation I am stuck, could anyone help?
Here are the relevant files:
vertex shader:
#version 150
uniform mat4 u_Model;
uniform mat4 u_ModelInvTr;
uniform mat4 u_ViewProj;
uniform vec4 u_Color;
uniform int u_Time;
in vec4 vs_Pos; // The array of vertex positions passed to the shader
in vec4 vs_Nor; // The array of vertex normals passed to the shader
in vec4 vs_Col; // The array of vertex colors passed to the shader.
in vec2 vs_UV; // UV coords for texture to pass thru to fragment shader
in float vs_Anim; // 0.f or 1.f To pass thru to fragment shader
in float vs_T2O;
out vec4 fs_Pos;
out vec4 fs_Nor;
out vec4 fs_LightVec;
out vec4 fs_Col;
out vec2 fs_UVs;
out float fs_Anim;
out float fs_dimVal;
out float fs_T2O;
uniform vec4 u_CamPos;
out vec4 fs_CamPos;
const vec4 lightDir = normalize(vec4(0.0, 1.f, 0.0, 0));
mat4 rotationMatrix(vec3 axis, float angle) {
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 0.0, 0.0, 0.0, 1.0);
}
vec4 rotateLightVec(float deg, vec4 LV) {
mat4 R = rotationMatrix(vec3(0,0,1), deg);
return R * LV;
}
float random1(vec3 p) {
return fract(sin(dot(p, vec3(127.1, 311.7, 191.999)))*43758.5453);
}
vec3 random2( vec3 p ) {
return fract( sin( vec3(dot(p, vec3(127.1, 311.7, 58.24)), dot(p, vec3(269.5, 183.3, 657.3)), dot(p, vec3(420.69, 69.420, 469.20))) ) * 43758.5453);
}
void main()
{
fs_Col = vs_Col;
fs_UVs = vs_UV;
fs_Anim = vs_Anim;
fs_T2O = vs_T2O;
mat3 invTranspose = mat3(u_ModelInvTr);
fs_Nor = vec4(invTranspose * vec3(vs_Nor), 0);
vec4 modelposition = u_Model * vs_Pos;
if (vs_Anim != 0) { // if we want to animate this surface
// check region in texture to decide which animatable type is drawn
bool lava = fs_UVs.x >= 13.f/16.f && fs_UVs.y < 2.f/16.f;
bool water = !lava && fs_UVs.x >= 13.f/16.f && fs_UVs.y <= 4.f/16.f;
if (water) {
// define an oscillating time so that model can transition back and forth
float t = (cos(u_Time * 0.05) + 1)/2; // u_Time increments by 1 every frame. Domain [0,1]
vec3 temp = random2(vec3(modelposition.x, modelposition.y, modelposition.z)); // range [0, 1]
temp = (temp - 0.5)/25; // [0, 1/scalar]
modelposition.x = mix(modelposition.x - temp.x, modelposition.x + temp.x, t);
modelposition.y = mix(modelposition.y - temp.y, modelposition.y + 3*temp.y, t);
modelposition.z = mix(modelposition.z - temp.z, modelposition.z + temp.z, t);
} else if (lava) {
// define an oscillating time so that model can transition back and forth
float t = (cos(u_Time * 0.01) + 1)/2; // u_Time increments by 1 every frame. Domain [0,1]
vec3 temp = random2(vec3(modelposition.x, modelposition.y, modelposition.z)); // range [0, 1]
temp = (temp - 0.5)/25; // [0, 1/scalar]
modelposition.x = mix(modelposition.x - temp.x, modelposition.x + temp.x, t);
modelposition.y = mix(modelposition.y - temp.y, modelposition.y + 3*temp.y, t);
modelposition.z = mix(modelposition.z - temp.z, modelposition.z + temp.z, t);
}
}
fs_dimVal = random1(modelposition.xyz/100.f);
fs_LightVec = rotateLightVec(0.001 * u_Time, lightDir); // Compute the direction in which the light source lies
fs_CamPos = u_CamPos; // uniform handle for the camera position instead of the inverse
fs_Pos = modelposition;
gl_Position = u_ViewProj * modelposition;// gl_Position is a built-in variable of OpenGL which is
// used to render the final positions of the geometry's vertices
}
fragment shader:
#version 330
uniform vec4 u_Color; // The color with which to render this instance of geometry.
uniform sampler2D textureSampler;
uniform int u_Time;
uniform mat4 u_ViewProj;
uniform mat4 u_Model;
in vec4 fs_Pos;
in vec4 fs_Nor;
in vec4 fs_LightVec;
in vec4 fs_Col;
in vec2 fs_UVs;
in float fs_Anim;
in float fs_T2O;
in float fs_dimVal;
out vec4 out_Col;
in vec4 fs_CamPos;
float random1(vec3 p) {
return fract(sin(dot(p,vec3(127.1, 311.7, 191.999)))
*43758.5453);
}
float random1b(vec3 p) {
return fract(sin(dot(p,vec3(169.1, 355.7, 195.999)))
*95751.5453);
}
float mySmoothStep(float a, float b, float t) {
t = smoothstep(0, 1, t);
return mix(a, b, t);
}
float cubicTriMix(vec3 p) {
vec3 pFract = fract(p);
float llb = random1(floor(p) + vec3(0,0,0));
float lrb = random1(floor(p) + vec3(1,0,0));
float ulb = random1(floor(p) + vec3(0,1,0));
float urb = random1(floor(p) + vec3(1,1,0));
float llf = random1(floor(p) + vec3(0,0,1));
float lrf = random1(floor(p) + vec3(1,0,1));
float ulf = random1(floor(p) + vec3(0,1,1));
float urf = random1(floor(p) + vec3(1,1,1));
float mixLoBack = mySmoothStep(llb, lrb, pFract.x);
float mixHiBack = mySmoothStep(ulb, urb, pFract.x);
float mixLoFront = mySmoothStep(llf, lrf, pFract.x);
float mixHiFront = mySmoothStep(ulf, urf, pFract.x);
float mixLo = mySmoothStep(mixLoBack, mixLoFront, pFract.z);
float mixHi = mySmoothStep(mixHiBack, mixHiFront, pFract.z);
return mySmoothStep(mixLo, mixHi, pFract.y);
}
float fbm(vec3 p) {
float amp = 0.5;
float freq = 4.0;
float sum = 0.0;
for(int i = 0; i < 8; i++) {
sum += cubicTriMix(p * freq) * amp;
amp *= 0.5;
freq *= 2.0;
}
return sum;
}
void main()
{
vec4 diffuseColor = texture(textureSampler, fs_UVs);
bool apply_lambert = true;
float specularIntensity = 0;
if (fs_Anim != 0) {
// check region in texture to decide which animatable type is drawn
bool lava = fs_UVs.x >= 13.f/16.f && fs_UVs.y < 2.f/16.f;
bool water = !lava && fs_UVs.x >= 13.f/16.f && fs_UVs.y < 4.f/16.f;
if (lava) {
// slowly gyrate texture and lighten and darken with random dimVal from vert shader
vec2 movingUVs = vec2(fs_UVs.x + fs_Anim * 0.065/16 * sin(0.01*u_Time),
fs_UVs.y - fs_Anim * 0.065/16 * sin(0.01*u_Time + 3.14159/2));
diffuseColor = texture(textureSampler, movingUVs);
vec4 warmerColor = diffuseColor + vec4(0.3, 0.3, 0, 0);
vec4 coolerColor = diffuseColor - vec4(0.1, 0.1, 0, 0);
diffuseColor = mix(warmerColor, coolerColor, 0.5 + fs_dimVal * 0.65*sin(0.02*u_Time));
apply_lambert = false;
} else if (water) {
// blend between 3 different points in texture to create a wavy subtle change over time
vec2 offsetUVs = vec2(fs_UVs.x - 0.5f/16.f, fs_UVs.y - 0.5f/16.f);
diffuseColor = texture(textureSampler, fs_UVs);
vec4 altColor = texture(textureSampler, offsetUVs);
altColor.x += fs_dimVal * pow(altColor.x+.15, 5);
altColor.y += fs_dimVal * pow(altColor.y+.15, 5);
altColor.z += 0.5 * fs_dimVal * pow(altColor.z+.15, 5);
diffuseColor = mix(diffuseColor, altColor, 0.5 + 0.35*sin(0.05*u_Time));
offsetUVs -= 0.25f/16.f;
vec4 newColor = texture(textureSampler, offsetUVs);
diffuseColor = mix(diffuseColor, newColor, 0.5 + 0.5*sin(0.025*u_Time)) + fs_dimVal * vec4(0.025);
diffuseColor.a = 0.7;
// ----------------------------------------------------
// Blinn-Phong Shading
// ----------------------------------------------------
vec4 lightDir = normalize(fs_LightVec - fs_Pos);
vec4 viewDir = normalize(fs_CamPos - fs_Pos);
vec4 halfVec = normalize(lightDir + viewDir);
float shininess = 400.f;
float specularIntensity = max(pow(dot(halfVec, normalize(fs_Nor)), shininess), 0);
}
}
// Calculate the diffuse term for Lambert shading
float diffuseTerm = dot(normalize(fs_Nor), normalize(fs_LightVec));
// Avoid negative lighting values
diffuseTerm = clamp(diffuseTerm, 0, 1);
float ambientTerm = 0.3;
float lightIntensity = diffuseTerm + ambientTerm; //Add a small float value to the color multiplier
//to simulate ambient lighting. This ensures that faces that are not
//lit by our point light are not completely black.
vec3 col = diffuseColor.rgb;
// Compute final shaded color
if (apply_lambert) {
col = col * lightIntensity + col * specularIntensity;
}
// & Check the rare, special case where we draw face between two diff transparent blocks as opaque
if (fs_T2O != 0) {
out_Col = vec4(col, 1.f);
} else {
out_Col = vec4(col, diffuseColor.a);
}
// distance fog!
vec4 fogColor = vec4(0.6, 0.75, 0.9, 1.0);
float FC = gl_FragCoord.z / gl_FragCoord.w / 124.f;
float falloff = clamp(1.05 - exp(-1.05f * (FC - 0.9f)), 0.f, 1.f);
out_Col = mix(out_Col, fogColor, falloff);
}
I tried implementing blinn-phong in the fragment shader, but I think it is wrong simple from the wrong normals. I think this can be done with some sort of tangent and cross product solution, but how can I know the tangent of the surface given we only know the vertex position?
I am not using unity, just bare c++ and most of the answers I am finding online are for java or unity which I do not understand.`

Why shadows are not rendered when PBR directional light is applied?

I've got a problem with rendering hard shadows in a PBR pipeline.
I believe there is something wrong with PBR calculations because with a Blinn-Phong lighting model everything looks fine.
These are lightning calculations - basic PBR
struct DirectionalLight
{
vec3 direction;
};
layout(std140, binding = 2) uniform Scene
{
DirectionalLight directionalLight;
vec3 viewPosition;
} u_scene;
layout(std140, binding = 4) uniform Material
{
vec4 baseColor;
float roughness;
float metalness;
} u_material;
const float PI = 3.14159265359;
const float epsilon = 0.00001;
int lightCount = 1;
vec3 CalculateDirectionalLight(vec3 N, vec3 V, float NdotV, vec3 F0)
{
vec3 result;
for(int i = 0; i < lightCount; ++i) {
vec3 L = normalize(-u_scene.directionalLight.direction);
float NdotL = max(0.0f, dot(N, L));
vec3 H = normalize(V + L);
float NdotH = max(0.0f, dot(N, H));
vec3 F = FresnelSchlickRoughness(max(0.0f, dot(H, V)), F0, u_material.roughness);
float D = NDFGGX(NdotH, u_material.roughness);
float G = GeometrySmith(NdotL, NdotV, u_material.roughness);
vec3 kd = (1.0f - F) * (1.0f - u_material.metalness);
vec3 diffuse = kd * u_material.baseColor.rgb;
vec3 nominator = F * G * D;
float denominator = max(epsilon, 4.0f * NdotV * NdotL);
vec3 specular = nominator / denominator;
specular = clamp(specular, vec3(0.0f), vec3(10.0f));
result += (diffuse + specular) /* u_material.radiance */ * NdotL;
}
return result;
}
float NDFGGX(float NdotH, float roughness)
{
float alpha = roughness * roughness;
float alphaSq = alpha * alpha;
float denom = (NdotH * NdotH) * (alphaSq - 1.0) + 1.0;
return alphaSq / (PI * denom * denom);
}
float GeometrySchlickGGX(float Ndot, float k)
{
float nom = Ndot;
float denom = Ndot * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(float NdotL, float NdotV, float roughness)
{
float r = (roughness + 1.0f);
float k = (r * r) / 8.0f;
float ggx2 = GeometrySchlickGGX(NdotV, k);
float ggx1 = GeometrySchlickGGX(NdotL, k);
return ggx1 * ggx2;
}
vec3 FresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
shadow functions
layout(binding = 2) uniform sampler2D u_shadowMap;
float ShadowFade = 1.0;
float GetShadowBias()
{
const float MINIMUM_SHADOW_BIAS = 0.002;
float bias = max(MINIMUM_SHADOW_BIAS * (1.0 - dot(normalize(v_normal), -normalize(u_scene.directionalLight.direction))), MINIMUM_SHADOW_BIAS);
return bias;
}
float HardShadows_DirectionalLight(vec4 fragPosLightSpace)
{
vec3 shadowCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
float bias = GetShadowBias();
float shadowMapDepth = texture(u_shadowMap, vec2(shadowCoords.xy * 0.5 + 0.5)).r;
return step(shadowCoords.z, shadowMapDepth + bias) * ShadowFade;
}
and the main function
void main()
{
vec3 F0 = vec3(0.04f);
F0 = mix(F0, u_material.baseColor.rgb, u_material.metalness);
vec3 N = normalize(v_normal);
vec3 V = normalize(u_scene.viewPosition - v_position);
float NdotV = max(0.0f, dot(N, V));
//v_positionFromLight is calculated in a vertex shader like this:
//v_positionFromLight = u_lightViewProjection * vec4(v_position, 1.0f);
//where v_position is modelMatrix * a_position;
//where a_position is a input position of a vertex
float shadow = HardShadows_DirectionalLight(v_positionFromLight);
vec3 ambient = u_material.baseColor.rgb * 0.3f;
vec3 lightContribution = ambient + CalculateDirectionalLight(N, V, NdotV, F0) * shadow;
f_color = vec4(lightContribution, 1.0);
}
and this is how the scene looks like - there should be visible shadows, but there aren't:
I've tested 2 things.
First - Blinn-Phong lighting model - shadows render just fine.
Second - output shadow calculations without PBR lightning
like this:
void main()
{
float shadow = HardShadows_DirectionalLight(v_positionFromLight);
vec3 ambient = u_material.baseColor.rgb * 0.3f;
f_color = vec4(ambient * shadow, 1.0f);
}
and it also works (besides that they're not placed in a good spot, but that is another topic):
Why this PBR model does not work with shadows?
How can I fix it?

Oren-Nayar lighting in OpenGL (how to calculate view direction in fragment shader)

I'm trying to implement Oren-Nayar lighting in the fragment shader as shown here.
However, I'm getting some strange lighting effects on the terrain as shown below.
I am currently sending the shader the 'view direction' uniform as the camera's 'front' vector. I am not sure if this is correct, as moving the camera around changes the artifacts.
Multiplying the 'front' vector by the MVP matrix gives a better result, but the artifacts are still very noticable when viewing the terrain from some angles. It is particularly noticable in dark areas and around the edges of the screen.
What could be causing this effect?
Artifact example
How the scene should look
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);
}
Tesselation Control 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;
}
Tesselation Evaluation 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);
}
First I've plugged your fragment shader into my renderer with my view/normal/light vectors and it works perfectly. So the problem has to be in the way you calculate those vectors.
Next, you say that you set view_dir to your camera's front vector. I assume that you meant "camera's front vector in the world space" which would be incorrect. Since you calculate the dot products with vectors in the camera space, the view_dir must be in the camera space too. That is vec3(0,0,1) would be an easy way to check that. If it works -- we found your problem.
However, using (0,0,1) for the view direction is not strictly correct when you do perspective projection, because the direction from the fragment to the camera then depends on the location of the fragment on the screen. The correct formula then would be view_dir = normalize(-pos) where pos is the fragment's position in camera space (that is with model-view matrix applied without the projection). Further, this quantity now depends only on the fragment location on the screen, so you can calculate it as:
view_dir = normalize(vec3(-(gl_FragCoord.xy - frame_size/2) / (frame_width/2), flen))
flen is the focal length of your camera, which you can calculate as flen = cot(fovx/2).
I know this is a long dead thread, but I've been having the same problem (for several years), and finally found the solution...
It can be partially solved by fixing the orientation of the surface normals to match the polygon winding direction, but you can also get rid of the artifacts in the shader, by changing the following two lines...
float angleVN = acos(cos_nv);
float angleLN = acos(cos_nl);
to this...
float angleVN = acos(clamp(cos_nv, -1.0, 1.0));
float angleLN = acos(clamp(cos_nl, -1.0, 1.0));
Tada!

Few problems with BRDF using Beckmann and GGX/Trowbridge-Reitz distribution for comparison

I have been trying to wrap my head around physical based rendering these last 2.5 weeks and so far I managed to learn a lot, ask a lot of questions, and have some results, although I still have few problems that I would like to fix but the last few days I am stuck. I am want to continue working/learning more but now I don't know what else to do or how to proceed further, thus I need some guidance :(
One of the first problems that I can not figure out what is happening is when I get close to a shape. There is a cut-off problem with BRDF function that I have implemented. The second and third row are BRDF functions using Spherical Gaussian for Fresnel, and Schlick approximation. The second row Beckmann distribution NDF and the third one uses GGX/Trowbridge-Reitz as NDF.
I started implementing this referring to "Real Shading in Unreal Engine 4" and few other posts found while Google-ing.
What I believe the remaining things to do are:
How to blend diffuse, reflection, and speculal better
Fix the problem with the BRDF cut-off problem
Evaluate if my shaders are producing good results based on the equation (it is the first time for me going this way and some comments would be very helpful as a guide on how to proceed in tweaking things)
Fix specular factor in Phong (first row) shader, now I use material roughness as a blend factor when I mix Phong, skybox reflection and diffuse
The code I use for BRDF's is
// geometry term Cook Torrance
float G(float NdotH, float NdotV, float VdotH, float NdotL) {
float NH2 = 2.0 * NdotH;
float g1 = (NH2 * NdotV) / VdotH;
float g2 = (NH2 * NdotL) / VdotH;
return min(1.0, min(g1, g2));
}
// Fresnel reflection term, Schlick approximation
float R_Fresnel(float VdotH) {
return F0 + (1.0 - F0) * pow(2, (-5.55473 * (VdotH)-6.98316) * (VdotH));
}
// Normal distribution function, GGX/Trowbridge-Reitz
float D_GGX(float NdotH, float roughtness2) {
float a = roughtness2 * roughtness2;
float a2 = a*a;
float t = ((NdotH * NdotH) * (a2 - 1.0) + 1.0);
return a2 / (PI * t * t);
}
// Normal distribution function, Beckmann distribution
float D_Beckmann(float NdotH, float mSquared) {
float r1 = 1.0 / (4.0 * mSquared * pow(NdotH, 4.0));
float r2 = (NdotH * NdotH - 1.0) / (mSquared * NdotH * NdotH);
return (r1 * exp(r2));
}
// COOK TORRANCE BRDF
vec4 cookTorrance(Light light, vec3 direction, vec3 normal) {
// do the lighting calculation for each fragment.
float NdotL = max(dot(normal, direction), 0.0);
float specular = 0.0;
if (NdotL > 0.0)
{
vec3 eyeDir = normalize(cameraPosition);
// calculate intermediary values
vec3 halfVector = normalize(direction + eyeDir);
float NdotH = max(dot(normal, halfVector), 0.0);
float NdotV = max(dot(normal, eyeDir), 0.0);
float VdotH = max(dot(eyeDir, halfVector), 0.0);
float matShininess = (material.shininess / 1000.0);
float mSquared = (0.99 - matShininess) * (0.99 - matShininess);
float geoAtt = G(NdotH, NdotV, VdotH, NdotL);
float roughness = D_Beckmann(NdotH, mSquared);
float fresnel = R_Fresnel(VdotH);
specular = (fresnel * geoAtt * roughness) / (NdotV * NdotL * PI);
}
vec3 finalValue = light.color * NdotL * (k + specular * (1.0 - k));
return vec4(finalValue, 1.0);
}
vec4 cookTorrance_GGX(Light light, vec3 direction, vec3 normal) {
// do the lighting calculation for each fragment.
float NdotL = max(dot(normal, direction), 0.0);
float specular = 0.0;
if (NdotL > 0.0)
{
vec3 eyeDir = normalize(cameraPosition);
// calculate intermediary values
vec3 halfVector = normalize(direction + eyeDir);
float NdotH = max(dot(normal, halfVector), 0.0);
float NdotV = max(dot(normal, eyeDir), 0.0);
float VdotH = max(dot(eyeDir, halfVector), 0.0);
float matShininess = (material.shininess / 1000.0);
float mSquared = (0.99 - matShininess) * (0.99 - matShininess);
float geoAtt = G(NdotH, NdotV, VdotH, NdotL);
// NDF CHANGED TO GGX
float roughness = D_GGX(NdotH, mSquared);
float fresnel = R_Fresnel(VdotH);
specular = (fresnel * geoAtt * roughness) / (NdotV * NdotL * PI);
}
vec3 finalValue = light.color * NdotL * (k + specular * (1.0 - k));
return vec4(finalValue, 1.0);
}
void main() {
//vec4 tempColor = vec4(material.diffuse, 1.0);
vec4 tempColor = vec4(0.1);
// interpolating normals will change the length of the normal, so renormalize the normal.
vec3 normal = normalize(Normal);
vec3 I = normalize(Position - cameraPosition);
vec3 R = reflect(I, normalize(Normal));
vec4 reflection = texture(skybox, R);
// fix blending
float shininess = (material.shininess / 1000.0);
vec4 tempFinalDiffuse = mix(tempColor, reflection, shininess);
vec4 finalValue = cookTorrance_GGX(directionalLight.light, directionalLight.position, normal) + tempFinalDiffuse;
// OR FOR COOK TORRANCE IN THE OTHER SHADER PROGRAM
//vec4 finalValue = cookTorrance(directionalLight.light, directionalLight.position, normal) + tempFinalDiffuse;
gl_FragColor = finalValue;
//gl_FragColor = vec4(1.0); // TESTING AND DEBUGGING FRAG OUT
}
The results i have so far are lik in pictures below
EDIT :: I managed to solve few problems and implement environment sampling given in "Real Shading in Unreal Engine 4" but still I just cant figure out why I have that cut-off problem and I have a problem with reflection now after sampling. :(
Also I moved Phong that i tough in books and online tutorial to BDRF Blinn-Phong for better comparison.
My shader now looks like this.
vec4 brdf_GGX(Light light, vec3 direction, vec3 normal) {
float specular = 0.0;
float matShininess = 1.0 - (material.shininess / 1000.0);
vec2 randomPoint;
vec4 finalColor = vec4(0.0);
vec4 totalLambert = vec4(0.0);
const uint numberSamples = 32;
for (uint sampleIndex = 0; sampleIndex < numberSamples; sampleIndex++)
{
randomPoint = hammersley2d(sampleIndex, numberSamples);
vec3 H = ImportanceSampleGGX(randomPoint, matShininess, normal);
vec3 L = 2.0 * dot(normal, H) * H - normal;
vec3 R = reflect(L, normalize(normal));
totalLambert += texture(skybox, -R);
}
totalLambert = totalLambert / numberSamples;
float NdotL = max(dot(normal, direction), 0.0);
if (NdotL > 0.0)
{
vec3 eyeDir = normalize(cameraPosition);
// calculate intermediary values
vec3 halfVector = normalize(direction + eyeDir);
float NdotH = max(dot(normal, halfVector), 0.0);
float NdotV = max(dot(normal, eyeDir), 0.0);
float VdotH = max(dot(eyeDir, halfVector), 0.0);
float mSquared = clamp(matShininess * matShininess, 0.01, 0.99);
float geoAtt = G(NdotH, NdotV, VdotH, NdotL);
float roughness = D_Beckmann(NdotH, mSquared);
float fresnel = R_Fresnel(VdotH);
specular = (fresnel * geoAtt * roughness) / (NdotV * NdotL * PI);
}
vec3 finalValue = light.color * NdotL * (k + specular * (1.0 - k));
return vec4(finalValue, 1.0) * totalLambert;
}
Current results look like this (NOTE: I used skybox sampling only in the third GGX model, do the same for other shaders tomorrow)
EDIT:: OK i am figuring out what is happening but still i can not fix it. I have problems when sampling. I have no idea how to translate normalized ray to proper cube map reflection after sampling. If you can notice in pictures I lost the correct reflection that sphere does to environment map. I just have a simple/flat texture on each sphere and now I have no idea how to fix that.

Physically based shader not producing desired results

Over the past ~2-3 weeks, i've been learning about Physically Based Shading and I just cannot wrap my head around some of the problems I'm having.
Fragment Shader
#version 430
#define PI 3.14159265358979323846
// Inputs
in vec3 inputNormal;
vec3 fNormal;
// Material
float reflectance = 1.0; // 0 to 1
float roughness = 0.5;
vec3 specularColor = vec3(1.0, 1.0, 1.0); // f0
// Values
vec3 lightVector = vec3(1, 1, 1); // Light (l)
vec3 eyeVector = vec3(2.75, 1.25, 1.25); // Camera (v)
vec3 halfVector = normalize(lightVector + eyeVector); // L + V / |L + V|
out vec4 fColor; // Output Color
// Specular Functions
vec3 D(vec3 h) // Normal Distribution Function - GGX/Trowbridge-Reitz
{
float alpha = roughness * roughness;
float alpha2 = alpha * alpha;
float NoH = dot(fNormal, h);
float finalTerm = ((NoH * NoH) * (alpha2 - 1.0) + 1.0);
return vec3(alpha2 / (PI * (finalTerm * finalTerm)));
}
vec3 Gsub(vec3 v) // Sub Function of G
{
float k = ((roughness + 1.0) * (roughness + 1.0)) / 8;
return vec3(dot(fNormal, v) / ((dot(fNormal, v)) * (1.0 - k) + k));
}
vec3 G(vec3 l, vec3 v, vec3 h) // Geometric Attenuation Term - Schlick Modified (k = a/2)
{
return Gsub(l) * Gsub(v);
}
vec3 F(vec3 v, vec3 h) // Fresnel - Schlick Modified (Spherical Gaussian Approximation)
{
vec3 f0 = specularColor; // right?
return f0 + (1.0 - f0) * pow(2, (-5.55473 * (dot(v, h)) - 6.98316) * (dot(v, h)));
}
vec3 specular()
{
return (D(halfVector) * F(eyeVector, halfVector) * G(lightVector, eyeVector, halfVector)) / 4 * ((dot(fNormal, lightVector)) * (dot(fNormal, eyeVector)));
}
vec3 diffuse()
{
float NoL = dot(fNormal, lightVector);
vec3 result = vec3(reflectance / PI);
return result * NoL;
}
void main()
{
fNormal = normalize(inputNormal);
fColor = vec4(diffuse() + specular(), 1.0);
//fColor = vec4(D(halfVector), 1.0);
}
So far I have been able to fix up some things and now I get a better result.
However it now seems clear that the highlight is way too big; this originates from the normal distribution function (Specular D).
Your coding of GGX/Trowbridge-Reitz is wrong:
vec3 NxH = fNormal * h;
The star * means a term by term product where you want a dot product
Also
float alphaTerm = (alpha * alpha - 1.0) + 1.0;
Is not correct since the formula multiplies n.m by (alpha * alpha - 1.0) before adding 1.0. Yours formula is equal to alpha*alpha!
Try:
// Specular
vec3 D(vec3 h) // Normal Distribution Function - GGX/Trowbridge-Reitz
{
float alpha = roughness * roughness;
float NxH = dot(fNormal,h);
float alpha2 = alpha*alpha;
float t = ((NxH * NxH) * (alpha2 - 1.0) + 1.0);
return alpha2 / (PI * t * t);
}
In many other places you use * instead of dot. You need to correct all these. Also, check for your formulas, many seem incorrect.