Implementing multithreaded dynamic cubic environment mapping

In this recipe, we will implement dynamic cubic environment mapping or cube mapping and explore how threading impacts the rendering performance. A cube map is commonly used for skyboxes with the camera located inside the cube. In this recipe, we will be using cube maps to implement reflections for objects. We will also be rendering directly to the cube map resource in order to implement dynamic reflections. In Direct3D, a cube map is implemented using a texture cube; this is a special texture array with six slices where each slice represents a face of the cube along an axis. The TextureCube HLSL shader resource provides built-in sampling support. The following figure shows a static cube map laid out flat, and the texture array indices are matched to the appropriate axis:

Implementing multithreaded dynamic cubic environment mapping

Static cubic environment map with texture array indices.

Note that the cube map is defined using a left-handed coordinate system (that is, +Z is forward, +X is to the right, and +Y is upward). Our recipe will, therefore require axis scaling and vertex winding changes when generating dynamic cube maps.

Note

The Collosseum cube map is by Emil Persson, who has a number of static cube map textures released under a Creative Commons Attribution 3.0 license available at http://www.humus.name/index.php?page=Textures.

Getting ready

For this recipe, we will start with the project from the previous recipe, Benchmarking multithreaded rendering. The scene file named Scene.fbx, used in the example screenshots, is available from the companion source download for this book. The completed project can also be found in the companion source named Ch09_02DynamicCubeMapping.

How to do it…

We will begin by creating the necessary HLSL code to generate and consume our cube maps. Generating the cube map will involve creating a new vertex shader (VS_CubeMap), geometry shader (GS_CubeMap), and pixel shader (PS_CubeMap); to consume cube maps, we will update the PerMaterial constant buffer and each pixel shader.

  1. Firstly, we will update the PerMaterial constant buffer in ShadersCommon.hlsl to include a flag indicating whether a material is reflective and how reflective it is. This will be used by the pixel shaders.
    cbuffer PerMaterial : register (b2)
    {...
        bool IsReflective;
        float ReflectionAmount;
    ...};

    Note

    Remember that HLSL structures are 16-byte aligned.

  2. Create a new HLSL source file named ShadersCubeMap.hlsl, and ensure that it has the correct encoding as described in Chapter 2, Rendering with Direct3D. Add the following include directive to use our existing constant buffers, vertex, and pixel structures:
    #include "Common.hlsl"
  3. Add the following per environment map constant buffer to be updated using the fifth buffer slot, and define the geometry shader input to be the same as the pixel shader input:
    // Cube map ViewProjections for each face
    cbuffer PerEnvironmentMap : register(b4) {
        float4x4 CubeFaceViewProj[6];
    };
    // Use the PixelShaderInput as GeometryShaderInput
    #define GeometryShaderInput PixelShaderInput
  4. The output from the geometry shader (and therefore, input to our new pixel shader) is exactly the same as the PixelShaderInput structure within ShadersCommon.hlsl, except that we have added one new property to control the render target (that is, the face of the cube map) used.
    // Pixel Shader input structure (from Geometry Shader)
    struct GS_CubeMapOutput
    {   float4 Position : SV_Position;
    ...SNIP (existing PixelShaderInput structure properties)
        // Allows writing to multiple render targets
        uint RTIndex : SV_RenderTargetArrayIndex;
    };
  5. The new vertex shader is almost the same as the existing one in ShadersVS.hlsl; however, we need to apply the World matrix transform on the Position property.
    // Vertex shader cubemap function
    GeometryShaderInput VS_CubeMap(VertexShaderInput vertex)
    {   GeometryShaderInput result = (GeometryShaderInput)0;
    ...SNIP
        // Only apply world transform (not WorldViewProjection)
        result.Position = mul(vertex.Position, World);
    ...SNIP
    }
  6. The new geometry shader is where we use the geometry shader instance attribute of Shader Model 5 to execute the shader six times per input primitive (that is, six times per triangle).
    [maxvertexcount(3)] // Outgoing vertex count (1 triangle)
    [instance(6)] // Number of times to execute for each input
    void GS_CubeMap(triangle GeometryShaderInput input[3], 
        uint instanceId: SV_GSInstanceID, 
        inout TriangleStream<GS_CubeMapOutput> stream)
    {
        // Output the input triangle using the View/Projection
        // of the cube face identified by instanceId
        float4x4 viewProj = CubeFaceViewProj[instanceId];
        GS_CubeMapOutput output;
    
        // Assign the render target instance
        // i.e. 0 = +X-face, 1 = -X-face and so on
        output.RTIndex = instanceId;
        // In order to render correctly into a TextureCube we
        // must either:
        // 1) use a left-handed view/projection; OR
        // 2) use a right-handed view/projection with -1 X-
        //    axis scale
        // Our meshes assume a right-handed coordinate system
        // therefore both cases above require vertex winding
        // to be switched.
        uint3 idx = uint3(0,2,1);
        [unroll]
        for (int v = 0; v < 3; v++)
        {
            // Apply cube face view/projection
            output.Position = 
                mul(input[idx[v]].Position, viewProj);
            // Copy other vertex properties as is
            output.WorldPosition = input[idx[v]].WorldPosition;
    ...SNIP
            // Append to the stream
            stream.Append(output);
        }
        stream.RestartStrip();
    }

    Note

    To achieve the best geometry shader performance, it is critical to try and minimize the amount of data passing in and out of the stage. The PixelShaderInput structure used in our recipes includes a number of additional properties for illustrative purposes that should be removed when not needed.

  7. Add a new pixel shader for generating a cube map; this is a copy of our Blinn-Phong pixel shader. There are three differences highlighted in the following snippet from the original shader:
    // Globals for texture sampling
    Texture2D Texture0 : register(t0);
    TextureCube Reflection : register(t1);
    SamplerState Sampler : register(s0);
    
    float4 PS_CubeMap(GS_CubeMapOutput pixel) : SV_Target
    {
      // Normalize our vectors 
      float3 normal = normalize(pixel.WorldNormal);
      float3 toEye = normalize(pixel.ToCamera);
    ...SNIP
      // Calculate reflection (if any)
      if (IsReflective) {
          float3 reflection = reflect(-toEye, normal);
          color = lerp(color, Reflection.Sample(Sampler, 
                  reflection).rgb, ReflectionAmount);
      }
      // Calculate final alpha value and return
      float alpha = pixel.Diffuse.a * sample.a;
      return float4(color, alpha);
    }
  8. Within each of our existing pixel shaders (for example, BlinnPhongPS.hlsl) that will implement reflections, add the highlighted changes (as shown in the preceding code snippet), except for the function signature. This includes the TextureCube shader resource and blending of the reflection sample.
  9. This completes our HLSL source changes. We will now make the corresponding changes to the C# PerMaterial structure, create our DynamicCubeMap renderer class, and update our MeshRenderer class to use the cube map for reflections.
  10. Within the ConstantBuffers.cs file, add the IsReflective and ReflectionAmount properties to the PerMaterial structure.
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct PerMaterial {
    ...
        public uint IsReflective; //reflective (0 false, 1 true)
        public float ReflectionAmount; // how reflective? 0-1
    ...
    }
  11. Create a new renderer class descending from Common.RendererBase called DynamicCubeMap.
    using SharpDX;
    using SharpDX.DXGI;
    using SharpDX.Direct3D11;
    using Common;
    using Buffer = SharpDX.Direct3D11.Buffer;
    // Represents a dynamic cubic environment map (cube map)
    public class DynamicCubeMap: Common.RendererBase
    {
    ...
    }
  12. Create a new structure for storing the cube face camera view projections.
    // Represents the camera for a cube face
    // Note: the View matrix includes the current position
    // Matrix.Transpose(Matrix.Invert(View)).Column4==Position
    public struct CubeFaceCamera
    {
        public Matrix View;
        public Matrix Projection;
    }
  13. Add the following private and public member fields and constructors to the DynamicCubeMap class:
    // The cubic environment map texture array (6 slices)
    Texture2D EnvMap;
    // The RTV for all cube map faces (for single pass)
    RenderTargetView EnvMapRTV;
    // The DSV for all cube map faces (for single pass)
    DepthStencilView EnvMapDSV;
    // The TextureCube SRV for use by the mesh/renderer
    public ShaderResourceView EnvMapSRV;
    
    // The 'per cube map buffer' to be assigned to the geometry
    // shader stage when rendering the cubemap. This will 
    // contain the 6 ViewProjection matrices for the cube map.
    public Buffer PerEnvMapBuffer;
    
    // The viewport based on Size x Size
    ViewportF Viewport;
    
    // The renderer instance using the cube map reflection
    public RendererBase Reflector { get; set; }
    // The cameras for each face of the cube
    public CubeFaceCamera[] Cameras = new CubeFaceCamera[6];
    // The cube map texture size (e.g. 256x256)
    public int Size { get; private set; }
    
    public DynamicCubeMap(int size = 256)
        : base()
    {   // Set the cube map resolution (e.g. 256 x 256)
        Size = size;
    }
  14. Create an override for DynamicCubeMap.CreateDeviceDependentResources with the following code to reset the resources and to retrieve the device reference:
    RemoveAndDispose(ref EnvMap);
    RemoveAndDispose(ref EnvMapSRV);
    RemoveAndDispose(ref EnvMapRTV);
    RemoveAndDispose(ref EnvMapDSV);
    RemoveAndDispose(ref PerEnvMapBuffer);
    var device = this.DeviceManager.Direct3DDevice;
  15. Within the same function, we will first initialize our texture array resource. The two important properties that define a resource compatible with the TextureCube HLSL shader resource are highlighted in the following code snippet:
    // Create the cube map TextureCube (array of 6 textures)
    var textureDesc = new Texture2DDescription()
    {
        Format = Format.R8G8B8A8_UNorm,
        Height = this.Size,
        Width = this.Size,
        ArraySize = 6, // 6-sides of the cube
        BindFlags = BindFlags.ShaderResource | BindFlags.RenderTarget,
        OptionFlags = ResourceOptionFlags.GenerateMipMaps | ResourceOptionFlags.TextureCube,
        SampleDescription = new SampleDescription(1, 0),
        MipLevels = 0,
        Usage = ResourceUsage.Default,
        CpuAccessFlags = CpuAccessFlags.None,
    };
    EnvMap = ToDispose(new Texture2D(device, textureDesc));
  16. Next, we will define the Shader Resource View (SRV) for the previous resource.
    // Create the SRV for the texture cube
    var descSRV = new ShaderResourceViewDescription();
    descSRV.Format = textureDesc.Format;
    descSRV.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.TextureCube;
    descSRV.TextureCube.MostDetailedMip = 0;
    descSRV.TextureCube.MipLevels = -1;
    EnvMapSRV = ToDispose(new ShaderResourceView(device, EnvMap, descSRV));
  17. After that, we will define the Render Target View (RTV) for our texture cube.
    // Create the RTVs
    var descRTV = new RenderTargetViewDescription();
    descRTV.Format = textureDesc.Format;
    descRTV.Dimension = RenderTargetViewDimension.Texture2DArray;
    descRTV.Texture2DArray.MipSlice = 0;
    // Single RTV array for single pass rendering
    descRTV.Texture2DArray.FirstArraySlice = 0;
    descRTV.Texture2DArray.ArraySize = 6;
    EnvMapRTV = ToDispose(new RenderTargetView(device, EnvMap, descRTV));
  18. And then, we will create the Depth Stencil View (DSV) for rendering our cube map.
    // Create DSVs
    using (var depth = new Texture2D(device, new Texture2DDescription
    {   Format = Format.D32_Float,
        BindFlags = BindFlags.DepthStencil,
        Height = Size,
        Width = Size,
        Usage = ResourceUsage.Default,
        SampleDescription = new SampleDescription(1, 0),
        CpuAccessFlags = CpuAccessFlags.None,
        MipLevels = 1,
        OptionFlags = ResourceOptionFlags.TextureCube,
        ArraySize = 6 // 6-sides of the cube
    }))
    {   var descDSV = new DepthStencilViewDescription();
        descDSV.Format = depth.Description.Format;
        descDSV.Dimension = DepthStencilViewDimension.Texture2DArray;
        descDSV.Flags = DepthStencilViewFlags.None;
        descDSV.Texture2DArray.MipSlice = 0;
        // Single DSV array for single pass rendering
        descDSV.Texture2DArray.FirstArraySlice = 0;
        descDSV.Texture2DArray.ArraySize = 6;
        EnvMapDSV = ToDispose(new DepthStencilView(device, 
    depth, descDSV));
    }
  19. Lastly, we will create the viewport and per environment map buffer.
    // Create the viewport
    Viewport = new Viewport(0, 0, Size, Size);
    
    // Create the per environment map buffer (to store the 6 
    // ViewProjection matrices)
    PerEnvMapBuffer = ToDispose(new Buffer(device, Utilities.SizeOf<Matrix>() * 6, ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0));

    This completes the initialization of our cube map's Direct3D resources.

  20. Still within the DynamicCubeMap class, we will create a new public method for updating the current camera positions.
    // Update camera position for cube faces
    public void SetViewPoint(Vector3 camera)
    {   // The LookAt targets for view matrices
        var targets = new[] {
            camera + Vector3.UnitX, // +X
            camera - Vector3.UnitX, // -X
            camera + Vector3.UnitY, // +Y
            camera - Vector3.UnitY, // -Y
            camera + Vector3.UnitZ, // +Z
            camera - Vector3.UnitZ  // -Z
        };
        // The "up" vector for view matrices
        var upVectors = new[] {
            Vector3.UnitY, // +X
            Vector3.UnitY, // -X
            -Vector3.UnitZ,// +Y
            +Vector3.UnitZ,// -Y
            Vector3.UnitY, // +Z
            Vector3.UnitY, // -Z
        };
        // Create view and projection matrix for each face
        for (int i = 0; i < 6; i++)
        {
            Cameras[i].View = Matrix.LookAtRH(camera, 
                targets[i], 
                upVectors[i]) * Matrix.Scaling(-1, 1, 1);
            Cameras[i].Projection = Matrix.PerspectiveFovRH(
                (float)Math.PI * 0.5f, 1.0f, 0.1f, 100.0f);
        }
    }

    Note

    To remain consistent, we have used a right-handed coordinate system for the view. However, the TextureCube resource will be a little backwards unless we also scale -1 along the x axis. We will also need to reverse the vertex winding order (as we have done in the geometry shader GS_CubeMap) or switch the culling from back face to front face (or use no culling).

  21. For the scene to be rendered in its entirety while generating the cube map, it is necessary to pass through delegate that will perform the rendering logic. Therefore, we will not use the DynamicCubeMap.DoRender override and instead create a new public function named UpdateSinglePass.
    protected override void DoRender()
    {
        throw new NotImplementedException("Use UpdateSinglePass instead.");
    }
    // Update the 6-sides of the cube map using a single pass 
    // via Geometry shader instancing with the provided context
    // renderScene: The method that will render the scene
    public void UpdateSinglePass(
        DeviceContext context, 
        Action<DeviceContext, Matrix, Matrix, RenderTargetView, DepthStencilView, DynamicCubeMap> renderScene)
    {
        // Don't render the reflector itself
        if (Reflector != null)
            Reflector.Show = false;
    
        // Prepare pipeline
        context.OutputMerger.SetRenderTargets(EnvMapDSV, 
            EnvMapRTV);
        context.Rasterizer.SetViewport(Viewport);
        // Prepare the view projections
        Matrix[] viewProjections = new Matrix[6];
        for (var i = 0; i < 6; i++)
            viewProjections[i] = Matrix.Transpose( 
                 Cameras[i].View * Cameras[i].Projection);
        // Update perEnvMapBuffer with the ViewProjections
        context.UpdateSubresource(viewProjections, 
            PerEnvMapBuffer);
        // Assign perEnvMapBuffer to the GS stage slot 4
        context.GeometryShader
             .SetConstantBuffer(4, PerEnvMapBuffer);
        // Render scene using the view, projection, RTV and DSV
        renderScene(context, Cameras[0].View, 
          Cameras[0].Projection, EnvMapRTV, EnvMapDSV, this);
        // Unbind the RTV and DSV
        context.OutputMerger.ResetTargets();
        // Prepare the SRV mip levels
        context.GenerateMips(EnvMapSRV);
        // Re-enable the Reflector renderer
        if (Reflector != null)
            Reflector.Show = true;
    }

    This completes our DynamicCubeMap renderer class. Next, we need to update the MeshRenderer class to consume DyanamicCubeMap.EnvMapSRV.

  22. Within MeshRenderer, add a new public property for assigning a cube map.
    public DynamicCubeMap EnvironmentMap { get; set; }
  23. In the MeshRenderer.DoRender method, where the material constant buffer is prepared, add the following code to assign the cube map SRV:
    ...SNIP
    // If this mesh has a cube map assigned set 
    // the material buffer accordingly
    if (this.EnvironmentMap != null)
    {
        material.IsReflective = 1;
        material.ReflectionAmount = 0.4f;
        context.PixelShader.SetShaderResource(1, this.EnvironmentMap.EnvMapSRV);
    }
    
    // Update material buffer
    context.UpdateSubresource(ref material, PerMaterialBuffer);
    ...SNIP

    For our final changes, we'll move over to D3DApp.cs where we will compile the *_CubeMap shaders, move our rendering logic into a reusable action, and implement threading.

  24. We will compile the shaders in CubeMap.hlsl within D3DApp.CreateDeviceDependentResources as we have done in previous chapters. Compile the shader functions VS_CubeMap, GS_CubeMap, and PS_CubeMap using the vs_5_0, gs_5_0, and ps_5_0 shader profiles respectively.
  25. We need to perform most of our DeviceContext initialization within a D3DApp.InitializationContext function. However, we will be calling this for each context before each rendering pass and need to preserve the existing render targets under certain circumstances. In addition, the vertex, pixel, and geometry shaders change depending on whether we are rendering the cube map or the final scene.
    VertexShader activeVertexShader = null;
    GeometryShader activeGeometryShader = null;
    PixelShader activePixelShader = null;
    
    protected void InitializeContext(DeviceContext context, 
                                     bool updateRenderTarget)
    {
    ...SNIP
        // Set the default vertex shader to run
        context.VertexShader.Set(activeVertexShader);
    
        // Set the constant buffer for geometry shader stage
        context.GeometryShader.SetConstantBuffer(0, 
            perObjectBuffer);
        context.GeometryShader.SetConstantBuffer(1, 
            perFrameBuffer);
        // Set the geometry shader
        context.GeometryShader.Set(activeGeometryShader);
    ...SNIP
        // Set the pixel shader to run
        context.PixelShader.Set(activePixelShader);
    ...SNIP
    // When rendering cube maps don't change the render target
       if (updateRenderTarget)
       {
         // Set viewport
         context.Rasterizer.SetViewport(this.Viewport);
         // Set render targets
         context.OutputMerger.SetTargets(this.DepthStencilView, 
             this.RenderTargetView);
       }
    }
  26. Within D3DApp.Run, add a new local List<DynamicCubeMap> instance to keep track of the available cube maps.
    // Keep track of list of cube maps
    List<DynamicCubeMap> envMaps = new List<DynamicCubeMap>();
  27. And then, for each mesh that supports reflections, create a new DynamicCubeMap instance and initialize.
    // If MeshRenderer instance is reflective:
    var mesh = ...some reflective MeshRenderer instance
    var envMap = ToDispose(new DynamicCubeMap(256));
    envMap.Reflector = mesh;
    envMap.Initialize(this);
    m.EnvironmentMap = envMap;
    // Add to list of cube maps
    envMaps.Add(envMap);
  28. The bulk of our rendering loop will now be moved into a local anonymous method. In our simple example, this should be located before the start of the rendering loop as shown in the following code snippet:
    // Action for rendering the entire scene
    Action<DeviceContext, Matrix, Matrix, RenderTargetView, 
    DepthStencilView, DynamicCubeMap> renderScene = 
    (context, view, projection, rtv, dsv, envMap) =>
    {
        // We must initialize the context every time we render
        // the scene as we are changing the state depending on 
        // whether we are rendering a cube map or final scene
        InitializeContext(context, false);
        // We always need the immediate context
        var immediateContext = this.DeviceManager.Direct3DDevice
                               .ImmediateContext;
        // Clear depth stencil view
        context.ClearDepthStencilView(dsv, 
            DepthStencilClearFlags.Depth | 
            DepthStencilClearFlags.Stencil, 1.0f, 0);
        // Clear render target view
        context.ClearRenderTargetView(rtv, background);
    
        // Create viewProjection matrix
        var viewProjection = Matrix.Multiply(view, projection);
    
        // Extract camera position from view
        var camPosition = Matrix.Transpose(Matrix.Invert(view)).Column4;
        cameraPosition = new Vector3(camPosition.X, camPosition.Y, camPosition.Z);
    ...SNIP perform all rendering actions and multithreading
    }
  29. If the current pass of renderScene is rendering the environment map, it is necessary to assign the per environment map constant buffer to the geometry shader stage for each deferred context.
    // If multithreaded
    ...
    // If environment map is being rendered
    if (envMap != null)
        renderContext.GeometryShader.SetConstantBuffer(4, envMap.PerEnvMapBuffer);
  30. Finally, we will update our main rendering loop, RenderLoop.Run(Window, () => { ... }), to first render each cube map, then render the final scene, as shown in the following code:
    // Retrieve immediate context
    var context = DeviceManager.Direct3DContext;
    #region Update environment maps
    // Assign the environment map rendering shaders
    activeVertexShader = envMapVSShader;
    activeGeometryShader = envMapGSShader;
    activePixelShader = envMapPSShader;
    
    // Render each environment map
    foreach (var envMap in envMaps)
    {
      var mesh = envMap.Reflector as MeshRenderer;
      if (mesh != null)
      {
        // Calculate view point for reflector
        var center = Vector3.Transform( 
            mesh.Mesh.Extent.Center, mesh.World * worldMatrix);
        envMap.SetViewPoint(new Vector3(center.X, center.Y, 
          center.Z));
        // Render envmap in single full render pass using
        // geometry shader instancing.
        envMap.UpdateSinglePass(context, renderScene);
      }
    }
    #endregion
    
    #region Render final scene
    // Reset the vertex, geometry and pixel shader
    activeVertexShader = vertexShader;
    activeGeometryShader = null;
    activePixelShader = blinnPhongShader;
    // Initialize context (also resetting the render targets)
    InitializeContext(context, true);
    // Render the final scene
    renderScene(context, viewMatrix, projectionMatrix, RenderTargetView, DepthStencilView, null);
    #endregion

How it works…

The following screenshot shows the dynamic cube map from this recipe used with seven reflective surfaces. The 100 cubes in the sky are rotating around the y axis, and the cube maps are updated dynamically. The close up of the spheres illustrates how reflections include the reflections on other surfaces:

How it works…

A scene with seven reflective surfaces using cubic environment maps

Rather than rendering the entire scene six times per cube map, we use multiple render targets and the instance attribute of the geometry shader to do this in a single render pass in a fraction of the time (approximately three to four times faster). For each triangle output from the vertex shader, the Direct3D pipeline calls the geometry shader six times as per the instance attribute. The SV_GSInstanceID input semantic contains the zero-based instance ID; this ID is used to index the view/projections that we calculated for each cube face. We indicate which render target to send the fragment to by setting the SV_RenderTargetArrayIndex input semantic (GS_CubeMapOutput.RTIndex in our example HLSL) to the value of the geometry shader's instance ID.

The following diagram outlines the process within the pipeline:

How it works…

Direct3D pipeline view of cube map generation with geometry shader instancing

To calculate the reflection in our pixel shader, we use the intrinsic HLSL reflect function; this takes the camera direction and surface normal to compute a reflection vector. This reflection vector is used to sample the cube from its SRV as shown in the following diagram:

How it works…

Sampling texture cube using reflection vector (right-handed coordinate system)

The view/projections are calculated for each face by taking the object's center point and creating "look at" view matrices for all the six faces. Because we are using a right-handed coordinate system, it is necessary for us to flip the x axis of the cube map view matrix by scaling the x axis by -1 and reversing the vertex winding order in the geometry shader. By implementing multithreaded deferred contexts, we can improve the performance when there is an increased CPU load or larger numbers of draw calls (for example, more reflective surfaces).

The following graph shows the performance impact of multithreading:

How it works…

Impact of multithreaded cube map rendering with varying reflective surfaces, scene objects, CPU load, and threads.

The worst case scenario indicates a situation where there are two dynamic cube maps, no CPU load, and only 100 cubes reflecting in the sky. The best case scenario is of three dynamic cube maps with 2,000 matrix operations per mesh and 300 cubes in the sky. The impact of multithreading did not hit a positive result when there was no additional CPU load for 100 or 200 cubes in the sky but did for 300 cubes. It is clear that once there is enough CPU load, multithreaded rendering produces significant performance benefits; however, there are certain cases where it can have a detrimental effect.

There's more…

It is important to note that our implementation does not implement frustum culling or object occlusion. We also do not take into consideration whether or not a face of the cube map will be visible; however, this gets more complicated when you consider the reflections of reflections.

Within the completed companion project for this recipe, any mesh with a name containing reflector will have a cube map associated, any mesh name containing rotate will be rotated around the y axis, and adding a mesh name containing replicate will cause the object to be duplicated and arranged in a grid pattern. The same mesh can contain any or all of the three of these key words. There is also an implementation of the six-pass cube map for performance comparison.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.162.65