Chapter 5, color calculations

I noticed that on the calculation for the point light, the color of the light was red (0,0,1) and the base color is blue (1, 0, 0) and thus when we were having the calculation of color we have color = lightColor*baseColor*diffuseIntensity = (0,0,0). So we change the base color to be (1,1,1) in order to see it. I think this is wrong, this is not how light works. If we have red and blue we can combine them to be (1,0 ,1). I think therefore that formulas in the book must be wrong. I have change it to float3 color = (light.color+baseColor)*diffuseIntensity; for Sunlight and for point light.

I am sure this is a vast subject and I do not know enough about it but at least I can see that this would work better. Also I notice an error, for the calculation of reflection.

The reflection should have as first argument the incidence of light not the position of light, that is incidence of light is the negative of the position of light. On your calculation for camera position you have in.worldPosition - fragmentsUniform.cameraPosition but it should be fragmentsUniform.cameraPosition - in.worldPosition, since you had the negative of the incidence everything was coming ok. But this way it should be clearer.

Also for the angle of the specular light, rising to the 32 power only brings numbers less than 1 very close to 0. However this is still done in a continuous way. If we want to have a cone that cuts the light we need could check when the dot product is smaller than some threshold. One could take the angle that ones to use and take cosine of it to be the threshold. I just directly used 0.25, this will create a discontinuity, i.e. a proper cone and thus an effect of shining. Below is my code. Hope someone finds it useful.

 #include <metal_stdlib>
 using namespace metal;
 #import "Common.h"

struct VertexIn {
  float4 position [[ attribute(0) ]];
    float3 normal [[ attribute(1)]];
};

struct VertexOut {
float4 position [[ position ]];
float3 worldPosition;
float3 worldNormal;
};

vertex VertexOut vertex_main(const VertexIn vertexIn [[ stage_in ]],
                         constant Uniforms &uniforms [[ buffer(1) ]]) {
VertexOut out;
out.position = uniforms.projectionMatrix*uniforms.viewMatrix*uniforms.modelMatrix*vertexIn.position;
out.worldPosition = (uniforms.modelMatrix*vertexIn.position).xyz;
out.worldNormal = uniforms.normalMatrix*vertexIn.normal;
return out;
}

fragment float4 fragment_main( VertexOut in [[ stage_in ]],
                          constant Light *lights [[ buffer(2) ]],
                          constant FragmentUniforms &fragmentUniforms [[ buffer(3)]]) {
float3 baseColor = float3(0, 0, 1);
float3 ambienColor = 0;
float3 diffuseColor = 0;
float3 specularColor = 0;
float materialShininess = 32;
float3 materialSpecularColor = float3(0.1, 0.1, 0.5);

float3 normalDirection = normalize(in.worldNormal);
for (uint i=0; i< fragmentUniforms.lightCount; i++) {
    Light light = lights[i];
    if (light.type == Sunlight) {
        float3 lightDirection = normalize(light.position);
        float diffuseIntensity = saturate(dot(lightDirection, normalDirection));
        
        if (diffuseIntensity > 0) {
            diffuseColor += light.intensity*light.color* baseColor*diffuseIntensity;
            float3 reflection = reflect(-lightDirection, normalDirection);
            float3 cameraPosition = normalize( fragmentUniforms.cameraPosition - in.worldPosition);
            float specularIntensity = pow(saturate(dot(reflection, cameraPosition)), materialShininess);
            if (specularIntensity > 0.25) {
                specularColor += (light.specularColor + baseColor)*specularIntensity;
            }
        }
    }
    
    if (light.type == Ambientlight) {
      
        float3 cameraPosition = normalize( fragmentUniforms.cameraPosition - in.worldPosition);
        float ambientSpecularIntensity = dot(in.worldNormal, cameraPosition);
        ambienColor += (baseColor + light.color)*light.intensity*ambientSpecularIntensity;
    }
    
    if (light.type == Pointlight) {
        float d = distance(light.position, in.worldPosition);
        float3 lightDirection = normalize(light.position - in.worldPosition);
        float attenuation = 1.0/(light.attenuation.x + light.attenuation.y*d + light.attenuation.z*d*d);
        float diffuseIntensity = saturate(dot(lightDirection, normalDirection));
        float3 color = (light.color+baseColor)*diffuseIntensity;
        color *= attenuation;
        diffuseColor += color;
    }
}
float3 color = specularColor + diffuseColor + ambienColor;
return float4(color, 1);

}

@carlos

One of the nice things about creating your own shaders is that, as long as you’re not attempting physically based lighting, you can define what looks right to you, and I would encourage everyone to play around with the calculations in the shader. I don’t always use the same strict values, and as you suggest, yours is probably a nicer result here :clap: :slight_smile:

The Phong shader is not a physically realistic one, and neither is your result. One of the properties of a physically realistic renderer is energy conservation, which means that the energy emitted cannot be greater than the energy received. (see Adopting a physically based shading model | Sébastien Lagarde)

If you are going for physically realistic (PBR), then I would recommend the seminal book which is now available free online: Physically Based Rendering by Matt Pharr, Wenzel Jakob, and Greg Humphreys: http://www.pbr-book.org. Also the more approachable PBR guide from Allegorithmic: The PBR Guide - Part 1 on Substance 3D Tutorials

Your suggestion of the first argument error, I hope to update in the next version of the book. I made a diagram of the cone direction: Chapter 5, cone direction - #2 by caroline. As long as the math is correct, it isn’t an error, but, as you say, could be clearer.

1 Like

Wow, thank you for your quick response. I love the references.

1 Like