Tuesday, 19 March 2013

Assignment 09: Render Targets as Textures


Requirements
  • Create a new Effect that computes Environment Mapping using a cube map
    • The environment map used must be data-driven and come from one of your files
      • It is probably best to specify a single one for your scene
      • It is also acceptable to specify different ones on a per-material basis
    • There must be a reflectivity uniform parameter that you set per-material
      • A value of 1 would mean that you only see the reflection (i.e. you would never see the diffuse map)
      • A value of 0 would mean that you don't see the reflection at all when your viewing angle is parallel with the normal. (However, you will still see the reflection at glancing angles because of the fresnel effect)
    • Your shader must compute the fresnel effect for environment mapping using Schlick's approximation:
      • fresnelModifier = ( 1 - saturate( dot( normal_world, viewDirection ) ) )^5
      • reflectedAmount = reflectance + ( ( 1 - reflectance ) * fresnelModifier )
    • There are two options for mixing the reflections with the diffuse color (as discussed in class):
      • You may combine it with the diffuse color before lighting
      • You may combine it with the diffuse color after lighting (but before adding specularity)
      • The way to "combine" it will look something like this:
        • diffuse = lerp( diffuse, reflectedColor, reflectedAmount )
  • You will have to create a new EnvironmentMap asset type
    • (In a real game you probably wouldn't do this, but since we kept our asset build process simple we need to do this to support the new DDS file extension we'll use for cube maps.)
    • You will need to create a new EnvironmentMapBuilder tool
      • It should just copy the cube maps, just like the other TextureBuilder
      • Don't forget to set up your project dependencies correctly!
  • Your ground plane must use environment mapping so that I can move the camera up and down and verify that the fresnel effect is working properly
  • You must have a sphere in your scene that also uses environment mapping
  • Render your Opaque bucket into a texture
  • Copy the Opaque bucket texture into the back buffer
  • Render your Alpha bucket into the back buffer
  • Create a new Effect that uses the Opaque bucket texture as input
    • You should do some kind of manipulation on what has already been rendered that you couldn't do using standard alpha blending
    • Using some kind of sine wave is always a good idea if you don't have any others; you can make the back ground wobble around
    • Just manipulating the transparency is not acceptable (we've already done that with standard alpha blending)
    • Just manipulating the color is not acceptable (we've done something similar with our additive shader)
    • Think of ways that you can actually move the drawn pixels around using texture coordinates; this is something that isimpossible to do with alpha blending
  • You must have at least one Entity that uses your specialty Effect that manipulates the Opaque bucket texture
    • It must be towards the foreground of the scene, so that I can easily move the camera around and verify that it is,indeed, manipulating what has been rendered behind it
  • Your writeup must include a screenshot from PIX of your Opaque bucket render target used as a texture. You can find this the same way that you would look at any other SetTexture() call, and it should look something like this example.

Details

Environment Mapping
  • Microsoft has provided a good cube map that you can use (this is the one I used in class as an example). You can find it at:
    • ($DXSDK_DIR)\Samples\C++\Direct3D\StateManager\Media\skybox02.dds
  • (Optional) You may also try your hand at creating your own using the DirectX Texture Tool that I showed in class
  • Using a cube map in C++ code has the following differences from the 2D textures we've used so far:
    • IDirect3DCubeTexture9
    • D3DXCreateCubeTextureFromFile()
  • Using a cube map in HLSL code has the following differences from the 2D samplers we've used so far:
    • samplerCUBE
    • texCUBE( sampler, someFloat3 )
  • The float3 that you use as a texture coordinate in a cube map is interpreted by the hardware as a direction from the origin of the cube map (for example, using (1,0,0) as the second parameter to texCUBE() will sample the very center of the +X face of the cube map). To compute a reflected color do the following:
    • Calculate the view direction the same way we did for specularity
      • Make sure you use the correct camera position rather than the wrong way I originally taught :(
    • Calculate how the view direction will reflect at the fragment position given the fragment's normal
      • When using the reflect() function make sure that the view direction is pointing in the direction that the function expects. You may need to use -viewDirection depending on how you calculated it
    • Use that reflected view direction to sample your environment map
    • You should then be able to calculate the reflectivity given the material's reflectivity and the fresnel equation, and then combine the reflected color with the diffuse color using that reflectivity (as discussed in the requirements section above)
Rendering to a texture
  • To set up your Opaque buffer texture:
    • You will need an IDirect3DTexture9*, just like for any other 2D texture
    • Instead of creating the texture from a file, though, just use the plain CreateTexture() function
      • The width and height should be the same as what your real back buffer uses
        • You can use UserSettings::GetWidth() and UserSettings::GetHeight()
      • No mipmaps should be created, because we'll be rendering to this every frame
      • The usage must be D3DUSAGE_RENDERTARGET
        • This tells Direct3D that we will be rendering to it
      • The format should be the same as the back buffer
    • We will need to get what Microsoft calls a "surface" from this texture in order to use it as a render target
      • Call IDirect3DTexture9::GetSurfaceLevel(), asking for level 0
      • The resulting IDirect3DSurface9* is what you'll use to set the texture as a render target
  • Before setting the Opaque buffer texture as a render target, you need to get a pointer to the real back buffer:
    • Use IDirect3DDevice9::GetRenderTarget(), asking for render target 0
      • The resulting IDirect3DSurface9* is the real back buffer that was created according to your specifications
      • (You can make this call as soon as your CreateDevice() call succeeds in your initialization code)
  • Now, when rendering your two different buckets, you can switch your render target between these two surfaces:
    • To render your Opaque bucket:
      • Set your Opaque bucket texture as the current render target using:
        • IDirect3DDevice9::SetRenderTarget()
      • Clear the target like you always have
        • Any rendering calls you make now will apply to the current render target
        • This means that you're clearing your Opaque texture, and not the real back buffer
      • Call BeginScene()
      • Make all of your draw calls, just like you have in previous assignments
      • Call EndScene()
    • To render your Translucent bucket:
      • Set the real back buffer as the current render target using:
        • IDirect3DDevice9::SetRenderTarget()
      • Instead of clearing like we did with the Opaque bucket, we want to start this bucket with everything we've done in the Opaque bucket so far, so we need to copy its contents to the back buffer. Use:
        • IDirect3DDevice9::StretchRect()
          • You will be copying the entire surface
          • The sizes should be the same (because you set it up that way), so no filtering needs to be done. Use D3DTEXF_NONE
      • Call BeginScene()
      • Now you can make all of the draw calls, just like you have in previous assignments
      • The one extra step you have to make is to set the Opaque bucket texture so that it's available to any shaders that use it. This is exactly the same as using any other 2D texture.
      • Call EndScene()
    • When you're done, call IDirect3DDevice9::Present() just like you've always done.
If things aren't working make sure to use PIX to help. It will show you the calls to SetRenderTarget(), and so you should be able to follow each of the steps and make sure that things look like you would expect in the Render tab (You can also open Surface tabs for each Surface and make sure that things are drawing in each at the right time).

No comments:

Post a Comment