Thoughts on shaders for metal weapons?

I tried implementing basecolor / normal / metallic / roughness shader to use on some weaponry in my game that I have said texture maps for, but it just doesn’t look right:

Screen141 - Copy

For metallic and roughness, I tried OrenNayer / CookTorrance for diffuse/specular - no real difference. I tried a Lambert for diffuse and lerp(0.04f, color.rgb, metallic) for specular. No improvement. This image, which looks basically the same as all the others with some small variations, is using this formula which I also dug up on some forum:

Diffuse.rgb = color.rgb * (1.0 - metallic) * atten;
Specular.rgb = lerp(0.04f, color.rgb, metallic) * atten;

And you can see how it looks. It definitely does not look like a badass steel sword, it’s almost cartoon-ish.

Any thoughts on what I’m doing wrong or how I could do this different? I am not using an environmental map for reflection, which I understand could be an issue but I still think at least the base maps would produce something more realistic-looking than this POS that looks like it was lifted out of the disney beauty and the beast cartoon from the 90s.

Maybe someone has some experience writing nice metal shaders and would be willing to share.

That formula is extremely far from Cook Torrance or any PBR shader. Getting proper metallic look is quite a bit more complex as major element for that are environmental reflection. From those two lines of code you really cant expect better result than you have. Also these shaders (PBR) pretty much require operating in correct color space with follow up tone mapping. My recommendation is Schlick’s Approximation but it has relatively small visual difference from CT, so probably get that one running correctly first (you are looking for way bigger and way more expensive shader than you have there tho).

Also no much point testing it without reflections. Luckily for your, your scene is simple, it should be reasonably cheap to keep one realtime probe in front of view.

This should be good introduction: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf

Thank you, my Cook Torrance one is more complex, but when I use that, I’m not clear on what I’m doing with the end result. I end up with a diffuse output and a specular output but I’m unclear on whether those are multipliers for the base color, or should be summed with it, or just summed themselves. If I multiply with base color or use them as-is, my model renders as almost completely black with a few specular highlights - and I’m not sure how that would be different when using a reflection map because my returns from Cook Torrance are still going to be mostly black, so if I multiply any color by it, it’ll end up as black. If I add to the base color, I get some reflection but it looks more like plastic than anything else. I was reading that the black parts should be reflection, but I don’t know how that would work unless it were just summed up with a reflection map or perhaps multiplied by the negative such that the white parts ended up white and the black parts ended up showing the reflection.

This is my cook torrance function which I lifted from kosmonaut’s DeferredEngine project:

float3 SpecularCookTorrance(float NdotL, float3 normal, float3 negativeLightDirection, float3 cameraDirectionP, float diffuseIntensity, float3 diffuseColor, float f0, float roughness)
{
    float3 specular = float3(0, 0, 0);

    [branch]
    if (NdotL > 0.0f)
    {
        float3 halfVector = normalize(negativeLightDirection + cameraDirectionP);

        float NdotH = saturate(dot(normal, halfVector));
        float NdotV = saturate(dot(normal, cameraDirectionP));
        float VdotH = saturate(dot(cameraDirectionP, halfVector));
        float mSquared = roughness * roughness;


        //Trowbridge-Reitz
        float D_lowerTerm = (NdotH * NdotH * (mSquared * mSquared - 1) + 1);
        float D = mSquared * mSquared / (3.14 * D_lowerTerm * D_lowerTerm);

        //fresnel        (Schlick)
        float F = pow(1.0 - VdotH, 5.0);
        F *= (1.0 - f0);
        F += f0;

        //Schlick Smith
        float k = (roughness + 1) * (roughness + 1) / 8;
        float g_v = NdotV / (NdotV * (1 - k) + k);
        float g_l = NdotL / (NdotL * (1 - k) + k);

        float G = g_l * g_v;

        specular = max(0, (D * F * G) / (4 * NdotV * NdotL)) * diffuseIntensity * diffuseColor * NdotL;
    }
    return specular;
}

I’m confused about why he has negativeLightDirection in there. When I’m reading up on cook torrance in other shaders, they typically seem to ask for just a regular light direction. So I don’t know if I should be flipping the sign on the lightdir or not when I pass it in.

Also for diffuseColor, I’m passing in the base color of the texture. Maybe that is supposed to just be diffuse light color.

This is my f0:
float f0 = lerp(0.04f, baseColor.rgb, metalness);

I really recommend reading linked pdf.

Thanks, read it, and a half-dozen others, quite a PITA lol. Came out decent.

3 Likes