0
\$\begingroup\$

I am looking to draw 2D sprites that cast artificial 3D shadows onto a flat terrain using an additional depth-texture when rendering the sprites. A black/white depth texture is used to add a height-dimension to the sprites. I would like to use the light-direction and the height value to project a shadow onto a flat XZ-plane. The sprites and the terrain would both share pos-Y = 0.

enter image description here

The .gif above is an example of self-shadowing. It also use a depth-texture...

enter image description here

..for look-up but is only "casting shadows" on itself. There is a code of this here, but not relevant to the question. The .gif gives you some idea of what I am aiming for. If you imagine the "pixel-clusters" as sprites.

My program, unlike self-shadowing, wouldn't alter the fragment it's running on, but a fragment at a projected location. From what I understand, this can not be done directly. Meaning: it's at least not easy to access and alter another fragment other than the one currently running.

I have ever only used the fragment-shader to output a final color of the current fragment.

1: Is there a way to write a value (in my case just a byte to indicate shadow) to a texture or another structure (I don't know about) instead of altering the fragment color

2: I am also wondering if my shader code (below) to find the projected fragment position is correct. it's the final vec2 fragScreenSpace that should be a position in screen-space. Could i then use this coordinate to write a value to a texture?

I am doing the light-projection in world-space then converting the plane-intersection to screen space. I would like to avoid matrix multiplication in the fragment shader altogether.

3. Could i do the same in another space to avoid matrix multiplication in the fragment shader?

(The plane intersection code is not the most important, it's the coordinate-space conversions I am more concerned about.)

/*
PLANE from a point and a normal
ax + by + cz + d = 0
point on plane = (0,0,0)
a = normal.x;
b = normal.y;
c = normal.z;
d = -a * point.x - b * point.y - c * point.z;
*/


const vec4 planeDef = vec4(0.0,1.0,0.0,0.0); // XZ-plane (world space)

float intercectsPlane(vec3 direction, vec3 point) 
{
// we are looking for t => p(t) = ray.origin + t * ray.dir
// retruns negative if there is no intersection.

float a = planeDef.x;
float b = planeDef.y;
float c = planeDef.z;
float d = planeDef.w;
float px = point.x;
float py = point.y;
float pz = point.z;
float dx = direction.x;
float dy = direction.y;
float dz = direction.z;

float denom = a * dx + b + dy + c + dz;
if(denom < 0) return -(a * px + b * py + c * pz + d) / denom;
return -1.0;
}

uniform vec3 lightDir;          // normalized direction in world space
uniform float depthModifier;        // height of fragment in world units 
uniform sampler2D depthTexture;     // one channel depth map/texture of sprite
uniform vec2 viewportSize;

in mat4 viewProj;           // perspective view-projection matrix
in vec2 texCoords;          // interpolated UV's
in vec3 fragPos;            // interpolated world space position

void main()
{

// Get the position of the fragment in world space, heightend by the depthModifier.

float fragHeight = texture(depthTexture,texCoords).r * depthModifier;
vec3 position = fragPos; // fragPos.y = 0 (lies flat on the XZ-Plane)
position.y += fragHeight;

// Cast shadow from that "position", following the lightDir onto the XZ-plane.
// ( Calculate the point of intersection on the plane, if exist)

float t = intersectsPlane(lightDir,position);

if(t >= 0) // if there was an intersection
{
vec4 fragInShadow = vec4(position + (lightDir * t), 1.0);

fragInShadow = viewProj * fragInShadow; // Clip space

fragInShadow /= fragInShadow.w // NDC (normalize with the w-component)

vec2 fragScreenSpace = ((fragInShadow.xy + 1.0) * 0.5) * viewportSize; // ScreenSpace.xy

// From here, i want to write a value to a texture at fragScreenSpace, To indicate that
// there is shadow at that pixel. I don't know how to do this yet, but one thing at a time.
// So next, when i render the terrain (flat-plane) i would use the texture to look up that value. 

}
\$\endgroup\$
2
  • \$\begingroup\$ You need to invert your thinking here. Render your sprite depths to a render target, then your shadow shader runs on the terrain, and looks up into the depth texture. Instead of asking "given this point in the depth map, where does its shadow fall?" you then ask "given this point that may be in shadow, which shadow map texels might shadow it? And so you raymarch the light ray backwards through the depth texture until you find an occluder or clear the maximum occluder height. \$\endgroup\$ Commented Apr 19, 2022 at 15:11
  • \$\begingroup\$ That makes a lot of sense :) . I have not thought about it that way. That's actually more like how self-shadowing works i think. Tracing backwards towards the light (for x iterations) until you hit something (below the height/depth value). something like that. I think i need to let it sink in. I'm not entirely sure how i would go about doing this. So render the sprites' depth textures like you would the sprite color textures. But to a render target instead. Then sample that texture when rendering the terrain. So i guess the newly drawn depth-texture would align with the terrain? \$\endgroup\$ Commented Apr 19, 2022 at 15:42

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.