three-d 0.7.2

A renderer which compiles to both desktop (OpenGL) and web (WebAssembly + WebGL).
Documentation

uniform vec3 eyePosition;

struct Surface
{
    vec3 position;
    vec3 normal;
    vec4 color;
    float diffuse_intensity;
    float specular_intensity;
    float specular_power;
};

struct BaseLight
{
    vec3 color;
    float intensity;
};

struct Attenuation
{
    float constant;
    float linear;
    float exp;
    float padding;
};

struct DirectionalLight
{
    BaseLight base;
    vec3 direction;
    float shadowEnabled;
    mat4 shadowMVP;
};

struct PointLight
{
    BaseLight base;
    Attenuation attenuation;
    vec3 position;
    float padding;
};

struct SpotLight
{
    BaseLight base;
    Attenuation attenuation;
    vec3 position;
    float cutoff;
    vec3 direction;
    float shadowEnabled;
    mat4 shadowMVP;
};

vec3 calculate_light(BaseLight light, vec3 lightDirection, vec3 position, vec3 normal,
    float diffuse_intensity, float specular_intensity, float specular_power)
{
    float DiffuseFactor = dot(normal, -lightDirection);

    vec3 DiffuseColor  = vec3(0.0);
    vec3 SpecularColor = vec3(0.0);

    if (DiffuseFactor > 0.0)
    {
        DiffuseColor = light.color * diffuse_intensity * light.intensity * DiffuseFactor;

        vec3 VertexToEye = normalize(eyePosition - position);
        vec3 lightReflect = normalize(reflect(lightDirection, normal));
        float SpecularFactor = dot(VertexToEye, lightReflect);
        if (SpecularFactor > 0.0)
        {
            SpecularFactor = pow(SpecularFactor, specular_power);
            SpecularColor = light.color * specular_intensity * light.intensity  * SpecularFactor;
        }
    }

    return DiffuseColor + SpecularColor;
}

vec3 calculate_attenuated_light(BaseLight light, Attenuation attenuation, vec3 light_position, vec3 position, vec3 normal,
    float diffuse_intensity, float specular_intensity, float specular_power)
{
    vec3 light_direction = position - light_position;
    float distance = length(light_direction);
    light_direction = light_direction / distance;

    vec3 color = calculate_light(light, light_direction, position, normal,
        diffuse_intensity, specular_intensity, specular_power);

    float att =  attenuation.constant +
        attenuation.linear * distance +
        attenuation.exp * distance * distance;

    return color / max(1.0, att);
}

float is_visible(sampler2D shadowMap, vec4 shadow_coord, vec2 offset)
{
    vec2 uv = (shadow_coord.xy + offset)/shadow_coord.w;
    float true_distance = (shadow_coord.z - 0.005)/shadow_coord.w;
    float shadow_cast_distance = texture(shadowMap, uv).x;
    return uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || shadow_cast_distance > true_distance ? 1.0 : 0.0;
}

float calculate_shadow(sampler2D shadowMap, mat4 shadowMVP, vec3 position)
{
    if(shadowMVP[3][3] < 0.1) // Shadow disabled
    {
        return 1.0;
    }
    vec4 shadow_coord = shadowMVP * vec4(position, 1.);
    float visibility = 0.0;
    vec2 poissonDisk[4] = vec2[](
                                 vec2( -0.94201624, -0.39906216 ),
                                 vec2( 0.94558609, -0.76890725 ),
                                 vec2( -0.094184101, -0.92938870 ),
                                 vec2( 0.34495938, 0.29387760 )
                                 );
    for (int i=0;i<4;i++)
    {
        visibility += is_visible(shadowMap, shadow_coord, poissonDisk[i] * 0.001f);
    }
    return visibility * 0.25;
}

vec3 calculate_directional_light(DirectionalLight directionalLight, vec3 surface_color, vec3 position, vec3 normal,
    float diffuse_intensity, float specular_intensity, float specular_power, sampler2D shadowMap)
{
    vec3 light = calculate_light(directionalLight.base, directionalLight.direction, position, normal,
        diffuse_intensity, specular_intensity, specular_power);
    if(directionalLight.shadowEnabled > 0.5) {
        light *= calculate_shadow(shadowMap, directionalLight.shadowMVP, position);
    }
    return surface_color * light;
}

vec3 calculate_point_light(PointLight pointLight, vec3 surface_color, vec3 position, vec3 normal,
    float diffuse_intensity, float specular_intensity, float specular_power)
{
    return surface_color * calculate_attenuated_light(pointLight.base, pointLight.attenuation, pointLight.position, position, normal,
        diffuse_intensity, specular_intensity, specular_power);
}

vec3 calculate_spot_light(SpotLight spotLight, vec3 surface_color, vec3 position, vec3 normal,
    float diffuse_intensity, float specular_intensity, float specular_power, sampler2D shadowMap)
{
    vec3 light_direction = normalize(position - spotLight.position);
    float angle = acos(dot(light_direction, normalize(spotLight.direction)));
    float cutoff = 3.14 * spotLight.cutoff / 180.0;

    vec3 light = vec3(0.0);
    if (angle < cutoff) {
        light = calculate_attenuated_light(spotLight.base, spotLight.attenuation, spotLight.position, position, normal,
            diffuse_intensity, specular_intensity, specular_power) * (1.0 - smoothstep(0.75 * cutoff, cutoff, angle));
        if(spotLight.shadowEnabled > 0.5) {
            light *= calculate_shadow(shadowMap, spotLight.shadowMVP, position);
        }
    }
    return surface_color * light;
}