Materials

Technically materials are defined in a separate module from the VkCV but because the scene module will take advantage of that, you won't need to look into huge details. The only thing important to you is that a material provides a descriptor set and a descriptor set layout.

You can think of a descriptor set as a bundle of different resources (like images, buffers or samplers) on your GPU but in a more abstract way. Each descriptor set does not contain the actual data but when you use it, your GPU knows exactly where to look for the data of its resources.

Each draw call with your graphics pipeline can use different descriptor sets as long as they match the same descriptor set layout. All the layouts of descriptor sets, you want to use sets with in your shaders, need to be passed as arguments during graphics pipeline configuration.

Fortunately the scene module loads all materials with the same descriptor set layout. So that means you can take the layout from any of the materials loaded (for example the first one) and pass it to your graphics pipeline.

const auto& material0 = scene.getMaterial(0); // first material

vkcv::GraphicsPipelineHandle gfxPipeline = core.createGraphicsPipeline(
  vkcv::GraphicsPipelineConfig(
    shaderProgram, // shader program
    renderPass,    // render pass
    vertexLayout,  // vertex layout
    
    {
      // descriptor set layout of the first material
      material0.getDescriptorSetLayout()
    }
  )
);

So the code here does pretty much as described before. It tells the GPU that draw calls use materials as defined by the first material for rendering. In a shader it looks like this:

shaders/shader.frag

#version 450

layout(location = 0) in vec3 passNormal; // interpolated normal
layout(location = 1) in vec2 passUV;     // interpolated uv-coordinate

layout(location = 0) out vec3 outColor;   // final color of shaded fragment

layout(set=0, binding=0) uniform texture2D diffuseTexture; // sampled image
layout(set=0, binding=1) uniform sampler   diffuseSampler; // sampler

void main() {
  // writing the sampled diffuse texture as color of the geometry
  // (in other words: applying the diffuse texture)
  outColor = texture(sampler2D(diffuseTexture, diffuseSampler), passUV).rgb;
}

Similar to the locations with vertex attributes being passed between shaders, you have to specify the descriptor set via its index (the index of the specific descriptor set layout during pipeline creation). Then each resource, like the image of the diffuse texture being used as "texture2D" here, need to specify its binding index as well.

Usually you would manually configure those bindings manually in Vulkan to create a descriptor set layout. But since the material module does this automatically for you, here is the overview of bindings from the PBRMaterial in the framework:

  • Binding 0: Sampled image of the diffuse texture
  • Binding 1: Sampler for the diffuse texture
  • Binding 2: Sampled image of the normal map
  • Binding 3: Sampler for the normal map
  • Binding 4: Sampled image of the metallic-roughness map
  • Binding 5: Sampler for the metallic-roughness map
  • Binding 6: Sampled image of the occlusion map
  • Binding 7: Sampler for the occlusion map
  • Binding 8: Sampled image of the emision map
  • Binding 9: Sampler for the emision map

You don't need to use all bound resources of a descriptor set in a shader though. Also the material module creates default values for each resource not found in the loaded GLTF file from the scene. So you can use as much of them as you need to write your shaders and look for proper assets later to fully utilize them.

Previous

Next

Popular posts from this blog

Introduction

Application development

First setup