#version 120 // shader level settings 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 emissiveIntensity = 5.0; const float lightIntensity = 5.0; uniform sampler2D wreckage; uniform samplerCube skybox; uniform float lightRadius[nLightCount]; uniform float life; varying vec4 pos; varying vec3 normal; varying vec3 npos; varying vec2 uv; vec3 light[nLightCount]; float dist[nLightCount]; // parallax scale, bias and steps const vec2 scaleBias = vec2(0.001, 0.0005); // pow alternatives vec3 square(vec3 x) { return x*x; } float square(float x) { return x*x; } vec2 square(vec2 x) { return x*x; } vec3 toLinear(vec3 x) { return pow(x, vec3(2.2)); } vec3 toGamma(vec3 x) { return pow(x, vec3(0.45)); } // 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; } 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; } // Schlick GGX approximation 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); } // 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; } vec3 Fresnel(vec3 substance, vec3 l, vec3 h) { return substance + (1.0 - substance) * pow(1.0 - clamp((dot(l, h)), 0.0, 1.0), 5.0); } vec3 Fresnel2(vec3 substance, float dotProduct, float roughness) { return substance + (1.0 - substance) * pow((1.0 - dotProduct), 5.0) / (6.0 - 5.0 * roughness); } 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); } void main() { vec2 uvP = uv; vec3 v = normalize(npos); vec3 n = normalize(normal); // first NdotV - used for parallax and low shading float NdotV = max(0.0, dot(normal, v)); mat3 TBN = mat3(0.0); // part one of normal mapping if (normalMapping){ // tbn screenspace cotangent derivative vec3 dp1 = dp1Calc(-v); vec3 dp2 = dp2Calc(-v); vec2 duv1 = duv1Calc(uvP); vec2 duv2 = duv2Calc(uvP); // solve the linear system vec3 dp2perp = cross(dp2, normal); vec3 dp1perp = cross(normal, dp1); vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x; vec3 binormal = dp2perp * duv1.y + dp1perp * duv2.y; // construct a scale-invariant frame float invmax = pow(max(dot(tangent, tangent), dot(binormal, binormal)), -0.5); TBN = mat3(tangent * invmax, binormal * invmax, normal); // do parallax for both main uv and paint, so paint doesn't float about if(parallax){ float p = texture2D(wreckage, uvP).a * scaleBias.r - scaleBias.g; float fDet = dot(dp1, dp2perp); vec2 vProjVScr = (1.0/fDet) * vec2(dot(dp2perp, v), dot(dp1perp, v)); vec2 vProjVTex = (duv1.xy * vProjVScr.x + duv2.xy * vProjVScr.y); float vProjVTexZ = NdotV * p; uvP += (vProjVTex.xy * vProjVTexZ); float sampDiscard = texture2D(wreckage, uvP, 0.0).a; if (sampDiscard <= 0.025f) { discard; } } else{ float sampDiscard = texture2D(wreckage, uvP, 0.0).a; if (sampDiscard <= 0.025f) { discard; } } } else{ float sampDiscard = texture2D(wreckage, uvP, 0.0).a; if (sampDiscard <= 0.025f) { discard; } } vec3 sampData = texture2D(wreckage, uvP).rgb; float metalness = min(1.0, sampData.b * 4.0); sampData.xy *= 2.0; sampData.xy -= 1.0; // Roughness setup float orgRoughness = sampData.b * 0.5 + 0.25; // Albedo setup vec3 albedo = toLinear(mix(vec3(0.271, 0.247, 0.216), vec3(0.541, 0.518, 0.502), sampData.b)); orgRoughness += 0.5; n = normalize(TBN * normalize(vec3(sampData.xy, deriveZ(sampData.xy)))); vec3 r = normalize(reflect(-v, n)); // Substance setup and Albedo adjust vec3 substance = clamp((0.04 - 0.04 * metalness) + albedo * metalness,0.0, 1.0); albedo -= substance; // Light model specials setup NdotV = max(0.0, dot(n, v)); // actual shading starts here vec3 color = vec3(0.0); vec3 ambientFresnel = Fresnel2(substance, NdotV ,orgRoughness); if (advancedAmbience){ color += square((textureCube(skybox, r, sqrt(orgRoughness) * 4.0).rgb) + 0.024) * ambientFresnel; // ambient light color += square(textureCube(skybox, n.xyz, 4.0).rgb + 0.024) * albedo * (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) * albedo * (1.0 - ambientFresnel); } else{ // Ambient color += vec3(0.006724, 0.014884, 0.067081) * (ambientFresnel + albedo * (1.0 - ambientFresnel)); } 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); 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; if (attenuation >0.0){ vec3 VplusL = L + v; vec3 halfVec = normalize(VplusL); float HdotN = max(0.0, dot(halfVec,n)); vec3 F = Fresnel(substance, L, halfVec); float D = max(0.0, D_GGX(HdotN, orgRoughness)); float V = max(0.0, V_SchlickforGGX((1.0 + orgRoughness) * 0.5, NdotV, NdotL)); float O = OrenNayerforDiffuseOnly(orgRoughness, NdotL, NdotV); color += ((D * V * F) + (1.0 - F) * O * albedo) * gl_LightSource[i].diffuse.rgb * attenuation; } } // hard cavity multiplier color *= (sampData.b + 1.0); } // this light model is pretty loose in terms of what it does, design focus to match // the pbr in terms of visibility and overall feel for a fraction of the price 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; vec3 VplusL = L + v; vec3 halfVec = normalize(VplusL); float HdotN = max(0.0, dot(halfVec, n)); vec3 S = Fresnel2(substance, HdotN ,orgRoughness); // albedo * (maskSamp.b * 0.5) to sorta hack albedo into a classical diffuse texture color += (albedo * (sampData.b * 0.5) + pow(S * HdotN, vec3(sampData.b + 5.0))) * gl_LightSource[i].diffuse.rgb * attenuation; } } float fadeCurve = clamp(1.0 - pow(life, 0.33) + 0.5, 0.0, 1.0); gl_FragColor.rgb = toGamma(color + toLinear(mix(vec3(0.69, 0.231, 0.027), vec3(1.0, 0.969, 0.6), fadeCurve) * fadeCurve * 5.0 * sampData.b)); gl_FragColor.a = 1.0; }