Casting custom shadows from planet rings to planet surface

I’ve got a surface shader for my planet surfaces and I’d like to have the shadow of the planet’s rings cast onto the surface (for planets which have rings).
I know I can add the current vertex vector and the direction to the sun to get a vector with a start point (surface vertex) and an end point (the sun). If I can calculate where this line passes through the ‘plane’ of the rings, I can use that point to see if the current pixel should be in shade (ie, if the distance from the centre of the planet to the point where the line crosses the plane of the rings is between the inner and outer extent of the rings, the pixel should be in shade).
I can pass in a normal for the plane of the rings (which would be the same as the rotation axis of the planet) but how would I use that normal and the start-and-end-points of the ‘line’ to check where the line crosses the plane of the rings?
I hope that makes sense. Any help, much appreciated!

Assuming your rings are a simple ring texture on a quad or similarly simple geometry that doesn’t have any affect on the UVs, then you just need to calculate the ray / plane intersection. Search around online for ray plane intersection shader and you should find plenty of examples to build off of, including some that calculate a UV at the hit position.

If your rings are more complex than a simple plane, and can’t be approximated with one, then you’ll have to fall back on render textures and use a shadow map-like approach.

The planet rings are, indeed, rendered on a flat plane. I can see what I can find elsewhere, but here I’m after the actual calculation to get the crossing-point between a line and a plane, when I have the start and end point of the line and the normal of the plane (which itself sits at the 0, 0, 0 position). If you have any details, let me know (and I’m still yet to try out your new solution for my other question, so thanks for your reply there too).

Doing ray plane intersections in a shader is a well solved problem that’s relatively straight forward. The shadow problem from the other thread … is less so. Technically you could solve that one with a similar solution to this, raytrace against a mathematical sphere and calculate the UVs with some trigonometry. However the hack is almost as effective, and offers a little more control while better matching the UV mapped clouds.

It might be straight forward, but I have not understood any of the solutions I’ve seen yet. It’s been difficult to know how the variables used in a solution correlate to the variables available in my shader. For example, I found this solution…

…but it’s not clear to me how to implement the broader equation. (If you check the link, you’ll see I’ve asked the answerer a question as a comment). If you do have any pointers, that would be great. Thanks again!

The usual bits of data needed for a ray plane intersection are:

  • The ray direction (in the form of a normalized unit vector)
  • The ray origin position
  • The plane normal (again in a normalized unit vector)
  • The plane’s distance from 0,0,0 along the plane’s normal, or possibly any point on the plane

Each example you’ll find will be a little different, as sometimes they’ll expect the plane’s position to be given relative to the ray’s origin, or vice versa. The versions that define the plane as a float4 are going to be the plane’s normal, and the distance to the plane from 0,0,0, and those will be slightly faster to calculate, but not by much.

Here’s what I’ve tried so far - which, sadly doesn’t seem to work. I’ve been able to establish that the o.ringCrossDistance is being set to 0, but NOT because a or b are 0 (I can go into how I’ve established that, but I suspect that will increase the boredom factor of this post).
But here’s part of the shader.

  • float3 _upVector = float3(0, 1, 0);

void vert(inout appdata_full v, out Input o) {

float3 vertexWorldPos = mul (unity_ObjectToWorld, v.vertex).xyz;
float3 surfacePixel = mul ((float3x3)unity_ObjectToWorld,
v.vertex.xyz);
float3 planetAxis = mul ((float3x3)unity_ObjectToWorld,
_upVector.xyz);
UNITY_INITIALIZE_OUTPUT(Input,o);
TANGENT_SPACE_ROTATION;
o.tangentSpaceLightPos = mul(rotation, vertexWorldPos -
_sunDirectionFromThis);

// The distance of the ray to an intersection with the plane
// This is in units relative to the length of ray.direction
float a = dot( -surfacePixel, planetAxis );
float b = dot( normalize(_sunDirectionFromThis), planetAxis );
if (a==0 || b==0) {
o.ringCrossDistance = 0;
} else {
float x = a / b;
// The point in space were the ray intersects the (infinite) plane
float3 I = surfacePixel + (x * normalize(_sunDirectionFromThis));
o.ringCrossDistance = length(I);
}
}

I guess the thing I’m not clear on is, given that I’ve used just the rotation portion of the object’s transform (3x3 matrix), are the POSITIONS of each vector relative to the object’s centre or relative to the player? If I knew - categorically - the answer to that, I’d be able to continue with more certainty. Once again, if you have any pointers, I’d appreciate hearing them. Thanks.

It’s relative to the object at that point. Using just the 3x3 matrix of the object to world is equivalent to having your objects at 0,0,0 in world space.

The surfacePixel value is also not the surface pixel, that’s an object relative position of a vertex. We don’t, and can’t know the pixel positions until the fragment shader. Really, you should be doing all of this in the fragment shader and not the vertex shader.

There is one thing that has me curious though.

That’s subtracting the sun “direction” from the world position still … I acknowledge that’s originally a bug from my own example in the other thread , but you need to be consistent about if it’s a direction or a position.