Last active
April 25, 2024 12:16
-
-
Save Xynonners/ddf30a5215a59db5690f6420621f0623 to your computer and use it in GitHub Desktop.
gdshader HHAA implementation (spatial/depth AA)
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; | |
void vertex() { | |
POSITION = vec4(VERTEX, 1.0); | |
} | |
uniform sampler2D BackBufferTex : hint_screen_texture, repeat_disable, filter_nearest; | |
uniform sampler2D DepthBufferTex : source_color, hint_depth_texture, filter_nearest; | |
uniform float luma_threshold: hint_range(0, 1) = 0.375; | |
uniform float low_threshold: hint_range(0, 1) = 0.05; | |
uniform float high_threshold: hint_range(0, 1) = 0.2; | |
uniform float depth_threshold: hint_range(0, 1) = 0.1; | |
uniform bool cosine_blending = true; | |
// Constants for offsets | |
const ivec2 OffSW = ivec2(-1, 1); | |
const ivec2 OffSE = ivec2(1, 1); | |
const ivec2 OffNE = ivec2(1, -1); | |
const ivec2 OffNW = ivec2(-1, -1); | |
// Function to calculate luminance | |
float getLuma(vec3 color) { | |
return dot(color, vec3(0.299, 0.587, 0.114)); | |
} | |
// Sampling functions | |
vec3 sampleColor(vec2 p) { | |
return texture(BackBufferTex, p).rgb; | |
} | |
// Function to sample luminance at an offset | |
float sampleLumaOff(vec2 uv, ivec2 offset, vec2 texSize) { | |
return getLuma(texture(BackBufferTex, uv + vec2(offset) / texSize).rgb); | |
} | |
// Sobel operator kernels | |
const mat3 sobel_kernel_x = mat3(vec3(-1, 0, 1), | |
vec3(-2, 0, 2), | |
vec3(-1, 0, 1)); | |
const mat3 sobel_kernel_y = mat3(vec3(-1, -2, -1), | |
vec3(0, 0, 0), | |
vec3(1, 2, 1)); | |
// Function to apply Sobel filter | |
vec2 applySobel(vec2 uv, vec2 texSize) { | |
float gx = 0.0; | |
float gy = 0.0; | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
vec3 sampleColor = texture(BackBufferTex, uv + vec2(float(x), float(y)) / texSize).rgb; | |
float luma = getLuma(sampleColor); | |
gx += luma * sobel_kernel_x[x + 1][y + 1]; | |
gy += luma * sobel_kernel_y[x + 1][y + 1]; | |
} | |
} | |
return vec2(gx, gy); | |
} | |
// Non-maximum suppression | |
bool isLocalMaximum(vec2 uv, vec2 gradient, vec2 texSize) { | |
float edgeStrength = length(gradient); | |
float angle = atan(gradient.y, gradient.x); | |
vec2 n = normalize(vec2(cos(angle), sin(angle))); | |
float luma1 = length(applySobel(uv + n / texSize, texSize)); | |
float luma2 = length(applySobel(uv - n / texSize, texSize)); | |
return edgeStrength >= luma1 && edgeStrength >= luma2; | |
} | |
// Gaussian kernel (5x5 approximation) | |
const float gaussian_kernel[25] = float[25]( | |
1.0, 4.0, 7.0, 4.0, 1.0, | |
4.0, 16.0, 26.0, 16.0, 4.0, | |
7.0, 26.0, 41.0, 26.0, 7.0, | |
4.0, 16.0, 26.0, 16.0, 4.0, | |
1.0, 4.0, 7.0, 4.0, 1.0 | |
); | |
// Function to apply Gaussian blur | |
vec3 applyGaussianBlur(vec2 uv, vec2 texSize) { | |
vec3 blurredColor = vec3(0.0); | |
float totalWeight = 0.0; | |
for (int x = -2; x <= 2; x++) { | |
for (int y = -2; y <= 2; y++) { | |
vec2 offset = vec2(float(x), float(y)) / texSize; | |
vec3 sampleColor = texture(BackBufferTex, uv + offset).rgb; | |
float weight = gaussian_kernel[(x + 2)*(y + 2)]; | |
blurredColor += sampleColor * weight; | |
totalWeight += weight; | |
} | |
} | |
return blurredColor / totalWeight; | |
} | |
// Laplacian kernel | |
const mat3 laplacian_kernel = mat3(vec3(0, 1, 0), | |
vec3(1, -4, 1), | |
vec3(0, 1, 0)); | |
// Function to apply Laplacian filter to blurred image | |
float applyLaplacianToBlurred(vec2 uv, vec2 texSize, vec3 blurredColor) { | |
float laplacianValue = 0.0; | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
vec2 offset = vec2(float(x), float(y)) / texSize; | |
vec3 sampleColor = texture(BackBufferTex, uv + offset).rgb; | |
if (x == 0 && y == 0) { | |
sampleColor = blurredColor; // Use the blurred color for the center pixel | |
} | |
laplacianValue += getLuma(sampleColor) * laplacian_kernel[x + 1][y + 1]; | |
} | |
} | |
return laplacianValue; | |
} | |
float calculateLocalContrast(vec3 neighborhoodColors[9]) { | |
float centerLuma = getLuma(neighborhoodColors[4]); // Center pixel luminance | |
float averageLuma = 0.0; | |
for (int i = 0; i < 9; i++) { | |
if (i != 4) { // Exclude center pixel | |
averageLuma += getLuma(neighborhoodColors[i]); | |
} | |
} | |
averageLuma /= 8.0; | |
return abs(centerLuma - averageLuma); | |
} | |
float calculateColorVariance(vec3 neighborhoodColors[9]) { | |
vec3 meanColor = vec3(0.0); | |
for (int i = 0; i < 9; i++) { | |
meanColor += neighborhoodColors[i]; | |
} | |
meanColor /= 9.0; | |
float variance = 0.0; | |
for (int i = 0; i < 9; i++) { | |
variance += distance(meanColor, neighborhoodColors[i]); | |
} | |
variance /= 9.0; | |
return variance; | |
} | |
// Function to linearize depth value | |
float linearizeDepth(float depth, vec2 uv, mat4 inv_projection_matrix) { | |
vec3 ndc = vec3(uv * 2.0 - 1.0, depth); | |
vec4 view = inv_projection_matrix * vec4(ndc, 1.0); | |
view.xyz /= view.w; | |
return -view.z; | |
} | |
// Function to calculate depth difference | |
float getDepthDifference(vec2 uv, vec2 texSize, mat4 inv_projection_matrix) { | |
float centerDepth = linearizeDepth(texture(DepthBufferTex, uv).x, uv, inv_projection_matrix); | |
float maxDepthDifference = 0.0; | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
if (x == 0 && y == 0) continue; // Skip the center pixel | |
vec2 offset = vec2(float(x), float(y)) / texSize; | |
float neighborDepth = linearizeDepth(texture(DepthBufferTex, uv + offset).x, uv + offset, inv_projection_matrix); | |
maxDepthDifference = max(maxDepthDifference, abs(centerDepth - neighborDepth)); | |
} | |
} | |
return maxDepthDifference; | |
} | |
// Cosine blend between two colors | |
vec3 cosineBlend(vec3 color1, vec3 color2, float factor) { | |
float cosFactor = smoothstep(0.0, 1.0, factor); | |
return mix(color1, color2, cosFactor); | |
} | |
// Simple noise function for dithering | |
float noise(vec2 co){ | |
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); | |
} | |
void fragment() { | |
vec2 texSize = vec2(textureSize(BackBufferTex, 0)); | |
vec2 RCP2 = 2.0 / texSize; | |
vec2 uv = FRAGCOORD.xy / texSize; | |
// Luminance-based edge detection | |
vec4 lumaA; | |
lumaA.x = sampleLumaOff(uv, OffSW, texSize); | |
lumaA.y = sampleLumaOff(uv, OffSE, texSize); | |
lumaA.z = sampleLumaOff(uv, OffNE, texSize); | |
lumaA.w = sampleLumaOff(uv, OffNW, texSize); | |
float gradientSWNE = lumaA.x - lumaA.z; | |
float gradientSENW = lumaA.y - lumaA.w; | |
vec2 dir = vec2(gradientSWNE + gradientSENW, gradientSWNE - gradientSENW); | |
vec2 dirM = abs(dir); | |
float lumaMax = max(max(lumaA.x, lumaA.y), max(lumaA.z, lumaA.w)); | |
float localLumaFactor = lumaMax * 0.5 + 0.5; | |
float localThres = luma_threshold * localLumaFactor; | |
bool luminanceEdge = abs(dirM.x - dirM.y) >= localThres; | |
float dirMMin = min(dirM.x, dirM.y); | |
vec2 offM = clamp(vec2(0.0625) * dirM / dirMMin, 0.0, 1.0); | |
vec2 offMult = RCP2 * sign(dir); | |
float offMMax = max(offM.x, offM.y); | |
if (abs(offMMax - 1.0) < 0.0001) { | |
bool horSpan = abs(offM.x - 1.0) < 0.0001; | |
bool negSpan = horSpan ? offMult.x < 0.0 : offMult.y < 0.0; | |
bool sowSpan = horSpan == negSpan; | |
vec2 uvC = uv; | |
if (horSpan) uvC.x += 2.0 * offMult.x; | |
if (!horSpan) uvC.y += 2.0 * offMult.y; | |
vec4 lumaAC = lumaA; | |
if (sowSpan) lumaAC.x = sampleLumaOff(uvC, OffSW, texSize); | |
if (!negSpan) lumaAC.y = sampleLumaOff(uvC, OffSE, texSize); | |
if (!sowSpan) lumaAC.z = sampleLumaOff(uvC, OffNE, texSize); | |
if (negSpan) lumaAC.w = sampleLumaOff(uvC, OffNW, texSize); | |
float gradientSWNEC = lumaAC.x - lumaAC.z; | |
float gradientSENWC = lumaAC.y - lumaAC.w; | |
vec2 dirC = vec2(gradientSWNEC + gradientSENWC, gradientSWNEC - gradientSENWC); | |
if (!horSpan) dirC = dirC.yx; | |
bool passC = abs(dirC.x) > 2.0 * abs(dirC.y); | |
if (passC) offMult *= 2.0; | |
} | |
// Apply Sobel filter to get gradient for Canny edge detection | |
vec2 gradient = applySobel(uv, texSize); | |
// Non-maximum suppression for Canny | |
bool isCannyEdge = isLocalMaximum(uv, gradient, texSize); | |
// Simple thresholding for Canny edge detection | |
float edgeStrength = length(gradient); | |
bool isStrongCannyEdge = edgeStrength > high_threshold; | |
bool isWeakCannyEdge = edgeStrength > low_threshold; | |
// Pseudo hysteresis for Canny | |
bool hasStrongNeighbor = false; | |
if (isWeakCannyEdge && !isStrongCannyEdge) { | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
if (x == 0 && y == 0) continue; // Skip the center pixel | |
vec2 neighborUV = uv + vec2(float(x), float(y)) / texSize; | |
vec2 neighborGradient = applySobel(neighborUV, texSize); | |
float neighborStrength = length(neighborGradient); | |
if (neighborStrength > high_threshold) { | |
hasStrongNeighbor = true; | |
break; | |
} | |
} | |
if (hasStrongNeighbor) break; | |
} | |
} | |
// Apply Gaussian blur | |
vec3 blurredColor = applyGaussianBlur(uv, texSize); | |
// Apply Laplacian filter to the blurred image | |
float laplacianValue = applyLaplacianToBlurred(uv, texSize, blurredColor); | |
// Check for zero-crossings in Laplacian (edge detection) | |
bool isLoGEdge = laplacianValue < 0.0; | |
// Combine edge detections: Luminance-based, Canny, and LoG | |
bool isEdge = luminanceEdge || isCannyEdge || isLoGEdge || hasStrongNeighbor; | |
// Blend colors based on combined edge detection | |
vec3 originalColor = texture(BackBufferTex, uv).rgb; | |
vec3 finalColor; | |
if (isEdge) { | |
// Collect neighborhood colors | |
vec3 neighborhoodColors[9]; | |
int index = 0; | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
vec2 offset = vec2(float(x), float(y)) / texSize; | |
neighborhoodColors[index++] = texture(BackBufferTex, uv + offset).rgb; | |
} | |
} | |
// Calculate depth difference for depth-aware blending | |
float depthDifference = getDepthDifference(uv, texSize, INV_PROJECTION_MATRIX); | |
// Advanced blending logic | |
vec2 offset = offM * offMult; | |
vec3 rgbM = sampleColor(uv); | |
vec3 rgbN = sampleColor(uv - offset); | |
vec3 rgbP = sampleColor(uv + offset); | |
// Calculate edge strength and other factors for adaptive blending | |
float edgeStrength = length(gradient); | |
float localContrast = calculateLocalContrast(neighborhoodColors); | |
float colorVariance = calculateColorVariance(neighborhoodColors); | |
// Modulate blend factor based on depth difference | |
float depthBlendFactor = 1.0 - smoothstep(0.0, depth_threshold, depthDifference); | |
// Combine factors for final blend factor | |
float adaptiveBlendFactor = smoothstep(0.0, 1.0, edgeStrength) * (1.0 - localContrast) * (1.0 - colorVariance) * depthBlendFactor; | |
vec3 rgbR; | |
if (cosine_blending) { | |
// Blend based on edge strength, local contrast, and color variance | |
vec3 blendedEdgeColor = cosineBlend(rgbN, rgbP, 0.5); // Smooth blend of neighboring colors | |
rgbR = cosineBlend(rgbM, blendedEdgeColor, adaptiveBlendFactor * 0.3 + 0.4); | |
} else { | |
// Blend based on combined factors | |
vec3 blendedEdgeColor = mix(rgbN, rgbP, 0.5); // Average of neighboring colors | |
rgbR = mix(rgbM, blendedEdgeColor, adaptiveBlendFactor * 0.3 + 0.4); | |
} | |
// Luminance check | |
float lumaR = getLuma(rgbR); | |
float lumaAMin = min(min(lumaA.x, lumaA.y), min(lumaA.z, lumaA.w)); | |
float lumaAMax = max(max(lumaA.x, lumaA.y), max(lumaA.z, lumaA.w)); | |
bool outOfRange = (lumaR < lumaAMin) || (lumaR > lumaAMax); | |
if (!outOfRange) { | |
finalColor = rgbR; | |
} else { | |
discard; | |
} | |
} else { | |
discard; | |
} | |
ALBEDO = finalColor; | |
// Apply dithering | |
//float dither = noise(uv * texSize); // Generate noise based on UV coordinates | |
//ALBEDO += (dither - 0.5) / 255.0; // Apply a small color offset | |
} |
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 canvas_item; | |
uniform sampler2D BackBufferTex : hint_screen_texture, repeat_disable; | |
uniform float luma_threshold: hint_range(0, 1) = 0.375; | |
uniform float low_threshold: hint_range(0, 1) = 0.05; | |
uniform float high_threshold: hint_range(0, 1) = 0.2; | |
uniform bool cosine_blending = true; | |
// Constants for offsets | |
const ivec2 OffSW = ivec2(-1, 1); | |
const ivec2 OffSE = ivec2(1, 1); | |
const ivec2 OffNE = ivec2(1, -1); | |
const ivec2 OffNW = ivec2(-1, -1); | |
// Function to calculate luminance | |
float getLuma(vec3 color) { | |
return dot(color, vec3(0.299, 0.587, 0.114)); | |
} | |
// Sampling functions | |
vec3 sampleColor(vec2 p) { | |
return texture(BackBufferTex, p).rgb; | |
} | |
// Function to sample luminance at an offset | |
float sampleLumaOff(vec2 uv, ivec2 offset, vec2 texSize) { | |
return getLuma(texture(BackBufferTex, uv + vec2(offset) / texSize).rgb); | |
} | |
// Sobel operator kernels | |
const mat3 sobel_kernel_x = mat3(vec3(-1, 0, 1), | |
vec3(-2, 0, 2), | |
vec3(-1, 0, 1)); | |
const mat3 sobel_kernel_y = mat3(vec3(-1, -2, -1), | |
vec3(0, 0, 0), | |
vec3(1, 2, 1)); | |
// Function to apply Sobel filter | |
vec2 applySobel(vec2 uv, vec2 texSize) { | |
float gx = 0.0; | |
float gy = 0.0; | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
vec3 sampleColor = texture(BackBufferTex, uv + vec2(float(x), float(y)) / texSize).rgb; | |
float luma = getLuma(sampleColor); | |
gx += luma * sobel_kernel_x[x + 1][y + 1]; | |
gy += luma * sobel_kernel_y[x + 1][y + 1]; | |
} | |
} | |
return vec2(gx, gy); | |
} | |
// Non-maximum suppression | |
bool isLocalMaximum(vec2 uv, vec2 gradient, vec2 texSize) { | |
float edgeStrength = length(gradient); | |
float angle = atan(gradient.y, gradient.x); | |
vec2 n = normalize(vec2(cos(angle), sin(angle))); | |
float luma1 = length(applySobel(uv + n / texSize, texSize)); | |
float luma2 = length(applySobel(uv - n / texSize, texSize)); | |
return edgeStrength >= luma1 && edgeStrength >= luma2; | |
} | |
// Gaussian kernel (5x5 approximation) | |
const float gaussian_kernel[25] = float[25]( | |
1.0, 4.0, 7.0, 4.0, 1.0, | |
4.0, 16.0, 26.0, 16.0, 4.0, | |
7.0, 26.0, 41.0, 26.0, 7.0, | |
4.0, 16.0, 26.0, 16.0, 4.0, | |
1.0, 4.0, 7.0, 4.0, 1.0 | |
); | |
// Function to apply Gaussian blur | |
vec3 applyGaussianBlur(vec2 uv, vec2 texSize) { | |
vec3 blurredColor = vec3(0.0); | |
float totalWeight = 0.0; | |
for (int x = -2; x <= 2; x++) { | |
for (int y = -2; y <= 2; y++) { | |
vec2 offset = vec2(float(x), float(y)) / texSize; | |
vec3 sampleColor = texture(BackBufferTex, uv + offset).rgb; | |
float weight = gaussian_kernel[(x + 2)*(y + 2)]; | |
blurredColor += sampleColor * weight; | |
totalWeight += weight; | |
} | |
} | |
return blurredColor / totalWeight; | |
} | |
// Laplacian kernel | |
const mat3 laplacian_kernel = mat3(vec3(0, 1, 0), | |
vec3(1, -4, 1), | |
vec3(0, 1, 0)); | |
// Function to apply Laplacian filter to blurred image | |
float applyLaplacianToBlurred(vec2 uv, vec2 texSize, vec3 blurredColor) { | |
float laplacianValue = 0.0; | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
vec2 offset = vec2(float(x), float(y)) / texSize; | |
vec3 sampleColor = texture(BackBufferTex, uv + offset).rgb; | |
if (x == 0 && y == 0) { | |
sampleColor = blurredColor; // Use the blurred color for the center pixel | |
} | |
laplacianValue += getLuma(sampleColor) * laplacian_kernel[x + 1][y + 1]; | |
} | |
} | |
return laplacianValue; | |
} | |
float calculateLocalContrast(vec3 neighborhoodColors[9]) { | |
float centerLuma = getLuma(neighborhoodColors[4]); // Center pixel luminance | |
float averageLuma = 0.0; | |
for (int i = 0; i < 9; i++) { | |
if (i != 4) { // Exclude center pixel | |
averageLuma += getLuma(neighborhoodColors[i]); | |
} | |
} | |
averageLuma /= 8.0; | |
return abs(centerLuma - averageLuma); | |
} | |
float calculateColorVariance(vec3 neighborhoodColors[9]) { | |
vec3 meanColor = vec3(0.0); | |
for (int i = 0; i < 9; i++) { | |
meanColor += neighborhoodColors[i]; | |
} | |
meanColor /= 9.0; | |
float variance = 0.0; | |
for (int i = 0; i < 9; i++) { | |
variance += distance(meanColor, neighborhoodColors[i]); | |
} | |
variance /= 9.0; | |
return variance; | |
} | |
// Cosine blend between two colors | |
vec3 cosineBlend(vec3 color1, vec3 color2, float factor) { | |
float cosFactor = smoothstep(0.0, 1.0, factor); | |
return mix(color1, color2, cosFactor); | |
} | |
// Simple noise function for dithering | |
float noise(vec2 co){ | |
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); | |
} | |
void fragment() { | |
vec2 texSize = vec2(textureSize(BackBufferTex, 0)); | |
vec2 RCP2 = 2.0 / texSize; | |
vec2 uv = FRAGCOORD.xy / texSize; | |
// Luminance-based edge detection | |
vec4 lumaA; | |
lumaA.x = sampleLumaOff(uv, OffSW, texSize); | |
lumaA.y = sampleLumaOff(uv, OffSE, texSize); | |
lumaA.z = sampleLumaOff(uv, OffNE, texSize); | |
lumaA.w = sampleLumaOff(uv, OffNW, texSize); | |
float gradientSWNE = lumaA.x - lumaA.z; | |
float gradientSENW = lumaA.y - lumaA.w; | |
vec2 dir = vec2(gradientSWNE + gradientSENW, gradientSWNE - gradientSENW); | |
vec2 dirM = abs(dir); | |
float lumaMax = max(max(lumaA.x, lumaA.y), max(lumaA.z, lumaA.w)); | |
float localLumaFactor = lumaMax * 0.5 + 0.5; | |
float localThres = luma_threshold * localLumaFactor; | |
bool luminanceEdge = abs(dirM.x - dirM.y) >= localThres; | |
float dirMMin = min(dirM.x, dirM.y); | |
vec2 offM = clamp(vec2(0.0625) * dirM / dirMMin, 0.0, 1.0); | |
vec2 offMult = RCP2 * sign(dir); | |
float offMMax = max(offM.x, offM.y); | |
if (abs(offMMax - 1.0) < 0.0001) { | |
bool horSpan = abs(offM.x - 1.0) < 0.0001; | |
bool negSpan = horSpan ? offMult.x < 0.0 : offMult.y < 0.0; | |
bool sowSpan = horSpan == negSpan; | |
vec2 uvC = uv; | |
if (horSpan) uvC.x += 2.0 * offMult.x; | |
if (!horSpan) uvC.y += 2.0 * offMult.y; | |
vec4 lumaAC = lumaA; | |
if (sowSpan) lumaAC.x = sampleLumaOff(uvC, OffSW, texSize); | |
if (!negSpan) lumaAC.y = sampleLumaOff(uvC, OffSE, texSize); | |
if (!sowSpan) lumaAC.z = sampleLumaOff(uvC, OffNE, texSize); | |
if (negSpan) lumaAC.w = sampleLumaOff(uvC, OffNW, texSize); | |
float gradientSWNEC = lumaAC.x - lumaAC.z; | |
float gradientSENWC = lumaAC.y - lumaAC.w; | |
vec2 dirC = vec2(gradientSWNEC + gradientSENWC, gradientSWNEC - gradientSENWC); | |
if (!horSpan) dirC = dirC.yx; | |
bool passC = abs(dirC.x) > 2.0 * abs(dirC.y); | |
if (passC) offMult *= 2.0; | |
} | |
// Apply Sobel filter to get gradient for Canny edge detection | |
vec2 gradient = applySobel(uv, texSize); | |
// Non-maximum suppression for Canny | |
bool isCannyEdge = isLocalMaximum(uv, gradient, texSize); | |
// Simple thresholding for Canny edge detection | |
float edgeStrength = length(gradient); | |
bool isStrongCannyEdge = edgeStrength > high_threshold; | |
bool isWeakCannyEdge = edgeStrength > low_threshold; | |
// Pseudo hysteresis for Canny | |
bool hasStrongNeighbor = false; | |
if (isWeakCannyEdge && !isStrongCannyEdge) { | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
if (x == 0 && y == 0) continue; // Skip the center pixel | |
vec2 neighborUV = uv + vec2(float(x), float(y)) / texSize; | |
vec2 neighborGradient = applySobel(neighborUV, texSize); | |
float neighborStrength = length(neighborGradient); | |
if (neighborStrength > high_threshold) { | |
hasStrongNeighbor = true; | |
break; | |
} | |
} | |
if (hasStrongNeighbor) break; | |
} | |
} | |
// Apply Gaussian blur | |
vec3 blurredColor = applyGaussianBlur(uv, texSize); | |
// Apply Laplacian filter to the blurred image | |
float laplacianValue = applyLaplacianToBlurred(uv, texSize, blurredColor); | |
// Check for zero-crossings in Laplacian (edge detection) | |
bool isLoGEdge = laplacianValue < 0.0; | |
// Combine edge detections: Luminance-based, Canny, and LoG | |
bool isEdge = luminanceEdge || isCannyEdge || isLoGEdge || hasStrongNeighbor; | |
// Blend colors based on combined edge detection | |
vec3 originalColor = texture(BackBufferTex, uv).rgb; | |
vec3 finalColor; | |
if (isEdge) { | |
// Collect neighborhood colors before offset | |
vec3 neighborhoodColors[9]; | |
int index = 0; | |
for (int x = -1; x <= 1; x++) { | |
for (int y = -1; y <= 1; y++) { | |
vec2 offset = vec2(float(x), float(y)) / texSize; | |
neighborhoodColors[index++] = texture(BackBufferTex, uv + offset).rgb; | |
} | |
} | |
// Advanced blending logic | |
vec2 offset = offM * offMult; | |
vec3 rgbM = sampleColor(uv); | |
vec3 rgbN = sampleColor(uv - offset); | |
vec3 rgbP = sampleColor(uv + offset); | |
// Calculate edge strength for adaptive blending | |
float edgeStrength = length(gradient); | |
float localContrast = calculateLocalContrast(neighborhoodColors); | |
float colorVariance = calculateColorVariance(neighborhoodColors); | |
float adaptiveBlendFactor = smoothstep(0.0, 1.0, edgeStrength) * (1.0 - localContrast) * (1.0 - colorVariance); | |
vec3 rgbR; | |
if (cosine_blending) { | |
// Blend based on edge strength, local contrast, and color variance | |
vec3 blendedEdgeColor = cosineBlend(rgbN, rgbP, 0.5); // Smooth blend of neighboring colors | |
rgbR = cosineBlend(rgbM, blendedEdgeColor, adaptiveBlendFactor * 0.3 + 0.4); | |
} else { | |
// Blend based on combined factors | |
vec3 blendedEdgeColor = mix(rgbN, rgbP, 0.5); // Average of neighboring colors | |
rgbR = mix(rgbM, blendedEdgeColor, adaptiveBlendFactor * 0.3 + 0.4); | |
} | |
// Luminance check | |
float lumaR = getLuma(rgbR); | |
float lumaAMin = min(min(lumaA.x, lumaA.y), min(lumaA.z, lumaA.w)); | |
float lumaAMax = max(max(lumaA.x, lumaA.y), max(lumaA.z, lumaA.w)); | |
bool outOfRange = (lumaR < lumaAMin) || (lumaR > lumaAMax); | |
if (!outOfRange) { | |
finalColor = rgbR; | |
} else { | |
discard; | |
} | |
} else { | |
discard; | |
} | |
COLOR = vec4(finalColor, 1.0); | |
// Apply dithering | |
//float dither = noise(uv * texSize); // Generate noise based on UV coordinates | |
//COLOR.rgb += (dither - 0.5) / 255.0; // Apply a small color offset | |
} |
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
[gd_scene load_steps=6 format=3 uid="uid://y2but6np0htd"] | |
[ext_resource type="Shader" path="res://HHAA-D.gdshader" id="1_bbn3p"] | |
[ext_resource type="Shader" path="res://HHAA-S.gdshader" id="2_24bkf"] | |
[sub_resource type="ShaderMaterial" id="ShaderMaterial_j20el"] | |
render_priority = -128 | |
shader = ExtResource("1_bbn3p") | |
shader_parameter/luma_threshold = 0.375 | |
shader_parameter/low_threshold = 0.05 | |
shader_parameter/high_threshold = 0.2 | |
shader_parameter/depth_threshold = 0.1 | |
shader_parameter/cosine_blending = true | |
[sub_resource type="QuadMesh" id="QuadMesh_6r3u8"] | |
material = SubResource("ShaderMaterial_j20el") | |
flip_faces = true | |
size = Vector2(2, 2) | |
[sub_resource type="ShaderMaterial" id="ShaderMaterial_lutf1"] | |
shader = ExtResource("2_24bkf") | |
shader_parameter/luma_threshold = 0.375 | |
shader_parameter/low_threshold = 0.05 | |
shader_parameter/high_threshold = 0.2 | |
shader_parameter/cosine_blending = true | |
[node name="HHAA-D" type="MeshInstance3D"] | |
extra_cull_margin = 16384.0 | |
mesh = SubResource("QuadMesh_6r3u8") | |
[node name="HHAA-S" type="ColorRect" parent="."] | |
material = SubResource("ShaderMaterial_lutf1") | |
anchors_preset = 15 | |
anchor_right = 1.0 | |
anchor_bottom = 1.0 | |
grow_horizontal = 2 | |
grow_vertical = 2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
to use, stack D&S, put D in 2x2 flip faces QuadMesh with as much Extra Cull Margin as possible - as child of Camera3D, then put S in full rect ColorRect as child of D.