#version 450 core #extension GL_ARB_separate_shader_objects : enable layout (constant_id = 0) const bool FOG = true; layout (constant_id = 1) const bool PBR = true; layout (constant_id = 2) const bool BIPLANAR = false; layout (constant_id = 3) const bool TRIPLANAR = false; layout (constant_id = 4) const bool STOCHASTIC = false; layout (constant_id = 5) const bool BLEND = true; // ... layout (constant_id = 16) const int UNIT_SIZE = 8; layout (binding = 0) uniform UniformBufferObject { mat4 view; mat4 proj; vec3 lightInvDirection_worldspace; vec4 fog; //color.rgb depth.a } UBO; layout (binding = 1) uniform sampler2DArray TextureAtlas; layout (binding = 2) uniform sampler2DArray NormalAtlas; layout (binding = 3) uniform sampler2DArray HOSAtlas; #ifdef GEOMETRY layout (location = 0) in GeometryData #else layout (location = 0) in VertexData #endif { vec3 Position_modelspace; #ifdef GEOMETRY flat uint Textures[3]; vec3 TextureRatio; #else flat uint Texture; #endif vec3 FaceNormal_modelspace; vec3 FaceNormal_worldspace; vec3 EyeDirection_cameraspace; vec3 LightDirection_cameraspace; float Depth; } vs; layout(location = 0) out vec4 color; vec3 expand(vec3 v) { return (v - 0.5) * 2; } vec2 hash2D(vec2 s) { // magic numbers return fract(sin(mod(vec2(dot(s, vec2(127.1, 311.7)), dot(s, vec2(269.5, 183.3))), 3.14159)) * 43758.5453); } vec4 textureStochasticGrad(sampler2DArray smpl, vec3 UV, vec2 dx, vec2 dy) { if(STOCHASTIC) { // triangular by approx 2*sqrt(3) vec2 skewUV = mat2(1.0, 0.0, -0.57735027, 1.15470054) * (UV.xy * 3.46400); // vertex id and barrycentric coords vec2 vxID = vec2(floor(skewUV)); vec3 barry = vec3(fract(skewUV), 0); barry.z = 1.0 - barry.x - barry.y; vec3 BW_vx0; vec3 BW_vx1; vec3 BW_vx2; vec3 BW_vx3; if(barry.z > 0) { BW_vx0 = vec3(vxID, 0); BW_vx1 = vec3(vxID + vec2(0, 1), 0); BW_vx2 = vec3(vxID + vec2(1, 0), 0); BW_vx3 = barry.zyx; } else { BW_vx0 = vec3(vxID + vec2(1, 1), 0); BW_vx1 = vec3(vxID + vec2(1, 0), 0); BW_vx2 = vec3(vxID + vec2(0, 1), 0); BW_vx3 = vec3(-barry.z, 1.0 - barry.y, 1.0 - barry.x); } return textureGrad(smpl, vec3(UV.xy + hash2D(BW_vx0.xy), UV.z), dx, dy) * BW_vx3.x + textureGrad(smpl, vec3(UV.xy + hash2D(BW_vx1.xy), UV.z), dx, dy) * BW_vx3.y + textureGrad(smpl, vec3(UV.xy + hash2D(BW_vx2.xy), UV.z), dx, dy) * BW_vx3.z; } else { return textureGrad(smpl, UV, dx, dy); } } vec4 textureStochastic(sampler2DArray smpl, vec3 UV) { if(STOCHASTIC) { return textureStochasticGrad(smpl, UV, dFdx(UV.xy), dFdy(UV.xy)); } else { return texture(smpl, UV); } } vec4 getTexture(sampler2DArray smpl, vec2 UV) { #ifdef GEOMETRY if(BLEND) { vec4 colx = textureStochastic(smpl, vec3(UV, vs.Textures[0])); if(vs.Textures[1] == vs.Textures[0]) { return vs.Textures[2] == vs.Textures[0] ? colx : mix(colx, textureStochastic(smpl, vec3(UV, vs.Textures[2])), vs.TextureRatio.z); } else { vec4 coly = textureStochastic(smpl, vec3(UV, vs.Textures[1])); return vs.Textures[2] == vs.Textures[0] ? mix(colx, coly, vs.TextureRatio.y) : ( vs.Textures[2] == vs.Textures[1] ? mix(coly, colx, vs.TextureRatio.x) : colx * vs.TextureRatio.x + coly * vs.TextureRatio.y + textureStochastic(smpl, vec3(UV, vs.Textures[2])) * vs.TextureRatio.z); } } else { int mainTexture = vs.TextureRatio.x >= vs.TextureRatio.y ? (vs.TextureRatio.x >= vs.TextureRatio.z ? 0 : 2) : (vs.TextureRatio.y >= vs.TextureRatio.z ? 1 : 2); return textureStochastic(smpl, vec3(UV, vs.Textures[mainTexture])); } #else return textureStochastic(smpl, vec3(UV, vs.Texture)); #endif } vec4 getTextureGrad(sampler2DArray smpl, vec2 UV, vec2 dx, vec2 dy) { #ifdef GEOMETRY if(BLEND) { vec4 colx = textureStochasticGrad(smpl, vec3(UV, vs.Textures[0]), dx, dy); if(vs.Textures[1] == vs.Textures[0]) { return vs.Textures[2] == vs.Textures[0] ? colx : mix(colx, textureStochasticGrad(smpl, vec3(UV, vs.Textures[2]), dx, dy), vs.TextureRatio.z); } else { vec4 coly = textureStochasticGrad(smpl, vec3(UV, vs.Textures[1]), dx, dy); return vs.Textures[2] == vs.Textures[0] ? mix(colx, coly, vs.TextureRatio.y) : ( vs.Textures[2] == vs.Textures[1] ? mix(coly, colx, vs.TextureRatio.x) : colx * vs.TextureRatio.x + coly * vs.TextureRatio.y + textureStochasticGrad(smpl, vec3(UV, vs.Textures[2]), dx, dy) * vs.TextureRatio.z); } } else { int mainTexture = vs.TextureRatio.x >= vs.TextureRatio.y ? (vs.TextureRatio.x >= vs.TextureRatio.z ? 0 : 2) : (vs.TextureRatio.y >= vs.TextureRatio.z ? 1 : 2); return textureStochasticGrad(smpl, vec3(UV, vs.Textures[mainTexture]), dx, dy); } #else return textureStochasticGrad(smpl, vec3(UV, vs.Texture), dx, dy); #endif } vec4 getTriTexture(sampler2DArray smpl, vec2 UVx, vec2 UVy, vec2 UVz, vec3 w) { vec4 x = getTexture(smpl, UVx); vec4 y = getTexture(smpl, UVy); vec4 z = getTexture(smpl, UVz); return x*w.x + y*w.y + z*w.z; } vec4 getBiTexture(sampler2DArray smpl, vec2 UVa, vec2 UVb, vec2 dxa, vec2 dxb, vec2 dya, vec2 dyb, vec2 w) { vec4 x = getTextureGrad(smpl, UVa, dxa, dya); vec4 y = getTextureGrad(smpl, UVb, dxb, dyb); return x*w.x + y*w.y; } void main() { float texScale = 1. / UNIT_SIZE; float transitionSpeed = 2; vec4 tex; vec3 worldNormal, texHOS; if(TRIPLANAR) { // Triplanar float plateauSize = 0.001; vec3 blendWeights = abs(vs.FaceNormal_modelspace); blendWeights = blendWeights - plateauSize; blendWeights = pow(max(blendWeights, 0), vec3(transitionSpeed)); blendWeights = blendWeights / (blendWeights.x + blendWeights.y + blendWeights.z); vec2 UVx = vs.Position_modelspace.yz * texScale; vec2 UVy = vs.Position_modelspace.zx * texScale; vec2 UVz = vs.Position_modelspace.xy * texScale; tex = getTriTexture(TextureAtlas, UVx, UVy, UVz, normalize(blendWeights)); if(PBR) { // Whiteout normal blend vec3 texNx = expand(getTexture(NormalAtlas, UVx).rgb); vec3 texNy = expand(getTexture(NormalAtlas, UVy).rgb); vec3 texNz = expand(getTexture(NormalAtlas, UVz).rgb); // Swizzle world normals into tangent space and apply Whiteout blend texNx = vec3(texNx.xy + vs.FaceNormal_worldspace.zy, abs(texNx.z) * vs.FaceNormal_worldspace.x); texNy = vec3(texNy.xy + vs.FaceNormal_worldspace.xz, abs(texNy.z) * vs.FaceNormal_worldspace.y); texNz = vec3(texNz.xy + vs.FaceNormal_worldspace.xy, abs(texNz.z) * vs.FaceNormal_worldspace.z); // Swizzle tangent normals to match world orientation and triblend worldNormal = normalize(texNx.zyx * blendWeights.x + texNy.xzy * blendWeights.y +texNz.xyz * blendWeights.z); texHOS = getTriTexture(HOSAtlas, UVx, UVy, UVz, blendWeights).rgb; } } else if(BIPLANAR) { // Biplanar vec3 pos = vs.Position_modelspace * texScale; vec3 dpdx = dFdx(pos); vec3 dpdy = dFdy(pos); vec3 blendWeights = abs(normalize(vs.FaceNormal_modelspace)); // determine major axis (in x; yz are following axis) ivec3 ma = (blendWeights.x>blendWeights.y && blendWeights.x>blendWeights.z) ? ivec3(0,1,2) : (blendWeights.y>blendWeights.z) ? ivec3(1,2,0) : ivec3(2,0,1); // determine minor axis (in x; yz are following axis) ivec3 mi = (blendWeights.x 1 // - light is perpendiular to the triangle -> 0 // - light is behind the triangle -> 0 float cosTheta = clamp(dot(Normal_cameraspace,l), 0,1 ); // Eye vector (towards the camera) vec3 E = normalize(vs.EyeDirection_cameraspace); // Direction in which the triangle reflects the light vec3 R = reflect(-l,Normal_cameraspace); // Cosine of the angle between the Eye vector and the Reflect vector, // clamped to 0 // - Looking into the reflection -> 1 // - Looking elsewhere -> < 1 float cosAlpha = clamp( dot( E,R ), 0,1 ); float visibility=1.0; // MAYBE: shadow color = vec4( // Ambient : simulates indirect lighting TextureAmbientColor + // Diffuse : "color" of the object visibility * TextureDiffuseColor * LightColor * LightPower * cosTheta / (distance * distance) + // Specular : reflective highlight, like a mirror visibility * TextureSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance * distance), // Restore alpha tex.a); } else { color = tex; } if(FOG) { float ratio = exp(vs.Depth * 0.69)-1; color = mix(color, vec4(UBO.fog.rgb, 1), clamp(ratio, 0, 1)); } }