Create inner box shadow for polygon mesh

Does anyone know how to achieve this effect on a polygon mesh? It’s similar to a CSS inset box-shadow effect.
I’ve created a shader, but it does not work very well and lacks rendering performance.
Expected:


Output:

  varying vec2 vUv;
  varying vec3 vPosition;
  varying vec3 vWorldPosition;
            
  void main() {
      vUv = uv;
      vPosition = position;
      vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
                uniform float shadowIntensity;
                uniform float shadowDepth;
                uniform float edgeSmoothness;
                uniform float gradientSoftness;
                uniform vec3 fillColor;
                uniform vec3 shadowColor;
                uniform float polygonPoints[${pointCount * 2}];
                uniform int pointCount;
                
                varying vec2 vUv;
                varying vec3 vPosition;
                varying vec3 vWorldPosition;
                
                bool pointInPolygon(vec2 point, int count) {
                    bool inside = false;
                    for (int i = 0, j = count - 1; i < count; j = i++) {
                        if (i >= count || i >= ${pointCount}) break;
                        vec2 pi = vec2(polygonPoints[i * 2], polygonPoints[i * 2 + 1]);
                        vec2 pj = vec2(polygonPoints[j * 2], polygonPoints[j * 2 + 1]);
                        if (((pi.y > point.y) != (pj.y > point.y)) &&
                            (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x)) {
                            inside = !inside;
                        }
                    }
                    return inside;
                }
                
                float distanceToPolygonEdge(vec2 point, int count) {
                    float minDist = 1000.0;
                    
                    for (int i = 0; i < count; i++) {
                        if (i >= count || i >= ${pointCount}) break;
                        int next = (i + 1) % count;
                        if (next >= count) next = 0;
                        
                        vec2 a = vec2(polygonPoints[i * 2], polygonPoints[i * 2 + 1]);
                        vec2 b = vec2(polygonPoints[next * 2], polygonPoints[next * 2 + 1]);
                        
                        vec2 pa = point - a;
                        vec2 ba = b - a;
                        float t = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
                        vec2 closest = a + t * ba;
                        float dist = length(point - closest);
                        
                        minDist = min(minDist, dist);
                    }
                    
                    return minDist;
                }
                
                void main() {
                    vec2 uv = vUv * 2.0 - 1.0;
                    
                    bool inside = pointInPolygon(uv, pointCount);
                    
                    if (!inside) {
                        discard;
                    }
                    
                    float edgeDistance = distanceToPolygonEdge(uv, pointCount);
                    
                    float shadowFactor = 1.0;
                    if (edgeDistance < shadowDepth) {
                        float shadowAmount = 1.0 - (edgeDistance / shadowDepth);
                        shadowAmount = pow(shadowAmount, gradientSoftness);
                        shadowFactor = 1.0 - shadowAmount * shadowIntensity;
                    }
                    
                    float alpha = 1.0 - smoothstep(0.0, edgeSmoothness, edgeDistance);
                    
                    vec3 finalColor = mix(shadowColor, fillColor, shadowFactor);
                    
                    gl_FragColor = vec4(finalColor, alpha);
                }

It looks like you have to tweak model manually, reduce vertex counts of map model edges then inset map shaped polygon pieces and use vertex colours/shading on inner and outer poly borders to achieve this result.

You could precompute the edge distance map by doing a distance transform and store it in a texture. You’re doing something like that in your shader already; just store the distances and reuse them.

There’s a method called Jump Flood Algorithm that performs an approximate signed distance transform on the GPU; it’s fast enough for realtime use. I’ve seen a Three-based implementation years ago, I’m sure you can find it somewhere on Github.

1 Like