Screen space scissor rect of a cube

Started by
9 comments, last by kalle_h 8 years, 7 months ago

Hi,

I try to compute a screen space scissor rect for a cube, but it don't work when the camera is inside the cube. I currently just transform the edges of the cube into clip space (using the view and projection matrix) and build the scissor rect from the min and max values of all 8 edges in screen space. It works when the camera is outside the cube, but it does not work properly when the camera is inside the cube because z is negative for some of the edges in this case and screen coordinates are wrong because it's a FoV projection matrix. Is there a way to compute the scissor rect properly in this case or should I simply set it to the screen size (which reduces performance a little bit so I'd like to avoid doing that)?

I only need to execute the post processing pixel shader for the pixels of the area of the screen space which is inside the cube. Is there maybe a better way than using scissor rects? I thought about using the stencil buffer, but since the pixel shader involves a lot of computation, I'd like to avoid executing it if unnecessary (outside the cube), which does not seem possible using a stencil buffer.

Advertisement

I think the correct way to handle the camera inside cube problem is to dissect the cube using the near clip plane, before transforming it. But the result is no longer a cube, so it's not very straight forward.

But why don't you just build geometry of the cube and render it using your post processing shader? This way the GPU / driver takes care of all that complicated clipping business. If you turn culling off, it will work even when the cube intersects with the near clip plane. If that, for some reason, won't work, you can still render the cube with an empty shader and write to the stencil buffer and you basically have what you did before, but faster, easier and more exact.

I hope that helps!

I never thought about simply rendering a cube instead of a fullscreen rect. But if I do so and turn off culling, won't most of the pixels be rendered twice?

Besides, I fixed the problem by dividing the cubes into 4x4x4 smaller cubes to compute the scissor rect. It may not be 100% correct, but I could not find any error until now.

Edit: There are still some errors, but now they occur when a cube is far away and only at some minimum vertical camera angle. Here is the code, maybe this could help identifying the problem.


//...

D3D11_RECT ScissorRect;
bool ScissorRectIsValid = false;
					
LONG2 RenderChunkPos = GetRenderChunkPosition(ChunkContainer.Position, UpdatedPlayerPosition.xz());
					
const float ChunkSizeBias = 0.5f;

#define DIVIDE_CHUNK_COUNT 4

for (int n = 0; n < 8; ++n){
	for (int x = 0; x < DIVIDE_CHUNK_COUNT; ++x){
		for (int y = 0; y < DIVIDE_CHUNK_COUNT; ++y){
			for (int z = 0; z < DIVIDE_CHUNK_COUNT; ++z){

				XMFLOAT3 ChunkPos(RenderChunkPos.x + x*(CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT), ChunkContainer.Chunks[8 * k + n].Position.y + y*(MINI_CHUNK_SIZE_Y / DIVIDE_CHUNK_COUNT), RenderChunkPos.y + z*(CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT));

				if (ViewingFrustum->Cuboid(XMFLOAT3(ChunkPos.x + CHUNK_SIZE_XZ / 2.0f / DIVIDE_CHUNK_COUNT, ChunkPos.y + MINI_CHUNK_SIZE_Y / 2.0f / DIVIDE_CHUNK_COUNT, ChunkPos.z + CHUNK_SIZE_XZ / 2.0f / DIVIDE_CHUNK_COUNT), XMFLOAT3(CHUNK_SIZE_XZ / 2.0f / DIVIDE_CHUNK_COUNT + ChunkSizeBias, MINI_CHUNK_SIZE_Y / 2.0f / DIVIDE_CHUNK_COUNT + ChunkSizeBias, CHUNK_SIZE_XZ / 2.0f / DIVIDE_CHUNK_COUNT + ChunkSizeBias))){


					XMFLOAT4 Edges[8];
					Edges[0] = XMFLOAT4(ChunkPos.x + RenderPositionOffset.x - ChunkSizeBias, ChunkPos.y + RenderPositionOffset.y - ChunkSizeBias, ChunkPos.z + RenderPositionOffset.z - ChunkSizeBias, 1.0f);
					Edges[1] = XMFLOAT4(ChunkPos.x + RenderPositionOffset.x - ChunkSizeBias, ChunkPos.y + RenderPositionOffset.y - ChunkSizeBias, ChunkPos.z + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.z + ChunkSizeBias, 1.0f);
					Edges[2] = XMFLOAT4(ChunkPos.x + RenderPositionOffset.x - ChunkSizeBias, ChunkPos.y + MINI_CHUNK_SIZE_Y / DIVIDE_CHUNK_COUNT + RenderPositionOffset.y + ChunkSizeBias, ChunkPos.z + RenderPositionOffset.z - ChunkSizeBias, 1.0f);
					Edges[3] = XMFLOAT4(ChunkPos.x + RenderPositionOffset.x - ChunkSizeBias, ChunkPos.y + MINI_CHUNK_SIZE_Y / DIVIDE_CHUNK_COUNT + RenderPositionOffset.y + ChunkSizeBias, ChunkPos.z + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.z + ChunkSizeBias, 1.0f);
					Edges[4] = XMFLOAT4(ChunkPos.x + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.x + ChunkSizeBias, ChunkPos.y + RenderPositionOffset.y - ChunkSizeBias, ChunkPos.z + RenderPositionOffset.z - ChunkSizeBias, 1.0f);
					Edges[5] = XMFLOAT4(ChunkPos.x + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.x + ChunkSizeBias, ChunkPos.y + RenderPositionOffset.y - ChunkSizeBias, ChunkPos.z + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.z + ChunkSizeBias, 1.0f);
					Edges[6] = XMFLOAT4(ChunkPos.x + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.x + ChunkSizeBias, ChunkPos.y + MINI_CHUNK_SIZE_Y / DIVIDE_CHUNK_COUNT + RenderPositionOffset.y + ChunkSizeBias, ChunkPos.z + RenderPositionOffset.z - ChunkSizeBias, 1.0f);
					Edges[7] = XMFLOAT4(ChunkPos.x + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.x + ChunkSizeBias, ChunkPos.y + MINI_CHUNK_SIZE_Y / DIVIDE_CHUNK_COUNT + RenderPositionOffset.y + ChunkSizeBias, ChunkPos.z + CHUNK_SIZE_XZ / DIVIDE_CHUNK_COUNT + RenderPositionOffset.z + ChunkSizeBias, 1.0f);

					XMFLOAT2 MinEdge(std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity());
					XMFLOAT2 MaxEdge(-std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity());

					for (int j = 0; j < 8; ++j){
						XMFLOAT3 VertexInClipSpace;
						XMStoreFloat3(&VertexInClipSpace, XMVector3TransformCoord(XMLoadFloat4(Edges + j), ViewProjectionMatrix));
						if (VertexInClipSpace.x < MinEdge.x) MinEdge.x = VertexInClipSpace.x;
						if (VertexInClipSpace.x > MaxEdge.x) MaxEdge.x = VertexInClipSpace.x;

						if (VertexInClipSpace.y < MinEdge.y) MinEdge.y = VertexInClipSpace.y;
						if (VertexInClipSpace.y > MaxEdge.y) MaxEdge.y = VertexInClipSpace.y;

					}
					if (MinEdge.x <= 1.0f && MinEdge.y <= 1.0f && MaxEdge.x >= -1.0f && MaxEdge.y >= -1.0f){

						int32_t ScissorRectLeft = 0,
							ScissorRectRight = ScreenWidth,
							ScissorRectTop = 0,
							ScissorRectBottom = ScreenHeight;

						//test if camera is outside the cube
						if ((Edges[0].x > 0 || Edges[7].x < 0) || (Edges[0].y > 0 || Edges[7].y < 0) || (Edges[0].z > 0 || Edges[7].z < 0)){
							ScissorRectLeft = std::floor((MinEdge.x + 1.0f)*0.5f*ScreenWidth);
							ScissorRectRight = std::ceil((MaxEdge.x + 1.0f)*0.5f*ScreenWidth);
							ScissorRectTop = std::floor((MinEdge.y + 1.0f)*0.5f*ScreenHeight);
							ScissorRectBottom = std::ceil((MaxEdge.y + 1.0f)*0.5f*ScreenHeight);
						}


						if (ScissorRectIsValid){
							ScissorRect.left = std::min((LONG)ScissorRectLeft, ScissorRect.left);
							ScissorRect.right = std::max((LONG)ScissorRectRight, ScissorRect.right);
							ScissorRect.bottom = std::max((LONG)ScissorRectBottom, ScissorRect.bottom);
							ScissorRect.top = std::min((LONG)ScissorRectTop, ScissorRect.top);
						}
						else{
							ScissorRect.left = ScissorRectLeft;
							ScissorRect.right = ScissorRectRight;
							ScissorRect.bottom = ScissorRectBottom;
							ScissorRect.top = ScissorRectTop;
							ScissorRectIsValid = true;
						}
					}
				}
			}
		}
	}
}

assert(ScissorRectIsValid);

DeviceContext->RSSetScissorRects(1, &ScissorRect);
//...


But if I do so and turn off culling, won't most of the pixels be rendered twice?

You could just check if the camera is inside the cube and only turn off culling when it is, or render near faces first and let depth-testing take care of it.


But if I do so and turn off culling, won't most of the pixels be rendered twice?

You could just check if the camera is inside the cube and only turn off culling when it is, or render near faces first and let depth-testing take care of it.


But if I do so and turn off culling, won't most of the pixels be rendered twice?

You could just check if the camera is inside the cube and only turn off culling when it is, or render near faces first and let depth-testing take care of it.

Well spotted and solved. In addition, if the platform has hidden surface removal (like iOS), it will also take care of that. In any case, using the cube render to set up the stencil buffer and then rendering a full screen quad will also do the trick.

Stencil buffer is more precise than scissor rect, because it has per pixel accuracy. I would say it's generally the best way to limit full screen overdraw, if you can find a good way to mask it. In your case it's a cube, which is a good way. :)

Dividing the cube into smaller cubes will not get rid of the problem, only hide it better, as long as your cubes are bigger than 1 pixel.

@Erik Rufelt: That's a good idea, I will try it later, thank you. But I think I need to disable culling if and only if the cube intersects the near plane of the viewing frustum (which not necessarily means that the camera itself is inside the cube), am I right?

Stencil buffers are not an option because stencil testing is done after the pixel shader is executed and since the execution of the pixel shader takes a relatively long time, this would not increase performance.


Stencil buffers are not an option because stencil testing is done after the pixel shader is executed and since the execution of the pixel shader takes a relatively long time, this would not increase performance.

Nope, stencil ops (like depth ops) are done before the pixel shader is run, unless the pixel shader contains stencil or depth instructions itself (which is not very common). Maybe you mistake it with alpha test, which is indeed run after the pixel shader, because it depends on whatever the pixel shader outputs.

https://www.opengl.org/discussion_boards/showthread.php/182690-Early-fragment-culling-using-the-stencil-buffe

There is no guarantee that the pixel shader is not executed if the stencil or depth test fails. The link is about OpenGL, but I don't think it's different in Direct3D 10 or 11.

There are very few guarantees on the PC platform, because it has to rely on vendor provided drivers to do the actual work. However, any vendor that cares about the performance of their products (both technical and economical) will implement a simple and effective optimization like that.

So, I wouldn't worry to much about that.

Is there a way to compute the scissor rect properly in this case or should I simply set it to the screen size (which reduces performance a little bit so I'd like to avoid doing that)?

project the points that are behind the near plane ( the negative ones) to the near plane.
that is usually not the right solution, but it should be ok if you just want to calculate the scissor rect.

This topic is closed to new replies.

Advertisement