Skybox without a box, Skysphere without a sphere

From XNAWiki
Jump to: navigation, search

This is a technique for rendering a sky without using a box or (hemi)sphere model to do so. Instead it uses a full screen quad and the bounding frustum corners to fire a frustum ray into a cube map texture. A handy trick with this method is the moveToFarPlane parameter on the vertex shader, when set to true and using depth-stencil states set how you would normally set them up for rendering 3d objects, the sky rendering can come after all opaque objects are rendered, saving a good amount of pixel processing time since it's not often the entire sky is visible in a game.

Generating required data about the frustum:

  • assumes you have a view and projection matrix.
  • also assumes you have camera orientation stored in a quaternion, if you store it in a matrix simply replace xform with that orientation matrix.
// update the bounding frustum
Matrix matViewProjection;
Matrix.Multiply(ref matView, ref matProjection, out matViewProjection);
boundingFrustum.Matrix = matViewProjection;
 
// create a reverse rotation matrix for camera frustum corners
Matrix xform;
Matrix.CreateFromQuaternion(ref camWorldOrientation, out xform);
Matrix.Multiply(ref matView, ref xform, out xform);
 
// fetch the frustum corners and reverse the camera rotation
Vector3[] frustumCorners = new Vector3[8];
boundingFrustum.GetCorners(frustumCorners);
Vector3.Transform(frustumCorners, 4, ref xform, frustumCorners, 0, 4);
 
// switch the bottom two corners for left to right ordering
Vector3 temp = frustumCorners[3];
frustumCorners[3] = frustumCorners[2];
frustumCorners[2] = temp;

That's all there is for supporting code, now to the sky portion. The below assumes you have a way to generate a full screen quad, if you do not refer to the Shape Generation page for a function that will do that for you.

Shader for rendering the sky:

// =============================================================================================
//	Parameters
// =============================================================================================
 
float3 FrustumCorners[4];
 
// =============================================================================================
//	Functions
// =============================================================================================
 
// interpolates the frustum corners to generate a ray that starts at the camera and ends at
//	the far clip plane of the bounding frustum
#define GetFrustumRay(texCoord) \
	lerp(lerp(FrustumCorners[0], FrustumCorners[1], ##texCoord.x), \
	     lerp(FrustumCorners[2], FrustumCorners[3], ##texCoord.x), \
	     ##texCoord.y)
 
// =============================================================================================
//	Textures & Samplers
// =============================================================================================
 
texture CubeMap;
sampler CubeMapSampler = sampler_state { Texture = <CubeMap>; }
 
// =============================================================================================
//	Vertex Shaders
// =============================================================================================
 
void VS_Passive(inout float4 position : POSITION0,
		inout float2 texCoord : TEXCOORD0,
		uniform bool moveToFarPlane)
{
	if (moveToFarPlane)
		position.zw = 1;
}
 
// =============================================================================================
//	Pixel Shaders
// =============================================================================================
 
float4 PS_Sky(float2 texCoord : TEXCOORD0) : COLOR0
{
	float3 frustumRay = GetFrustumRay(texCoord);
	return texCUBE(CubeMapSampler, frustumRay);
}
 
// =============================================================================================
//	Techniques
// =============================================================================================
 
technique Sky
{
	pass Sky
	{
		VertexShader = compile vs_2_0 VS_Passive(true);
		PixelShader = compile ps_2_0 PS_Sky();
	}
}