Also posted on Reddit. See this thread on the Unity forum for more about the problem and conceptual solution.
When using billboarded sprites in 3D, a common problem without a ready solution in Godot is restricting how the sprite clips with its surroundings. I've been learning shader coding and decided to try tackling this issue. I'm tantalizingly close to getting it, but I'm missing a critical transformation and I haven't been able to track down what it is and where to apply it. I would be grateful if someone could lend me a hand over this hurdle. ?
As for the problem I'm trying to solve, there are two types of unwanted clipping:
Clipping Problem #1: Frequently, a sprite imitating an object in 3D space will have an imaginary 3D origin that may be several pixels above the bottom of the texture. But a Sprite3D cannot appear to rest on the middle of the texture; it exists in 3D space, so it clips through the floor/ground.
Clipping Problem #2: Enabling regular billboard mode on a Sprite3D twists it around in 3D space in a way that is perspective-correct for a 3D quad, but this causes it to clip through nearby 3D meshes. Opting for Y billboard in a bid to prevent this makes the sprite appear flat and squashes its proportions at elevated angles.
Solution: Orient the sprite as a standard billboard, but use a shader to modify its DEPTH value based on two planes: upright like a Y billboard, and flat across XZ. Whichever depth is closer determines DEPTH.
This fixes problem #1 because DEPTH for the pixels that would clip through the floor/ground will instead be based on the XZ plane. A character's foot will appear above the floor, but behind anything higher than the floor where they're standing.
This also fixes problem #2 because DEPTH for the pixels that would clip through a wall will instead be based on the upright plane. A character standing next to a wall can retain the perspective of the source sprite, but remain in front of the wall when looking down from a higher position.
Progress: I've got a recognizable ALBEDO value that can display vertical and lateral planes upon the face of a Sprite3D, proving they're aligned correctly with the point of view. However, the value isn't scaled/curved correctly for DEPTH, causing near values to clip in front of things and far values to rapidly exceed the far clipping distance of the camera. (Once it's working right, I guess the maximum depth should be capped somehow.)
Here's the shader WIP:
shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back;
varying float y_pos;
float depth_to_plane(vec3 dir, vec3 origin, vec3 normal) {
float denom = dot(normal, dir);
if (abs(denom) > 1e-5) return dot(origin, normal) / denom;
else return -1.0;
}
void vertex() {
// Straight billboard
//MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);
// Y billboard
//MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(vec4(normalize(cross(vec3(0.0, 1.0, 0.0), CAMERA_MATRIX[2].xyz)),0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(normalize(cross(CAMERA_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))),0.0),WORLD_MATRIX[3]);
// Straight billboard with Y billboard shading
MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))),0.0),WORLD_MATRIX[3]);
y_pos = VERTEX.y;
}
void fragment() {
// plane origins are both vec3(0.0) in this frame of reference
vec3 point = CAMERA_MATRIX[3].xyz - WORLD_MATRIX[3].xyz;
vec3 front_normal = normalize(vec3(point.x, 0.0, point.z));
// transform from world space to view space
vec3 point_vs = (INV_CAMERA_MATRIX * vec4(point, 0.0)).xyz;
front_normal = (INV_CAMERA_MATRIX * vec4(front_normal, 0.0)).xyz;
vec3 up = (INV_CAMERA_MATRIX * vec4(0.0, 1.0, 0.0, 0.0)).xyz;
float vert_depth = depth_to_plane(VIEW, point_vs, front_normal);
float lat_depth = depth_to_plane(VIEW, point_vs, up);
// abs() or not?
vert_depth = abs(vert_depth);
lat_depth = abs(lat_depth);
float above = float(point.y > 0.0);
float upper_depth = min(vert_depth, lat_depth) * above + vert_depth * (1.0 - above);
float depth = float(y_pos >= 0.0) * upper_depth + float(y_pos < 0.0) * min(vert_depth, lat_depth);
//depth = (0.5 * depth) + 1.0;
//depth = 2.0 * depth - 1.0;
//depth = 1.0 / depth;
ALBEDO = vec3(depth);
DEPTH = depth;
}