First video
Second video
I have noticed that shadows seem off and there are some artifacts probably because of floating point lack of precision. The 2 videos are based on the rendering of the image from previous post just that the light position has been changed to be a directional light coming down vertically.
In the first video you can see the shadow texture that results from the compute shader that does the calculation for tracing rays from world space to the light source (in this case the light direction is 0, -1, 0 pointing directly downwards). Red means that the ray didn't hit anything. White when it does hit something. The min_distance for intersection detection is 0 and you can see a lot of errors especially on the rotating balls. The "shadow" seems to creep upward on the right side for some reason and moving the camera somehow influences this. When I go above the ball you can see that the shadow is now covering the right half of the ball, even though the light is now behind the camera so the ball shouldn't be shadowed from that angle.
In the second video I've increase the min_distance to 0.01 and you can see the issue not being as glaring. But it's still there somewhat. The shadows on the ground seem fine in both cases but the shadows coming from self-shadowing look really bad.
The compute shader is reconstructing the worldspace position for each pixel from the depth as in the ReconstructFromDepth tutorial. It sends the corners for the camera as CompositorPassQuadDef::WORLD_SPACE_CORNERS_CENTERED.
Cleaned up compute intersection code:
Code: Select all
#include <metal_stdlib>
#include <simd/simd.h>
#define GEOMETRY_MASK_TRIANGLE 1
#define GEOMETRY_MASK_SPHERE 2
#define GEOMETRY_MASK_LIGHT 4
#define GEOMETRY_MASK_GEOMETRY (GEOMETRY_MASK_TRIANGLE | GEOMETRY_MASK_SPHERE)
#define RAY_MASK_PRIMARY (GEOMETRY_MASK_GEOMETRY | GEOMETRY_MASK_LIGHT)
#define RAY_MASK_SHADOW GEOMETRY_MASK_GEOMETRY
#define RAY_MASK_SECONDARY GEOMETRY_MASK_GEOMETRY
using namespace metal;
using namespace raytracing;
struct INPUT
{
float4x4 invProjectionMat;
float4x4 invViewMat;
float4 cameraCorner0;
float4 cameraCorner1;
float4 cameraCorner2;
float4 cameraCorner3;
float4 cameraPos;
float4 cameraRight;
float4 cameraUp;
float4 cameraFront;
float2 projectionParams;
float width;
float height;
};
struct Light
{
float4 position; //.w contains the objLightMask
float4 diffuse; //.w contains numNonCasterDirectionalLights
float3 specular;
float3 attenuation;
//Spotlights:
// spotDirection.xyz is direction
// spotParams.xyz contains falloff params
float4 spotDirection;
float4 spotParams;
#define lightTexProfileIdx spotDirection.w
};
float origin() { return 1.0f / 32.0f; }
float float_scale() { return 1.0f / 65536.0f; }
float int_scale() { return 256.0f; }
// Normal points outward for rays exiting the surface, else is flipped. 6 float3 offset_ray(const float3 p, const float3 n)
Taken from Ray tracing gems 2019 chapter A FAST AND ROBUST METHOD FOR AVOIDING SELF-INTERSECTION
float3 offset_ray(const float3 p, const float3 n)
{
int3 of_i(int_scale() * n.x, int_scale() * n.y, int_scale() * n.z);
float3 p_i(
as_type<float>(as_type<int>(p.x)+((p.x < 0) ? -of_i.x : of_i.x)),
as_type<float>(as_type<int>(p.y)+((p.y < 0) ? -of_i.y : of_i.y)),
as_type<float>(as_type<int>(p.z)+((p.z < 0) ? -of_i.z : of_i.z)));
return float3(fabs(p.x) < origin() ? p.x+ float_scale()*n.x : p_i.x,
fabs(p.y) < origin() ? p.y+ float_scale()*n.y : p_i.y,
fabs(p.z) < origin() ? p.z+ float_scale()*n.z : p_i.z);
}
kernel void main_metal
(
depth2d<@insertpiece(texture0_pf_type), access::read> depthTexture [[texture(0)]],
texture2d<@insertpiece(texture1_pf_type), access::read> normalsTexture [[texture(1)]],
// sampler samplerState [[sampler(0)]],
texture2d<float, access::write> shadowTexture [[texture(UAV_SLOT_START)]], // Destination
//constant float2 &projectionParams [[buffer(PARAMETER_SLOT)]], // TODO PARAMTER_SLOT should be const buffer??
constant Light *lights, // TODO replace with correct light source.
constant INPUT *in,
instance_acceleration_structure accelerationStructure,
intersection_function_table<triangle_data, instancing> intersectionFunctionTable,
ushort3 gl_LocalInvocationID [[thread_position_in_threadgroup]],
ushort3 gl_GlobalInvocationID [[thread_position_in_grid]],
ushort3 gl_WorkGroupID [[threads_per_threadgroup]]
)
{
ushort3 pixelPos = gl_GlobalInvocationID;
float fDepth = depthTexture.read( pixelPos.xy );
float3 fNormal = normalize( normalsTexture.read( pixelPos.xy ).xyz * 2.0 - 1.0 );
fNormal.z = -fNormal.z; //Normal should be left handed.
float linearDepth = in->projectionParams.y / (fDepth - in->projectionParams.x);
// The ray to cast.
ray shadowRay;
// Pixel coordinates for this thread.
float2 pixel = (float2)gl_GlobalInvocationID.xy;
float2 uv = float2( pixel.x / in->width, pixel.y / in->height );
float3 interp = mix( mix( in->cameraCorner0.xyz, in->cameraCorner2.xyz, uv.x ),
mix( in->cameraCorner1.xyz, in->cameraCorner3.xyz, uv.x),
uv.y );
float3 worldSpacePosition = in->cameraPos.xyz + interp * linearDepth;
// Create an intersector to test for intersection between the ray and the geometry in the scene.
intersector<triangle_data, instancing> i;
// Shadow rays check only whether there is an object between the intersection point
// and the light source. Tell Metal to return after finding any intersection.
i.accept_any_intersection( true );
i.assume_geometry_type(geometry_type::triangle);
i.force_opacity(forced_opacity::opaque);
typename intersector<triangle_data, instancing>::result_type intersection;
// Rays start at the camera position.
float4 normalInWorldSpace = in->invViewMat * float4( fNormal.xyz, 1.0f );
shadowRay.origin = offset_ray( worldSpacePosition.xyz, normalInWorldSpace.xyz ); // Using worldSpacePosition.xyz directly yields no different result
//for( int lightIndex = 0; lightIndex < /*lightCount*/1; ++i )
//{
// Map normalized pixel coordinates into camera's coordinate system.
shadowRay.direction = normalize( float3( 0.0f, 1.0f, 0.0f ) );
// Don't limit intersection distance.
shadowRay.max_distance = INFINITY;
shadowRay.min_distance = 0.01f;
intersection = i.intersect( shadowRay, accelerationStructure, RAY_MASK_SHADOW );
if( intersection.type == intersection_type::triangle )
{
shadowTexture.write( float4( 1.0f, 1.0f, 1.0f, 1.0f ), gl_GlobalInvocationID.xy );
}
else
{
shadowTexture.write( float4( 1.0f, 0.0f, 0.0f, 1.0f ), gl_GlobalInvocationID.xy );
}
}
Btw the repo is here on branch RTShadows.
EDIT: I will try to shoot the rays directly from camera and ignore the depthTexture. See if I get any other results....
EDIT2: I am doing something very wrong somewhere. Shooting rays directly from camera returns a compressed on Y axis shadow texture.
Image here
Code: Select all
// Metal compute shader
// First method to cast rays.
float2 uv = float2( pixel.x / in->width, pixel.y / in->height );
uv.x = uv.x * 2.0f - 1.0f;
uv.y = ( 1.0f - uv.y ) * 2.0f - 1.0f;
// The rays start at the camera position.
shadowRay.origin = in->cameraPos.xyz;
// Map normalized pixel coordinates into the camera's coordinate system.
shadowRay.direction = normalize( float3( uv.x * normalize( in->cameraRight.xyz ) +
uv.y * normalize( in->cameraUp.xyz ) +
normalize( in->cameraFront.xyz ) ) );
// Second method to cast rays. Same result as first method as in linked image.
float imageAspectRatio = in->width / in->height; // assuming width > height
float Px = ( 2 * ( ( pixel.x + 0.5 ) / in->width ) - 1 ) * tan( in->fovY / 2 ) * imageAspectRatio;
float Py = ( 1 - 2 * ( ( pixel.y + 0.5 ) / in->height ) ) * tan( in->fovY / 2 );
float3 rayOrigin = float3( in->cameraPos.xyz );
float3 rayDirection = ( in->invViewMat * float4( Px, Py, -1, 1 ) ).xyz;
rayDirection = normalize(rayDirection);
// C++ where the camera data, width and height are written.
const Ogre::Vector3 cameraPos = mCamera->getDerivedPosition();
Ogre::Vector3 cameraRight = mCamera->getRight();
Ogre::Vector3 cameraUp = mCamera->getUp();
Ogre::Vector3 cameraFront = mCamera->getDirection();
rtInput->width = mRenderWindow->getWidth();
rtInput->height = mRenderWindow->getHeight();
rtInput->fovY = mCamera->getFOVy().valueRadians();
EDIT 3: Camera::getAspectRatio() is just a default 1.333 not the actual 1.77 from 16:9 actually used. Doesn't really change anything as the aspectRatio is calculated in the compute program.