The sphere is blockey, I have a 3D array of characters and i use a distance function to fill up the array, so i cant use floats which make small circles really blockey, that's how i represent the sphere, you just gave me an idea maybe i should stop representing the sphere as a 3D array and start representing it as a distance function if the distance between the ray and sphere is less or equal than the radius of the sphere then the ray is intersecting the sphere
In your comment above, it looks like you've hit on the key insight you need for smooth lighting.
Given a ray, and a sphere's center point and radius, you can detect precisely where the ray hits the sphere, using algorithms like the ones in this past Q&A.
Now that you have a point on the sphere's surface, you can compute the surface normal at that point: a unit vector pointing perpendicular to the surface, directly out from the sphere. If your intersection routine doesn't already generate this, you can deduce it from the position of the reported intersection point:
Vector3 normal = Vector3.Normalize(rayHitPosition - sphereCenter);
(Here, Vector3.Normalize()
refers to the operation of dividing all components of a vector by that vector's length, to create a unit vector with length 1 in the same direction as the original)
You can compute the direction to the light source the same way:
Vector3 toLight = Vector3.Normalize(lightPosition - rayHitPosition)
Given these two vectors, we can compute the diffuse illumination at this location. That's the scattered light you get bouncing off of a matte surface, like plaster. It's brightest when the light is hitting the surface dead-on (when the toLight
and normal
vectors point in the same direction), and fades as the surface turns away from the light, catching it just edge-on. We can model that with what's called a Lambertian term:
float diffuse = Mathf.Max(Vector3.Dot(toLight, normal), 0);
Here Vector3.Dot()
is the scalar product, multiplying the x component of one vector by the x component of the other, then y with y, z with z, and adding the three products together to make one number. Because we used unit vectors, that number is the cosine of the angle between the vectors - ie. it goes from 1 when they're pointing in the same direction, to 0 when they're perpendicular, to -1 when they're pointing in opposite directions.
I use a clamping function so that any values below zero - corresponding to the surface facing away from the light source - produce zero scattered light, instead of "negative light".
This will give you a smoothly varying value, from 1 on the part of your sphere closest to the light, and getting darker and darker until it hits zero at the terminator - the equator separating the lit portion of the sphere from the shadowed half.
You can scale this diffuse component by the brightness (or colour) of your light source, relative to the exposure of your image, then map the resulting value into your character range for display.
We can use simple vector tools like this to compute additional lighting terms to get different surface appearances - like adding the specular reflections you'd see on shiny surfaces, or metallic reflections you'd see from metal, but those might not be very noticeable in your text-based representation.
a
andb
:if (a.brightness == 0 && b.brightness == 11) a.brightness = 5
\$\endgroup\$