Hi!
So over the summer I decided to continue working on my 3D project that i started earlier this year, and one of the things I wanted to try was implementing pbr, since I wanted to refresh my shader programming as well as try something a bit fancier than what I have done before. For the information I have used the notes from when frostbite moved to pbr as well as some notes and code from one of the teachers at my school. I am definitly no expert at the subject which is why I'm now looking for some help with it :) I have tried inputing the same materials and models in another program (marmoset) to get some kind of reference of how it should look when it's done (though they might of course use more effects or different implementations than I do so I'm not expecting it to be exactly the same), pictures can be seen here.
So to start I used the information to implement the lighting calculations, the result can be seen here
After reading some more I realised that I needed to implement some sort of reflections, but I still think that there might be some slight problems with my calculations even before that, at least I feel like the grip on the gun is too dark, since it isn't metallisc, shouldn't it be more brownish even at this stage? Same things kinda go for the wood planks on the box.
For reflections my first thought was to utilize a cubemap and use that, so I have implemented a skybox and cubemap rendering. But at this point I am not really sure how to then use it in my shader. The frostbite notes do not seem to mention it (at least not that I found), so I used some info and code that I got from my teacher to experiment. The result can be seen here
It's not terrible (in my eyes) but it doesn't seem correct either. The reflection seems to be to dominant and things seem to be too "shiny", especially the wood. Also for some reason now it doesn't actually seem to mather where I place my light or how far it's range is (triangles seems to get light in someway, even those on the turned away from the source, as well as ones that should definitly be out of range). the white (or whatever colour I set the light to) glossy parts are affected by the light data but the rest seems to disregard it? either way it's obviously not correct to do it the way I have.
the shaders are as follows
Vertex Shader
cbuffer WorldMatrix : register(b0)
{
float4x4 WorldMatrix;
}
cbuffer CameraMatrices : register(b1)
{
float4x4 ViewMatrix;
float4x4 ProjectionMatrix;
}
cbuffer CameraPosition : register(b2)
{
float4 camPos;
}
struct VS_IN
{
float3 Pos : POSITION;
float2 Tex : TEXCOORD;
float3 Normal : NORMAL;
float3 Tangent : TANGENT;
float3 Bitangent : BITANGENT;
};
struct VS_OUT
{
float4 Pos : SV_POSITION;
float4 WorldPos : WORLDPOS;
float2 Tex : TEXCOORD;
float3 Normal : NORMAL;
float3 Tangent : TANGENT;
float3 Bitangent : BITANGENT;
};
//-----------------------------------------------------------------------------------------
// VertexShader: VSScene
//-----------------------------------------------------------------------------------------
VS_OUT VS_main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.WorldPos = output.Pos = float4(input.Pos, 1);
output.Pos = mul(mul(mul(output.Pos, WorldMatrix), ViewMatrix), ProjectionMatrix);
output.WorldPos = mul(output.WorldPos, WorldMatrix);
//output.Pos = mul(mul(output.Pos, ViewMatrix), ProjectionMatrix);
output.Tex = input.Tex;
output.Normal = normalize(mul(input.Normal, WorldMatrix));
output.Tangent = normalize(mul(input.Tangent, WorldMatrix));
output.Bitangent = normalize(mul(input.Bitangent, WorldMatrix));
return output;
}
Pixel Shader
#define NROFLIGHTS 256
static const float PI = 3.14159265359f;
cbuffer LightData : register(b0)
{
float4 LightPos[NROFLIGHTS];
float4 LightColour[NROFLIGHTS];
float4 LightRangeType[NROFLIGHTS];
};
cbuffer CameraData : register(b1)
{
float4 CameraPos;
float4 ViewVec;
};
struct VS_OUT
{
float4 Pos : SV_POSITION;
float4 WorldPos : WORLDPOS;
float2 Tex : TEXCOORD;
float3 Normal : NORMAL;
float3 Tangent : TANGENT;
float3 Bitangent : BITANGENT;
};
Texture2D diffuse : register(t0);
Texture2D roughness : register(t1);
Texture2D metallic : register(t2);
Texture2D normalMap : register(t3);
Texture2D displacementMap : register(t4);
TextureCube reflection : register(t5);
SamplerState standardSamp : register(s0);
float3 F_Schlick(float3 f0, float f90, float u)
{
return f0 + (f90 - f0) * pow(1.0f - u, 5.0f);
}
float Fr_DisneyDiffuse(float NdotV, float NdotL, float LdotH, float linearRoughness)
{
float energyBias = lerp(0, 0.5, linearRoughness);
float energyFactor = lerp(1.0, 1.0 / 1.51, linearRoughness);
float fd90 = energyBias + 2.0 * LdotH * LdotH * linearRoughness;
float3 f0 = float3 (1.0f, 1.0f, 1.0f);
float lightScatter = F_Schlick(f0, fd90, NdotL).r;
float viewScatter = F_Schlick(f0, fd90, NdotV).r;
return lightScatter * viewScatter * energyFactor;
}
float V_SmithGGXCorrelated(float NdotV, float NdotL, float alphaG) {
// Original formulation of G_SmithGGX Correlated
// lambda_v = (-1 + sqrt ( alphaG2 * (1 - NdotL2 ) / NdotL2 + 1)) * 0.5f;
// lambda_l = (-1 + sqrt ( alphaG2 * (1 - NdotV2 ) / NdotV2 + 1)) * 0.5f;
// G_SmithGGXCorrelated = 1 / (1 + lambda_v + lambda_l );
// V_SmithGGXCorrelated = G_SmithGGXCorrelated / (4.0 f * NdotL * NdotV );
float alphaG2 = alphaG * alphaG;
//Caution: the "NdotL*" and "NdotV *" are explicitely inversed, this is not a mistake
float Lambda_GGXV = NdotL * sqrt((-NdotV * alphaG2 + NdotV) * NdotV + alphaG2);
float Lambda_GGXL = NdotV * sqrt((-NdotL * alphaG2 + NdotL) * NdotL + alphaG2);
return 0.5f / (Lambda_GGXV + Lambda_GGXL);
}
float D_GGX(float NdotH, float m)
{
// Divide by PI is apply later
float m2 = m * m;
float f = (NdotH * m2 - NdotH) * NdotH + 1;
return m2 / (f * f);
}
float CalcFr(float3 f0, float f90, float LdotH, float NdotV, float NdotL, float roughness, float NdotH)
{
float3 F = F_Schlick(f0, f90, LdotH);
float Vis = V_SmithGGXCorrelated(NdotV, NdotL, roughness);
float D = D_GGX(NdotH, roughness);
float Fr = D * F * Vis / PI;
return Fr;
}
float3 CalcBumpedNormal(VS_OUT indata)
{
float3 Normal = normalize(indata.Normal);
float3 Tangent = normalize(indata.Tangent);
Tangent = normalize(Tangent - dot(Tangent, Normal) * Normal);
float3 Bitangent = normalize(cross(Tangent, Normal));
float3 BumpMapNormal = normalMap.Sample(standardSamp, indata.Tex).xyz; //texture(gNormalMap, TexCoord0).xyz;
//return BumpMapNormal;
BumpMapNormal = normalize(2.0 * BumpMapNormal - float3(1.0, 1.0, 1.0));
float3 NewNormal;
float3x3 TBN = float3x3(Tangent, Bitangent, Normal);
NewNormal = mul(BumpMapNormal,TBN);
NewNormal = normalize(NewNormal);
return NewNormal;
}
float3 FresnelSchlickWithRoughness(float3 SpecularColor, float3 E, float3 N, float Gloss)
{
return SpecularColor + (max(Gloss, SpecularColor) - SpecularColor) * pow(1 - saturate(dot(E, N)), 5);
}
float3 SpecularEnvmap(float3 E, float3 N, float3 R, float3 SpecularColor, float Gloss)
{
//float3 Envcolor = texCUBElod(EnvironmentTexture, float4(R, EnvMapMipmapScaleBias.x * Gloss + EnvMapMipmapScaleBias.y)).rgb;
float3 Envcolor = reflection.Sample(standardSamp, R).rgb;
return FresnelSchlickWithRoughness(SpecularColor, E, N, Gloss) * Envcolor.rgb; // * EnvMapScaleAndModulate; // EnvMapScaleAndModulate is used to decompress range
}
float4 PS_main(VS_OUT input) : SV_Target
{
//float4 texFloat = diffuse.Sample(standardSamp, input.Tex);
//return texFloat;
//---------------------------------------------------------------------------------------------------------------------------------
float EPSILON = 0.00001f;
float3 LightVec = LightPos[0].xyz - input.WorldPos.xyz;
float dist = length(LightVec);
float LightPower = max(1.0f - (dist / LightRangeType[0].x), 0.0f);
float3 calculatedNormal = CalcBumpedNormal(input);
LightVec = normalize(LightVec);
float3 V = normalize(CameraPos.xyz - input.WorldPos.xyz);
float NdotV = abs(dot(calculatedNormal, V)) + EPSILON;
float3 H = normalize(V + LightVec);
float LdotH = saturate(dot(LightVec, H));
float NdotH = saturate(dot(calculatedNormal, H));
float NdotL = saturate(dot(calculatedNormal, LightVec));
//LightPower = 0.0f;
float3 baseColor = diffuse.Sample(standardSamp, input.Tex).rgb;
float metalness = metallic.Sample(standardSamp, input.Tex).r;
//roughness same for both diffuse and specular, as in Frostbite
float linearRoughness = saturate(roughness.Sample(standardSamp, input.Tex).r + EPSILON);
float roughness = pow(linearRoughness, 2);
float3 diffuseColor = lerp(baseColor.rgb, 0.0f.rrr, metalness.r);
//float3 diffuseColor = (1 - metalness.r) * baseColor;
float3 f0 = lerp(0.03f.rrr, baseColor.rgb, metalness.r);
float3 specularColor = lerp(f0, baseColor.rgb, metalness.r);
//float3 specularColor_f0 = lerp(0.04f.rrr, baseColor.rgb, metalness.r);
float3 incident = -V;
float3 reflectionVector = reflect(incident, calculatedNormal);
//float4 reflectionColor = reflection.Sample(samAnisotropic, reflectionVector);
float3 reflectionColor = reflection.Sample(standardSamp, reflectionVector).rgb;
float4 litColor = float4(reflectionColor, 1.0f);// *metalness;
//return litColor;
//Calculate fd (diffuse)
float fd = Fr_DisneyDiffuse(NdotV, NdotL, LdotH, linearRoughness) / PI;
float3 diffuse = fd.xxx * LightColour[0].xyz * LightPower * diffuseColor;
//Calculate the fr (specular)
float f90 = 0.16f * metalness * metalness;
float fr = CalcFr(f0, f90, LdotH, NdotV, NdotL, roughness, NdotH);
float3 se = SpecularEnvmap(CameraPos, calculatedNormal, reflectionVector, specularColor, 0.15f);
//return float4(se, 1.0f);
float3 specular = fr.xxx * LightColour[0].xyz * LightPower;// *specularColor;
float4 finalColor = float4(saturate(diffuse), 1);
finalColor.rgb += saturate(specular);
//finalColor.rgb = float3(0.0f, 0.0f, 0.0f);
float normDotCam = max(dot(lerp(input.Normal, calculatedNormal, max(dot(input.Normal, V), 0)), V), 0);
//float normDotCam = max(dot(calculatedNormal, V), 0);
//float normDotCam = max(dot(lerp(calculatedNormal, calculatedNormal, max(dot(calculatedNormal, V), 0)), V), 0);
float3 schlickFresnel = saturate(f0 + (float3(1.0f, 1.0f, 1.0f) - f0)*pow(1 - normDotCam, 5));
//float3 schlickFresnel = F_Schlick(f0, f90, normDotCam);
finalColor.rgb = lerp(finalColor.rgb, litColor.rgb, schlickFresnel);
//finalColor.rgb = lerp(finalColor.rgb, se, schlickFresnel);
//return float4(diffuse + specular, 1.0f);
return float4(finalColor.rgb, 1.0f);
//return float4(diffuse + specular, 1);
}
All help is appreciated as I am currently pretty stuck with it. The light in the scene is a pointlight and currently calculations are only made using one light (to simplify).