mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add normal mapping and parallax occlusion mapping for WMO surfaces
Generate normal+height maps from diffuse textures at load time using luminance-to-height and Sobel 3x3 filtering. Compute per-vertex tangents via Lengyel's method for TBN basis construction. Fragment shader uses screen-space UV derivatives (dFdx/dFdy) for smooth LOD crossfade and angle-adaptive POM sample counts. Flat textures naturally produce low height variance, causing POM to self-select off. Settings: Normal Mapping on by default, POM off by default with Low/Medium/High quality presets. Persisted across sessions.
This commit is contained in:
parent
1b16bcf71f
commit
eaceb58e77
8 changed files with 424 additions and 33 deletions
|
|
@ -22,23 +22,104 @@ layout(set = 1, binding = 1) uniform WMOMaterial {
|
|||
int isInterior;
|
||||
float specularIntensity;
|
||||
int isWindow;
|
||||
int enableNormalMap;
|
||||
int enablePOM;
|
||||
float pomScale;
|
||||
int pomMaxSamples;
|
||||
float heightMapVariance;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 2) uniform sampler2D uNormalHeightMap;
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2DShadow uShadowMap;
|
||||
|
||||
layout(location = 0) in vec3 FragPos;
|
||||
layout(location = 1) in vec3 Normal;
|
||||
layout(location = 2) in vec2 TexCoord;
|
||||
layout(location = 3) in vec4 VertColor;
|
||||
layout(location = 4) in vec3 Tangent;
|
||||
layout(location = 5) in vec3 Bitangent;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
// LOD factor from screen-space UV derivatives
|
||||
float computeLodFactor() {
|
||||
vec2 dx = dFdx(TexCoord);
|
||||
vec2 dy = dFdy(TexCoord);
|
||||
float texelDensity = max(dot(dx, dx), dot(dy, dy));
|
||||
// Low density = close/head-on = full detail (0)
|
||||
// High density = far/steep = vertex normals only (1)
|
||||
return smoothstep(0.0001, 0.005, texelDensity);
|
||||
}
|
||||
|
||||
// Parallax Occlusion Mapping with angle-adaptive sampling
|
||||
vec2 parallaxOcclusionMap(vec2 uv, vec3 viewDirTS, float lodFactor) {
|
||||
float VdotN = abs(viewDirTS.z); // 1=head-on, 0=grazing
|
||||
float angleFactor = clamp(VdotN, 0.15, 1.0);
|
||||
int maxS = pomMaxSamples;
|
||||
int minS = max(maxS / 4, 4);
|
||||
int numSamples = int(mix(float(minS), float(maxS), angleFactor));
|
||||
numSamples = int(mix(float(minS), float(numSamples), 1.0 - lodFactor));
|
||||
|
||||
float layerDepth = 1.0 / float(numSamples);
|
||||
float currentLayerDepth = 0.0;
|
||||
|
||||
// Direction to shift UV per layer
|
||||
vec2 P = viewDirTS.xy / max(abs(viewDirTS.z), 0.001) * pomScale;
|
||||
vec2 deltaUV = P / float(numSamples);
|
||||
|
||||
vec2 currentUV = uv;
|
||||
float currentDepthMapValue = 1.0 - texture(uNormalHeightMap, currentUV).a;
|
||||
|
||||
// Ray march through layers
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (i >= numSamples || currentLayerDepth >= currentDepthMapValue) break;
|
||||
currentUV -= deltaUV;
|
||||
currentDepthMapValue = 1.0 - texture(uNormalHeightMap, currentUV).a;
|
||||
currentLayerDepth += layerDepth;
|
||||
}
|
||||
|
||||
// Interpolate between last two layers for smooth result
|
||||
vec2 prevUV = currentUV + deltaUV;
|
||||
float afterDepth = currentDepthMapValue - currentLayerDepth;
|
||||
float beforeDepth = (1.0 - texture(uNormalHeightMap, prevUV).a) - currentLayerDepth + layerDepth;
|
||||
float weight = afterDepth / (afterDepth - beforeDepth + 0.0001);
|
||||
return mix(currentUV, prevUV, weight);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 texColor = hasTexture != 0 ? texture(uTexture, TexCoord) : vec4(1.0);
|
||||
float lodFactor = computeLodFactor();
|
||||
|
||||
vec3 vertexNormal = normalize(Normal);
|
||||
if (!gl_FrontFacing) vertexNormal = -vertexNormal;
|
||||
|
||||
// Compute final UV (with POM if enabled)
|
||||
vec2 finalUV = TexCoord;
|
||||
|
||||
// Build TBN matrix
|
||||
vec3 T = normalize(Tangent);
|
||||
vec3 B = normalize(Bitangent);
|
||||
vec3 N = vertexNormal;
|
||||
mat3 TBN = mat3(T, B, N);
|
||||
|
||||
if (enablePOM != 0 && heightMapVariance > 0.001 && lodFactor < 0.99) {
|
||||
mat3 TBN_inv = transpose(TBN);
|
||||
vec3 viewDirWorld = normalize(viewPos.xyz - FragPos);
|
||||
vec3 viewDirTS = TBN_inv * viewDirWorld;
|
||||
finalUV = parallaxOcclusionMap(TexCoord, viewDirTS, lodFactor);
|
||||
}
|
||||
|
||||
vec4 texColor = hasTexture != 0 ? texture(uTexture, finalUV) : vec4(1.0);
|
||||
if (alphaTest != 0 && texColor.a < 0.5) discard;
|
||||
|
||||
vec3 norm = normalize(Normal);
|
||||
if (!gl_FrontFacing) norm = -norm;
|
||||
// Compute normal (with normal mapping if enabled)
|
||||
vec3 norm = vertexNormal;
|
||||
if (enableNormalMap != 0 && lodFactor < 0.99) {
|
||||
vec3 mapNormal = texture(uNormalHeightMap, finalUV).rgb * 2.0 - 1.0;
|
||||
vec3 worldNormal = normalize(TBN * mapNormal);
|
||||
if (!gl_FrontFacing) worldNormal = -worldNormal;
|
||||
norm = normalize(mix(worldNormal, vertexNormal, lodFactor));
|
||||
}
|
||||
|
||||
vec3 result;
|
||||
|
||||
|
|
@ -82,39 +163,29 @@ void main() {
|
|||
float alpha = texColor.a;
|
||||
|
||||
// Window glass: opaque but simulates dark tinted glass with reflections.
|
||||
// No real alpha blending — we darken the base texture and add reflection
|
||||
// on top so it reads as glass without needing the transparent pipeline.
|
||||
if (isWindow != 0) {
|
||||
vec3 viewDir = normalize(viewPos.xyz - FragPos);
|
||||
float NdotV = abs(dot(norm, viewDir));
|
||||
// Fresnel: strong reflection at grazing angles
|
||||
float fresnel = 0.08 + 0.92 * pow(1.0 - NdotV, 4.0);
|
||||
|
||||
// Glass darkness depends on view angle — bright when sun glints off,
|
||||
// darker when looking straight on with no sun reflection.
|
||||
vec3 ldir = normalize(-lightDir.xyz);
|
||||
vec3 reflectDir = reflect(-viewDir, norm);
|
||||
float sunGlint = pow(max(dot(reflectDir, ldir), 0.0), 32.0);
|
||||
|
||||
// Base ranges from dark (0.3) to bright (0.9) based on sun reflection
|
||||
float baseBrightness = mix(0.3, 0.9, sunGlint);
|
||||
vec3 glass = result * baseBrightness;
|
||||
|
||||
// Reflection: blend sky/ambient color based on Fresnel
|
||||
vec3 reflectTint = mix(ambientColor.rgb * 1.2, vec3(0.6, 0.75, 1.0), 0.6);
|
||||
glass = mix(glass, reflectTint, fresnel * 0.8);
|
||||
|
||||
// Sharp sun glint on glass
|
||||
vec3 halfDir = normalize(ldir + viewDir);
|
||||
float spec = pow(max(dot(norm, halfDir), 0.0), 256.0);
|
||||
glass += spec * lightColor.rgb * 0.8;
|
||||
|
||||
// Broad warm sheen when sun is nearby
|
||||
float specBroad = pow(max(dot(norm, halfDir), 0.0), 12.0);
|
||||
glass += specBroad * lightColor.rgb * 0.12;
|
||||
|
||||
result = glass;
|
||||
// Fresnel-based transparency: more transparent at oblique angles
|
||||
alpha = mix(0.4, 0.95, NdotV);
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -21,17 +21,33 @@ layout(location = 0) in vec3 aPos;
|
|||
layout(location = 1) in vec3 aNormal;
|
||||
layout(location = 2) in vec2 aTexCoord;
|
||||
layout(location = 3) in vec4 aColor;
|
||||
layout(location = 4) in vec4 aTangent;
|
||||
|
||||
layout(location = 0) out vec3 FragPos;
|
||||
layout(location = 1) out vec3 Normal;
|
||||
layout(location = 2) out vec2 TexCoord;
|
||||
layout(location = 3) out vec4 VertColor;
|
||||
layout(location = 4) out vec3 Tangent;
|
||||
layout(location = 5) out vec3 Bitangent;
|
||||
|
||||
void main() {
|
||||
vec4 worldPos = push.model * vec4(aPos, 1.0);
|
||||
FragPos = worldPos.xyz;
|
||||
Normal = mat3(push.model) * aNormal;
|
||||
|
||||
mat3 normalMatrix = mat3(push.model);
|
||||
Normal = normalMatrix * aNormal;
|
||||
TexCoord = aTexCoord;
|
||||
VertColor = aColor;
|
||||
|
||||
// Compute TBN basis vectors for normal mapping
|
||||
vec3 T = normalize(normalMatrix * aTangent.xyz);
|
||||
vec3 N = normalize(Normal);
|
||||
// Gram-Schmidt re-orthogonalize
|
||||
T = normalize(T - dot(T, N) * N);
|
||||
vec3 B = cross(N, T) * aTangent.w;
|
||||
|
||||
Tangent = T;
|
||||
Bitangent = B;
|
||||
|
||||
gl_Position = projection * view * worldPos;
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue