Files
starruler-linux/data/shaders/source/ringworld_procedural_ps.txt
T
2018-07-17 14:15:37 +02:00

527 lines
19 KiB
Plaintext

#version 120
const bool advancedProcedurals = #{{level:extreme}};
const bool parallax = #{{level:extreme}};
const bool scattering = #{{level:extreme}};
const bool pbrLight = #{{level:high}};
const bool simpleProcedurals = #{{level:high}};
const bool advancedAmbience = #{{level:high}};
const bool normalMapping = #{{level:medium}};
const bool simpleAmbience = #{{level:medium}};
const bool selfIllumination = #{{level:medium}};
const int nLightCount = 2;
const float pi = 3.14159265358;
const float tau = 6.28318530717;
const vec4 hashSeed = vec4(0.16532,0.17369,0.15787,0.14987);
// constants for emissive lights and starlight intensity
const float emissiveIntensity = 2.5;
const float lightIntensity = 2.5;
uniform sampler2D biomes, cities, differenceNoise, lookup, cityGlow, surfaceData;
uniform samplerCube skybox;
uniform float lightRadius[nLightCount];
uniform vec4 ownerColor;
uniform float time;
uniform float cycle;
uniform float population;
vec4 playerPlateProfile = vec4(vec3((1.0 - ownerColor.rgb) * 0.2 + 0.8), 0.75);
// complimentary triad color harmony, should in theory always generate an appealing theme.
vec3 colorLightsPrimary = ownerColor.rgb * 0.45 + 0.55;
vec3 colorLightsSecondary = vec3(ownerColor.brg) * 0.45 + 0.55;
vec3 colorLightsTertiary = vec3(ownerColor.gbr) * 0.85 + 0.15;
const float splatSharpness = 2.0;
////Amount of population considered 'half full'
const vec3 basePopulation = vec3(1.0, 0.25, 0.25);
varying vec2 uv, uv2, uvNoise, uvSplat, uvB[5];
varying vec4 pos;
varying vec3 light[nLightCount];
varying vec3 normal, npos, origo;
varying float pulse, poleMask;
varying vec3 splatRadial;
varying float atmoMask;
uniform vec4[3] biomesPicks;
// parallax scale, bias and steps
const vec2 scaleBias = vec2(0.005, 0.0025);
// smoothstep without the edges
float smoothCurve(float x){
return x * x * (3.0 - 2.0 * x);
}
vec3 toLinear(vec3 x) {
return pow(x, vec3(2.2));
}
vec3 toGamma(vec3 x) {
return pow(x, vec3(0.45));
}
vec3 square(vec3 x) {
return x*x;
}
float square(float x) {
return x*x;
}
vec2 square(vec2 x) {
return x*x;
}
float pow5(float x) {
float y = x*x;
return y*y*x;
}
vec2 pow5(vec2 x) {
vec2 y = x*x;
return y*y*x;
}
vec3 pow5(vec3 x) {
vec3 y = x*x;
return y*y*x;
}
vec3 pow3(vec3 x) {
return x*x*x;
}
vec2 pow3(vec2 x) {
return x*x*x;
}
float pow3(float x) {
return x*x*x;
}
float pow8(float x) {
x = x*x;
x = x*x;
return x*x;
}
// speculer term part 1
float D_GGX(float HdotN, float Roughness)
{
float m = Roughness * Roughness;
float m2 = m * m;
float denominator = HdotN * HdotN * (m2 - 1.0) + 1.0;
float D = m2 /( pi * denominator * denominator);
return D;
}
// specular term part 2
float V_SchlickforGGX(float Roughness, float NdotV, float NdotL)
{
float k = Roughness * Roughness * 0.5f;
float G1V = NdotV * (1.0 - k) + k;
float G1L = NdotL * (1.0 - k) + k;
return 0.25f / (G1V * G1L);
}
// fresnel for specular term
vec3 Fresnel(vec3 substance, vec3 l, vec3 h)
{
return max(vec3(0.0), substance + (substance - 1.0) * pow(1.0 - max(0.0,(dot(l, h))), 5.0));
}
// fresnel for ambient light
vec3 Fresnel2(vec3 substance, float dotProduct, float roughness)
{
return substance + (1.0 - substance) * pow((1.0 - dotProduct), 5.0) / (6.0 - 5.0 * roughness);
}
// diffuse term
float OrenNayerforDiffuseOnly(float roughness, float NdotL, float NdotV)
{
float O = 0.62 - pow(1.0-NdotL * clamp(1.0-NdotV/2.0, 0.0,1.0), pi) * 0.62;
O = mix(O, NdotL, roughness);
return O;
}
// fail-safe get normal map z component
float deriveZ(vec2 n) {
float z = pow(abs(1.0 - n.x * n.x - n.y * n.y), 0.5);
return z;
}
// calculates palette for cracks
vec3 paletteCracks(float c)
{
return 0.5 + 0.5 * cos(tau * (1.0 * c + vec3(0.0, 0.33, 0.67)) );
}
// calculates light tinting, simulating atmospheric scattering.
vec3 paletteAtmoTint( float t )
{
t *= t;
return square(min(vec3(1.0), 0.45 + 0.45*cos( tau*((t * 0.9)+vec3(0.45, 0.55, 0.65)) ) + (1.0 - t) * 0.25));
}
vec3 dp1Calc(vec3 p)
{
return dFdx(p);
}
vec3 dp2Calc(vec3 p)
{
return dFdy(p);
}
vec2 duv1Calc(vec2 uv)
{
return dFdx(uv);
}
vec2 duv2Calc(vec2 uv)
{
return dFdy(uv);
}
// random noise functions ahead
float hash12(vec2 p)
{
vec3 p3 = fract(vec3(p.xyx) * hashSeed.xyz);
p3 += dot(p3, p3.yzx + 19.19);
return fract((p3.x + p3.y) * p3.z);
}
float noise(vec2 n) {
const vec2 d = vec2(0.0, 1.0);
vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n));
return mix(mix(hash12(b), hash12(b + d.yx), f.x), mix(hash12(b + d.xy), hash12(b + d.yy), f.x), f.y);
}
float cloudMaker(vec2 n, vec2 f, int iterations, float w) {
float total = 0.0, amplitude = 1.0;
for (int i = 0; i < iterations; i++) {
total += noise(n + w + f + total * 0.5) * amplitude;
n += n;
w *= 0.75;
f *= 1.5;
amplitude *= 0.4;
}
return total;
}
vec2 rotator(vec2 rotate, float rate)
{
return vec2(dot(rotate, vec2(cos(rate), -sin(rate))), dot(rotate, vec2(sin(rate), cos(rate))));
}
// regular atan() fails at 0,0
float atan2(float y, float x)
{
return x == 0.0 ? sign(y)*pi/2 : atan(y, x);
}
void main() {
// atmoMask 1 = ground geometry, atmoMask 0 = cloud geometry
int atmoSteps;
if (advancedProcedurals){
atmoSteps = 8 - int(atmoSteps * 4.0); // reduce cloud iterations for softer shadows and performance
}
else if (simpleProcedurals){
atmoSteps = 6 - int(atmoSteps * 3.0);
}
else{
atmoSteps = 4 - int(atmoSteps * 2.0);
}
// create cloud shadows and animated clouds
float shadow = abs(mod(uv.x + cycle - (1.0 - atmoMask), 1.0/8.0) * 8.0 - 0.5) * 2.0;
shadow = 1.0 - smoothstep(0.7,0.8, shadow) * 0.95;
float cloudSeamKill = 1.0 - max(0.0, abs((uv.x - 0.5) * 128.0) - 63.0);
float cloudDistort = noise(uv * vec2(32.0, 1.0) - vec2(time * 0.125, 0.0)) * 2.0 - 1.0;
float cloudAnimated = 1.0 - cloudMaker(uv * vec2(256.0, 8.0), vec2(time * 0.5, 0.0), atmoSteps, cloudDistort);
cloudAnimated *= cloudSeamKill * cloudSeamKill * (3.0 - cloudSeamKill * 2.0);
cloudAnimated = clamp(cloudAnimated * ((1.0 - atmoMask) * 0.5 + 1.0), 0.0, 1.0);
// shared
vec3 v = normalize(npos);
vec4 n = vec4(normalize(normal), 0.5); // heightmap is stored in a later
vec2 NdotV = vec2(max(0.0, dot(n.xyz, v)), 0.0);
vec3 color = vec3(0.0);
// branch cloud shading off
if(atmoMask > 0.0){
vec4 splatMap = texture2D(surfaceData, uvB[0]) * 2.0;
for (int i = 0; i < 4; i++) {
splatMap.rgb += texture2D(surfaceData, uvB[i+1]).rgb;
}
splatMap.rgb *= 0.166666666;
splatMap.b = pow(max(0.0,(splatMap.b - 0.5)* 2.0), 0.25);
vec2 uvS = uv2;
vec3 r = n.xyz;
vec4 albedoR = vec4(0.0); // pure color of a surface and roughness
vec3 substance = vec3(0.0); // essentially an rgb specular color extracted from the albedo through metalness
float metalness = 0.0; // dielectric or metallic surface
float cavity = 0.5; // hard multiplier
float ao = 1.0; // detail occluder for lights
// results
vec3 lights = vec3(0.0);
vec3 citylights = vec3(0.0);
// because we have two uvs, for poles and equator, to avoid polar distortions and hide tiling, we need to run most things twice
mat3 TBN = mat3(0.0);
// build splatting basis
vec3 detailSamp = vec3(texture2D(biomes, uvS + biomesPicks[0].xy).a, texture2D(biomes, uvS + biomesPicks[1].xy).a, texture2D(biomes, uvS + biomesPicks[2].xy).a);
vec3 splatMaskMixed = vec3(detailSamp.r + detailSamp.g, detailSamp.g + detailSamp.b, detailSamp.b); // last one comes later
splatMaskMixed = min(vec3(1.0), (splatMaskMixed.rgb + splatMap.rgb) * splatMap.rgb);
// optional
splatMaskMixed = pow(splatMaskMixed.rgb, vec3(splatSharpness));
// create combined detail texture and calculate weighted splat mapping for the two uv sets
detailSamp.r = mix(mix(detailSamp.r, detailSamp.g, splatMaskMixed.r), detailSamp.b, splatMaskMixed.g) - splatMap.b;
if (normalMapping){
vec3 dp1 = dp1Calc(-v);
vec3 dp2 = dp2Calc(-v);
// derive for both uv's
vec2 duv1 = duv1Calc(uvS);
vec2 duv2 = duv2Calc(uvS);
vec3 dp2perp = cross(dp2, n.xyz);
vec3 dp1perp = cross(n.xyz, dp1);
// create matrix A
vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 binormal = dp2perp * duv1.y + dp1perp * duv2.y;
float invmax = pow(max(dot(tangent, tangent), dot(binormal, binormal)), -0.5);
TBN = mat3(tangent * invmax, binormal * invmax, n.xyz);
if (parallax){
// water depth refraction
float depthRefraction = mix(1.33,0.66, square(NdotV.r)); // water refraction index is 1.33, square so transparency wont cancel it out later
detailSamp.r *= mix(depthRefraction, 1.0, ceil(1.0 - detailSamp.r));
// do parallax
float fDet = dot(dp1, dp2perp);
vec2 vProjVScr = (1.0/fDet) * vec2(dot(dp2perp, v), dot(dp1perp, v));
vec2 vProjVTex = (duv1 * vProjVScr.x + duv2 * vProjVScr.y);
float vProjVTexZ = NdotV.r * (detailSamp.r * scaleBias.r - scaleBias.g);
uvS += vProjVTex * vProjVTexZ;
}
}
// sample all four biomes once for each of the first two uv sets
vec4 firstbiome = texture2D(biomes, uvS + biomesPicks[0].xy);//red
vec4 secondbiome = texture2D(biomes, uvS + biomesPicks[1].xy);//green
vec4 thirdbiome = texture2D(biomes, uvS + biomesPicks[2].xy);//black
// mix the normals cavity and resampled heightmap
vec4 dataSamp = mix(mix(firstbiome, secondbiome, splatMaskMixed.r), thirdbiome, splatMaskMixed.g);
float waterMask = clamp(floor((dataSamp.a + splatMap.b) * splatMap.b), 0.0, 1.0);
dataSamp.a = dataSamp.b - splatMap.b;
dataSamp.z = max(0.0, ceil(dataSamp.a));
dataSamp.x = mix(0.5, dataSamp.x, dataSamp.z);
ao = min(1.0,(dataSamp.x * 2.0));
vec4 textureNoise = texture2D(differenceNoise, uvNoise.xy);
vec2 waterNorms = vec2(mix(vec2(textureNoise.r, textureNoise.g), vec2(textureNoise.g, textureNoise.b), pulse)) - 0.5;// // could maybe be replaced by a small simple 2D noise function
float underWaterBlur = pow((1.0 - dataSamp.z) * (1.0 - dataSamp.a - 1.0), 0.5) * 7.0 + 1.0;
vec4 city = texture2D(cities, uvS * vec2(4.0, 2.0), underWaterBlur);
citylights = texture2D(cityGlow, uvS.xy * vec2(4.0, 2.0), underWaterBlur).rgb;
float citySplat = clamp(pow5((1.0 - abs(dataSamp.a - 0.5) * 0.2)) * (abs(city.a - 0.5) + city.a) * splatMap.a, 0.0, 1.0);
if (normalMapping){
// perform TBN matrix multiplication for each of the two normal maps separately and mix, save instructions by mix in height in alpha.
n.xy = vec2(mix(dataSamp.xy, city.xy, citySplat));
n *= 2.0;
n -=1.0;
//create waterNormalMap
n.xy = mix(waterNorms * 0.1, n.xy, dataSamp.z);
n.xyz = mix(vec3(0.0,0.0,1.0), vec3(n.xy, deriveZ(n.xy)), atmoMask);
n = vec4(normalize(TBN * n.xyz), dataSamp.a);
r = normalize(reflect(-v, n.xyz));
}
NdotV.r = dot(n.xyz, v);
NdotV.g = (1.0 - NdotV.r) * 0.5 + 0.5;
NdotV = max(vec2(0.0), NdotV);
vec3 cityData = vec3(city.ba, citySplat);
// create combined lookup gradients for all four biomes, clamped to not wrap the texture.
vec3 albedoGradients = vec3(firstbiome.a, secondbiome.a, thirdbiome.a);
// and make the flat slopes have less color shifts - think silt vs cliff sides.
albedoGradients = clamp(1 - albedoGradients, vec3(0.00390625), vec3(0.984375));
vec3 lookUpValue = vec3(biomesPicks[0].z,biomesPicks[1].z,biomesPicks[2].z) + vec3(biomesPicks[0].w,biomesPicks[1].w,biomesPicks[2].w) * poleMask;
// sample albedo and roughness from lookup table
vec4 firstBiomesAlbedoR = texture2D(lookup, vec2(lookUpValue.x, albedoGradients.r)); //, 0.0
vec4 secondBiomesAlbedoR = texture2D(lookup, vec2(lookUpValue.y, albedoGradients.g)); //, 0.0
vec4 thirdBiomesAlbedoR = texture2D(lookup, vec2(lookUpValue.z, albedoGradients.b)); //, 0.0
// create final splatmap , save instructions by mix in cavity in alpha.
vec4 albedoSplatCavity = vec4(splatMaskMixed, dataSamp.b);
// mix final albedo and tweak with cavity roughness values
albedoR = mix(mix(firstBiomesAlbedoR, secondBiomesAlbedoR, albedoSplatCavity.r), thirdBiomesAlbedoR, albedoSplatCavity.g);
cityData.rg = cityData.rg * 0.33 + 0.5;
albedoR = mix(albedoR, vec4(playerPlateProfile.rgb * cityData.g, playerPlateProfile.a) * cityData.r, cityData.b);
// creates ocean color
vec3 oceanColor = texture2D(lookup, vec2(biomesPicks[0].z, max(0.995, waterMask * 0.05))).rgb; // from 0.0065 to 0.0025
//creates lights
citylights.rg *= (1.5 - underWaterBlur * 0.1) * min(1.0, cityData.b + (1.0 - dataSamp.z) * 0.5 * splatMap.a);
citylights.g *= 0.5;
citylights.b *= splatMap.a * 0.5;
citylights = clamp(vec3(population) - vec3(0.0, 15.0, 30.0), vec3(0.0), vec3(1.0)) * citylights * emissiveIntensity;
citylights = toLinear(citylights.r * colorLightsPrimary) + toLinear(citylights.g * colorLightsSecondary) + toLinear(citylights.b * colorLightsTertiary);
citylights *= min(vec3(1.0), square(oceanColor.rgb) + dataSamp.z);
// creates oceans
albedoR = mix(vec4((oceanColor / (1.0 + (1.0 - NdotV.y) * 0.75)) * mix(min(1.0, dataSamp.y * dataSamp.x * pow3(splatMap.b) * 16.0 + 0.25), 1.0, NdotV.y), 0.25), albedoR, dataSamp.z);
// clamp to keep in PBR safe ranges - nothing in reality either albedo nor roughness is 0 or 1 and can make the math fail
albedoR = clamp(albedoR, vec4(0.05), vec4(0.975));
// create sss mask
vec4 invertedAlbedo = 1.0 - albedoR;
float SSSmask = mix(NdotV.r * 0.5 + 0.5, 1.0 - min(1.0, pow8((1.0 - invertedAlbedo.a * invertedAlbedo.b) * (1.0 - invertedAlbedo.r * invertedAlbedo.g))) * dataSamp.y * dataSamp.x, dataSamp.z);
// creates a metalness value if biomes are above 1950 on the lookup, for metallic specular on crystals.
metalness = square(n.a) * ceil(mix(mix(biomesPicks[0].z, biomesPicks[1].z, albedoSplatCavity.r), biomesPicks[2].z, albedoSplatCavity.g) - 0.9521484375) * dataSamp.z;
// Substance setup and Albedo adjust
albedoR.rgb = toLinear(albedoR.rgb);
substance = clamp((0.04 - 0.04 * metalness) + albedoR.rgb * metalness,0.0, 1.0);
albedoR.rgb -= substance;
// actual shading starts here
vec3 ambientFresnel = Fresnel2(substance, NdotV.r ,albedoR.a);
if (advancedAmbience){
color += square((textureCube(skybox, r, sqrt(albedoR.a) * 8.0).rgb) + 0.024) * ambientFresnel;
// ambient light
color += square(textureCube(skybox, n.xyz, 8.0).rgb + 0.024) * albedoR.rgb * (1.0 - ambientFresnel);
}
else if (simpleAmbience){
// Ambient reflections with fix mip and n instead of refect
color += square(textureCube(skybox, r, 3.0).rgb * ambientFresnel);
// Ambient light - average color of skybox squared
color += vec3(0.006724, 0.014884, 0.067081) * albedoR.rgb * (1.0 - ambientFresnel);
}
else{
// Ambient
color += vec3(0.006724, 0.014884, 0.067081) * (ambientFresnel + albedoR.rgb * (1.0 - ambientFresnel));
}
color *= ao;
float cloudShadow = 1.0 - cloudAnimated;
if (pbrLight){
for (int i = 0; i < nLightCount; i++) {
float distance = length(gl_LightSource[i].position.xyz - pos.xyz); // EYE SPACE, I'm sorry:o(
// rest is world space
vec3 L = normalize(light[i] / distance);
vec2 NdotL = max(vec2(0.0), vec2(dot(mix(n.xyz, normalize(normal), 0.5),L) * 0.8 + 0.2, dot(n.xyz,L)));
float sqrLightRadius = square(lightRadius[i]);
float illuminance = lightIntensity * pi * (sqrLightRadius / (max(sqrLightRadius, dot(L,L) )));
// note the square to kill hard spec in deep space!
float attenuation = square(1.0 / (1.0 + (gl_LightSource[i].constantAttenuation
+ gl_LightSource[i].linearAttenuation * distance
+ gl_LightSource[i].quadraticAttenuation * square(distance)))) * illuminance * NdotL.x * shadow;
citylights *= clamp(1.0 - attenuation, 0.0, 1.0) * atmoMask;
if (attenuation >0.0){
vec3 VplusL = L + v * 0.5; // *0.5 is not correct but without it grazing angle specular fails, propably somewhere something is flipped space/normal space
vec3 halfVec = normalize(VplusL);
float HdotN = max(0.0, dot(halfVec, n.xyz));
vec3 F = Fresnel(substance, L, halfVec);
float D = max(0.0, D_GGX(HdotN, albedoR.a));
float V = max(0.0, V_SchlickforGGX((1.0 + albedoR.a) * 0.5, NdotV.r, NdotL.y));
float O = OrenNayerforDiffuseOnly(albedoR.a, NdotL.y, NdotV.r);
vec3 A = vec3(1.0);
vec3 SSS = vec3(0.0);
if (scattering) {
// atmospheric light tinting simulating scattering
A = vec3(paletteAtmoTint(1.0 - max(0.0, min(0.33, dot(normal,L) * square(shadow)) + 0.15)));
// sub surface scattering model
float inScatter = pow(clamp(dot(L, -v), 0.0, 1.0), 12.0) * mix(8.0, 0.1, SSSmask);
float normalContribution = clamp(dot(mix(normal, n.xyz, SSSmask), halfVec) * SSSmask + 1.0 - SSSmask, 0.0, 1.0);
float backScatter = dataSamp.x * normalContribution / tau;
SSS = mix(backScatter, 1.0, inScatter) * square(oceanColor) * NdotL.x;
}
color += ((D * V * F) + ((1.0 - F) * O * albedoR.rgb) + SSS) * cloudShadow * gl_LightSource[i].diffuse.rgb * attenuation;
}
}
// hard cavity multiplier
color *= dataSamp.x - metalness + 1.0;
}
// probably not worth it to check attenuation like pbr
else{
for (int i = 0; i < nLightCount; i++) {
float distance = length(gl_LightSource[i].position.xyz - pos.xyz); // EYE SPACE, I'm sorry:o(
// rest is world space
vec3 L = normalize(light[i] / distance);
float NdotL = max(0.0, dot(n.xyz,L));
float sqrLightRadius = square(lightRadius[i]);
float illuminance = lightIntensity * pi * (sqrLightRadius / (max(sqrLightRadius, dot(L,L))));
// note the square to kill hard spec in deep space!
float attenuation = square(1.0 / (1.0 + (gl_LightSource[i].constantAttenuation
+ gl_LightSource[i].linearAttenuation * distance
+ gl_LightSource[i].quadraticAttenuation * square(distance)))) * illuminance * NdotL;
citylights *= clamp(1.0 - attenuation, 0.0, 1.0);
vec3 VplusL = L + v;
vec3 halfVec = normalize(VplusL);
float HdotN = max(0.0, dot(halfVec, n.xyz));
vec3 S = Fresnel2(substance, HdotN ,albedoR.a);
// albedoR.rgb * (dataSamp.x * 0.75) to sorta hack albedo into a classical diffuse texture
color += max(vec3(0.001), albedoR.rgb * (dataSamp.x * 0.75) + pow(S * HdotN, vec3(albedoR.a + 5.0))) * gl_LightSource[i].diffuse.rgb * attenuation;
}
}
gl_FragColor.rgb = toGamma(clamp(color + lights + citylights, vec3(0.0), vec3(1.0)));
gl_FragColor.a = 1.0;
}
else{
float atmosphere = 1.0 - NdotV.r;
for (int i = 0; i < nLightCount; i++) {
// atmo color is terran atmosphere blue part precalculated to linear
color += mix(vec3(0.218,0.456,0.978) * atmosphere, gl_LightSource[i].diffuse.rgb, cloudAnimated) * shadow;
}
gl_FragColor.rgb = toGamma(color);
gl_FragColor.a = mix(square(atmosphere) * 0.75, cloudAnimated, cloudAnimated);
}
}