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:
t²(3-2t)

source: developer.download.nvidia.com/cg/smoothstep.… 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)

simplifying:
t = invLerp( r, 0, d ) =
(d-r)/(0-r) =
1-d/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 📈
desmos.com/calculator/aoq…
~FAQ~
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!

PDF

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

Mar 7
transform gizmo pre-orders are now open!! 🎉

get your own lil snap-fit assembly kit!
👉👈 handedness agnostic
🔄 positive rotation direction markers
↔ move objects irl
🖨 janky 3D print vibes
🐱 may contain cat hair

ko-fi.com/s/b0ab71b134 photos of all 4 different t...
there's a pretty limited amount for sale, so please don't get more than like 4 per person!

this was the initial stock:
200 🌹 viv long
200 🌹 viv mini
50 🌸 pst long
100 🌸 pst mini

the storefront shows current amount left for each of them
shipping will take a while - I'm still manufacturing the last 40% of parts, but I figured a pre-order would let me deal with shipping and whatnot in bulk and at my own pace, rather than sporadically! stacked bar chart of the pr...
Read 13 tweets
Mar 6
feeling pretty overwhelmed and demotivated with the amount of options and things to keep track of for sending things internationally through postnord

so many things are so separated when all I want is one page to enter all the info and print all the labels I need
like oh there's a batch label printing service for regular printers like I have, great!

but that's only for the shipping label, it doesn't include the customs declaration

and the customs declaration page is entirely separate and doesn't seem to have batch operation support
oh and there's a separate service depending on where you're shipping it

and so like, wuhhh, why is this so hard
Read 6 tweets
Dec 28, 2022
I think part of why derivatives are hard to understand the first time you learn about them, is because they're introduced in 1D instead of 2D

the derivative is just the velocity of any given function!
to be more precise, given a function f(t):

f(t) = position at time t
f'(t) = velocity at time t
f''(t) = acceleration at time t
f'''(t) = jerk/jolt at time t
in physics, you learn about the velocity vector

this also works in 1D, but it's rarely presented as such. instead we talk about "slope", which is often completely unrelated to a physical slope in 2D space, it's just an artifact of mapping our input to x and the 1D output to y
Read 7 tweets
Nov 25, 2022
fuck it, I did the math, swizzle your quaternions (w,x,y,z) why not

(-x, w, -z, y) is 180° around X
(-y, z, w, -x) is 180° around Y
(-z, -y, x, w) is 180° around Z
btw those are "local space"/intrinsic rotations, here are the "world space"/extrinsic rotations:

(-x, w, z, -y) is 180° around world X
(-y, -z, w, x) is 180° around world Y
(-z, y, -x, w) is 180° around world Z
also I might be wrong bc this is completely untested so don't trust me
Read 5 tweets
Nov 25, 2022
my big takeaway studying quaternions a little closer as of late, is that interpreting them as a 4D object is a red herring
they make a lot more sense when you think of them as a special encoding of an angle-axis model with special multiplication rules, or, as rotors in the framework of geometric algebra
treating the xyzw components as the same type of element in the same type of space just isn't helpful (to me), because they operate with completely different rules

w is just a regular ol number on the number line

x, y and z are very much not
Read 5 tweets
Oct 31, 2022
blender first impressions thread! (as a maya user)

1. it's super hard to see what the heck is going on when you parent objects using the default parenting method

child objects have some sort of hidden (??) offset based on their relation when parenting
this offset seems to be completely hidden in the UI

basically, both transform and delta transform can be at (0,0,0), and yet the object is neither at (0,0,0) world space, nor at the parent position

what is that offset? *who knows* I couldn't figure it out and neither could chat
basically, this very specific option is the only reasonable parenting method to me

it makes me super uncomfortable that there even is an option to parent objects and then not being able edit or even see their relationship to each other ;-; ??
Read 6 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

Don't want to be a Premium member but still want to support us?

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

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!

:(