Vulkan - Load to cube map only works for one face

#1

I’m trying to follow learnOpenGL IBL diffuse irradiance tutorial using vulkan. The tutorial basically uses a equirectangular image to render to six different cubes passing a different view matrix , looking at each cube’s face.

Since i’m getting a very weird result, I’m not sure if it’s done correctly.
is this process correct?

 	std::vector<float> vertices = {
		// back face
		-1.0f, -1.0f, -1.0f, 
		 1.0f,  1.0f, -1.0f, 
		 1.0f, -1.0f, -1.0f,     
		 1.0f,  1.0f, -1.0f, 
		-1.0f, -1.0f, -1.0f, 
		-1.0f,  1.0f, -1.0f, 
		// front face
		-1.0f, -1.0f,  1.0f, 
		 1.0f, -1.0f,  1.0f, 
		 1.0f,  1.0f,  1.0f, 
		 1.0f,  1.0f,  1.0f, 
		-1.0f,  1.0f,  1.0f, 
		-1.0f, -1.0f,  1.0f, 
		// left face
		-1.0f,  1.0f,  1.0f, 
		-1.0f,  1.0f, -1.0f, 
		-1.0f, -1.0f, -1.0f, 
		-1.0f, -1.0f, -1.0f, 
		-1.0f, -1.0f,  1.0f, 
		-1.0f,  1.0f,  1.0f, 
		// right face
		 1.0f,  1.0f,  1.0f, 
		 1.0f, -1.0f, -1.0f, 
		 1.0f,  1.0f, -1.0f,  
		 1.0f, -1.0f, -1.0f, 
		 1.0f,  1.0f,  1.0f, 
		 1.0f, -1.0f,  1.0f, 
		// bottom face
		-1.0f, -1.0f, -1.0f, 
		 1.0f, -1.0f, -1.0f, 
		 1.0f, -1.0f,  1.0f, 
		 1.0f, -1.0f,  1.0f, 
		-1.0f, -1.0f,  1.0f, 
		-1.0f, -1.0f, -1.0f, 
		// top face
		-1.0f,  1.0f, -1.0f, 
		 1.0f,  1.0f , 1.0f, 
		 1.0f,  1.0f, -1.0f, 
		 1.0f,  1.0f,  1.0f, 
		-1.0f,  1.0f, -1.0f, 
		-1.0f,  1.0f,  1.0f,   
	};
	
	//HDR image
	VkImage hdrText;
	VkDeviceMemory hdrMemory;
	VkImageView hdrImgView;
	
	createtextureImage("C:\\Users\\Icaro\\source\\repos\\VulkanRender\\model\\scene\\models\\Old_Industrial_Hall\\\Old_Industrial_Hall\\fin4_Env.hdr", &hdrText, &hdrMemory);
	hdrImgView = createImageView(hdrText, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
	//cube vertexBuffer

	VkDeviceSize bufferSize = sizeof(CubeVertex) * vertices.size();

	VkBuffer stagingBuffer;
	VkDeviceMemory stagingBufferMemory;
	createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);

	void* data;
	vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
	memcpy(data, vertices.data(), (size_t)bufferSize);
	vkUnmapMemory(device, stagingBufferMemory);

	createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, cube.vertexBuffer, cube.vertexBufferMemory);

	copyBuffer(stagingBuffer, cube.vertexBuffer, bufferSize);

	vkDestroyBuffer(device, stagingBuffer, nullptr);
	vkFreeMemory(device, stagingBufferMemory, nullptr);


	//View Matricex
	glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 1.0f);
	glm::mat4 captureViews[] =
	{
        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f,  0.0f,  0.0f), glm::vec3(0.0f, -1.0f,  0.0f)),
        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f,  0.0f,  0.0f), glm::vec3(0.0f, -1.0f,  0.0f)),
        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f,  1.0f,  0.0f), glm::vec3(0.0f,  0.0f,  1.0f)),
        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, -1.0f,  0.0f), glm::vec3(0.0f,  0.0f, -1.0f)),
        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f,  0.0f,  1.0f), glm::vec3(0.0f, -1.0f,  0.0f)),
        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f,  0.0f, -1.0f), glm::vec3(0.0f, -1.0f,  0.0f))
   };


	//Generate CubeMap target;

	const VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT;
	const int32_t width = 800;
	const int32_t height = 800;


	// Sampler
	VkSamplerCreateInfo samplerCI = initializeVulkanStructure::samplerCreateInfo();
	samplerCI.magFilter = VK_FILTER_LINEAR;
	samplerCI.minFilter = VK_FILTER_LINEAR;
	samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
	samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
	samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
	samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
	samplerCI.minLod = 0.0f;
	samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
	vkCreateSampler(device, &samplerCI, nullptr, &cube.texture.sampler);





	// FB, Att, RP, Pipe, etc.
	VkAttachmentDescription attDesc = {};
	// Color attachment
	attDesc.format = format;
	attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
	attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
	attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
	attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
	attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
	attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
	attDesc.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
	VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };

	VkSubpassDescription subpassDescription = {};
	subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
	subpassDescription.colorAttachmentCount = 1;
	subpassDescription.pColorAttachments = &colorReference;

	// Use subpass dependencies for layout transitions
	std::array<VkSubpassDependency, 2> dependencies;
	dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
	dependencies[0].dstSubpass = 0;
	dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
	dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
	dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
	dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
	dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
	dependencies[1].srcSubpass = 0;
	dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
	dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
	dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
	dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
	dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
	dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;

	// Renderpass
	VkRenderPassCreateInfo renderPassCI = initializeVulkanStructure::renderPassCreateInfo();
	renderPassCI.attachmentCount = 1;
	renderPassCI.pAttachments = &attDesc;
	renderPassCI.subpassCount = 1;
	renderPassCI.pSubpasses = &subpassDescription;
	renderPassCI.dependencyCount = 2;
	renderPassCI.pDependencies = dependencies.data();
	VkRenderPass rdpass;
	vkCreateRenderPass(device, &renderPassCI, nullptr, &rdpass);


	const int32_t dim = 800;
	const uint32_t numMips = static_cast<uint32_t>(floor(log2(dim))) + 1;

	{
		// Color attachment
		VkImageCreateInfo imageCreateInfo = initializeVulkanStructure::imageCreateInfo();
		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
		imageCreateInfo.format = format;
		imageCreateInfo.extent.width = width;
		imageCreateInfo.extent.height = height;
		imageCreateInfo.extent.depth = 1;
		imageCreateInfo.mipLevels = 1;
		imageCreateInfo.arrayLayers = 6;
		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
		imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |VK_IMAGE_USAGE_SAMPLED_BIT;
		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
		imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
		vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image);

		VkMemoryAllocateInfo memAlloc = initializeVulkanStructure::memoryAllocateInfo();
		VkMemoryRequirements memReqs;
		vkGetImageMemoryRequirements(device, offscreen.image, &memReqs);
		memAlloc.allocationSize = memReqs.size;
		memAlloc.memoryTypeIndex = findMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

		vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory);
		vkBindImageMemory(device, offscreen.image, offscreen.memory, 0);

		VkImageViewCreateInfo colorImageView = initializeVulkanStructure::imageViewCreateInfo();
		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
		colorImageView.format = format;
		colorImageView.subresourceRange = {};
		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
		colorImageView.subresourceRange.baseMipLevel = 0;
		colorImageView.subresourceRange.levelCount = 1;
		colorImageView.subresourceRange.baseArrayLayer = 0;
		colorImageView.subresourceRange.layerCount = 6;
		colorImageView.image = offscreen.image;
		vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view);

		VkFramebufferCreateInfo fbufCreateInfo = initializeVulkanStructure::framebufferCreateInfo();
		fbufCreateInfo.renderPass = rdpass;
		fbufCreateInfo.attachmentCount = 1;
		fbufCreateInfo.pAttachments = &offscreen.view;
		fbufCreateInfo.width = width;
		fbufCreateInfo.height = height;
		fbufCreateInfo.layers = 6;
		vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer);


	}



	//Descriptors 
	std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
		initializeVulkanStructure::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),

	};
	VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = initializeVulkanStructure::descriptorSetLayoutCreateInfo(setLayoutBindings);
	vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &cube.descriptorsetLayout);

	std::vector<VkDescriptorPoolSize> poolSizes = { initializeVulkanStructure::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };
	VkDescriptorPoolCreateInfo descriptorPoolCI = initializeVulkanStructure::descriptorPoolCreateInfo(poolSizes, 2);
	VkDescriptorPool descriptorpool;
	vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool);

	VkDescriptorSetAllocateInfo allocInfo =initializeVulkanStructure::descriptorSetAllocateInfo(descriptorpool, &cube.descriptorsetLayout, 1);
	(vkAllocateDescriptorSets(device, &allocInfo, &cube.descritproset));


	VkDescriptorImageInfo hdrTexture = {};
	hdrTexture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
	hdrTexture.imageView = hdrImgView;
	hdrTexture.sampler = cube.texture.sampler;
	
	//AQUIIIII

	VkWriteDescriptorSet writeDescriptorSet = initializeVulkanStructure::writeDescriptorSet(cube.descritproset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &hdrTexture);
	vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
	
	



	VkPipelineLayoutCreateInfo pipelineLayoutCI = initializeVulkanStructure::pipelineLayoutCreateInfo(&cube.descriptorsetLayout, 1);
	std::vector<VkPushConstantRange> pushConstantRanges = {
			initializeVulkanStructure::pushConstantRange( VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushBlock), 0),
	};
	
	
	pipelineLayoutCI.pushConstantRangeCount = 1;
	pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data();
		
	(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &cube.pipelineLayout));

	// Pipeline
	VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = initializeVulkanStructure::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
	VkPipelineRasterizationStateCreateInfo rasterizationState = initializeVulkanStructure::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
	VkPipelineColorBlendAttachmentState blendAttachmentState = initializeVulkanStructure::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
	VkPipelineColorBlendStateCreateInfo colorBlendState = initializeVulkanStructure::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
	VkPipelineDepthStencilStateCreateInfo depthStencilState = initializeVulkanStructure::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
	VkPipelineViewportStateCreateInfo viewportState = initializeVulkanStructure::pipelineViewportStateCreateInfo(1, 1, VK_DYNAMIC_STATE_VIEWPORT);
	VkPipelineMultisampleStateCreateInfo multisampleState = initializeVulkanStructure::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
	std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
	VkPipelineDynamicStateCreateInfo dynamicState = initializeVulkanStructure::pipelineDynamicStateCreateInfo(dynamicStateEnables);
	// Vertex input state


	std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;

	VkPipelineVertexInputStateCreateInfo vertexInputState = {};
	vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
	auto bindingDescription = CubeVertex::getBindingDescription();
	auto attributeDescriptions = CubeVertex::getAttributeDescriptions();
	vertexInputState.vertexBindingDescriptionCount = 1;
	vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
	vertexInputState.pVertexBindingDescriptions = &bindingDescription;
	vertexInputState.pVertexAttributeDescriptions = attributeDescriptions.data();



	auto vertShaderCode = readFile("C:\\Users\\Icaro\\source\\repos\\VulkanRender\\shaders\\spir-v\\postvert.spv");
	auto fragShaderCode = readFile("C:\\Users\\Icaro\\source\\repos\\VulkanRender\\shaders\\spir-v\\postfrag.spv");

	auto vert = createShaderModule(vertShaderCode);
	auto frag = createShaderModule(fragShaderCode);

	VkPipelineShaderStageCreateInfo vertShader = {};
	vertShader.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	vertShader.stage = VK_SHADER_STAGE_VERTEX_BIT;
	vertShader.module = vert;
	vertShader.pName = "main";
	VkPipelineShaderStageCreateInfo fragShader = {};

	fragShader.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	fragShader.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
	fragShader.module = frag;
	fragShader.pName = "main";

	shaderStages = { vertShader,fragShader };

	VkGraphicsPipelineCreateInfo pipelineCI = initializeVulkanStructure::pipelineCreateInfo(cube.pipelineLayout, rdpass);
	pipelineCI.pInputAssemblyState = &inputAssemblyState;
	pipelineCI.pRasterizationState = &rasterizationState;
	pipelineCI.pColorBlendState = &colorBlendState;
	pipelineCI.pMultisampleState = &multisampleState;
	pipelineCI.pViewportState = &viewportState;
	pipelineCI.pDepthStencilState = &depthStencilState;
	pipelineCI.pDynamicState = &dynamicState;
	pipelineCI.stageCount = 2;
	pipelineCI.pStages = shaderStages.data();
	pipelineCI.pVertexInputState = &vertexInputState;
	pipelineCI.renderPass = rdpass;
	vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &cube.pipeline);


	PushBlock p;
	p.projection = captureProjection;
	
	//Render 
	VkClearValue clearValues[1];
	clearValues[0].color = { { 0.3f, 1.0f, 0.2f, 0.0f } };

	VkRenderPassBeginInfo renderPassBeginInfo =  initializeVulkanStructure::renderPassBeginInfo();
	// Reuse render pass from example pass
	renderPassBeginInfo.renderPass = rdpass;
	renderPassBeginInfo.framebuffer = offscreen.framebuffer;
	renderPassBeginInfo.renderArea.extent.width = width;
	renderPassBeginInfo.renderArea.extent.height = height;
	renderPassBeginInfo.clearValueCount = 1;
	renderPassBeginInfo.pClearValues = clearValues;



	VkViewport viewport;
	viewport.height = height;
	viewport.width = width;
	viewport.minDepth = 0;
	viewport.maxDepth = 1;

	VkDescriptorSet descriptorset1;
	
	VkCommandBuffer cmd = beginSingleTimeCommands();

	vkCmdBeginRenderPass(cmd, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

	for (unsigned int i = 0; i < 6; i++) {


		VkDeviceSize	offsets[1] = { 0 };
		vkCmdSetViewport(cmd, 0, 1, &viewport);

		
		p.projection = captureProjection;
		p.view = captureViews[i];
		VkRect2D rect = {};
		rect.extent.width = width;
		rect.extent.height = height;
	
		rect.offset = { 0,0 };

		vkCmdSetScissor(cmd, 0, 1, &rect);


		descriptorset1 = cube.descritproset;

		vkCmdBindVertexBuffers(cmd, 0, 1, &cube.vertexBuffer, offsets);
		vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, cube.pipelineLayout, 0, 1, &descriptorset1, 0, NULL);
		vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, cube.pipeline);


		vkCmdPushConstants(
			cmd,
			cube.pipelineLayout,
			VK_SHADER_STAGE_VERTEX_BIT,
			0,
			sizeof(PushBlock),
			&p);

		vkCmdDraw(cmd,36, 1, 0, 0);

	}

	vkCmdEndRenderPass(cmd);

	endSingleTimeCommands(cmd);

I know there are many function specific to my project (or sascha willems repo) the overall process, is it done correctly?

When I try to render a cube using this offscreen.image texture.
I see that the texture is aplied only to one face, always the same face. When changing the view it just the change de “part” of the image to be loaded, but it always load to the same face


the other 5 faces are just setted to the clean value…

#2

I don’t see any line of code that actually selects the face you want to render to, so that’s why you always write to the same face. Unless you do that in a geometry shader (which I don’t see in that code either), it’s simply not working because there is nothing telling the gpu which face to render to.

If you want to load a cube map face-by-face from a file then simply use vkCmdCopyBufferToImage to copy image data to a specific face by setting the baseArrayLayer member of the buffer copy structure. If your file contents map 1:1 to your image layout you could even go with a single copy.

If you want to dynamically render a cube map, you can go for a multi-pass approach using vkCmdCopyImage with a separate frame buffer or go with a geometry shader approach using a layered attachment and writing to the different faces via gl_Layer in the shader.

1 Like
#3

It worked.
I feel kind stupid now.