Windows (DirectX 12) |
Linux (Vulkan) |
MacOS (Metal) |
iOS (Metal) |
---|---|---|---|
* No simulator support |
This tutorial demonstrates cube-map array texturing and sky-box rendering with Methane Kit:
Tutorial demonstrates the following techniques:
- Loading face images to cube map texture and using it for sky-box rendering in the background;
- Creating a cube-map array render target texture;
- Rendering text labels to the faces of the cube-map array texture via separate render passes using the helper class TextureLabeler;
- Instanced rendering of multiple cubes displaying all faces of the pre-rendered cube-map array texture;
- Using the Sky-box rendering extension with a panoramic cube-map texture loaded from image files.
Common keyboard controls are enabled by the Platform
, Graphics
and UserInterface
application controllers:
- Methane::Platform::AppController
- Methane::Graphics::AppController, AppContextController
- Methane::UserInterface::AppController
Cube-map texture is created using settings generated with rhi::ITexture::Settings::ForCubeImage(...)
and is set to the
TexturedMeshBuffers
instance with m_cube_buffers_ptr->SetTexture(...)
. Then this texture is bound to the Pixel shader
argument g_texture_array
in frame.cube.program_bindings
initialization.
The content of the texture is rendered to its faces using the TextureLabeler
class, which renders face identification text
labels to the texture faces. Rendering is done after the UserInterfaceApp::CompleteInitialization()
call to ensure that all
necessary resources have been uploaded to the GPU.
void CubeMapArrayApp::Init()
{
...
// Create cube mesh buffer resources
gfx::CubeMesh<CubeVertex> cube_mesh(CubeVertex::layout);
m_cube_buffers_ptr = std::make_unique<TexturedMeshBuffers>(render_cmd_queue, std::move(cube_mesh), "Cube");
// Create cube-map render target texture
m_cube_buffers_ptr->SetTexture(
rhi::Texture(
GetRenderContext(),
rhi::ITexture::Settings::ForCubeImage(
g_cube_texture_size, CUBE_MAP_ARRAY_SIZE, gfx::PixelFormat::RGBA8Unorm, false,
rhi::ResourceUsageMask({ rhi::ResourceUsage::RenderTarget, rhi::ResourceUsage::ShaderRead })
)));
...
// Create frame buffer resources
for(CubeMapArrayFrame& frame : GetFrames())
{
// Configure program resource bindings
frame.cube.program_bindings = m_render_state.GetProgram().CreateBindings({
{ { rhi::ShaderType::Pixel, "g_texture_array" }, m_cube_buffers_ptr->GetTexture().GetResourceView() },
{ { rhi::ShaderType::Pixel, "g_sampler" }, m_texture_sampler.GetResourceView() },
}, frame.index);
frame.cube.uniforms_argument_binding_ptr = &frame.cube.program_bindings.Get({ rhi::ShaderType::Vertex, "g_uniforms" });
...
}
// Create all resources for texture labels rendering before resources upload in UserInterfaceApp::CompleteInitialization()
TextureLabeler cube_texture_labeler(GetUIContext(), GetFontContext(), m_cube_buffers_ptr->GetTexture(),
rhi::ResourceState::Undefined, { g_cube_texture_size / 4U, 10U });
// Upload all resources, including font texture and text mesh buffers required for rendering
UserInterfaceApp::CompleteInitialization();
// Encode and execute texture labels rendering commands when all resources are uploaded and ready on GPU
cube_texture_labeler.Render();
GetRenderContext().WaitForGpu(rhi::IContext::WaitFor::RenderComplete);
}
Rendering of the cube instances is done similarly to other tutorials, but with the use of the instance_count
parameter
in the m_cube_buffers_ptr->Draw(..., CUBE_MAP_ARRAY_SIZE)
call. The instance_id
related to this count is passed in the
vertex shader and used to form texture coordinates for cube-map array texture sampling.
bool CubeMapArrayApp::Render()
{
...
META_DEBUG_GROUP_VAR(s_debug_group, "Cube Instances Rendering");
frame.render_cmd_list.ResetWithState(m_render_state, &s_debug_group);
frame.render_cmd_list.SetViewState(GetViewState());
m_cube_buffers_ptr->Draw(frame.render_cmd_list, frame.cube.program_bindings, 0U, CUBE_MAP_ARRAY_SIZE);
...
}
Uniforms structure with array of MVP matrices for all cube instances:
```cpp
#define CUBE_MAP_ARRAY_SIZE 8 // NOSONAR
struct Uniforms
{
float4x4 mvp_matrix_per_instance[CUBE_MAP_ARRAY_SIZE]; // NOSONAR
};
Vertex shader prepares texture coordinates uvwi
for cube-map texture sampling by writing the cube vertex position to uvw
texture coordinates and the instance id to the i
component, which is used as an index in the texture array. Sampling of the
cube-map array texture g_texture_array
is done in the pixel shader from interpolated coordinates uvwi
.
```cpp
#include "CubeMapArrayUniforms.h"
struct VSInput
{
uint instance_id : SV_InstanceID;
float3 position : POSITION;
};
struct PSInput
{
float4 position : SV_POSITION;
float4 uvwi : UVFACE;
};
ConstantBuffer<Uniforms> g_uniforms : register(b0, META_ARG_FRAME_CONSTANT);
TextureCubeArray g_texture_array : register(t0, META_ARG_CONSTANT);
SamplerState g_sampler : register(s0, META_ARG_CONSTANT);
PSInput CubeVS(VSInput input)
{
PSInput output;
output.position = mul(float4(input.position, 1.F), g_uniforms.mvp_matrix_per_instance[input.instance_id]);
output.uvwi = float4(-input.position, input.instance_id); // use position with negative sign to fix texture reflection
return output;
}
float4 CubePS(PSInput input) : SV_TARGET
{
return g_texture_array.Sample(g_sampler, input.uvwi);
}
Continue learning Methane Graphics programming in the next tutorial ParallelRendering,
which is demonstrating multi-threaded rendering with IParallelRenderCommandList
.