Hello,
I have started implementing forward+ rendering to my own graphic engine and I've noticed a problem. Only half of the screen "see" my point lights. In my opinion a problem is: in calculating frustum for specific tile or in calculating depth from the depth buffer.
Here is my code for calculating frustum for specific tile:
//Four planes of a view frustum (in view space).
//The planes are:
// * Left,
// * Right,
// * Top,
// * Bottom.
//The back and/or front planes can be computed from depth values in the
//light culling compute shader.
struct Frustum
{
vec4 planes[4]; //left, right, top, bottom frustum planes.
};
//Convert clip space coordinates to view space
vec4 clipToView(vec4 tmp_clip, mat4 tmp_invProjMatrix)
{
//View space position.
vec4 tmp_view = tmp_invProjMatrix * tmp_clip;
//Perspective projection.
tmp_view = tmp_view / tmp_view.w;
return tmp_view;
}
//Convert screen space coordinates to view space.
vec4 screenToView(vec4 tmp_screen, vec2 tmp_screenSize, mat4 tmp_invProjMatrix)
{
//Convert to normalized texture coordinates
vec2 tmp_texCoord = tmp_screen.xy / tmp_screenSize;
// Convert to clip space
vec4 tmp_clip = vec4(vec2(tmp_texCoord.x, 1.0f - tmp_texCoord.y) * 2.0f - 1.0f, tmp_screen.z, tmp_screen.w);
return clipToView(tmp_clip, tmp_invProjMatrix);
}
vec4 computePlane(vec3 tmp_p0, vec3 tmp_p1, vec3 tmp_p2)
{
vec4 tmp_plane;
vec3 tmp_v0 = tmp_p1 - tmp_p0;
vec3 tmp_v2 = tmp_p2 - tmp_p0;
tmp_plane.xyz = normalize(cross(tmp_v0, tmp_v2));
//Compute the distance to the origin using p0.
tmp_plane.w = dot(tmp_plane.xyz, tmp_p0);
return tmp_plane;
}
Frustum calculateFrustumForGroup(int tmp_pixelPerTile, ivec2 tmp_dispatchThreadID,
vec2 tmp_screenSize, mat4 tmp_invProjMatrix)
{
vec3 tmp_eyePos = vec3(0, 0, 0);
//Compute the 4 corner points on the far clipping plane to use as the
//frustum vertices.
vec4 tmp_screenSpace[4];
//Top left point
tmp_screenSpace[0] = vec4(tmp_dispatchThreadID.xy * tmp_pixelPerTile, -1.0f, 1.0f);
//Top right point
tmp_screenSpace[1] = vec4(vec2(tmp_dispatchThreadID.x + 1, tmp_dispatchThreadID.y) * tmp_pixelPerTile, -1.0f, 1.0f);
//Bottom left point
tmp_screenSpace[2] = vec4(vec2(tmp_dispatchThreadID.x, tmp_dispatchThreadID.y + 1) * tmp_pixelPerTile, -1.0f, 1.0f);
//Bottom right point
tmp_screenSpace[3] = vec4(vec2(tmp_dispatchThreadID.x + 1, tmp_dispatchThreadID.y + 1) * tmp_pixelPerTile, -1.0f, 1.0f);
vec3 tmp_viewSpace[4];
//Now convert the screen space points to view space
for(int i = 0; i < 4; i++)
{
tmp_viewSpace[i] = screenToView(tmp_screenSpace[i], tmp_screenSize, tmp_invProjMatrix).xyz;
}
//Now build the frustum planes from the view space points
Frustum tmp_frustum;
//Left plane
tmp_frustum.planes[0] = computePlane(tmp_eyePos, tmp_viewSpace[2], tmp_viewSpace[0]);
//Right plane
tmp_frustum.planes[1] = computePlane(tmp_eyePos, tmp_viewSpace[1], tmp_viewSpace[3]);
//Top plane
tmp_frustum.planes[2] = computePlane(tmp_eyePos, tmp_viewSpace[0], tmp_viewSpace[1]);
//Bottom plane
tmp_frustum.planes[3] = computePlane(tmp_eyePos, tmp_viewSpace[3], tmp_viewSpace[2]);
return tmp_frustum;
}
Next here is my code to check if specific light is inside tile frustum:
struct Sphere
{
vec3 c; //Center point.
float r; //Radius.
};
struct Cone
{
vec3 T; //Cone tip.
float h; //Height of the cone.
vec3 d; //Direction of the cone.
float r; //bottom radius of the cone.
};
//Check to see if a sphere is fully behind (inside the negative halfspace of) a plane.
//Source: Real-time collision detection, Christer Ericson (2005)
bool SphereInsidePlane(Sphere sphere, vec4 plane)
{
return dot(plane.xyz, sphere.c) - plane.w < -sphere.r;
}
//Check to see if a point is fully behind (inside the negative halfspace of) a plane.
bool PointInsidePlane(vec3 p, vec4 plane)
{
return dot(plane.xyz, p) - plane.w < 0;
}
//Check to see if a cone if fully behind (inside the negative halfspace of) a plane.
//Source: Real-time collision detection, Christer Ericson (2005)
bool ConeInsidePlane(Cone cone, vec4 plane)
{
//Compute the farthest point on the end of the cone to the positive space of the plane.
vec3 m = cross(cross(plane.xyz, cone.d), cone.d);
vec3 Q = cone.T + cone.d * cone.h - m * cone.r;
//The cone is in the negative halfspace of the plane if both
//the tip of the cone and the farthest point on the end of the cone to the
//positive halfspace of the plane are both inside the negative halfspace
//of the plane.
return PointInsidePlane(cone.T, plane) && PointInsidePlane(Q, plane);
}
//Check to see of a light is partially contained within the frustum.
bool SphereInsideFrustum(Sphere sphere, Frustum frustum, float zNear, float zFar)
{
bool result = true;
//First check depth
//Note: Here, the view vector points in the -Z axis so the
//far depth value will be approaching -infinity.
if(sphere.c.z - sphere.r > zNear || sphere.c.z + sphere.r < zFar)
{
result = false;
}
//Then check frustum planes
for(int i = 0; i < 4 && result; i++)
{
if(SphereInsidePlane( sphere, frustum.planes[i]))
{
result = false;
}
}
return result;
}
bool ConeInsideFrustum(Cone cone, Frustum frustum, float zNear, float zFar)
{
bool result = true;
vec4 nearPlane = vec4(0.0, 0.0, -1.0, -zNear);
vec4 farPlane = vec4(0.0, 0.0, 1.0, zFar);
// First check the near and far clipping planes.
if(ConeInsidePlane(cone, nearPlane) || ConeInsidePlane(cone, farPlane))
{
result = false;
}
// Then check frustum planes
for(int i = 0; i < 4 && result; i++)
{
if(ConeInsidePlane(cone, frustum.planes[i]))
{
result = false;
}
}
return result;
}
And finally code to read depth from the depth buffer and check all lights:
vec2 tmp_tex = vec2(dispatchThreadID) / u_screenSize;
float tmp_depth = texture(u_depthMap, tmp_tex).r;
uint tmp_uDepth = floatBitsToUint(tmp_depth);
atomicMin(uMinDepth, tmp_uDepth);
atomicMax(uMaxDepth, tmp_uDepth);
barrier();
float fMinDepth = uintBitsToFloat(uMinDepth);
float fMaxDepth = uintBitsToFloat(uMaxDepth);
//Convert depth values to view space.
float minDepthVS = screenToView(vec4(0, 0, fMinDepth, 1), u_screenSize, inv_ProjMatrix).z;
float maxDepthVS = screenToView(vec4(0, 0, fMaxDepth, 1), u_screenSize, inv_ProjMatrix).z;
float nearClipVS = screenToView(vec4(0, 0, 0, 1), u_screenSize, inv_ProjMatrix).z;
//Clipping plane for minimum depth value
//(used for testing lights within the bounds of opaque geometry).
vec4 minPlane = vec4(0, 0, -1, -minDepthVS);
//Cull lights
//Each thread in a group will cull 1 light until all lights have been culled.
for(uint i = groupIndex; i < u_lightCount; i += TILE_SIZE * TILE_SIZE)
{
Light currentLight = lightBuffer.data[i];
uint lightType = uint(currentLight.type);
switch(lightType)
{
case DIRECTIONAL_LIGHT_TYPE:
{
//Directional lights always get added to our light list.
//(Hopefully there are not too many directional lights!)
t_appendLight(i);
o_appendLight(i);
}
break;
case POINT_LIGHT_TYPE:
{
vec3 lightPos = vec3(currentLight.position[0], currentLight.position[1], currentLight.position[2]);
Sphere sphere = {lightPos, currentLight.radius};
if(SphereInsideFrustum(sphere, GroupFrustum, nearClipVS, maxDepthVS))
{
//Add light to light list for transparent geometry.
t_appendLight(i);
if(!SphereInsidePlane(sphere, minPlane))
{
//Add light to light list for opaque geometry.
o_appendLight(i);
}
}
}
break;
case SPOT_LIGHT_TYPE:
{
vec3 lightPos = vec3(currentLight.position[0], currentLight.position[1], currentLight.position[2]);
vec3 lightDir = vec3(currentLight.direction[0], currentLight.direction[1], currentLight.direction[2]);
float coneRadius = tan(radians(currentLight.outerAngle)) * currentLight.radius;
Cone cone = {lightPos, currentLight.radius, lightDir, coneRadius};
if(ConeInsideFrustum(cone, GroupFrustum, nearClipVS, maxDepthVS))
{
//Add light to light list for transparent geometry.
t_appendLight(i);
if(!ConeInsidePlane(cone, minPlane))
{
// Add light to light list for opaque geometry.
o_appendLight(i);
}
}
}
break;
}
}
My code is based on: https://www.3dgep.com/forward-plus/ and I have tried to convert directx code to OpenGL. Any help will be appreciate (sorry for my bad english)