From 4a9688532381ac5813da164ba287f653bdf97213 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Dec 2023 21:45:15 +0300 Subject: [PATCH] Untangle normals and parallax handling Move tangent space generation to the vertex shaders Support diffuse parallax when no normal map is present Don't use diffuse parallax if there's no diffuse map Generalize normal-to-view conversion Rewrite parallax --- components/shader/shadervisitor.cpp | 18 +++--- files/shaders/CMakeLists.txt | 1 + files/shaders/compatibility/bs/default.frag | 15 ++--- files/shaders/compatibility/bs/default.vert | 15 +++-- files/shaders/compatibility/groundcover.frag | 22 ++----- files/shaders/compatibility/groundcover.vert | 12 ++-- files/shaders/compatibility/normals.glsl | 14 ++++ files/shaders/compatibility/objects.frag | 67 +++++++------------- files/shaders/compatibility/objects.vert | 23 ++++--- files/shaders/compatibility/terrain.frag | 38 +++-------- files/shaders/compatibility/terrain.vert | 16 +++-- files/shaders/lib/material/parallax.glsl | 7 +- 12 files changed, 118 insertions(+), 130 deletions(-) create mode 100644 files/shaders/compatibility/normals.glsl diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a08652620d..70464f571e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -304,14 +304,6 @@ namespace Shader if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true; - int applyMode; - // Oblivion parallax - if (node.getUserValue("applyMode", applyMode) && applyMode == 4) - { - mRequirements.back().mShaderRequired = true; - mRequirements.back().mDiffuseHeight = true; - } - // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); @@ -359,7 +351,17 @@ namespace Shader normalMap = texture; } else if (texName == "diffuseMap") + { + int applyMode; + // Oblivion parallax + if (node.getUserValue("applyMode", applyMode) && applyMode == 4) + { + mRequirements.back().mShaderRequired = true; + mRequirements.back().mDiffuseHeight = true; + mRequirements.back().mTexStageRequiringTangents = unit; + } diffuseMap = texture; + } else if (texName == "specularMap") specularMap = texture; else if (texName == "bumpMap") diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 1b73acf758..6ead738477 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -45,6 +45,7 @@ set(SHADER_FILES compatibility/shadowcasting.vert compatibility/shadowcasting.frag compatibility/vertexcolors.glsl + compatibility/normals.glsl compatibility/multiview_resolve.vert compatibility/multiview_resolve.frag compatibility/depthclipped.vert diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index f1be8da80c..7e2be9aa8f 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -24,7 +24,6 @@ varying vec2 emissiveMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif varying float euclideanDepth; @@ -46,11 +45,10 @@ uniform bool useTreeAnim; #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_fragment.glsl" #include "compatibility/fog.glsl" +#include "compatibility/normals.glsl" void main() { - vec3 normal = normalize(passNormal); - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); @@ -65,15 +63,10 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); #endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); float shadowing = unshadowedLightRatio(linearDepth); vec3 diffuseLight, ambientLight; diff --git a/files/shaders/compatibility/bs/default.vert b/files/shaders/compatibility/bs/default.vert index 712a3f3d0c..d9d47843c0 100644 --- a/files/shaders/compatibility/bs/default.vert +++ b/files/shaders/compatibility/bs/default.vert @@ -36,6 +36,7 @@ varying vec3 passNormal; #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_vertex.glsl" +#include "compatibility/normals.glsl" void main(void) { @@ -45,6 +46,14 @@ void main(void) gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; + +#if @normalMap + normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw, passNormal); +#endif #if @diffuseMap diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; @@ -56,15 +65,11 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw; #endif - passColor = gl_Color; - passViewPos = viewPos.xyz; - passNormal = gl_Normal.xyz; #if @shadows_enabled - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + vec3 viewNormal = normalToView(passNormal); setupShadowCoords(viewPos, viewNormal); #endif } diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index 9dfd71b32e..f87beaf447 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -18,7 +18,6 @@ varying vec2 diffuseMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif // Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. @@ -44,23 +43,10 @@ varying vec3 passNormal; #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" #include "fog.glsl" +#include "compatibility/normals.glsl" void main() { - vec3 normal = normalize(passNormal); - -#if @normalMap - vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); -#endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else @@ -72,6 +58,12 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif + float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; diff --git a/files/shaders/compatibility/groundcover.vert b/files/shaders/compatibility/groundcover.vert index dd8d083a47..c1bb35da05 100644 --- a/files/shaders/compatibility/groundcover.vert +++ b/files/shaders/compatibility/groundcover.vert @@ -21,7 +21,6 @@ varying vec2 diffuseMapUV; #if @normalMap varying vec2 normalMapUV; -varying vec4 passTangent; #endif // Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. @@ -41,6 +40,7 @@ centroid varying vec3 shadowDiffuseLighting; varying vec3 passNormal; #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -149,8 +149,14 @@ void main(void) linearDepth = getLinearDepth(gl_Position.z, viewPos.z); + passNormal = rotation3(rotation) * gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; +#if @normalMap + normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw * rotation, passNormal); +#endif + #if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); + vec3 viewNormal = normalToView(passNormal); #endif #if @diffuseMap @@ -159,10 +165,8 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw * rotation; #endif - passNormal = rotation3(rotation) * gl_Normal.xyz; #if PER_PIXEL_LIGHTING passViewPos = viewPos.xyz; #else diff --git a/files/shaders/compatibility/normals.glsl b/files/shaders/compatibility/normals.glsl new file mode 100644 index 0000000000..8df16a4b12 --- /dev/null +++ b/files/shaders/compatibility/normals.glsl @@ -0,0 +1,14 @@ +varying mat3 normalToViewMatrix; + +mat3 generateTangentSpace(vec4 tangent, vec3 normal) +{ + vec3 normalizedNormal = normalize(normal); + vec3 normalizedTangent = normalize(tangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * tangent.w; + return mat3(normalizedTangent, binormal, normalizedNormal); +} + +vec3 normalToView(vec3 normal) +{ + return normalize(normalToViewMatrix * normal); +} diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 6de1f6d02f..dd9c3e5f3b 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -37,7 +37,6 @@ varying vec2 emissiveMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif #if @envMap @@ -79,6 +78,9 @@ uniform float emissiveMult; uniform float specStrength; varying vec3 passViewPos; varying vec3 passNormal; +#if @normalMap || @diffuseParallax +varying vec4 passTangent; +#endif #if @additiveBlending #define ADDITIVE_BLENDING @@ -91,6 +93,7 @@ varying vec3 passNormal; #include "fog.glsl" #include "vertexcolors.glsl" #include "shadows_fragment.glsl" +#include "compatibility/normals.glsl" #if @softParticles #include "lib/particle/soft.glsl" @@ -113,62 +116,32 @@ void main() applyOcclusionDiscard(orthoDepthMapCoord, texture2D(orthoDepthMap, orthoDepthMapCoord.xy * 0.5 + 0.5).r); #endif - vec3 normal = normalize(passNormal); - vec3 viewVec = normalize(passViewPos.xyz); + // only offset diffuse and normal maps for now, other textures are more likely to be using a completely different UV set + vec2 offset = vec2(0.0); -#if @normalMap - vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); -#endif - -#if !@diffuseMap - gl_FragData[0] = vec4(1.0); -#else - vec2 adjustedDiffuseUV = diffuseMapUV; - -#if @normalMap && (@parallax || @diffuseParallax) - vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; - vec3 eyeDir = normalize(cameraPos - objectPos); +#if @parallax || @diffuseParallax #if @parallax - float height = normalTex.a; + float height = texture2D(normalMap, normalMapUV).a; float flipY = (passTangent.w > 0.0) ? -1.f : 1.f; #else float height = texture2D(diffuseMap, diffuseMapUV).a; // FIXME: shouldn't be necessary, but in this path false-positives are common float flipY = -1.f; #endif - vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, height, flipY); - adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set - - // TODO: check not working as the same UV buffer is being bound to different targets - // if diffuseMapUV == normalMapUV -#if 1 - // fetch a new normal using updated coordinates - normalTex = texture2D(normalMap, adjustedDiffuseUV); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); + offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY); #endif -#endif - - vec4 diffuseTex = texture2D(diffuseMap, adjustedDiffuseUV); - gl_FragData[0].xyz = diffuseTex.xyz; -#if !@diffuseParallax - gl_FragData[0].a = diffuseTex.a * coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); -#else +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV + offset); +#if @diffuseParallax gl_FragData[0].a = 1.0; +#else + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV + offset); #endif +#else + gl_FragData[0] = vec4(1.0); #endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - vec4 diffuseColor = getDiffuseColor(); gl_FragData[0].a *= diffuseColor.a; @@ -179,6 +152,14 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif + + vec3 viewVec = normalize(passViewPos); + #if @detailMap gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; #endif diff --git a/files/shaders/compatibility/objects.vert b/files/shaders/compatibility/objects.vert index 1ea4a1553f..1ec0917ea8 100644 --- a/files/shaders/compatibility/objects.vert +++ b/files/shaders/compatibility/objects.vert @@ -31,7 +31,6 @@ varying vec2 emissiveMapUV; #if @normalMap varying vec2 normalMapUV; -varying vec4 passTangent; #endif #if @envMap @@ -59,9 +58,13 @@ uniform float emissiveMult; #endif varying vec3 passViewPos; varying vec3 passNormal; +#if @normalMap || @diffuseParallax +varying vec4 passTangent; +#endif #include "vertexcolors.glsl" #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -84,9 +87,18 @@ void main(void) vec4 viewPos = modelToView(gl_Vertex); gl_ClipVertex = viewPos; + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; -#if (@envMap || !PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); +#if @normalMap || @diffuseParallax + passTangent = gl_MultiTexCoord7.xyzw; + normalToViewMatrix *= generateTangentSpace(passTangent, passNormal); +#endif + +#if @envMap || !PER_PIXEL_LIGHTING || @shadows_enabled + vec3 viewNormal = normalToView(passNormal); #endif #if @envMap @@ -118,7 +130,6 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw; #endif #if @bumpMap @@ -133,10 +144,6 @@ void main(void) glossMapUV = (gl_TextureMatrix[@glossMapUV] * gl_MultiTexCoord@glossMapUV).xy; #endif - passColor = gl_Color; - passViewPos = viewPos.xyz; - passNormal = gl_Normal.xyz; - #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 744a56d18b..734a358590 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -40,49 +40,31 @@ uniform float far; #include "lib/light/lighting.glsl" #include "lib/material/parallax.glsl" #include "fog.glsl" +#include "compatibility/normals.glsl" void main() { vec2 adjustedUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; - vec3 normal = normalize(passNormal); - -#if @normalMap - vec4 normalTex = texture2D(normalMap, adjustedUV); - - vec3 normalizedNormal = normal; - vec3 tangent = vec3(1.0, 0.0, 0.0); - vec3 binormal = normalize(cross(tangent, normalizedNormal)); - tangent = normalize(cross(normalizedNormal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal - mat3 tbnTranspose = mat3(tangent, binormal, normalizedNormal); - - normal = tbnTranspose * (normalTex.xyz * 2.0 - 1.0); -#endif - #if @parallax - vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; - vec3 eyeDir = normalize(cameraPos - objectPos); - adjustedUV += getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, 1.f); - - // update normal using new coordinates - normalTex = texture2D(normalMap, adjustedUV); - - normal = tbnTranspose * (normalTex.xyz * 2.0 - 1.0); + adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, 1.f); #endif - - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - vec4 diffuseTex = texture2D(diffuseMap, adjustedUV); gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + #if @blendMap vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; #endif - vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index 5e154d912a..f74bc1a95f 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -24,6 +24,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -37,13 +38,20 @@ void main(void) euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); -#if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); -#endif - passColor = gl_Color; passNormal = gl_Normal.xyz; passViewPos = viewPos.xyz; + normalToViewMatrix = gl_NormalMatrix; + +#if @normalMap + mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, 1.0), passNormal); + tbnMatrix[0] = normalize(cross(tbnMatrix[2], tbnMatrix[1])); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + normalToViewMatrix *= tbnMatrix; +#endif + +#if !PER_PIXEL_LIGHTING || @shadows_enabled + vec3 viewNormal = normalToView(passNormal); +#endif #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; diff --git a/files/shaders/lib/material/parallax.glsl b/files/shaders/lib/material/parallax.glsl index 7f4ce2d1dc..5525281f75 100644 --- a/files/shaders/lib/material/parallax.glsl +++ b/files/shaders/lib/material/parallax.glsl @@ -4,10 +4,9 @@ #define PARALLAX_SCALE 0.04 #define PARALLAX_BIAS -0.02 -vec2 getParallaxOffset(vec3 eyeDir, mat3 tbnTranspose, float height, float flipY) +vec2 getParallaxOffset(vec3 eyeDir, float height, float flipY) { - vec3 TSeyeDir = normalize(eyeDir * tbnTranspose); - return vec2(TSeyeDir.x, TSeyeDir.y * flipY) * ( height * PARALLAX_SCALE + PARALLAX_BIAS ); + return vec2(eyeDir.x, eyeDir.y * flipY) * ( height * PARALLAX_SCALE + PARALLAX_BIAS ); } -#endif \ No newline at end of file +#endif