What is the formula for REFRACTION?

Hello I am making a pixel system, and I have the formula for reflection, not refraction.

Reflection code:

local norm = rayc.Normal
local cf = CFrame.lookAt(ray.Origin,rayc.Position)
local currentNormal = cf.LookVector
local reflect = (currentNormal - (2 * currentNormal:Dot(norm) * norm))

What is the formula (in code) for refraction?


The ratio of the angles in and out is the inverse of the ratios of the IORs.


uhh what? Sorry if I sound rude but I dont speak math.
Do you mind explaining the “ratio of angles” and “in and out is the revers of the ratios of the “”“IORS””"""??

Refraction occurs whenever the index of refraction changes along the path of some ray of light. The IOR of air is close to 1. The index has been defined so that it is the ratio of the sines of the angles for a ray going from one material with one IOR, (n1) to another material with IOR n2. Rearranging this to work with vectors is hard so give me a minute to figure that out and I’ll post it.


Any refraction in 3D with the same incoming angle can be rotated so that it’s the same one in 2D, so you can simplify the problem to be in terms of two axes, one of which is the surface normal and the other is perpendicular to that. Lets calls the normal i and the other axis j. You start out knowing i and you can get j by taking the cross between the incoming ray and the normal, then crossing with the normal again. In this form, the direction of the refracted ray is vr = sin(θ2)i - cos(θ1)j
Since you don’t know one of the θ, you have to calculate it using the ratios, so that: n1 / n2 = sin(θ2) / sin(θ1)

Note that this will be undefined when the incoming angle is zero, but in that case there is also no refraction. It will also be undefined for total internal refraction, which occurs whenever the result of solving the second equation is not real, which can only happen when going from higher to lower n at high angles. You can choose to handle this however you want, but in real life something Completely Ridiculous happens at this point and the idea of light as rays completely breaks down.

1 Like

Okay so, so far I am writing it out, and the first issue is what do you mean-


what do I cross the normal with again?

um and also what is “θ”? does that mean angle? or what?

I gotta brb but here’s this snell

1 Like

When the light ray hits the surface of another material, the angle to the normal at which it continues changes slightly.

When you think of the example azqjanna provided, the normal is that magenta line you see. The “normal” line is perpendicular to the surface of the second material and the angles on both sides are 90 degrees.

When the light ray hits the surface, you can find θ1 (θ is just a variable, think x or y) by finding the angle between the normal and the ray.

The relation between θ1 and θ2 is n1*sin(θ1) = n2*sin(θ2). n1 and n2 are the indices of refraction for both materials. These never change as long as the material is the same. What you have is n1,n2, and θ2. What you need to find is θ2.

If we change the subject of the formula it becomes: θ2 = arcsin((n1*sin(θ1))/n2).

Now, I gave you the formula you need to find θ2. All you need to implement is a function to find θ1 depending on how you implement your ray and to use θ2 out of it as well.

Hopefully this was helpful. If you need any help with anything, please feel free to ask.

1 Like

So for ↓

Can I use-

local i = rayc.Normal -- raycast normal
local j = base.CFrame.LookVector:Angle(i) -- the base's cframe lookvector is the direction of the raycast

Edit: I’m not sure how to get angle either

And for ↓

What do I set those as? (n1 and n2)

Edit: I may have done it?? (probably not)

local norm = rayc.Normal
local a1 = base.CFrame.LookVector:Angle(norm)
local n1 = 1
local n2 = 1
local a2 =math.asin((n1*math.sin(a1))/n2)

but how do I make a2 a vector3/direction?



r = d - 2 * d:Dot(n) * n

Where d is the direction, n is the normal’s unit vector, and r is the result/reflected vector.

This works in 2D and 3D.


Is this for reflection or refraction?

My bad, that’s for reflection.



I’m not very familiar with the refraction formula. This stack overflow thread has a solution/general formula:

Let V_incedence be the normalized incoming vector. Let n1 and n2 be the refracting indices of the two surfaces. You want to calculate V_refraction . Let n be the normalized normal vector.

V_refraction = r*V_incedence + (rc - sqrt(1-Math.pow(r,2)(1-Math.pow(c,2))))n

where r = n1/n2 and c = -n dot V_incedence.

(You need to pick the refracting indices of the materials. n_1 is basically 1 (1.000293) assuming the first material is air.)

(This is for 3D)

1 Like


So I have V_incedence which is “the normalized incoming vector”
but also Let n be the normalized normal vector??

Edit: what is rc and c and r?

n is the unit vector of the surface’s normal. It’s what raycasting returns for the normal.

This is the direction of the incoming light but normalized/unit (which just means you make the length 1. You can do that with vector/vector.Magnitude).

1 Like

Okay so, so far I got it without errors, but I just don’t know what r and c is.

Edit: oh nvm i dont know how to read

r is n1/n2

c is (-normal):Dot(V_incedence)

n1/n2 is a property of a material, called the refracting index.

n1 is the refracting index of the first material (what material the light is coming from).

n2 is the refracting index of the second material (what material the light is going into).

Here is a chart of refracting indices:

Source: Refractive index - Wikipedia

Here is the code written in Lua:

local function refraction(unitLightDirection, unitNormalDirection, refractionIndex1, refractionIndex2)
    local r = refractionIndex1/refractionIndex2
    local c = (-unitNormalDirection):Dot(unitLightDirection)
    return r*unitLightDirection + (r*c - sqrt(1-math.pow(r,2)(1-math.pow(c,2)))) * unitNormalDirection


local function refraction(V_incedence, n, n1, n2)
    local r = n1/n2
    local c = (-n):Dot(V_incedence)
    return r*V_incedence + (r*c - sqrt(1-math.pow(r,2)(1-math.pow(c,2)))) * n
1 Like

Oh okay so I did it and I got this.

invalid argument #2 (Vector3 expected, got number)
local c = -n:Dot(V_incedence) -- the error

So you said vector/vector.Magnitude, but magnitude makes the vector3 a number, but :Dot() requires a vector3, so do I just ignore the Magnitude?

Edit: uh oh, the big confusing part has an error.

local V_refraction = r*V_incedence + (r*c - math.sqrt(1-math.pow(r,2)(1-math.pow(c,2))))*n

Workspace.RaycastBase.Script:41: attempt to call a number value

Your types are wrong somewhere.

vector is a Vector3

vector.Magnitude is a number/scalar

A vector multiplied or divided by a number is a vector still.

I would add parenthesis too:

local c = (-1 * n):Dot(V_incedence)

so that you make sure the -1 gets multiplied by n before the dot product.

c will be a number/scalar

You can debug it by breaking the equation into smaller bits.

Try using the code I sent but with type checks:

local function refraction(unitLightDirection, unitNormalDirection, refractionIndex1, refractionIndex2)
    -- Type checks
    assert(typeof(unitLightDirection) == "Vector3", "Should be Vector3 but is a "..typeof(unitLightDirection).."!") 
    assert(typeof(unitNormalDirection) == "Vector3", "Should be Vector3 but is a "..typeof(unitNormalDirection).."!") 
    assert(typeof(refractionIndex1) == "number", "Should be number but is a "..typeof(unitLightDirection).."!") 
    assert(typeof(refractionIndex2) == "number", "Should be number but is a "..typeof(unitLightDirection).."!")

    -- Force the vector inputs to be unit vectors
    unitLightDirection = unitLightDirection.Unit
    unitNormalDirection = unitNormalDirection.Unit

    local r = refractionIndex1/refractionIndex2
    local c = (-unitNormalDirection):Dot(unitLightDirection)
    return r * unitLightDirection + (r * c - math.sqrt(1 - math.pow(r,2) * (1 - math.pow(c,2)))) * unitNormalDirection 
1 Like

I apologize for keep editing and posting right before you update lmao

Okay so I did the function (idk why unitNormalDirection is supposed to be a number) but roblox is acting weird, its saying that -1, -0, -0 is a “”“number”"" (bruh) (its a vector3 right?) (am I going crazy?)
Workspace.RaycastBase.Script:11: attempt to index number with 'Dot'