Search code examples
htmlsvgsvg-filters

SVG light and shadow a 3D feelling


I want to make an svg looks like it's on 3D by adding a small light in the top and left BORDER and a shadow in the bottom and right BORDER

something like this

#div1 {
  background: #ddd;
}
#div1, #div2, #div3 {
  width: 100px;
  height: 100px;
  position: relative;
}
#div2 {
  box-shadow: inset -2px -2px 10px 1px #000;
  position: absolute;
}
#div3 {
  box-shadow: inset 2px 2px 14px 1px #fff;
  position: absolute;
}
<div id="div1">
  <div id="div2"></div>
  <div id="div3"></div>
</div>

But I don't know how to do that with svg filter

<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="1000">


<defs>
  <filter id="filter1" x="0" y="0">
    <feSpecularLighting result="specOut"
        specularExponent="20" lighting-color="#bbbbbb">
      <fePointLight x="-100" y="-100" z="600"/>
    </feSpecularLighting>
    <feComposite in="SourceGraphic" in2="specOut"
        operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
  </filter>
</defs>

<path filter="url(#filter1)" fill="#fff" stroke="#000" d="M20,20 L220,20 L220,220 L20,220 L20,20 "></path>



</svg>

Help please and thanks


Solution

  • Firstly, you are trying to light a pure white rectangle with a dim white light. You aren't going to see anything.

    If you make the rectangle darker, you'll start to see some effect.

    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="300">
    
      <defs>
        <filter id="filter1" x="0" y="0">
          <feSpecularLighting result="specOut"
              specularExponent="20" lighting-color="#bbbbbb">
            <fePointLight x="-100" y="-100" z="600"/>
          </feSpecularLighting>
          <feComposite in="SourceGraphic" in2="specOut"
              operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
        </filter>
      </defs>
    
      <path filter="url(#filter1)" fill="#666" stroke="#000" d="M20,20 L220,20 L220,220 L20,220 L20,20 "></path>
    </svg>

    But in the above example we are only getting gradient of light over our rectangle. How do we make a sort-of bevel edge on the rectangle?

    It is important to know that it is not really the RGB channels of an element that determine how the lighting filter components behave. The lighting components treat the alpha component of the colour as a bump map. Varying values of alpha become a topological map that effects the way the pixels are lit.

    One way to create varying values of alpha is to use a gaussian blur filter. Here's what that looks like. Note that it is the alpha channel (SourceAlpha) of our shape that we are blurring.

    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="300">
      <defs>
        <filter id="filter2">
          <feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur1"/>
          <feBlend in="SourceGraphic" in2="blur1" mode="multiply"/>
        </filter>
      </defs>
    
      <path filter="url(#filter2)" fill="#666" stroke="#000" d="M20,20 L220,20 L220,220 L20,220 L20,20 "></path>
    </svg>

    Now if use that blurred alpha channel, we get something close to what you are after. You can fiddle with the blur, the lighting filter values, and the feComposite values to adjust the effect.

    Note that I've also switched to using an feDistantLight here. I think it is more appropriate for this purpose.

    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="300">
      <defs>
        <filter id="filter2">
          <feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur1"/>
          <feSpecularLighting result="specOut" in="blur1" specularConstant="1.2" specularExponent="12" lighting-color="#fff">
            <feDistantLight azimuth="225" elevation="45"/>
          </feSpecularLighting>
          <feComposite in="SourceGraphic" in2="specOut" operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
        </filter>
      </defs>
    
      <path filter="url(#filter2)" fill="#666" stroke="#000" d="M20,20 L220,20 L220,220 L20,220 L20,20 "></path>
    </svg>

    Update

    To handle the situation where shapes overlap (see comments), then you will need to clip away any parts of the blur that is outside the shape. You can do that with another feComposite operation.

    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="300">
      <defs>
        <filter id="filter2">
          <feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur1"/>
          <feSpecularLighting result="specOut" in="blur1" specularConstant="1.2" specularExponent="12" lighting-color="#fff">
            <feDistantLight azimuth="225" elevation="45"/>
          </feSpecularLighting>
          <feComposite in="SourceGraphic" in2="specOut" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" result="result"/>
          <feComposite operator="atop" in2="SourceGraphic"/>
        </filter>
      </defs>
    
      <path filter="url(#filter2)" fill="#666" stroke="#000" d="M20,20 L220,20 L220,220 L20,220 L20,20 "></path>
      <path filter="url(#filter2)" fill="#666" stroke="#000" d="M40,40 L200,40 L200,110 L40,110 L40,40 "></path>
      <path filter="url(#filter2)" fill="#666" stroke="#000" d="M40,120 L200,120 L200,200 L40,200 L40,120 "></path>
    </svg>