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.
The .gif above is an example of self-shadowing. It also use a depth-texture...
..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.
}

