How-to: Use Physically Based Rendering

Physically Based Rendering is a set of rendering techniques which simulate how light and surface materials interact in the natural world. The goal is to create images which are more realistic than traditional shading approaches.

HPS

This is a very simple HPS sample to create a shell using Physically Based Rendering (or PBR).

PBR uses UV source parameterization and expects explicit parameterization of the shells using such a material. More details are available about PBR and related properties under the Materials documentation.

Instructions

  1. Add the following code to a CHPSView User Code method in the MFC Sandbox application:
void CHPSView::OnUserCode3()
{
	PortfolioKey portfolio = Database::CreatePortfolio();
	SegmentKey my_seg = GetCanvas().GetFrontView().GetAttachedModel().GetSegmentKey();
	my_seg.GetPortfolioControl().Push(portfolio);
	{
		auto load_png = [&](const char * file_name, const char * image_name) -> bool {
			HPS::Image::ImportOptionsKit iok;
			iok.SetFormat(HPS::Image::Format::Png);
			ImageKit image_kit = Image::File::Import(file_name, iok);
			ImageDefinition image_def = portfolio.DefineImage(image_name, image_kit);
			TextureDefinition txr_def = portfolio.DefineTexture(image_name, image_def);
			return true;
		};

		load_png("ship.png", "ship");

		load_png("IMAGE_PBR_PATH/chipped-paint-metal/chipped-paint-metal-albedo.png", "chipped-paint-metal-albedo");
		load_png("IMAGE_PBR_PATH/chipped-paint-metal/chipped-paint-ao.png", "chipped-paint-metal-ao");
		load_png("IMAGE_PBR_PATH/chipped-paint-metal/chipped-paint-metal-metal.png", "chipped-paint-metal-metalness");
		load_png("IMAGE_PBR_PATH/chipped-paint-metal/chipped-paint-metal-rough2.png", "chipped-paint-metal-roughness");
		load_png("IMAGE_PBR_PATH/chipped-paint-metal/chipped-paint-metal-normal-dx.png", "chipped-paint-metal-normal");

		GlyphKit glyphKit = GlyphKit();
		glyphKit.SetRadius(0);
		glyphKit.SetOffset(GlyphPoint(0, 0));

		ImageGlyphElement imageGlyphElement;
		imageGlyphElement.SetSource("ship");

		GlyphElementArray glyphElements;
		glyphElements.push_back(imageGlyphElement);
		glyphKit.SetElements(glyphElements);

		portfolio.DefineGlyph("shipGlyph", glyphKit);
	}
	SegmentKey shellSegment = my_seg.Subsegment();
	shellSegment.GetVisibilityControl().SetEverything(false);

	// Insert a small test shell with parameterization
	ShellKey shellKey;
	{
		float const quad_points[] =
		{
			-0.75f, 0.75f, 0.0f,
			0.75f, 0.75f, 0.0f,
			0.75f, -0.75f, 0.0f,
			-0.75f, -0.75f, 0.0f
		};

		int const quad_flist[] =
		{
			4, 0, 1, 2, 3
		};

		float const quad_uvs[] =
		{
			0.0f, 1.0f,
			1.0f, 1.0f,
			1.0f, 0.0f,
			0.0f, 0.0f,
		};

		shellKey = shellSegment.InsertShell(4, (Point const *)&quad_points[0], 5, quad_flist);
		shellKey.SetVertexParametersByRange(0, 8, quad_uvs, 2);
	}

	my_seg.GetVisibilityControl().SetEverything(false).SetFaces(true).SetLights(true);
	my_seg.ReferenceGeometry(shellKey);

	PBRMaterialKit pbr;
	pbr.SetBaseColorMap("chipped-paint-metal-albedo");
	pbr.SetNormalMap("chipped-paint-metal-normal");
	pbr.SetMetalnessMap("chipped-paint-metal-metalness", HPS::Material::Texture::ChannelMapping::Red);
	pbr.SetRoughnessMap("chipped-paint-metal-roughness", HPS::Material::Texture::ChannelMapping::Red);
	pbr.SetOcclusionMap("chipped-paint-metal-ao", HPS::Material::Texture::ChannelMapping::Red);

	my_seg.SetPBRMaterial(pbr);

	my_seg.InsertDistantLight(Vector(0.4174f, 0.2564f, -0.8951f));
}
  1. Download the Chipped Paint Metal PBR Material images archive from FreePBR.com called chipped-paint-metal-ue and install it on your system.
  2. Replace “IMAGE_PBR_PATH” in the code with the actual path to the unzipped Chipped Paint Metal files.

This should result in a scene similar to:

3DF

This is a very simple 3DF sample function to create a shell using Physically Based Rendering (or PBR).

PBR uses UV source parameterization and expects explicit parameterization of the shells using such a material. More details are available about PBR and related properties under the Materials documentation.

Instructions

  1. Add the following test function to a HOOPS 3DF application:
int TestPBR ()
{
	if (!IsShaderDriver()) // make sure driver is ogl or dx11
		return TEST_UNSUPPORTED;

    HC_Open_Segment ("a");
		HC_Open_Segment("image"); {
			HC_Set_Visibility("images=off");
			auto load_png = [](const char * file_name, const char * image_name) -> bool {
				FILE *file = fopen(file_name), "rb");
				if (!file)
					return false;
				fseek(file, 0, SEEK_END);
				int length = ftell(file);
				fseek(file, 0, SEEK_SET);
				unsigned char * buffer = new unsigned char[length];
				fread(buffer, sizeof(unsigned char), length, file);
				fclose(file);
				HC_Insert_Compressed_Image(0, 0, 0, H_FORMAT_TEXT("png, name = %s", image_name), -1, -1, length, buffer);
				delete[] buffer;
				return true;
			};

			if (!load_png("IMAGE_PBR_PATH/sandstonecliff-albedo.png", "sandstonecliff-albedo"))
				return TEST_IMAGE_MISSING;
			if (!load_png("IMAGE_PBR_PATH/sandstonecliff-ao.png", "sandstonecliff-ao"))
				return TEST_IMAGE_MISSING;
			if (!load_png("IMAGE_PBR_PATH/sandstonecliff-metalness.png", "sandstonecliff-metalness"))
				return TEST_IMAGE_MISSING;
			if (!load_png("IMAGE_PBR_PATH/sandstonecliff-roughness.png", "sandstonecliff-roughness"))
				return TEST_IMAGE_MISSING;
			if (!load_png("IMAGE_PBR_PATH/sandstonecliff-normal-ue.png", "sandstonecliff-normal"))
				return TEST_IMAGE_MISSING;
		} HC_Close_Segment();

		HC_Set_PBR_Material("sandstonecliff-albedo", "sandstonecliff-normal", nullptr,
							"sandstonecliff-metalness", 0,
							"sandstonecliff-roughness", 0,
							"sandstonecliff-ao", 0,
							nullptr,
							1.0, 1.0, 1.0, 1.0, 1.0,
							nullptr);
		HC_Insert_Distant_Light(1, 1, -1);

		HC_Open_Segment("shell"); {
			float const quad_points[] = {
				-0.75f, 0.75f, 0.0f,
				0.75f, 0.75f, 0.0f,
				0.75f, -0.75f, 0.0f,
				-0.75f, -0.75f, 0.0f
			};
			int const quad_flist[] = {4, 0, 1, 2, 3};
			float const quad_uvs[] = {
				0.0f, 1.0f,
				1.0f, 1.0f,
				1.0f, 0.0f,
				0.0f, 0.0f,
			};

			HC_Set_Visibility("no edges, no markers");
			HC_KEY	shell_key = HC_Insert_Shell(4, quad_points, 5, quad_flist);
			HC_MSet_Vertex_Parameters(shell_key, 0, 4, 2, quad_uvs);
		} HC_Close_Segment();
	
    HC_Close_Segment();

    return TEST_COMPLETE;
}
  1. Download the Sand Stone Cliff images archive from FreePBR.com called sandstonecliff-ue and install it on your system. Also included is “ship.png” (right-click and save as):
    ship

  2. Replace “IMAGE_PBR_PATH” in the code with the actual path to the unzipped Sand Stone Cliff files.

Do not worry about IsShaderDriver() or the specific return values.

This should result in a scene similar to:

PBR Materials Without Textures

It’s also possible to specify PBR materials without using textures or maps. Consider the following sample code;

HPS

void CHPSView::OnUserCode4()
{
	float sphereRadius = 0.8;
	float sphereOffset = 2.0;
	float factorOffset = 0.2;
	int factorCount = 7;
	float normalFactor = 0.0;
	float metalFactor = 0.0;
	float roughFactor = 0.0;
	float occlusionFactor = 0.0;
	float alphaFactor = 1.0;
	HPS::Point position(0,0,0);
	char segmentName[64];
	RGBAColor cyan(0, 1, 1, 1);

	SegmentKey testKey = GetCanvas().GetFrontView().GetAttachedModel().GetSegmentKey().Subsegment("test");
	testKey.InsertDistantLight(Vector(-0.4174f, -0.2564f, 0.8951f));

	sprintf(segmentName, "sphere baseline");
	SegmentKey baselineSphereKey = testKey.Subsegment(segmentName);
	baselineSphereKey.InsertSphere(position, sphereRadius);
	baselineSphereKey.GetMaterialMappingControl().SetFaceColor(HPS::RGBAColor(0, 1, 1));

	SegmentKey spheresKey = testKey.Subsegment("spheres");

	for (int i = 0; i < factorCount; ++i)
	{
		for (int j = 0; j < factorCount; ++j)
		{
			sprintf(segmentName, "sphere %d %d", i, j);
			position.x += sphereOffset;
			metalFactor = i * factorOffset;
			roughFactor = j * factorOffset;
			SegmentKey aSphereKey = spheresKey.Subsegment(segmentName);
			aSphereKey.InsertSphere(position, sphereRadius);
			HPS::PBRMaterialKit pbrMaterialKit;
			pbrMaterialKit.
				SetBaseColorFactor(cyan).
				SetNormalFactor(normalFactor).
				SetMetalnessFactor(metalFactor).
				SetRoughnessFactor(roughFactor).
				SetOcclusionFactor(occlusionFactor).
				SetAlphaFactor(alphaFactor);
			aSphereKey.SetPBRMaterial(pbrMaterialKit);
		}
		position.x = 0;
		position.y += sphereOffset;
	}
}

3DF

void TestSimplePBR()
{
	typedef struct { float r, g, b, a; } RGBA;
	float sphereRadius = 0.8;
	float sphereOffset = 2.0;
	float factorOffset = 0.2;
	int factorCount = 7;
	float normalFactor = 0.0;
	float metalFactor = 0.0;
	float roughFactor = 0.0;
	float occlusionFactor = 0.0;
	float alphaFactor = 1.0;
	HPoint position;
	char segmentName[64];
	RGBA cyan;
	cyan.r = 0;
	cyan.g = 1;
	cyan.b = 1;
	cyan.a = 1;

	HC_Open_Segment("test"); {

		sprintf(segmentName, "sphere baseline");
		position.Set(0, 0, 0);
		HC_Open_Segment(segmentName); {
			HC_Insert_Sphere(&position, sphereRadius, nullptr, nullptr);
			HC_Set_Color("faces = (r=0.0 g=1 b=1)");
		} HC_Close_Segment();

		HC_Open_Segment("spheres"); {
			for (int i = 0; i < factorCount; ++i)
			{
				for (int j = 0; j < factorCount; ++j)
				{
					sprintf(segmentName, "sphere %d %d", i, j);
					position.x += sphereOffset;
					metalFactor = i * factorOffset;
					roughFactor = j * factorOffset;
					HC_Open_Segment(segmentName); {
						HC_Insert_Sphere(&position, sphereRadius, nullptr, nullptr);
						HC_Set_PBR_Material(nullptr, nullptr, nullptr, nullptr, 0, nullptr, 0, nullptr, 0, &cyan, normalFactor, metalFactor, roughFactor, occlusionFactor, alphaFactor, nullptr);
					} HC_Close_Segment();
				}
				position.x = 0;
				position.y += sphereOffset;
			}
		} HC_Close_Segment();
	} HC_Close_Segment();
}

Rendering Result

This code produces a scene as follows where the rough factor changes from 0.0 to 1.0 moving from left to right and the metal factor change from 0.0 to 1.0 moving from the bottom to the top;

The bottom left sphere is a baseline using regular color for comparison.

Hello Gabriel,
In the PBR sample for HPS using textures, may you please throw light on the following;

  1. For quad_uvs, it looks that it is the responsibility of the end user to normalize the uv parameters before passing them to this array.
  2. Also, in this examples why the vertex normal(s) (normal(s) at the vertices) are not used?
  3. What is recommended, while using PBR if the vertex normal(s) data is not implicit? Is there a provision to provide it explicitly?
  4. For 3D Models, wouldn’t the vertex normal(s) data yield better results in terms of rendering quality?
  5. Also the alpha factor in the “TestSimplePBR()” (3DF), is found to have no impact on the rendering. OR Would it be possible to share a sample that demonstrates it’s impact?
  6. Overall, what are the recommendations for using PBR with textures and also what is the roadmap for supporting following set of properties {Anisotropy, Clearcoat, Dispersion, Emissive Strength, Index of Refraction, Iridescence, Sheen, Specular, Transmission, Volume}. Especially Transmission.
  7. Less important, but in the HPS Sample with texture at the top “ship.png” is not consumed, and can not be located there in the package too.
    Thank you in advance,
    With Best Regards,
    Amol Khoje

Hello @amol.khoje,

Please find below our replies to your questions:

  1. When applying UV coordinates, the understanding is that such coordinates are rangebound from 0 to 1 unless there is a particular effect you are trying to achieve such as tiling.
  2. Indeed, the example presented in the article is rather rudimentary. As such, vertex normals were not explicitly set.
  3. Well, beyond using PBR, Visualize will approximate (average) the direction of the normals based on surrounding normals. That said, for greater control on rendering results, our recommendation is to set vertex normals.
  4. We would agree with this. Admittedly, there is a preference to use primitive geometries in our examples to keep the code lightweight and easier to follow — with the emphasis on using the API itself.
  5. When changing the value to float alphaFactor = 0.1, this is my result:
  6. On the technical side, supplying vertex normals will provide more flexibility on the direction of the normals relative to the light source. Overall, this will likely yield the best results for complex models.
    Additionally, given the multitude of parameters availabe for PBR and textures, our recommendation is to try different combinations.
    The properties you listed are not on the horizon. To that, we invite you create a feature request in our Support Portal.
  7. The original post has been edited to include “ship.png”.

Thanks,
Tino