how do you calculate correct normals when doing vertex displacement shaders?

here's a lil guide/example on the overly complicated but mathematically correct way of doing this~ Image
say you have this simple smoothstep bump, with a radius (r) and an amplitude (a)

the space we're working in will be in uv space, where (0,0) is in the center of this plane

o.uv = the current 2d uv coordinate

d = length(o.uv)
height = smoothstep(r,0,d)*a;
what we need is called the partial derivatives (rate of change) of this function, in order to calculate the normal

now, we're going the unnecessarily accurate route, so we're not using the ddx/ddy functions in the frag shader, that's cheating~
in order to do this, we have to unpack the math a little so we can differentiate it, so, let's do that!

first off, smoothstep(a,b,v) is a (clamped) inverse lerp:
t = saturate((v-a)/(b-a))

...followed by a cubic smooth function:

source:… Image
the point of inverse lerp here is to remap the distance d from 0 to r into 1 to 0

(1 in the center, 0 at radius distance away from the center)

t = invLerp( r, 0, d ) =
(d-r)/(0-r) =

so, now that we know t = 1-d/r, let's apply the cubic smooth function
now, working out this math is tedious and boring so let's throw what we've got into wolfram alpha:
t²(3-2t) where t = 1-d/r

and, we get a nice formula back! thx wolfie~ Image
finally, we need to scale it by the amplitude with a simple multiply, and we end up with:
height = (a(d-r)²(2d+r))/r³

beautiful! however, we forgot the clamp~

in code we can just clamp d to never be greater than r:
d = min( length(o.uv), r )
now, it's a little hidden in the formula, but we actually have *two* inputs to this function, not one

d = length(o.uv) is kinda hiding that it's based on u and v, so let's be even more verbose:

d = length(o.uv) = √(u²+v²)
so what did we just do?

instead of:
d = length(o.uv)
height = smoothstep(r,0,d)*a;

we now have:
height = (a(√(u²+v²)-r)²(2√(u²+v²)+r))/r³

wow this looks much less clean, thanks math!

however, with math, we can do some magic~
we need to know - what is the rate of change (derivative) of this formula, along u and v, respectively?

well, we just throw this to the wolves again to get the partial derivatives:
f(u,v) = (a(√(u²+v²)-r)²(2√(u²+v²)+r))/r³

and wolfie gives us exactly what we need~ Image
∂f/∂u = 6au(√(u²+v²)-r)/r³
∂f/∂v = 6av(√(u²+v²)-r)/r³

we can write this in one line as a vector in shader code, since there's only one place they differ!
pd = 6a*o.uv*(√(u²+v²)-r)/r³

we can also simplify since √(u²+v²) = d:
pd = 6a*o.uv*(d-r)/r³
so what we have now, is the *slope* of this surface, along u and v, but we don't have the normal

now, whenever you want to convert from a slope to a vector, you insert the slope as components, while the remaining components equal 1

normal = normalize(float3(pd.x, pd.y, 1))
that's it! hell yea normals 💜

d = min( length(o.uv), r )
h = (a(d-r)²(2d+r))/r³
pd = 6a*o.uv*(d-r)/r³
normal = normalize(float3(pd,1)) Image
also, here's an interactive version of this hump (in 2D instead of 3D) if you want to play with sliders 📈…
Q: isn't it easier to just-
A: yes! this is the long way around. very slow to work with as you need to redo the math for every new input, but it's accurate as heck/not an approximation~

Q: what if my shape isn't a plane
A: transform this from tangent space or something idk

• • •

Missing some Tweet in this thread? You can try to force a refresh

Keep Current with Freya Holmér

Freya Holmér Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!


Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @FreyaHolmer

13 Nov 20
I haven't been able to @PlayOverwatch for a year

it just, doesn't start, and I have *no idea* why

does anyone know what might be the issue?

(thread w. crash log & things I've tried so far that made no difference)
error from windows event viewer Image
Blizz files:
• Deleted all blizz/OW related things in %appdata%, %programdata%
• Repaired OW
• Reinstalled OW and
• Reset OW config
• Deleted Documents/Overwatch
Read 11 tweets
19 Oct 20
(long thread. CW: cynical and depressing introspection)

sometimes it feels like the more you get to know yourself, the more you start questioning what your true self really is, versus what is just a layer of acting you mask it with

I don't even know if that distinction exists
quarantine has made me realize things about myself that I was sort of pretending wasn't there, and now I'm a little uncomfortable admitting that that's really who I am, or if that's even who I want to be, and if I even have a choice
I quit my job as co-founder/tech artist at Neat corp near april, right as covid hit, and we started self quarantining

I don't think I've ever had such an abrupt cutoff from friends that I used to meet every day at work, and from friends I used to meet a few times per year
Read 26 tweets
16 Oct 20
given three points, you can always find a circle passing through them all

1. draw lines from a to b to c
2. draw perpendicular bisectors (dashed)
3. the circle center is their intersection point
4. the circle radius is the distance from the center to any of the three points
this works because

• the circle center is by definition, the same distance to all three points
• given a pair of points, the region where the distance to both is the same, is a perpendicular line between them
• the intersection point is therefore where all three are the same
note that if all three points lie along a line, the circle is undefined! the bisectors would never intersect, so there is no circle passing through all points

(also if you comment about this because you don't read threads I will link you this tweet and make you feel *shame*)
Read 5 tweets
2 Sep 20
there are lot of misconceptions on high refresh rate monitors, so, quick facts!

• the eye doesn't have a "max framerate"
• the eye can absolutely distinguish between 144Hz and 1440Hz monitors

the eye is continuously picking up light - if something is moving really fast, then it won't just get a "frame", it will smear in a blur, the size of which depends on the lighting conditions and the properties of your own eye

so - what if something is moving fast on a monitor?
try moving your cursor in a large circle quickly

do you see gaps between the cursors?

double your refresh rate and you would get one more cursor in the middle of those gaps

quadruple and you get three cursors covering that gap

see where this is going?
Read 7 tweets
29 Mar 20
barycentric hecking interpolation!!

a little thread on a neat way to interpolate across a triangle!

say you have three points, each with a color - how do you get the blended color for an arbitrary point inside? 🤔
byerp (coining this term now~) uses the area of the triangle on the *opposite* side of each vertex, as a fraction of the total area, to determine how much influence each vertex has!
these three influences, or, weights, together form the barycentric coordinate of that point
we can then multiply each weight with their corresponding color, and add them all up, to get the final blended color at that coordinate!

since models in games are made of triangles, this is actually how mesh data like normals, UVs, vertex colors etc. is interpolated by your GPU!
Read 5 tweets
27 Feb 20
my favorite way to see if a point is inside or outside a path, is using its winding number🍥

traverse the path from the perspective of a point and add up the amount of turning along the way

if it made a full turn, it's inside
if it wound back to 0, it's outside

it's so neat~💖
this is one of my favorite techniques, because it works not only for convex paths, but for concave ones too, which, is kinda rare and beautiful

plus it's real fast if you measure quadrant traversal rather than real angles~

more info here…
if you want more math and game dev shenanigans, do consider supporting me on patreon, if you are able to and want to 💖
Read 5 tweets

Did Thread Reader help you today?

Support us! We are indie developers!

This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!

Follow Us on Twitter!