c++directxhlslwavefront

How to use pixel shader to render material from a Wavefront Obj file?


Some 3d meshes that get exported to Wavefront.obj format usually come with a .mtl file that has additional data to the texture it uses and its materials, when exported from Blender they always come with Ambient, Diffuse, Specular, and Emissive RGB data as part of its material, but I'm not sure how I can use this data in the pixel shader and get the right color output.

I would appreciate it if anyone can explain to me how to use these materials and any code sample would be very welcome.


Solution

  • Traditional materials and lighting models use "Ambient", "Diffuse", "Specular", and "Emissive" colors which is why you find those in Wavefront OBJ files. These can often be replaced or used in multiplicative conjunction with texture colors.

    The (now defunct) XNA Game Studio product did a good job of providing simple 'classic' shaders in the BasicEffect "Stock Shaders". I use them in the DirectX Tool Kit for DX11 and DX12.

    Take a look at BasicEffect.fx for a traditional material pixel shader. If you are looking mostly for pixel-shader handling, that's "per-pixel lighting" as opposed to "vertex lighting" which was more common back when we had less powerful GPUs.

    Here's a 'inlined' version so you can follow it all in one place:

    struct VSInputNmTx
    {
        float4 Position : SV_Position;
        float3 Normal   : NORMAL;
        float2 TexCoord : TEXCOORD0;
    };
    
    Texture2D<float4> Texture : register(t0);
    sampler Sampler : register(s0);
    
    cbuffer Parameters : register(b0)
    {
        float4 DiffuseColor             : packoffset(c0);
        float3 EmissiveColor            : packoffset(c1);
        float3 SpecularColor            : packoffset(c2);
        float  SpecularPower            : packoffset(c2.w);
    
        float3 LightDirection[3]        : packoffset(c3);
        float3 LightDiffuseColor[3]     : packoffset(c6);
        float3 LightSpecularColor[3]    : packoffset(c9);
    
        float3 EyePosition              : packoffset(c12);
    
        float3 FogColor                 : packoffset(c13);
        float4 FogVector                : packoffset(c14);
    
        float4x4 World                  : packoffset(c15);
        float3x3 WorldInverseTranspose  : packoffset(c19);
        float4x4 WorldViewProj          : packoffset(c22);
    };
    
    struct VSOutputPixelLightingTx
    {
        float2 TexCoord   : TEXCOORD0;
        float4 PositionWS : TEXCOORD1;
        float3 NormalWS   : TEXCOORD2;
        float4 Diffuse    : COLOR0;
        float4 PositionPS : SV_Position;
    };
    
    // Vertex shader: pixel lighting + texture.
    VSOutputPixelLighting VSBasicPixelLightingTx(VSInputNmTx vin)
    {
        VSOutputPixelLighting vout;
    
        vout.PositionPS = mul(vin.Position, WorldViewProj);
    
        vout.PositionWS.xyz = mul(vin.Position, World).xyz;
    
        // ComputeFogFactor
        vout.PositionWS.w = saturate(dot(vin.Position, FogVector));
    
        vout.NormalWS = normalize(mul(vin.Normal, WorldInverseTranspose));
    
        vout.Diffuse = float4(1, 1, 1, DiffuseColor.a);
    
        vut.TexCoord = vin.TexCoord;
    
        return vout;
    }
    
    struct PSInputPixelLightingTx
    {
        float2 TexCoord   : TEXCOORD0;
        float4 PositionWS : TEXCOORD1;
        float3 NormalWS   : TEXCOORD2;
        float4 Diffuse    : COLOR0;
    };
    
    // Pixel shader: pixel lighting + texture.
    float4 PSBasicPixelLightingTx(PSInputPixelLightingTx pin) : SV_Target0
    {
        float4 color = Texture.Sample(Sampler, pin.TexCoord) * pin.Diffuse;
    
        float3 eyeVector = normalize(EyePosition - pin.PositionWS.xyz);
    
        float3 worldNormal = normalize(pin.NormalWS);
    
        ColorPair lightResult = ComputeLights(eyeVector, worldNormal, 3);
    
        color.rgb *= lightResult.Diffuse;
    
        // AddSpecular
        color.rgb += lightResult.Specular * color.a;
    
        // ApplyFog (we passed fogfactor in via PositionWS.w)
        color.rgb =  lerp(color.rgb, FogColor * color.a, pin.PositionWS.w);
    
        return color;
    }
    

    Here is the helper function ComputeLights which implements a Blinn-Phong reflection model for the specular highlight.

    struct ColorPair
    {
        float3 Diffuse;
        float3 Specular;
    };
    
    
    ColorPair ComputeLights(float3 eyeVector, float3 worldNormal, uniform int numLights)
    {
        float3x3 lightDirections = 0;
        float3x3 lightDiffuse = 0;
        float3x3 lightSpecular = 0;
        float3x3 halfVectors = 0;
        
        [unroll]
        for (int i = 0; i < numLights; i++)
        {
            lightDirections[i] = LightDirection[i];
            lightDiffuse[i]    = LightDiffuseColor[i];
            lightSpecular[i]   = LightSpecularColor[i];
            
            halfVectors[i] = normalize(eyeVector - lightDirections[i]);
        }
    
        float3 dotL = mul(-lightDirections, worldNormal);
        float3 dotH = mul(halfVectors, worldNormal);
        
        float3 zeroL = step(0, dotL);
    
        float3 diffuse  = zeroL * dotL;
        float3 specular = pow(max(dotH, 0) * zeroL, SpecularPower) * dotL;
    
        ColorPair result;
        
        result.Diffuse  = mul(diffuse,  lightDiffuse)  * DiffuseColor.rgb + EmissiveColor;
        result.Specular = mul(specular, lightSpecular) * SpecularColor;
    
        return result;
    }
    

    These BasicEffect shaders don't make use of ambient color, but you could modify them to do so if you wanted. All ambient color does is provide a 'minimum color value' that's independent of dynamic lights.

    Note that there also some unofficial Physically-Based Rendering (PBR) materials extension in some Wavefront OBJ files. See Extending Wavefront MTL for Physically-Based. More modern geometry formats like glTF assume PBR materials properties which is things like an albedo texture, normal texture, roughness/metalness texture, etc..