# How can I transform the circular blinn-phong specular into a square shaped highlight?

I am creating a custom shading model in Unreal Engine and I would like to create a square shaped specular highlight, without changing the shape of the light itself.

I tried following the approach described in the paper "stylized highlights for cartoon rendering and animation" by Ken-ichi Anjyo, but I am having difficulty parsing the math notation they are using.

The idea is to start with a simple blinn-phong specular highlight, and apply transformations to its half vector H. The vectors $$\textbf{du}$$ and $$\textbf{dv}$$ represent the tangent and bitangent vectors at the pixel normal.

The squaring operation, which we denote by $$sqr(H)$$, makes a highlight area more square shaped. We define this operation for a highlight vector $$H$$ as follows:

$$\theta := \min(\cos^{-1}(\textbf{H}, \textbf{du}), \cos^{-1}(\textbf{H}, \textbf{dv}))$$

$$\text{sqrnorm} := \sin(2\theta)^n$$

$$\textbf{H}^\wedge := \textbf{H} - \sigma \times \text{sqrnorm}(\langle \textbf{H}, \textbf{du}\rangle \textbf{du} + \langle \textbf{H}, \textbf{dv}\rangle \textbf{dv})$$

$$sqr(\textbf{H}) := \frac{\textbf{H}^{\wedge}}{||\textbf{H}^{\wedge}||}$$

where integer n and positive number σ(0.0 ≤σ≤ 1.0) are given. The highlight area would then be square shaped along the du- and dv-axis. With a rotation operation, we can get the highlight area square shaped in a desired direction. With a larger n, the area becomes more sharpened, whereas σ prescribes the magnitude of the squared area.

I tried implementing the previous equation in HLSL like so:

float3 H = normalize(ToCamera + ToLight);
float3 du = cross(N, float3(0.f, 1.f, 0.f)); // Tangent.
float3 dv = cross(N, du); // Bitangent.

// Square the highlight.
float theta = min(acos(dot(H, du)), acos(H, dv));
float sqrnorm = pow(sin(2.f * theta), n);
float3 H_hat = H - sigma * sqrnorm * dot(H, du) * du + dot(H, dv) * dv;
H_hat = normalize(H_hat);

return round(pow(dot(N, H_hat), SpecularPower));


But this is just my best guess, the end result looks wrong. The expected result is a more square shaped specular highlight: What I get instead is something that looks like this: You can see specular squaring in motion here: https://youtu.be/svhMHGIT4bI?t=66

• This is quite untypical, because the Blinn-Phong model works with the vectors view direction of light and normal. The orientation is missing. Normally this kind of reflection is created by so called environment mapping. There you have a cubemap texture into which the environment is rendered. In your case you would insert the two rectangular light points into this cubemap to get the wanted results Feb 10 at 7:59

I took some time and got the square specular from the example posted to work. I was able to split, rotate and square it. It looks like using max instead of min is the key. The results are fairly unstable, they work for key frame animations I'm sure but they move a lot.

I also took some time to work with the step version of square. It actually is more of a cut off function that produces a perfectly round specular highlight like a spot. Setting the cut off in the step to 0.99 gave a very nice spot.

Finally I worked on yet another version of the square spec highlight. This one actually generates a perfectly square highlight using a box filter(again using the step function). It contains the actual highlight inside a box, so if parameterized it could generate a wide range of effects. It could also be rotated. ( I won't be coding that up) I also include the step version that creates the perfect spot. A similar technique could be used on the square version to get a perfect square for the highlight.

Here is the code: This code has been tested and works. I even created a shader toy to show it but have decided to keep it private mostly because the code is a conglomeration of several small tests.

float boxFilter(float x, float size) {
return step(size, x) - step(2.0 * size, x);
}

float squareSpecular(vec3 halfVec, vec3 surfaceNormal)
{
float angle = dot(halfVec, surfaceNormal);
angle = clamp(angle, 0.0, 1.0);
float specular = pow(angle, 50.);
//return step(0.99, angle) * specular; // return a "spot"
float specSize = 0.3; // Size of the square highlight
float specSquare = boxFilter(halfVec.x, specSize) * boxFilter(halfVec.y, specSize);

return specular * specSquare;
}


I included stretching, splitting and translating. ddu and ddv must be recomputed using the new halfway vector to combine the effects.

Here is the code for the versions from the paper:

float sgn(float x )
{
if( x== 0.0 ) return 1.0;
return x/abs(x);
}

float Specular( vec3 n, vec3 h )
{
const float shininess = 20.0;
vec3 du = cross(n, vec3(0,0,1));
vec3 dv = cross(du, n);

float ddu = dot(h, du);
float ddv = dot(h, dv);

//vec3 hprimed = h + 0.5*dv + 0.5*du; // translation
//vec3 hprimed = h - (0.95*ddu * du); // stretching / lengthing
// vec3 hprimed = h - (0.7 * sgn(ddu) * du) - (0.0 *sgn(ddv)*dv); // splitting

float sigma = 0.7;
float theta = max( acos(ddu), acos(ddv));
float sqrnorm = pow( sin(2.0*theta), 14.);
vec3 hprimed = h - sigma * sqrnorm * (ddu*du + ddv*dv);

return pow( clamp( dot( n, normalize(hprimed) ), 0.0, 1.0 ),shininess);
}


Many other shapes are possible...stars, ragged, filtered, the list goes on and on.

• This code seems to just threshold the specular highlight beyond a certain value. What I'm looking for is something that would change the shape of the specular highlight
– mbl
Feb 11 at 2:09
• I added a video that shows what I'm trying to do, the technique in the video is what' described in the paper
– mbl
Feb 11 at 2:13
• It was not my intent to give a copy and paste function, but to discuss how to go about it and give some pseudocode towards that end. I have reformulated the code but again I have not tested it. Feb 11 at 13:02
• Another question would be: does the shape has to be rotation variant? Meaning, when rolling the camera, does the shape shouldn't roll with respect to world orientation Feb 11 at 17:03
• The rotation matrix could come from upper 3x3 matrix of the camera view matrix. I would expect that to roll the highlight with the camera. Or the rotation itself could be fixed, fixing the highlight at a particular angle. Feb 11 at 19:29