Create SVG Lighting Effects With The feSpecularLighting Filter Primitive

One type of light illuminates an object and another type highlights it. Today we’ll talk about the latter, specifically the filter primitive feSpecularLighting, which is used to create highlights on an SVG image or graphic.

This is the third of the lighting effects posts. Two weeks ago I talked about lighting effects and the three different types of light sources. Last week I continued and talked about the two types of lighting and the primitive for one of them, feDiffuseLighting. Today I want to continue and talk about the primitive for the other type of lighting, feSpecularLighting.

These three lighting effects posts followed two posts in which I talked about feColorMatrix and feComponentTransfer filter primitives for working with color.

Note: This post assumes you read the one from two weeks ago where I talked about the three different light sources, feDistantLight, fePointLight, and feSpotLight. If you’re unfamiliar with them, you’ll want to read that post before continuing with this one.

The feSpecularLighting Filter Primitive

Like feDiffuseLighting, the feSpecularLighting filter primitive lights an image using its alpha channel as a bump map. The result also depends on the light color, light position and surface geometry of the input bump map. The difference is that this primitive uses the standard specular component of the Phong lighting model in calculations instead of using the diffuse component.

Where feDiffuseLighting produces an opaque image feSpecularLighting produces a non-opaque image. The specular result is meant to be added to a textured image.

This primitive takes four attributes, surfaceScale, specularConstant, specularExponent, and kernelUnitLength.

  • surfaceScale = “<number>”—The height of the surface for an alpha value of 1. It’s a factor that is multiplied by the alpha value. The default value is 1.
  • specularConstant = “<number>”—ks in the Phong lighting model. It’s used to determine the final RGB value of a given pixel and the brighter your lighting-color, the smaller you want this number to be. The diffuseConstant can be any non-negative number and the default is also 1.
  • specularExponent = “<number>”—Exponent for the specular term. The larger the value, the more “shiny” the result. Values can range between 1.0 and 128.0. The default is 1.
  • kernelUnitLength = “<number-optional-number>”—The same as feDiffuseLighting, it indicates the intended distance for dx and dy in the surface normal calculation formulas. The first number is dx and the second if included is dy. If only one value is supplied it becomes both dx and dy.

I’m not sure that’s any clearer than it was for feDiffuseLighting, even though the attributes are pretty much the same. Hopefully the following examples will help.

Let’s start again with an unlit green circle so we have a baseline for comparison. I trust the circle element below needs no explanation.

<svg width="100%" height="250" style="outline: 1px solid red"> 
 <circle cx="330" cy="125" r="100" fill="green" />

The result is an ordinary green circle inside the viewport.

Now let’s add a filter. Remember the basic form of the lighting filter primitives is the following.

<type-of-lighting in="" lighting-color="">
   <light source />
 </type of lighting>

The type-of-lighting will be feSpecularLighting and I’ll use white light. I’ll show you an example using each of the three types of light sources starting with feDistantLight.

An feDistantLight Example

If you read last week’s post on feDiffuseLighting, this example will probably look familiar. Again we have a green circle, though I added a reference to a filter to it. The filter contains an feSpecularLighting primitive with white light. I set each of the four attributes for this primitive to 1.

Inside the feSpecularLighting primitive is an feDistantLight subelement. I set the azimuth to 45 degrees and the elevation to 10 degrees. You might remember last week I set the elevation to 90 degrees, but that was a different kind of lighting. With specular lighting a small elevation leads to a lighter result. In fact the circle will continue to get darker as the elevation increases to 90 degrees.

Finally I used feComposite to combine the lighting with the original SourceGraphic so that the lighting would sit on top.

<svg width="100%" height="250" style="outline: 1px solid red"> 
 <filter id="distant">
  <feSpecularLighting in="SourceGraphic" result="light" lighting-color="white" surfaceScale="1" specularConstant="1" kernelUnitLength="1" specularExponent="1">
   <feDistantLight azimuth="45" elevation="10"/>

  <feComposite in="SourceGraphic" in2="light" operator="arithmetic" k1="1" k2="0" k3="0" k4="0" />

  <circle cx="330" cy="125" r="100" fill="green" filter="url(#distant)" />

Here’s the result which you can see is a lighter circle., not unlike the distant light example from last week.

One thing I’d like to point out is the effect of the specularExponent. With a value of 1 it doesn’t do much, but increasing the value will generally lighten the graphic, except at the edge opposite the light source. For example here’s the same example with the specularExponent set to 10.

The closer the elevation is to 90 degrees, the more you have to increase the specularExponent to achieve the same effect and the location of the small area that remains darker is dependent on the azimuth angle.

An fePointLight Example

This next example is mostly the same as the previous one, with a couple of exceptions. First I changed the light source to fePointLight and set the location of the light to 140, 140, and 50 for x, y, and z.

I also started with a specularExponent of 5 as opposed to 1, because leaving it at 1 left the result looking the same as the previous example.

<svg width="100%" height="250" style="outline: 1px solid red">
 <filter id="point">
  <feSpecularLighting in="SourceGraphic" result="light" lighting-color="white" surfaceScale="1" diffuseConstant="1" kernelUnitLength="1" specularExponent="5">
   <fePointLight x="140" y="140" z=50" />

  <feComposite in="SourceGraphic" in2="light" operator="arithmetic" k1="1" k2="0" k3="0" k4="0" />

  <circle cx="330" cy="125" r="100" fill="green" filter="url(#point)" /> ~

Here’s the result, which might seem a little odd given that specular lighting is supposed to add highlights. You can see the location where the point light strikes the circle is actually darker and everything else appears to be highlighted.

Here’s the example again with one small change. I changed the value of k2 in the feComposite primitive from 0 to 1, which brings back more of the original graphic in the final image. Now the result is reversed and the point where the light strikes the circle does indeed highlight it.

This example also shows why it can be difficult to know in advance what will happen with lighting filters as there are several different parts. If you’re not getting the result you expect it could be the type of lighting, the light source, or, as in this case, how you combine the result of the lighting with the original graphic.

An feSpotLight Example

Finally here’s the example again with an feSpotLight light source. I’m using the same values in the light source that I used last week to create a light source in the upper left pointing down and to the right.

I left the specularExponent at 5 and also kept k2 as 1 in the feComposite primitive.

<svg width="100%" height="250" style="outline: 1px solid red">
 <filter id="spot">
  <feSpecularLighting in="SourceGraphic" result="light" lighting-color="white" surfaceScale="1" diffuseConstant="1" kernelUnitLength="1" specularExponent="5">
   <feSpotLight x="505" y=130" z="50" limitingConeAngle="15" pointsAtX="245" pointsAtY="230" pointsAtZ="0"/>

  <feComposite in="SourceGraphic" in2="light" operator="arithmetic" k1="1" k2="1" k3="0" k4="0"/>

 <circle cx="330" cy="125" r="100" fill="green" filter="url(#spot)" />

Here’s the result which hopefully looks something like the way a spotlight would look shining across the circle.

Let me point out one last thing. Here’s the example again with one change. I set k2 back to 0. You can now see the spotlight, but the original circle is gone. With feDiffuseLighting everything outside the spotlight was black. With feSpecularLighting it’s white, which is why I needed to increase what we see of the original graphic by setting k2 to 1.

Closing Thoughts

I hope these few posts on SVG lighting effects haven’t been too confusing. I know I found them the most difficult to work with in large part because I found it hard to know which attribute on which primitive in the filter would have the most impact.

Hopefully in keeping the examples as simple as I could, it helped to isolate the types of light and the different light sources. I encourage you to take my examples or others you find online and experiment with different values. While I won’t claim to be a lighting expert after working through all these examples, I do have a much stronger sense of what’s going on even as I know I need to do a lot more experimenting to gain a better sense.

Next week I want to start talking about the remaining few filter primitives. They’re more straightforward than the lighting primitives and I left them for the end, since they don’t fit neatly into a group.

« »

Download a free sample from my book, Design Fundamentals.

Leave a Reply

Your email address will not be published.