Skip to content

Instantly share code, notes, and snippets.

@stevenctl
Created July 26, 2023 07:34
Show Gist options
  • Save stevenctl/e9d5b850e8d9e3b5ea7d643f224eff8a to your computer and use it in GitHub Desktop.
Save stevenctl/e9d5b850e8d9e3b5ea7d643f224eff8a to your computer and use it in GitHub Desktop.
Got a combo heightmap (deep PoM) and world UV situtation working
shader_type spatial;
render_mode unshaded;
uniform float uvScale = 1.0;
uniform float blendSharpness;
uniform sampler2D textureMap : source_color;
uniform sampler2D normalMap : hint_normal;
uniform float normalScale = 1.0;
uniform float debug : hint_range(0, 1) = 0.5 ;
uniform float zDiv : hint_range(0, 1) = 0.5 ;
uniform sampler2D heightMap : hint_default_white;
uniform int heightMinLayers = 8;
uniform int heightMaxLayers = 64;
uniform float heightScale = 1.0;
varying vec3 worldPos;
varying vec3 worldNormal;
varying vec3 binormal;
varying vec3 tangent;
void vertex() {
// Transform the vertex position to world space
worldPos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Transform the vertex normal to world space
worldNormal = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz);
}
vec4 triplanarSample(sampler2D texMap, vec2 texCoordX, vec2 texCoordY, vec2 texCoordZ, vec3 blend) {
// Sample the texture using the calculated texture coordinates
vec4 texColorX = texture(texMap, texCoordX);
vec4 texColorY = texture(texMap, texCoordY);
vec4 texColorZ = texture(texMap, texCoordZ);
// Blend the samples together
return texColorX * blend.x
+ texColorY * blend.y
+ texColorZ * blend.z;
}
// https://bgolus.medium.com/normal-mapping-for-a-triplanar-shader-10bf39dca05a
vec3 triplanarNormal(vec2 uvX, vec2 uvY, vec2 uvZ, vec3 blend) {
// Tangent space normal maps
vec3 tnormalX = texture(normalMap, uvX).rgb;
vec3 tnormalY = texture(normalMap, uvY).rgb;
vec3 tnormalZ = texture(normalMap, uvZ).rgb;
// Get the sign (-1 or 1) of the surface normal
vec3 axisSign = sign(worldNormal);
// Flip tangent normal z to account for surface normal facing
tnormalX.z *= axisSign.x;
tnormalY.z *= axisSign.y;
tnormalZ.z *= axisSign.z;
// Swizzle tangent normals to match world orientation and triblend
return normalize(
tnormalX.zyx * blend.x +
tnormalY.xzy * blend.y +
tnormalZ.xyz * blend.z
);
}
// https://www.youtube.com/watch?v=LrnE5f3h2SU
vec2 pomUV(vec2 m_base_uv, vec3 viewDir) {
float viewDot = dot(viewDir, vec3(1, 0, 0));
float minLayers = float(min(heightMinLayers, heightMaxLayers));
float maxLayers = float(max(heightMinLayers, heightMaxLayers));
float numLayers = mix(maxLayers, minLayers, abs(viewDot));
numLayers = clamp(numLayers, minLayers, maxLayers);
float layerDepth = 1.0f / numLayers;
// seems like turning off zDiv makes things work on "negative" faces
// probably something I can do with `sign` to fix this
vec2 S = viewDir.xy / mix(1.0, viewDir.z, zDiv) * heightScale;
vec2 uvOffset = S / numLayers;
// tracks how "deep" we are on each iteration
float currentLayerDepth = 0.0;
// tracks how deep the heightmap; adjusted on each iteration as UVs shift
float depthMapValue = 1.0 - texture(heightMap, m_base_uv).r;
// loop until the current layer is deeper than the heightmap (hit)
// the 100 iteration cap is because I'm paranoid
for (int i = 0; i < 100 && currentLayerDepth < depthMapValue; i++) {
m_base_uv -= uvOffset;
depthMapValue = 1.0 - texture(heightMap, m_base_uv).r;
currentLayerDepth += layerDepth;
}
// occlusion (interpolate with prev value)
vec2 prevUV = m_base_uv + uvOffset;
float afterDepth = depthMapValue - currentLayerDepth;
float beforeDepth = 1.0 - texture(heightMap, prevUV).r - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth);
m_base_uv = prevUV * weight + m_base_uv * (1.0 - weight);
return m_base_uv;
}
void fragment() {
// Sample the heightmap using the calculated texture coordinates
vec2 texCoordHeight = worldPos.xy * uvScale;
float height = texture(heightMap, texCoordHeight).r;
// view dir will be swizzled to match coordinates
vec3 viewDir = normalize(CAMERA_POSITION_WORLD - worldPos);
// Calculate texture coordinates
vec2 texCoordX = pomUV(worldPos.zy * uvScale, viewDir.zyx);
vec2 texCoordY = pomUV(worldPos.zx * uvScale, viewDir.zxy);
vec2 texCoordZ = pomUV(worldPos.xy * uvScale, viewDir.xyz);
// Calculate blending
vec3 blend = vec3(
smoothstep(blendSharpness, 1.0, abs(dot(worldNormal, vec3(1.0, 0.0, 0.0)))),
smoothstep(blendSharpness, 1.0, abs(dot(worldNormal, vec3(0.0, 1.0, 0.0)))),
smoothstep(blendSharpness, 1.0, abs(dot(worldNormal, vec3(0.0, 0.0, 1.0))))
);
// sample and output
ALBEDO = triplanarSample(textureMap, texCoordX, texCoordY, texCoordZ, blend).rgb;
// NORMAL = triplanarNormal(texCoordX, texCoordY, texCoordZ, blend);
NORMAL = normalize(triplanarSample(normalMap, texCoordX, texCoordY, texCoordZ, blend).rgb) * normalScale;
ALBEDO = mix(vec3(viewDir), ALBEDO, debug);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment