Jump to content
  • Advertisement
Sign in to follow this  
Magogan

Screen space scissor rect of a cube

This topic is 1055 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites

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);
//...
Edited by Magogan

Share this post


Link to post
Share on other sites


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.

Share this post


Link to post
Share on other sites

 


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.

Share this post


Link to post
Share on other sites

@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.

Edited by Magogan

Share this post


Link to post
Share on other sites


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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!