At the moment I am implementing Specular Lighting for my engine in DirectX on top of Diffuse Lighting. I am using a physically based technique and as you can see from the image below something is clearly wrong. Can somebody please help me?
One thing I think that may be causing the issue is a buffer packing error (I only started learning to do this yesterday) so some values may not be sent across to HLSL properly. Here's my cbuffer struct + the light struct.
struct cbPerFrame
{
Light light;
XMVECTOR cameraPosition;
float pad;
float pad2;
float pad3;
float Roughness;
float Metalness;
};
struct Light
{
Light()
{
ZeroMemory(this, sizeof(Light));
}
XMFLOAT3 dir;
float pad;
XMFLOAT4 ambient;
XMFLOAT4 diffuse;
};
Or is it how I'm sending my Camera Position over to HLSL?
constbuffPerFrame.light = light;
constbuffPerFrame.Roughness = 10.0f;
constbuffPerFrame.Metalness = 10.0f;
constbuffPerFrame.cameraPosition = camPosition; //camPosition is a XMVector
If that's fine then there must be something dead stupid & simple I've done in my shader:
Effects.fx
#include "common.hlsli"
struct Light
{
float3 dir;
float4 ambient;
float4 diffuse;
};
cbuffer cbPerFrame
{
Light light;
float3 cameraPosition;
float Roughness;
float Metalness;
};
cbuffer cbPerObject
{
float4x4 WVP;
float4x4 World;
};
Texture2D ObjTexture;
SamplerState ObjSamplerState
{
Filter = ANISOTROPIC;
MaxAnisotropy = 16;
AddressU = Wrap;
AddressV = Wrap;
};
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float2 TexCoord : TEXCOORD;
float3 normal : NORMAL;
};
VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL)
{
VS_OUTPUT output;
output.Pos = mul(inPos, WVP);
output.normal = mul(normal, World);
output.TexCoord = inTexCoord;
return output;
}
float4 PS(VS_OUTPUT input) : SV_TARGET
{
input.normal = normalize(input.normal);
float4 textureColor = ObjTexture.Sample(ObjSamplerState, input.TexCoord);
float3 specularAlbedo = float3(Metalness, Metalness, Metalness);
float3 lighting = 0.0f;
float3 lightPos = float3(100.0f, 100.0f, 0.0f);
float3 pixelToLight = lightPos - input.Pos;
float lightDist = length(pixelToLight);
float3 lightDir = pixelToLight / lightDist;
float nDotL = saturate(dot(input.normal, light.dir));
float3 diffuseLighting = textureColor * light.ambient * light.diffuse;
diffuseLighting += saturate(DirectDiffuseBRDF(textureColor, nDotL));
float3 specularLighting = textureColor * light.ambient * light.diffuse;
specularLighting += DirectSpecularBRDF(specularAlbedo, input.Pos, input.normal, lightDir, cameraPosition, Roughness);
lighting = diffuseLighting + specularLighting;
return float4(lighting, textureColor.a);
}
common.hlsli
//=================================================================================================
// Constant Variables
//=================================================================================================
static const float Pi = 3.141592654f;
static const float Pi2 = 6.283185307f;
static const float Pi_2 = 1.570796327f;
static const float Pi_4 = 0.7853981635f;
static const float InvPi = 0.318309886f;
static const float InvPi2 = 0.159154943f;
//=================================================================================================
// Sampler States
//=================================================================================================
SamplerState SamplerLinear
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};
SamplerState SamplerAnisotropic
{
Filter = ANISOTROPIC;
MaxAnisotropy = 16;
AddressU = Wrap;
AddressV = Wrap;
};
// ===============================================================================================
// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
// ===============================================================================================
float2 Hammersley(uint i, uint N)
{
float ri = reversebits(i) * 2.3283064365386963e-10f;
return float2(float(i) / float(N), ri);
}
// ===============================================================================================
// http://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html
// ===============================================================================================
float GGX(float NdotV, float a)
{
float k = a / 2;
return NdotV / (NdotV * (1.0f - k) + k);
}
// ===============================================================================================
// Geometry Term
// -----------------------------------------------------------------------------------------------
// Defines the shadowing from the microfacets.
//
// Smith approximation:
// http://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf
// http://graphicrants.blogspot.fr/2013/08/specular-brdf-reference.html
//
// ===============================================================================================
float G_Smith(float a, float nDotV, float nDotL)
{
return GGX(nDotL, a * a) * GGX(nDotV, a * a);
}
// ================================================================================================
// Fresnel
// ------------------------------------------------------------------------------------------------
// The Fresnel function describes the amount of light that reflects from a mirror surface
// given its index of refraction.
//
// Schlick's approximation:
// http://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf
// http://graphicrants.blogspot.fr/2013/08/specular-brdf-reference.html
//
// ================================================================================================
float3 Schlick_Fresnel(float3 f0, float3 h, float3 l)
{
return f0 + (1.0f - f0) * pow((1.0f - dot(l, h)), 5.0f);
}
// ===============================================================================================
// http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
// ===============================================================================================
float3 ImportanceSampleGGX(float2 Xi, float Roughness, float3 N)
{
float a = Roughness * Roughness; // DISNEY'S ROUGHNESS [see Burley'12 siggraph]
float Phi = 2 * Pi * Xi.x;
float CosTheta = sqrt((1 - Xi.y) / (1 + (a * a - 1) * Xi.y));
float SinTheta = sqrt(1 - CosTheta * CosTheta);
float3 H;
H.x = SinTheta * cos(Phi);
H.y = SinTheta * sin(Phi);
H.z = CosTheta;
float3 UpVector = abs(N.z) < 0.999 ? float3(0, 0, 1) : float3(1, 0, 0);
float3 TangentX = normalize(cross(UpVector, N));
float3 TangentY = cross(N, TangentX);
// Tangent to world space
return TangentX * H.x + TangentY * H.y + N * H.z;
}
//The Direct Diffuse BRDF
float3 DirectDiffuseBRDF(float3 diffuseAlbedo, float nDotL)
{
return (diffuseAlbedo * nDotL);
}
//The Direct Specular BRDF
float3 DirectSpecularBRDF(float3 specularAlbedo, float3 positionWS, float3 normalWS, float3 lightDir, float3 CameraPos, float Roughness)
{
float3 viewDir = normalize(CameraPos - positionWS);
float3 halfVec = normalize(viewDir + lightDir);
float nDotH = saturate(dot(normalWS, halfVec));
float nDotL = saturate(dot(normalWS, lightDir));
float nDotV = max(dot(normalWS, viewDir), 0.0001f);
float alpha2 = Roughness * Roughness;
// Computes the distribution of the microfacets for the shaded surface.
// Trowbridge-Reitz/GGX normal distribution function.
float D = alpha2 / (Pi * pow(nDotH * nDotH * (alpha2 - 1) + 1, 2.0f));
// Computes the amount of light that reflects from a mirror surface given its index of refraction.
// Schlick's approximation.
float3 F = Schlick_Fresnel(specularAlbedo, halfVec, lightDir);
// Computes the shadowing from the microfacets.
// Smith's approximation.
float G = G_Smith(Roughness, nDotV, nDotL);
return D * F * G;
}
EDIT :
cbuffer cbPerFrame
{
Light light;
float pad0;
float pad1;
float pad2;
float Roughness;
float Metalness;
float4 cameraPosition;
};
Have I packed my cbuffer structure correctly now? If not can you guys tell me what to do to fix it (I'm clueless). If it is packed correctly what is the problem then?!
Light
. Also, note that usingXMVECTOR
in a data structure means it must have 16-byte alignment. See MSDN \$\endgroup\$float3
data is getting padded tofloat4
(i.e.) as defined, yourLight
struct elementdir
is actually 16 bytes long with 4 bytes of internal padding--it can't put yourfloat3
andfloat4
together so thefloat4
starts a new 16-byte vector. \$\endgroup\$