192 lines
6.7 KiB
Plaintext
192 lines
6.7 KiB
Plaintext
shader_type spatial;
|
|
|
|
uniform float blendSharpness;
|
|
uniform float specular = 0.0;
|
|
uniform vec4 albedoTint : source_color = vec4(1.0);
|
|
uniform float roughnessMultiplier = 1.0;
|
|
uniform vec4 floorAlbedoTint : source_color = vec4(1.0);
|
|
uniform float floorRoughnessMultiplier = 1.0;
|
|
|
|
uniform sampler2D textureMap : source_color;
|
|
uniform sampler2D roughnessMap : hint_roughness_gray;
|
|
uniform sampler2D normalMap : hint_normal;
|
|
uniform sampler2D heightMap : hint_default_white;
|
|
uniform float normalMapStrength : hint_range(0, 1) = 1.0;
|
|
uniform float uvScale = 1.0;
|
|
|
|
uniform bool enableFloor = false;
|
|
uniform sampler2D floorTextureMap : source_color;
|
|
uniform sampler2D floorRoughnessMap : hint_roughness_gray;
|
|
uniform sampler2D floorNormalMap : hint_normal;
|
|
uniform sampler2D floorHeightMap : hint_default_white;
|
|
uniform float floorUvScale = 1.0;
|
|
|
|
uniform bool enablePom = true;
|
|
uniform int heightMinLayers = 8;
|
|
uniform int heightMaxLayers = 64;
|
|
uniform float heightScale = 1.0;
|
|
|
|
varying vec3 worldPos;
|
|
varying vec3 worldNormal;
|
|
varying vec3 diffuse;
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
// TODO conditionals...
|
|
vec2 scaleUV(float yDot, vec2 uv) {
|
|
return uv * (enableFloor && yDot > 0.0 ? floorUvScale : uvScale);
|
|
}
|
|
|
|
// TODO conditionals...
|
|
vec4 sampleColor(float yDot, vec2 uv) {
|
|
return enableFloor && yDot > 0.0 ? texture(floorTextureMap, uv)*floorAlbedoTint : texture(textureMap, uv)*albedoTint;
|
|
}
|
|
|
|
// TODO conditionals...
|
|
vec4 sampleRoughness(float yDot, vec2 uv) {
|
|
return enableFloor && yDot > 0.0 ? texture(floorRoughnessMap, uv)*floorRoughnessMultiplier : texture(roughnessMap, uv)*roughnessMultiplier;
|
|
}
|
|
|
|
// TODO conditionals...
|
|
vec4 sampleHeight(float yDot, vec2 uv) {
|
|
return enableFloor && yDot > 0.0 ? texture(floorHeightMap, uv) : texture(heightMap, uv);
|
|
}
|
|
|
|
// TODO conditionals...
|
|
vec4 sampleNormal(float yDot, vec2 uv) {
|
|
return enableFloor && yDot > 0.0 ? texture(floorNormalMap, uv) : texture(normalMap, uv);
|
|
}
|
|
|
|
vec4 triplanarSample(vec2 uvX, vec2 uvY, vec2 uvZ, vec3 blend, float yDot) {
|
|
// Sample the texture using the calculated texture coordinates
|
|
vec4 texColorX = texture(textureMap, uvX);
|
|
vec4 texColorY = sampleColor(yDot, uvY);
|
|
vec4 texColorZ = texture(textureMap, uvZ);
|
|
|
|
// Blend the samples together
|
|
return texColorX * blend.x
|
|
+ texColorY * blend.y
|
|
+ texColorZ * blend.z;
|
|
}
|
|
|
|
vec4 triplanarRoughness(vec2 uvX, vec2 uvY, vec2 uvZ, vec3 blend, float yDot) {
|
|
// Sample the texture using the calculated texture coordinates
|
|
vec4 texColorX = texture(textureMap, uvX);
|
|
vec4 texColorY = sampleRoughness(yDot, uvY);
|
|
vec4 texColorZ = texture(textureMap, uvZ);
|
|
|
|
// Blend the samples together
|
|
return texColorX * blend.x
|
|
+ texColorY * blend.y
|
|
+ texColorZ * blend.z;
|
|
}
|
|
|
|
// The simplest appoach suggested in the goat's article:
|
|
// https://bgolus.medium.com/normal-mapping-for-a-triplanar-shader-10bf39dca05a
|
|
vec3 triplanarNormal(float yDot, vec2 uvX, vec2 uvY, vec2 uvZ, vec3 blend) {
|
|
|
|
// Tangent space normal maps
|
|
vec3 tnormalX = texture(normalMap, uvX).rgb;
|
|
vec3 tnormalY = sampleNormal(yDot, 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
|
|
);
|
|
|
|
}
|
|
|
|
// Adapted from the tutorial. Changed to accept a viewDir which represents each plane.
|
|
// https://www.youtube.com/watch?v=LrnE5f3h2SU
|
|
vec2 pomUV(float yDot, 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;
|
|
|
|
vec2 uvOffset = viewDir.xy * heightScale / 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 - sampleHeight(yDot, 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 - sampleHeight(yDot, 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 - sampleHeight(yDot, 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() {
|
|
// Calculate blending
|
|
float yDot = dot(worldNormal, vec3(0.0, 1.0, 0.0));
|
|
vec3 blend = vec3(
|
|
smoothstep(blendSharpness, 1.0, abs(dot(worldNormal, vec3(1.0, 0.0, 0.0)))),
|
|
smoothstep(blendSharpness, 1.0, abs(yDot)),
|
|
smoothstep(blendSharpness, 1.0, abs(dot(worldNormal, vec3(0.0, 0.0, 1.0))))
|
|
);
|
|
|
|
// view dir will be swizzled to match coordinates
|
|
vec3 viewDir = normalize(CAMERA_POSITION_WORLD - worldPos);
|
|
|
|
// Calculate texture coordinates
|
|
vec2 texCoordX = worldPos.zy * uvScale;
|
|
vec2 texCoordY = scaleUV(yDot, worldPos.zx);
|
|
vec2 texCoordZ = worldPos.xy * uvScale;
|
|
// TODO conditionals...
|
|
if (enablePom) {
|
|
texCoordX = pomUV(yDot, texCoordX, viewDir.zyx);
|
|
texCoordY = pomUV(yDot, texCoordY, viewDir.zxy);
|
|
texCoordZ = pomUV(yDot, texCoordZ, viewDir.xyz);
|
|
}
|
|
|
|
// sample and output
|
|
SPECULAR = specular;
|
|
ALBEDO = triplanarSample(texCoordX, texCoordY, texCoordZ, blend, yDot).rgb;
|
|
diffuse = ALBEDO;
|
|
ROUGHNESS = triplanarRoughness(texCoordX, texCoordY, texCoordZ, blend, yDot).r;
|
|
NORMAL = mix(worldNormal, triplanarNormal(yDot, texCoordX, texCoordY, texCoordZ, blend), normalMapStrength);
|
|
NORMAL = normalize((VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
|
}
|
|
|
|
void light() {
|
|
float lambert = dot(NORMAL, LIGHT);
|
|
float halfLambert = pow(lambert*0.5 + 0.5, 2);
|
|
DIFFUSE_LIGHT = halfLambert * ATTENUATION * LIGHT_COLOR / PI;
|
|
}
|