// Shared functions for all shaders in the engine. Contents of this
// file will be *automatically* included in all shaders!
precision highp float;
// Tries to solve quadratic equation. Returns true iff there are any real roots.
bool S_SolveQuadraticEq(float a, float b, float c, out float minT, out float maxT)
{
float twoA = 2.0 * a;
float det = b * b - 2.0 * twoA * c;
if (det < 0.0)
{
minT = 0.0;
maxT = 0.0;
return false;
}
float sqrtDet = sqrt(det);
float root1 = (-b - sqrtDet) / twoA;
float root2 = (-b + sqrtDet) / twoA;
minT = min(root1, root2);
maxT = max(root1, root2);
return true;
}
// Returns attenuation in inverse square model. It falls to zero at given radius.
float S_LightDistanceAttenuation(float distance, float radius)
{
float attenuation = clamp(1.0 - distance * distance / (radius * radius), 0.0, 1.0);
return attenuation * attenuation;
}
// Projects world space position (typical use case) by given matrix.
vec3 S_Project(vec3 worldPosition, mat4 matrix)
{
vec4 screenPos = matrix * vec4(worldPosition, 1);
screenPos.xyz /= screenPos.w;
return screenPos.xyz * 0.5 + 0.5;
}
// Returns matrix-space position from given screen position.
// Real space of returned value is defined by matrix and can
// be any, but there are few common use cases:
// - To get position in view space pass inverse projection matrix.
// - To get position in world space pass inverse view-projection matrix.
vec3 S_UnProject(vec3 screenPos, mat4 matrix)
{
vec4 clipSpacePos = vec4(screenPos * 2.0 - 1.0, 1.0);
vec4 position = matrix * clipSpacePos;
return position.xyz / position.w;
}
// Calculates specular factor using Blinn-Phong model.
float S_SpecularFactor(vec3 lightVector, vec3 cameraPosition, vec3 fragmentPosition, vec3 fragmentNormal, float power)
{
vec3 v = cameraPosition - fragmentPosition;
vec3 h = normalize(lightVector + v);
return pow(clamp(dot(fragmentNormal, h), 0.0, 1.0), power);
}
// Blinn-Phong lighting model input parameters.
struct TBlinnPhongContext {
// Light position in world coordinates.
vec3 lightPosition;
float lightRadius;
vec3 fragmentNormal;
// Fragment position on world coordinates.
vec3 fragmentPosition;
vec3 cameraPosition;
float specularPower;
};
// Blinn-Phong lighting output parameters.
struct TBlinnPhong {
// Total "brightness" of fragment.
float attenuation;
// Specular component of lighting.
float specular;
// Distance from light to fragment.
float distance;
// Normalized vector from fragment position to light.
// It can be useful if you need this vector later on,
// for other calculations.
vec3 direction;
};
// Calculates lighting parameters for point light using Blinn-Phong model.
// This function also suitable for calculations of spot lighting, because
// spot light is a point light but with defined lighting cone.
TBlinnPhong S_BlinnPhong(TBlinnPhongContext ctx)
{
vec3 lightVector = ctx.lightPosition - ctx.fragmentPosition;
float distance = length(lightVector);
lightVector = lightVector / distance;
float specular = S_SpecularFactor(lightVector, ctx.cameraPosition, ctx.fragmentPosition, ctx.fragmentNormal, ctx.specularPower);
float lambertian = max(dot(ctx.fragmentNormal, lightVector), 0.0);
float distance_attenuation = S_LightDistanceAttenuation(distance, ctx.lightRadius);
float attenuation = lambertian * distance_attenuation;
return TBlinnPhong(attenuation, specular, distance, lightVector);
}
// Returns scatter amount for given parameters.
// https://cseweb.ucsd.edu/~ravir/papers/singlescat/scattering.pdf
// https://blog.mmacklin.com/2010/05/29/in-scattering-demo/
float S_InScatter(vec3 start, vec3 dir, vec3 lightPos, float d)
{
// light to ray origin
vec3 q = start - lightPos;
// coefficients
float b = dot(dir, q);
float c = dot(q, q);
// evaluate integral
float s = 1.0 / sqrt(c - b*b);
float l = s * (atan((d + b) * s) - atan(b*s));
return l;
}
// https://en.wikipedia.org/wiki/Rayleigh_scattering
vec3 S_RayleighScatter(vec3 start, vec3 dir, vec3 lightPos, float d)
{
float scatter = S_InScatter(start, dir, lightPos, d);
// Apply simple version of Rayleigh scattering. Just increase
// intensity of blue light over other colors.
return vec3(0.55, 0.75, 1.0) * scatter;
}
// Tries to find intersection of given ray with specified sphere. If there is an intersection, returns true.
// In out parameters minT, maxT will be min and max ray parameters of intersection.
bool S_RaySphereIntersection(vec3 origin, vec3 dir, vec3 center, float radius, out float minT, out float maxT)
{
vec3 d = origin - center;
float a = dot(dir, dir);
float b = 2.0 * dot(dir, d);
float c = dot(d, d) - radius * radius;
return S_SolveQuadraticEq(a, b, c, minT, maxT);
}
// Calculates point shadow factor where 1.0 - no shadow, 0.0 - fully in shadow.
// Why value is inversed? To be able to directly multiply color to shadow factor.
float S_PointShadow(
bool shadowsEnabled,
bool softShadows,
float fragmentDistance,
float shadowBias,
vec3 toLight,
in samplerCube shadowMap)
{
if (shadowsEnabled)
{
float biasedFragmentDistance = fragmentDistance - shadowBias;
if (softShadows)
{
const int samples = 20;
const vec3 directions[samples] = vec3[samples] (
vec3(1, 1, 1), vec3(1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1),
vec3(1, 1, -1), vec3(1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1),
vec3(1, 1, 0), vec3(1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0),
vec3(1, 0, 1), vec3(-1, 0, 1), vec3(1, 0, -1), vec3(-1, 0, -1),
vec3(0, 1, 1), vec3(0, -1, 1), vec3(0, -1, -1), vec3(0, 1, -1)
);
const float diskRadius = 0.0025;
float accumulator = 0.0;
for (int i = 0; i < samples; ++i)
{
vec3 fetchDirection = -toLight + directions[i] * diskRadius;
float shadowDistanceToLight = texture(shadowMap, fetchDirection).r;
if (biasedFragmentDistance > shadowDistanceToLight)
{
accumulator += 1.0;
}
}
return clamp(1.0 - accumulator / float(samples), 0.0, 1.0);
}
else
{
float shadowDistanceToLight = texture(shadowMap, -toLight).r;
return biasedFragmentDistance > shadowDistanceToLight ? 0.0 : 1.0;
}
} else {
return 1.0; // No shadow
}
}
// Calculates spot light shadow factor where 1.0 - no shadow, 0.0 - fully in shadow.
// Why value is inversed? To be able to directly multiply color to shadow factor.
float S_SpotShadowFactor(
bool shadowsEnabled,
bool softShadows,
float shadowBias,
vec3 fragmentPosition,
mat4 lightViewProjMatrix,
float shadowMapInvSize,
in sampler2D spotShadowTexture)
{
if (shadowsEnabled)
{
vec3 lightSpacePosition = S_Project(fragmentPosition, lightViewProjMatrix);
float biasedLightSpaceFragmentDepth = lightSpacePosition.z - shadowBias;
if (softShadows)
{
float accumulator = 0.0;
for (float y = -0.5; y <= 0.5; y += 0.5)
{
for (float x = -0.5; x <= 0.5; x += 0.5)
{
vec2 fetchTexCoord = lightSpacePosition.xy + vec2(x, y) * shadowMapInvSize;
if (biasedLightSpaceFragmentDepth > texture(spotShadowTexture, fetchTexCoord).r)
{
accumulator += 1.0;
}
}
}
return clamp(1.0 - accumulator / 9.0, 0.0, 1.0);
}
else
{
return biasedLightSpaceFragmentDepth > texture(spotShadowTexture, lightSpacePosition.xy).r ? 0.0 : 1.0;
}
} else {
return 1.0; // No shadow
}
}
float Internal_FetchHeight(in sampler2D heightTexture, vec2 texCoords) {
return texture(heightTexture, texCoords).r;
}
vec2 S_ComputeParallaxTextureCoordinates(in sampler2D heightTexture, vec3 eyeVec, vec2 texCoords, vec3 normal) {
const float minLayers = 8.0;
const float maxLayers = 15.0;
const int maxIterations = 15;
const float parallaxScale = 0.05;
float numLayers = mix(maxLayers, minLayers, abs(dot(normal, eyeVec)));
float layerHeight = 1.0 / numLayers;
float curLayerHeight = 0.0;
vec2 dtex = parallaxScale * eyeVec.xy / numLayers;
vec2 currentTexCoords = texCoords;
float height = Internal_FetchHeight(heightTexture, currentTexCoords);
for (int i = 0; i < maxIterations; i++) {
if (height > curLayerHeight) {
curLayerHeight += layerHeight;
currentTexCoords -= dtex;
height = Internal_FetchHeight(heightTexture, currentTexCoords);
} else {
break;
}
}
vec2 prev = currentTexCoords + dtex;
float nextH = height - curLayerHeight;
float prevH = Internal_FetchHeight(heightTexture, prev) - curLayerHeight + layerHeight;
float weight = nextH / (nextH - prevH);
return prev * weight + currentTexCoords * (1.0 - weight);
}