Created
July 26, 2023 07:34
-
-
Save stevenctl/e9d5b850e8d9e3b5ea7d643f224eff8a to your computer and use it in GitHub Desktop.
Got a combo heightmap (deep PoM) and world UV situtation working
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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