PBR Skybox Texturing
Repository source: PBR_Skybox_Texturing
Description¶
Demonstrates physically based rendering (PBR) using image based lighting, texturing and a skybox.
Physically based rendering sets metallicity, roughness, occlusion strength, the emissive factor and normal scaling of the object. Textures are used to set base color, ORM, emissivity and normals. Textures for the image based lighting and the skymap are supplied from a cubemap.
Image based lighting uses a cubemap texture to specify the environment. A Skybox is used to create the illusion of distant three-dimensional surroundings. Textures for the image based lighting and the skybox are supplied from an HDR or JPEG equirectangular Environment map or cubemap consisting of six image files.
A good source for Skybox HDRs and Textures is Poly Haven. Start with the 4K HDR versions of Skyboxes.
The parameters used to generate the example image are loaded from a JSON file with the same name as the example. In this case:
<DATA>/PBR_Skybox_Texturing.json
Where <DATA>
is the path to vtk-examples/src/Testing/Data
.
By default we use the equirectangular file to generate the texture for the lighting and skybox. We have optionally provided six individual cubemap files to generate lighting and a skybox.
For information about the parameters in the JSON file, please see PBR_JSON_format.
Options¶
Positionals:
fileName The path to the JSON file containing the parameters.
Options:
-h,--help Print this help message and exit
-s,--surface The name of the surface. Overrides the surface entry in the json file.
-c,--use_cubemap Build the cubemap from the six cubemap files. Overrides the equirectangular entry in the json file.
-t, --use_tonemapping Use tone mapping.
Additionally, you can save a screenshot by pressing "k".
Further Reading¶
- Introducing Physically Based Rendering with VTK
- PBR Journey Part 1: High Dynamic Range Image Based Lighting with VTK
- PBR Journey Part 2 : Anisotropy model with VTK
- PBR Journey Part 3 : Clear Coat Model with VTK
- Object Shading Properties
Note
<DATA>/PBR_Skybox_Texturing.json
assumes that the skyboxes and textures are in the subfoldersSkyboxes
andTextures
relative to this file. This allows you to copy this JSON file and the associated subfolders to any other location on your computer.- You can turn off the skybox in the JSON file by setting
"skybox":false
. Image based lighting will still be active.
Note
- The C++ example requires C++17 as
std::filesystem
is used. If your compiler does not support C++17 comment out the filesystem stuff.
Other languages
See (Python), (PythonicAPI)
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
PBR_Skybox_Texturing.cxx
#include <vtkActor.h>
#include <vtkAxesActor.h>
#include <vtkCallbackCommand.h>
#include <vtkCameraPass.h>
#include <vtkCleanPolyData.h>
#include <vtkClipPolyData.h>
#include <vtkCubeSource.h>
#include <vtkFloatArray.h>
#include <vtkHDRReader.h>
#include <vtkImageFlip.h>
#include <vtkImageReader2Factory.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkLightsPass.h>
#include <vtkLinearSubdivisionFilter.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkOpaquePass.h>
#include <vtkOpenGLRenderer.h>
#include <vtkOrientationMarkerWidget.h>
#include <vtkOverlayPass.h>
#include <vtkPBRIrradianceTexture.h>
#include <vtkParametricBoy.h>
#include <vtkParametricFunctionSource.h>
#include <vtkParametricMobius.h>
#include <vtkParametricRandomHills.h>
#include <vtkParametricTorus.h>
#include <vtkPlane.h>
#include <vtkPointData.h>
#include <vtkPolyDataMapper.h>
#include <vtkPolyDataNormals.h>
#include <vtkPolyDataTangents.h>
#include <vtkProperty.h>
#include <vtkProperty2D.h>
#include <vtkRenderPassCollection.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkSequencePass.h>
#include <vtkSkybox.h>
#include <vtkSliderRepresentation2D.h>
#include <vtkSliderWidget.h>
#include <vtkSmartPointer.h>
#include <vtkTextProperty.h>
#include <vtkTexture.h>
#include <vtkTexturedSphereSource.h>
#include <vtkToneMappingPass.h>
#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkTriangleFilter.h>
#include <vtkVersion.h>
#include <vtk_cli11.h>
#include <vtk_jsoncpp.h>
// For writing out the image.
#include <vtkImageWriter.h>
#include <vtkJPEGWriter.h>
#include <vtkPNGWriter.h>
#include <vtkWindowToImageFilter.h>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <sstream>
#include <string>
#if VTK_VERSION_NUMBER >= 90020210809ULL
#define VTK_HAS_COW 1
#endif
#if VTK_HAS_COW
#include <vtkCameraOrientationWidget.h>
#endif
namespace fs = std::filesystem;
namespace {
/**
* Check the VTK version.
*
* @param major: Major version.
* @param major: Minor version.
* @param major: Build version.
*
* @return True if the requested VTK version is greater or equal to the actual
* VTK version.
*/
bool VTKVersionOk(unsigned long long const& major,
unsigned long long const& minor,
unsigned long long const& build);
struct Parameters
{
std::map<std::string, std::string> parameters;
std::vector<std::string> cubemap;
bool skybox{true};
bool parsedOk{false};
};
/**
* Read the parameters from a json file and check that the file paths exist.
*
* @param fnPath: The path to the json file.
* @param parameters: The parameters.
*/
void GetParameters(const fs::path fnPath, Parameters& parameters);
std::string DisplayParameters(Parameters& parameters);
/**
* Read six images forming a cubemap.
*
* @param fileNames: The paths to the six cubemap files.
*
* @return The cubemap texture.
*/
vtkNew<vtkTexture> ReadCubemap(std::vector<std::string> const& fileNames);
/**
* Read an equirectangular environment file and convert it to a texture.
*
* @param fileName: The equirectangular file path.
*
* @return The texture.
*/
vtkNew<vtkTexture> ReadEquirectangularFile(std::string const& fileName);
/**
* Read an image and convert it to a texture.
*
* @param path: The image path.
*
* @return The texture.
*/
vtkNew<vtkTexture> ReadTexture(std::string const& path);
/**
* Check that the needed textures exist.
*
* @param parameters: The parameters.
* @param wantedTextures: The wanted textures.
*
* @return true if all the wanted textures are present.
*/
bool CheckForMissingTextures(Parameters& parameters,
std::vector<std::string> const& wantedTextures);
// Some sample surfaces to try.
vtkSmartPointer<vtkPolyData> GetBoy();
vtkSmartPointer<vtkPolyData> GetMobius();
vtkSmartPointer<vtkPolyData> GetRandomHills();
vtkSmartPointer<vtkPolyData> GetTorus();
vtkSmartPointer<vtkPolyData> GetSphere();
vtkSmartPointer<vtkPolyData> GetClippedSphere();
vtkSmartPointer<vtkPolyData> GetCube();
vtkSmartPointer<vtkPolyData> GetClippedCube();
/**
* Generate u, v texture coordinates on a parametric surface.
*
* @param uResolution: u resolution
* @param vResolution: v resolution
* @param pd: The polydata representing the surface.
*
* @return The polydata with the texture coordinates added.
*/
vtkNew<vtkPolyData> UVTcoords(const float& uResolution,
const float& vResolution, vtkNew<vtkPolyData> pd);
class SliderCallbackExposure : public vtkCommand
{
public:
static SliderCallbackExposure* New()
{
return new SliderCallbackExposure;
}
virtual void Execute(vtkObject* caller, unsigned long, void*)
{
vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);
double value = static_cast<vtkSliderRepresentation2D*>(
sliderWidget->GetRepresentation())
->GetValue();
this->property->SetExposure(value);
}
SliderCallbackExposure() : property(nullptr)
{
}
vtkToneMappingPass* property;
};
class SliderCallbackMetallic : public vtkCommand
{
public:
static SliderCallbackMetallic* New()
{
return new SliderCallbackMetallic;
}
virtual void Execute(vtkObject* caller, unsigned long, void*)
{
vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);
double value = static_cast<vtkSliderRepresentation2D*>(
sliderWidget->GetRepresentation())
->GetValue();
this->property->SetMetallic(value);
}
SliderCallbackMetallic() : property(nullptr)
{
}
vtkProperty* property;
};
class SliderCallbackRoughness : public vtkCommand
{
public:
static SliderCallbackRoughness* New()
{
return new SliderCallbackRoughness;
}
virtual void Execute(vtkObject* caller, unsigned long, void*)
{
vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);
double value = static_cast<vtkSliderRepresentation2D*>(
sliderWidget->GetRepresentation())
->GetValue();
this->property->SetRoughness(value);
}
SliderCallbackRoughness() : property(nullptr)
{
}
vtkProperty* property;
};
class SliderCallbackOcclusionStrength : public vtkCommand
{
public:
static SliderCallbackOcclusionStrength* New()
{
return new SliderCallbackOcclusionStrength;
}
virtual void Execute(vtkObject* caller, unsigned long, void*)
{
vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);
double value = static_cast<vtkSliderRepresentation2D*>(
sliderWidget->GetRepresentation())
->GetValue();
this->property->SetOcclusionStrength(value);
}
SliderCallbackOcclusionStrength() : property(nullptr)
{
}
vtkProperty* property;
};
class SliderCallbackNormalScale : public vtkCommand
{
public:
static SliderCallbackNormalScale* New()
{
return new SliderCallbackNormalScale;
}
virtual void Execute(vtkObject* caller, unsigned long, void*)
{
vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);
double value = static_cast<vtkSliderRepresentation2D*>(
sliderWidget->GetRepresentation())
->GetValue();
this->property->SetNormalScale(value);
}
SliderCallbackNormalScale() : property(nullptr)
{
}
vtkProperty* property;
};
struct SliderProperties
{
// Set up the sliders
double tubeWidth{0.008};
double sliderLength{0.075};
double sliderWidth{0.025};
double endCapLength = 0.025;
double endCapWidth = 0.025;
double titleHeight{0.025};
double labelHeight{0.020};
double minimumValue{0.0};
double maximumValue{1.0};
double initialValue{0.0};
std::array<double, 2> p1{0.02, 0.1};
std::array<double, 2> p2{0.18, 0.1};
std::string title{""};
std::string titleColor{"Black"};
std::string labelColor{"Black"};
std::string valueColor{"DarkSlateGray"};
std::string sliderColor{"BurlyWood"};
std::string selectedColor{"Lime"};
std::string barColor{"Black"};
std::string barEndsColor{"Indigo"};
};
vtkNew<vtkSliderWidget> MakeSliderWidget(SliderProperties const& properties);
class PrintCallback : public vtkCallbackCommand
{
public:
PrintCallback() : fn_{""}, imageQuality_(1), rgba_(true)
{
}
static PrintCallback* New()
{
return new PrintCallback;
}
/*
* Create a vtkCallbackCommand and reimplement it.
*
*/
void Execute(vtkObject* caller, unsigned long /*evId*/, void*) override
{
if (this->fn_.empty())
{
std::cerr << "A file name is required." << std::endl;
return;
}
// Note the use of reinterpret_cast to cast the caller to the expected type.
auto rwi = reinterpret_cast<vtkRenderWindowInteractor*>(caller);
// Get the keypress
std::string key = rwi->GetKeySym();
if (key == "k")
{
vtkNew<vtkWindowToImageFilter> w2If;
w2If->SetInput(rwi->GetRenderWindow());
w2If->SetScale(this->imageQuality_, this->imageQuality_);
if (rgba_)
{
w2If->SetInputBufferTypeToRGBA();
}
else
{
w2If->SetInputBufferTypeToRGB();
}
// Read from the front buffer.
w2If->ReadFrontBufferOn();
w2If->Update();
std::vector<std::string> jpeg{".jpeg", ".jpg"};
auto writer = vtkSmartPointer<vtkImageWriter>::New();
if (ext_ == ".png")
{
writer = vtkSmartPointer<vtkPNGWriter>::New();
}
if (std::find(jpeg.begin(), jpeg.end(), ext_) != jpeg.end())
{
writer = vtkSmartPointer<vtkJPEGWriter>::New();
}
writer->SetFileName(this->fn_.c_str());
writer->SetInputConnection(w2If->GetOutputPort());
writer->Write();
std::cout << "Screenshot saved to: " << writer->GetFileName()
<< std::endl;
}
}
/**
* Set the parameters for writing the
* render window view to an image file.
*
* @param fileName The png image file name.
* @param imageQuality The image quality.
* @param rgba The buffer type, (if true, there is no background in the
* screenshot).
*
*/
void SetParameters(const std::string& fileName, int imageQuality = 1,
bool rgba = true)
{
if (fileName.empty())
{
std::cerr << "A file name is required." << std::endl;
return;
}
auto pth = fs::absolute(fileName);
auto ext = pth.extension().generic_string();
if (!pth.has_extension())
ext = ".png";
std::transform(ext.begin(), ext.end(), ext.begin(),
[](char c) { return std::tolower(c); });
std::vector<std::string> validSuffixes{".jpeg", ".jpg", ".png"};
if (std::find(validSuffixes.begin(), validSuffixes.end(), ext) ==
validSuffixes.end())
{
ext = ".png";
}
this->ext_ = ext;
this->fn_ = pth.replace_extension(this->ext_).generic_string();
this->imageQuality_ = imageQuality;
this->rgba_ = rgba;
}
private:
PrintCallback(const PrintCallback&) = delete;
void operator=(const PrintCallback&) = delete;
std::string fn_;
std::string ext_;
int imageQuality_;
bool rgba_;
};
} // namespace
int main(int argc, char* argv[])
{
if (!VTKVersionOk(9, 0, 0))
{
std::cerr << "You need VTK version 9.0 or greater to run this program."
<< std::endl;
return EXIT_FAILURE;
}
vtkNew<vtkNamedColors> colors;
// Set the background color.
std::array<unsigned char, 4> bkg{{26, 51, 102, 255}};
colors->SetColor("BkgColor", bkg.data());
// VTK blue
std::array<unsigned char, 4> col1{{6, 79, 141, 255}};
colors->SetColor("VTKBlue", col1.data());
// Let's make a complementary colour to VTKBlue.
std::transform(col1.begin(), std::prev(col1.end()), col1.begin(),
[](unsigned char c) { return 255 - c; });
colors->SetColor("VTKBlueComp", col1.data());
CLI::App app{"Demonstrates physically based rendering, a skybox, "
"image based lighting and texturing."};
// Define options
std::string fileName;
app.add_option("fileName", fileName,
"The path to the JSON file containing the parameters.");
std::string surfaceName;
app.add_option("-s, --surface", surfaceName,
"The name of the surface. Overrides the surface entry in the "
"json file.");
auto useCubemap{false};
app.add_flag("-c, --use_cubemap", useCubemap,
"Build the cubemap from the six cubemap files. Overrides the "
"equirectangular entry in the json file.");
auto useTonemapping{false};
app.add_flag("-t, --use_tonemapping", useTonemapping, "Use tone mapping.");
CLI11_PARSE(app, argc, argv);
auto fnPath = fs::path(fileName);
if (!fnPath.has_extension())
{
fnPath.replace_extension(".json");
}
if (!fs::is_regular_file(fnPath))
{
std::cerr << "Unable to find: " << fnPath << std::endl;
return EXIT_FAILURE;
}
Parameters parameters;
GetParameters(fnPath, parameters);
if (!parameters.parsedOk)
{
return EXIT_FAILURE;
}
if (!surfaceName.empty())
{
parameters.parameters["object"] = surfaceName;
}
// Check for missing parameters and assign defaults.
parameters.parameters.try_emplace("bkgcolor", "BkgColor");
parameters.parameters.try_emplace("objcolor", "White");
auto res = DisplayParameters(parameters);
std::cout << res << std::endl;
std::vector<std::string> wantedTextures{"albedo", "normal", "material",
"emissive"};
if (!CheckForMissingTextures(parameters, wantedTextures))
{
return EXIT_FAILURE;
}
// Build the pipeline.
// ren1 is for the slider rendering,
// ren2 is for the object rendering.
vtkNew<vtkRenderer> ren1;
vtkNew<vtkOpenGLRenderer> ren2;
ren1->SetBackground(colors->GetColor3d("Snow").GetData());
ren2->SetBackground(
colors->GetColor3d(parameters.parameters["bkgcolor"]).GetData());
vtkNew<vtkRenderWindow> renderWindow;
// The order here is important.
// This ensures that the sliders will be in ren1.
renderWindow->AddRenderer(ren2);
renderWindow->AddRenderer(ren1);
ren1->SetViewport(0.0, 0.0, 0.2, 1.0);
ren2->SetViewport(0.2, 0.0, 1, 1);
vtkNew<vtkRenderWindowInteractor> interactor;
interactor->SetRenderWindow(renderWindow);
vtkNew<vtkInteractorStyleTrackballCamera> style;
interactor->SetInteractorStyle(style);
// Set up tone mapping so we can vary the exposure.
// Custom Passes.
vtkNew<vtkCameraPass> cameraP;
vtkNew<vtkSequencePass> seq;
vtkNew<vtkOpaquePass> opaque;
vtkNew<vtkLightsPass> lights;
vtkNew<vtkOverlayPass> overlay;
vtkNew<vtkRenderPassCollection> passes;
passes->AddItem(lights);
passes->AddItem(opaque);
passes->AddItem(overlay);
seq->SetPasses(passes);
cameraP->SetDelegatePass(seq);
vtkNew<vtkToneMappingPass> toneMappingP;
toneMappingP->SetToneMappingType(vtkToneMappingPass::GenericFilmic);
toneMappingP->SetGenericFilmicUncharted2Presets();
toneMappingP->SetExposure(1.0);
toneMappingP->SetDelegatePass(cameraP);
if (useTonemapping)
{
vtkOpenGLRenderer::SafeDownCast(ren2)->SetPass(toneMappingP);
}
vtkSmartPointer<vtkPBRIrradianceTexture> irradiance =
ren2->GetEnvMapIrradiance();
irradiance->SetIrradianceStep(0.3);
vtkNew<vtkSkybox> skybox;
// Choose how to generate the skybox.
vtkNew<vtkTexture> cubeMap;
vtkNew<vtkTexture> envTexture;
auto isHDR = false;
auto hasSkybox = false;
auto gammaCorrect = false;
if (useCubemap && !parameters.cubemap.empty())
{
std::cout << "Using the cubemap files to generate the environment texture."
<< std::endl;
envTexture = ReadCubemap(parameters.cubemap);
if (parameters.skybox)
{
skybox->SetTexture(envTexture);
hasSkybox = true;
}
}
else if (parameters.parameters.find("equirectangular") !=
parameters.parameters.end() &&
!parameters.parameters["equirectangular"].empty())
{
std::cout
<< "Using the equirectangular file to generate the environment texture."
<< std::endl;
envTexture =
ReadEquirectangularFile(parameters.parameters["equirectangular"]);
std::string extension = fs::path(parameters.parameters["equirectangular"])
.extension()
.generic_string();
std::transform(extension.begin(), extension.end(), extension.begin(),
[](char c) { return std::tolower(c); });
std::string extensions{".hdr .pic"};
if (std::string(extensions).find(extension, 0) != std::string::npos)
{
isHDR = true;
gammaCorrect = true;
}
if (parameters.skybox)
{
skybox->SetTexture(envTexture);
skybox->SetFloorRight(0, 0, 1);
skybox->SetProjection(vtkSkybox::Sphere);
skybox->SetTexture(envTexture);
hasSkybox = true;
}
}
else
{
std::cerr << "An environment texture is required,\n"
<< "please add the necessary equirectangular"
<< " or cubemap file paths to the json file." << std::endl;
return EXIT_FAILURE;
}
// Turn off the default lighting and use image based lighting.
ren2->AutomaticLightCreationOff();
ren2->UseImageBasedLightingOn();
if (isHDR)
{
ren2->UseSphericalHarmonicsOn();
ren2->SetEnvironmentTexture(envTexture, false);
}
else
{
ren2->UseSphericalHarmonicsOff();
ren2->SetEnvironmentTexture(envTexture, true);
}
// Get the textures
auto baseColor = ReadTexture(parameters.parameters["albedo"]);
baseColor->UseSRGBColorSpaceOn();
auto normal = ReadTexture(parameters.parameters["normal"]);
auto material = ReadTexture(parameters.parameters["material"]);
auto emissive = ReadTexture(parameters.parameters["emissive"]);
emissive->UseSRGBColorSpaceOn();
// Get the surface.
std::string desiredSurface = parameters.parameters["object"];
std::transform(desiredSurface.begin(), desiredSurface.end(),
desiredSurface.begin(),
[](char c) { return std::tolower(c); });
std::map<std::string, int> availableSurfaces = {
{"boy", 0}, {"mobius", 1}, {"randomhills", 2},
{"torus", 3}, {"sphere", 4}, {"clippedsphere", 5},
{"cube", 6}, {"clippedcube", 7}};
if (availableSurfaces.find(desiredSurface) == availableSurfaces.end())
{
std::cout << "The requested surface: " << parameters.parameters["object"]
<< " not found, reverting to Boys Surface." << std::endl;
desiredSurface = "boy";
}
vtkSmartPointer<vtkPolyData> source;
switch (availableSurfaces[desiredSurface])
{
case 1:
source = GetMobius();
break;
case 2:
source = GetRandomHills();
break;
case 3:
source = GetTorus();
break;
case 4:
source = GetSphere();
break;
case 5:
source = GetClippedSphere();
break;
case 6:
source = GetCube();
break;
case 7:
source = GetClippedCube();
break;
case 0:
default:
source = GetBoy();
};
// Let's use a nonmetallic surface.
auto diffuseCoefficient = 1.0;
auto roughnessCoefficient = 0.3;
auto metallicCoefficient = 0.0;
// Other parameters.
auto occlusionStrength = 1.0;
auto normalScale = 1.0;
// auto emissiveCol = colors->GetColor3d("VTKBlueComp").GetData();
// std::array<double, 3> emissiveFactor{emissiveCol[0], emissiveCol[1],
// emissiveCol[2]};
// Make VTK silvery in appearance.
std::array<double, 3> emissiveFactor{1.0, 1.0, 1.0};
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputData(source);
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
// Enable PBR on the model.
actor->GetProperty()->SetInterpolationToPBR();
// Configure the basic properties.
actor->GetProperty()->SetColor(colors->GetColor3d("White").GetData());
actor->GetProperty()->SetDiffuse(diffuseCoefficient);
actor->GetProperty()->SetRoughness(roughnessCoefficient);
actor->GetProperty()->SetMetallic(metallicCoefficient);
// Configure textures (needs tcoords on the mesh).
actor->GetProperty()->SetBaseColorTexture(baseColor);
actor->GetProperty()->SetORMTexture(material);
actor->GetProperty()->SetOcclusionStrength(occlusionStrength);
actor->GetProperty()->SetEmissiveTexture(emissive);
actor->GetProperty()->SetEmissiveFactor(emissiveFactor.data());
// Needs tcoords, normals and tangents on the mesh.
actor->GetProperty()->SetNormalTexture(normal);
actor->GetProperty()->SetNormalScale(normalScale);
ren2->AddActor(actor);
if (hasSkybox)
{
if (gammaCorrect)
{
skybox->GammaCorrectOn();
}
else
{
skybox->GammaCorrectOff();
}
ren2->AddActor(skybox);
}
// Create the slider callbacks to manipulate various parameters.
auto stepSize = 1.0 / 5.0;
auto posY = 0.1;
auto posX0 = 0.02;
auto posX1 = 0.18;
auto slwP = SliderProperties();
slwP.initialValue = 1.0;
slwP.maximumValue = 5.0;
slwP.title = "Exposure";
// Screen coordinates.
slwP.p1[0] = posX0;
slwP.p1[1] = posY;
slwP.p2[0] = posX1;
slwP.p2[1] = posY;
auto swExposure = MakeSliderWidget(slwP);
swExposure->SetInteractor(interactor);
swExposure->SetAnimationModeToAnimate();
if (useTonemapping)
{
swExposure->EnabledOn();
}
else
{
swExposure->EnabledOff();
}
swExposure->SetCurrentRenderer(ren1);
vtkNew<SliderCallbackExposure> swExpCB;
swExpCB->property = dynamic_cast<vtkToneMappingPass*>(ren2->GetPass());
swExposure->AddObserver(vtkCommand::InteractionEvent, swExpCB);
posY += stepSize;
slwP.initialValue = metallicCoefficient;
slwP.maximumValue = 1.0;
slwP.title = "Metallicity";
// Screen coordinates.
slwP.p1[0] = posX0;
slwP.p1[1] = posY;
slwP.p2[0] = posX1;
slwP.p2[1] = posY;
auto swMetallic = MakeSliderWidget(slwP);
swMetallic->SetInteractor(interactor);
swMetallic->SetAnimationModeToAnimate();
swMetallic->EnabledOn();
swMetallic->SetCurrentRenderer(ren1);
vtkNew<SliderCallbackMetallic> swMetallicCB;
swMetallicCB->property = actor->GetProperty();
swMetallic->AddObserver(vtkCommand::InteractionEvent, swMetallicCB);
posY += stepSize;
slwP.initialValue = roughnessCoefficient;
slwP.title = "Roughness";
// Screen coordinates.
slwP.p1[0] = posX0;
slwP.p1[1] = posY;
slwP.p2[0] = posX1;
slwP.p2[1] = posY;
auto swRoughness = MakeSliderWidget(slwP);
swRoughness->SetInteractor(interactor);
swRoughness->SetAnimationModeToAnimate();
swRoughness->EnabledOn();
swRoughness->SetCurrentRenderer(ren1);
vtkNew<SliderCallbackRoughness> swRoughnessCB;
swRoughnessCB->property = actor->GetProperty();
swRoughness->AddObserver(vtkCommand::InteractionEvent, swRoughnessCB);
posY += stepSize;
slwP.initialValue = occlusionStrength;
slwP.maximumValue = 5;
slwP.title = "Occlusion";
// Screen coordinates.
slwP.p1[0] = posX0;
slwP.p1[1] = posY;
slwP.p2[0] = posX1;
slwP.p2[1] = posY;
auto swOccStr = MakeSliderWidget(slwP);
swOccStr->SetInteractor(interactor);
swOccStr->SetAnimationModeToAnimate();
swOccStr->EnabledOn();
swOccStr->SetCurrentRenderer(ren1);
vtkNew<SliderCallbackOcclusionStrength> swOccStrCB;
swOccStrCB->property = actor->GetProperty();
swOccStr->AddObserver(vtkCommand::InteractionEvent, swOccStrCB);
posY += stepSize;
slwP.initialValue = normalScale;
slwP.maximumValue = 5;
slwP.title = "Normal";
// Screen coordinates.
slwP.p1[0] = posX0;
slwP.p1[1] = posY;
slwP.p2[0] = posX1;
slwP.p2[1] = posY;
auto swNormal = MakeSliderWidget(slwP);
swNormal->SetInteractor(interactor);
swNormal->SetAnimationModeToAnimate();
swNormal->EnabledOn();
swNormal->SetCurrentRenderer(ren1);
vtkNew<SliderCallbackNormalScale> swNormalCB;
swNormalCB->property = actor->GetProperty();
swNormal->AddObserver(vtkCommand::InteractionEvent, swNormalCB);
auto name = fs::path(argv[0]).stem().generic_string();
renderWindow->SetSize(1000, 625);
renderWindow->Render();
renderWindow->SetWindowName(name.c_str());
#if VTK_HAS_COW
vtkNew<vtkCameraOrientationWidget> camOrientManipulator;
camOrientManipulator->SetParentRenderer(ren2);
// Enable the widget.
camOrientManipulator->On();
#else
vtkNew<vtkAxesActor> axes;
vtkNew<vtkOrientationMarkerWidget> widget;
double rgba[4]{0.0, 0.0, 0.0, 0.0};
colors->GetColor("Carrot", rgba);
widget->SetOutlineColor(rgba[0], rgba[1], rgba[2]);
widget->SetOrientationMarker(axes);
widget->SetInteractor(interactor);
widget->SetViewport(0.0, 0.0, 0.2, 0.2);
widget->EnabledOn();
widget->InteractiveOn();
#endif
vtkNew<vtkRenderWindowInteractor> iRen;
vtkNew<PrintCallback> printCallback;
printCallback->SetParameters(name, 1, false);
// printCallback->SetParameters(name + ".jpg", 1, false);
interactor->AddObserver(vtkCommand::KeyPressEvent, printCallback);
interactor->Start();
return EXIT_SUCCESS;
}
namespace {
bool VTKVersionOk(unsigned long long const& major,
unsigned long long const& minor,
unsigned long long const& build)
{
unsigned long long neededVersion =
10000000000ULL * major + 100000000ULL * minor + build;
#ifndef VTK_VERSION_NUMBER
vtkNew<vtkVersion>();
ver;
unsigned long long vtk_version_number =
10000000000ULL * ver->GetVTKMajorVersion() +
100000000ULL * ver->GetVTKMinorVersion() + ver->GetVTKBuildVersion();
if (vtk_version_number >= neededVersion)
{
return true;
}
return false;
#else
if (VTK_VERSION_NUMBER >= neededVersion)
{
return true;
}
return false;
#endif
}
void GetParameters(const fs::path fnPath, Parameters& parameters)
{
std::ifstream ifs(fnPath);
Json::Value root;
if (ifs)
{
std::string str;
std::string errors;
Json::CharReaderBuilder builder{};
auto reader = std::unique_ptr<Json::CharReader>(builder.newCharReader());
std::ostringstream ss;
ss << ifs.rdbuf(); // Read in the file comtents
str = ss.str();
auto parsingSuccessful =
reader->parse(str.c_str(), str.c_str() + str.size(), &root, &errors);
ifs.close();
if (!parsingSuccessful)
{
std::cout << errors << std::endl;
parameters.parsedOk = false;
return;
}
parameters.parsedOk = true;
}
else
{
std::cerr << "Unable to open: " << fnPath << std::endl;
parameters.parsedOk = false;
}
// Extract the values.
std::set<std::string> keysNoPaths{"title", "object", "objcolor", "bkgcolor",
"skybox"};
std::set<std::string> keysWithPaths{"cubemap", "equirectangular", "albedo",
"normal", "material", "coat",
"anisotropy", "emissive"};
fs::path cubemapPath;
for (Json::Value::const_iterator outer = root.begin(); outer != root.end();
++outer)
{
if (keysNoPaths.find(outer.name()) != keysNoPaths.end())
{
if (outer.name() == "skybox")
{
parameters.skybox = outer->asBool();
}
else
{
parameters.parameters[outer.name()] = outer->asString();
}
continue;
}
if (keysWithPaths.find(outer.name()) != keysWithPaths.end())
{
if (outer.name() == "cubemap")
{
std::string path;
for (Json::Value::const_iterator pth = root["cubemap"].begin();
pth != root["cubemap"].end(); ++pth)
{
if (pth.name() == "root")
{
cubemapPath = fs::path(pth->asString());
std::cout << path << std::endl;
}
if (pth.name() == "files")
{
for (Json::Value::const_iterator fls =
root["cubemap"]["files"].begin();
fls != root["cubemap"]["files"].end(); ++fls)
{
parameters.cubemap.push_back(fls->asString());
}
}
}
}
else
{
parameters.parameters[outer.name()] = outer->asString();
}
}
}
// Build and check the paths.
if (!parameters.cubemap.empty())
{
if (parameters.cubemap.size() != 6)
{
std::cerr << "There must be six filenames for the cubemap.";
parameters.parsedOk = false;
}
else
{
for (size_t i = 0; i < parameters.cubemap.size(); i++)
{
auto pth = fnPath.parent_path() / cubemapPath /
fs::path(parameters.cubemap[i]);
if (parameters.cubemap[i].empty())
{
std::cerr << "A missing path in the cubemap." << std::endl;
parameters.parsedOk = false;
}
else
{
parameters.cubemap[i] = pth.make_preferred().string();
if (!(fs::is_regular_file(pth) && fs::exists(pth)))
{
std::cerr << "Not a file or path does not exist: "
<< parameters.cubemap[i] << std::endl;
parameters.parsedOk = false;
}
}
}
}
}
// Check the remaining paths, we don't need cubemap now.
keysWithPaths.erase("cubemap");
for (auto& p : parameters.parameters)
{
if (keysWithPaths.find(p.first) != keysWithPaths.end())
{
if (p.second.empty())
{
std::cerr << "No path for the key " << p.first << std::endl;
parameters.parsedOk = false;
}
else
{
auto pth = fnPath.parent_path() / fs::path(p.second);
p.second = pth.make_preferred().string();
if (!(fs::is_regular_file(pth) && fs::exists(pth)))
{
std::cerr << "Not a file or path does not exist: " << p.second
<< std::endl;
parameters.parsedOk = false;
}
}
}
}
return;
}
std::string DisplayParameters(Parameters& parameters)
{
std::stringstream res;
std::vector<std::string> parameterKeys{
"title", "object", "objcolor", "bkgcolor", "skybox",
"cubemap", "equirectangular", "albedo", "normal", "material",
"coat", "anisotropy", "emissive"};
for (auto const& e : parameterKeys)
{
if (e == "cubemap")
{
for (std::vector<std::string>::const_iterator it =
parameters.cubemap.begin();
it != parameters.cubemap.end(); ++it)
{
if (it == parameters.cubemap.cbegin())
{
res << std::setw(15) << std::left << "cubemap" << ": " << *it << "\n";
}
else
{
res << std::setw(17) << " " << *it << "\n";
}
}
}
else if (e == "skybox")
{
res << std::setw(15) << std::left << e << ": " << std::boolalpha
<< parameters.skybox << "\n";
}
else
{
if (parameters.parameters.find(e) != parameters.parameters.end())
{
res << std::setw(15) << std::left << e << ": "
<< parameters.parameters[e] << "\n";
}
}
}
return res.str();
}
vtkNew<vtkTexture> ReadCubemap(std::vector<std::string> const& fileNames)
{
vtkNew<vtkTexture> cubeMap;
cubeMap->CubeMapOn();
auto i = 0;
for (auto const& fn : fileNames)
{
// Read the images
vtkNew<vtkImageReader2Factory> readerFactory;
vtkSmartPointer<vtkImageReader2> imgReader;
imgReader.TakeReference(readerFactory->CreateImageReader2(fn.c_str()));
imgReader->SetFileName(fn.c_str());
// Each image must be flipped in Y due to canvas
// versus vtk ordering.
vtkNew<vtkImageFlip> flip;
flip->SetInputConnection(imgReader->GetOutputPort());
flip->SetFilteredAxis(1); // flip y axis
cubeMap->SetInputConnection(i, flip->GetOutputPort(0));
++i;
}
cubeMap->MipmapOn();
cubeMap->InterpolateOn();
return cubeMap;
}
vtkNew<vtkTexture> ReadEquirectangularFile(std::string const& fileName)
{
vtkNew<vtkTexture> texture;
std::string extension = fs::path(fileName).extension().generic_string();
std::transform(extension.begin(), extension.end(), extension.begin(),
[](char c) { return std::tolower(c); });
if (std::string(".jpeg .jpg .png").find(extension, 0) != std::string::npos)
{
vtkNew<vtkImageReader2Factory> readerFactory;
vtkSmartPointer<vtkImageReader2> imgReader;
imgReader.TakeReference(
readerFactory->CreateImageReader2(fileName.c_str()));
imgReader->SetFileName(fileName.c_str());
texture->SetInputConnection(imgReader->GetOutputPort());
}
else
{
vtkNew<vtkHDRReader> reader;
auto extensions = reader->GetFileExtensions();
if (std::string(extensions).find(extension, 0) != std::string::npos)
{
if (reader->CanReadFile(fileName.c_str()))
{
reader->SetFileName(fileName.c_str());
texture->SetInputConnection(reader->GetOutputPort());
texture->SetColorModeToDirectScalars();
}
else
{
std::cerr << "Unable to read the file: " << fileName << std::endl;
return texture;
}
}
}
texture->MipmapOn();
texture->InterpolateOn();
return texture;
}
vtkNew<vtkTexture> ReadTexture(std::string const& path)
{
// Read the image which will be the texture
std::string extension;
if (path.find_last_of(".") != std::string::npos)
{
extension = path.substr(path.find_last_of("."));
}
// Make the extension lowercase
std::transform(extension.begin(), extension.end(), extension.begin(),
::tolower);
std::vector<std::string> validExtensions{".jpg", ".png", ".bmp", ".tiff",
".pnm", ".pgm", ".ppm"};
vtkNew<vtkTexture> texture;
texture->InterpolateOn();
if (std::find(validExtensions.begin(), validExtensions.end(), extension) ==
validExtensions.end())
{
std::cout << "Unable to read the texture file:" << path << std::endl;
return texture;
}
// Read the images
vtkNew<vtkImageReader2Factory> readerFactory;
vtkSmartPointer<vtkImageReader2> imgReader;
imgReader.TakeReference(readerFactory->CreateImageReader2(path.c_str()));
imgReader->SetFileName(path.c_str());
texture->SetInputConnection(imgReader->GetOutputPort());
texture->Update();
return texture;
}
bool CheckForMissingTextures(Parameters& parameters,
std::vector<std::string> const& wantedTextures)
{
bool haveTextures = true;
for (auto const& textureName : wantedTextures)
{
if (parameters.parameters.find("equirectangular") ==
parameters.parameters.end())
{
std::cerr << "Missing texture: " << textureName << std::endl;
haveTextures = false;
}
else if (parameters.parameters[textureName].empty())
{
std::cerr << "No texture path for: " << textureName << std::endl;
haveTextures = false;
}
}
return haveTextures;
}
vtkSmartPointer<vtkPolyData> GetBoy()
{
auto uResolution = 51;
auto vResolution = 51;
vtkNew<vtkParametricBoy> surface;
vtkNew<vtkParametricFunctionSource> source;
source->SetUResolution(uResolution);
source->SetVResolution(vResolution);
source->GenerateTextureCoordinatesOn();
source->SetParametricFunction(surface);
source->Update();
// Build the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(source->GetOutputPort());
tangents->Update();
return tangents->GetOutput();
}
vtkSmartPointer<vtkPolyData> GetMobius()
{
auto uResolution = 51;
auto vResolution = 51;
vtkNew<vtkParametricMobius> surface;
surface->SetMinimumV(-0.25);
surface->SetMaximumV(0.25);
vtkNew<vtkParametricFunctionSource> source;
source->SetUResolution(uResolution);
source->SetVResolution(vResolution);
source->GenerateTextureCoordinatesOn();
source->SetParametricFunction(surface);
source->Update();
// Build the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(source->GetOutputPort());
tangents->Update();
vtkNew<vtkTransform> transform;
transform->RotateX(-90.0);
vtkNew<vtkTransformPolyDataFilter> transformFilter;
transformFilter->SetInputConnection(tangents->GetOutputPort());
transformFilter->SetTransform(transform);
transformFilter->Update();
return transformFilter->GetOutput();
}
vtkSmartPointer<vtkPolyData> GetRandomHills()
{
auto uResolution = 51;
auto vResolution = 51;
vtkNew<vtkParametricRandomHills> surface;
surface->SetRandomSeed(1);
surface->SetNumberOfHills(30);
// If you want a plane
// surface->SetHillAmplitude(0);
vtkNew<vtkParametricFunctionSource> source;
source->SetUResolution(uResolution);
source->SetVResolution(vResolution);
source->GenerateTextureCoordinatesOn();
source->SetParametricFunction(surface);
source->Update();
// Build the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(source->GetOutputPort());
tangents->Update();
vtkNew<vtkTransform> transform;
transform->Translate(0.0, 5.0, 15.0);
transform->RotateX(-90.0);
vtkNew<vtkTransformPolyDataFilter> transformFilter;
transformFilter->SetInputConnection(tangents->GetOutputPort());
transformFilter->SetTransform(transform);
transformFilter->Update();
return transformFilter->GetOutput();
}
vtkSmartPointer<vtkPolyData> GetTorus()
{
auto uResolution = 51;
auto vResolution = 51;
vtkNew<vtkParametricTorus> surface;
vtkNew<vtkParametricFunctionSource> source;
source->SetUResolution(uResolution);
source->SetVResolution(vResolution);
source->GenerateTextureCoordinatesOn();
source->SetParametricFunction(surface);
source->Update();
// Build the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(source->GetOutputPort());
tangents->Update();
vtkNew<vtkTransform> transform;
transform->RotateX(-90.0);
vtkNew<vtkTransformPolyDataFilter> transformFilter;
transformFilter->SetInputConnection(tangents->GetOutputPort());
transformFilter->SetTransform(transform);
transformFilter->Update();
return transformFilter->GetOutput();
}
vtkSmartPointer<vtkPolyData> GetSphere()
{
auto thetaResolution = 32;
auto phiResolution = 32;
vtkNew<vtkTexturedSphereSource> surface;
surface->SetThetaResolution(thetaResolution);
surface->SetPhiResolution(phiResolution);
// Now the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(surface->GetOutputPort());
tangents->Update();
return tangents->GetOutput();
}
vtkSmartPointer<vtkPolyData> GetClippedSphere()
{
auto thetaResolution = 32;
auto phiResolution = 32;
vtkNew<vtkTexturedSphereSource> surface;
surface->SetThetaResolution(thetaResolution);
surface->SetPhiResolution(phiResolution);
vtkNew<vtkPlane> clip_plane;
clip_plane->SetOrigin(0, 0.3, 0);
clip_plane->SetNormal(0, -1, 0);
vtkNew<vtkClipPolyData> clipper;
clipper->SetInputConnection(surface->GetOutputPort());
clipper->SetClipFunction(clip_plane);
clipper->GenerateClippedOutputOn();
// Now the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(clipper->GetOutputPort());
tangents->Update();
return tangents->GetOutput();
}
vtkSmartPointer<vtkPolyData> GetCube()
{
vtkNew<vtkCubeSource> surface;
// Triangulate
vtkNew<vtkTriangleFilter> triangulation;
triangulation->SetInputConnection(surface->GetOutputPort());
// Subdivide the triangles
vtkNew<vtkLinearSubdivisionFilter> subdivide;
subdivide->SetInputConnection(triangulation->GetOutputPort());
subdivide->SetNumberOfSubdivisions(3);
// Now the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(subdivide->GetOutputPort());
tangents->Update();
return tangents->GetOutput();
}
vtkSmartPointer<vtkPolyData> GetClippedCube()
{
vtkNew<vtkCubeSource> surface;
// Triangulate
vtkNew<vtkTriangleFilter> triangulation;
triangulation->SetInputConnection(surface->GetOutputPort());
// Subdivide the triangles
vtkNew<vtkLinearSubdivisionFilter> subdivide;
subdivide->SetInputConnection(triangulation->GetOutputPort());
subdivide->SetNumberOfSubdivisions(5);
vtkNew<vtkPlane> clip_plane;
clip_plane->SetOrigin(0, 0.3, 0);
clip_plane->SetNormal(0, -1, -1);
vtkNew<vtkClipPolyData> clipper;
clipper->SetInputConnection(subdivide->GetOutputPort());
clipper->SetClipFunction(clip_plane);
clipper->GenerateClippedOutputOn();
vtkNew<vtkCleanPolyData> cleaner;
cleaner->SetInputConnection(clipper->GetOutputPort());
cleaner->SetTolerance(0.005);
cleaner->Update();
vtkNew<vtkPolyDataNormals> normals;
normals->SetInputConnection(cleaner->GetOutputPort());
normals->FlipNormalsOn();
normals->SetFeatureAngle(60);
// Now the tangents
vtkNew<vtkPolyDataTangents> tangents;
tangents->SetInputConnection(normals->GetOutputPort());
tangents->ComputeCellTangentsOn();
tangents->ComputePointTangentsOff();
tangents->Update();
return tangents->GetOutput();
}
vtkNew<vtkPolyData> UVTcoords(const float& uResolution,
const float& vResolution, vtkNew<vtkPolyData> pd)
{
float u0 = 1.0;
float v0 = 0.0;
float du = 1.0 / (uResolution - 1.0);
float dv = 1.0 / (vResolution - 1.0);
vtkIdType numPts = pd->GetNumberOfPoints();
vtkNew<vtkFloatArray> tCoords;
tCoords->SetNumberOfComponents(2);
tCoords->SetNumberOfTuples(numPts);
tCoords->SetName("Texture Coordinates");
vtkIdType ptId = 0;
float u = u0;
for (auto i = 0; i < uResolution; ++i)
{
float v = v0;
for (auto j = 0; j < vResolution; ++j)
{
float tc[2]{u, v};
tCoords->SetTuple(ptId, tc);
v += dv;
ptId++;
}
u -= du;
}
pd->GetPointData()->SetTCoords(tCoords);
return pd;
}
vtkNew<vtkSliderWidget> MakeSliderWidget(SliderProperties const& properties)
{
vtkNew<vtkNamedColors> colors;
vtkNew<vtkSliderRepresentation2D> slider;
slider->SetMinimumValue(properties.minimumValue);
slider->SetMaximumValue(properties.maximumValue);
slider->SetValue(properties.initialValue);
slider->SetTitleText(properties.title.c_str());
slider->GetPoint1Coordinate()->SetCoordinateSystemToNormalizedDisplay();
slider->GetPoint1Coordinate()->SetValue(properties.p1[0], properties.p1[1]);
slider->GetPoint2Coordinate()->SetCoordinateSystemToNormalizedDisplay();
slider->GetPoint2Coordinate()->SetValue(properties.p2[0], properties.p2[1]);
slider->SetTubeWidth(properties.tubeWidth);
slider->SetSliderLength(properties.sliderLength);
slider->SetSliderWidth(properties.sliderWidth);
slider->SetEndCapLength(properties.endCapLength);
slider->SetEndCapWidth(properties.endCapWidth);
slider->SetTitleHeight(properties.titleHeight);
slider->SetLabelHeight(properties.labelHeight);
// Set the color properties
// Change the color of the title.
slider->GetTitleProperty()->SetColor(
colors->GetColor3d(properties.titleColor).GetData());
// Change the color of the label.
slider->GetLabelProperty()->SetColor(
colors->GetColor3d(properties.labelColor).GetData());
// Change the color of the bar.
slider->GetTubeProperty()->SetColor(
colors->GetColor3d(properties.barColor).GetData());
// Change the color of the ends of the bar.
slider->GetCapProperty()->SetColor(
colors->GetColor3d(properties.barEndsColor).GetData());
// Change the color of the knob that slides.
slider->GetSliderProperty()->SetColor(
colors->GetColor3d(properties.sliderColor).GetData());
// Change the color of the knob when the mouse is held on it.
slider->GetSelectedProperty()->SetColor(
colors->GetColor3d(properties.selectedColor).GetData());
// Change the color of the text displaying the value.
slider->GetLabelProperty()->SetColor(
colors->GetColor3d(properties.valueColor).GetData());
vtkNew<vtkSliderWidget> sliderWidget;
sliderWidget->SetRepresentation(slider);
return sliderWidget;
}
} // namespace
CMakeLists.txt¶
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(PBR_Skybox_Texturing)
find_package(VTK COMPONENTS
CommonColor
CommonComputationalGeometry
CommonCore
CommonDataModel
CommonTransforms
FiltersCore
FiltersGeneral
FiltersModeling
FiltersSources
IOImage
ImagingCore
InteractionStyle
InteractionWidgets
RenderingAnnotation
RenderingContextOpenGL2
RenderingCore
RenderingFreeType
RenderingGL2PSOpenGL2
RenderingOpenGL2
cli11
jsoncpp
)
if (NOT VTK_FOUND)
message(FATAL_ERROR "PBR_Skybox_Texturing: Unable to find the VTK build folder.")
endif()
# Prevent a "command line is too long" failure in Windows.
set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.")
add_executable(PBR_Skybox_Texturing MACOSX_BUNDLE PBR_Skybox_Texturing.cxx )
target_link_libraries(PBR_Skybox_Texturing PRIVATE ${VTK_LIBRARIES}
)
# vtk_module_autoinit is needed
vtk_module_autoinit(
TARGETS PBR_Skybox_Texturing
MODULES ${VTK_LIBRARIES}
)
Download and Build PBR_Skybox_Texturing¶
Click here to download PBR_Skybox_Texturing and its CMakeLists.txt file. Once the tarball PBR_Skybox_Texturing.tar has been downloaded and extracted,
cd PBR_Skybox_Texturing/build
If VTK is installed:
cmake ..
If VTK is not installed but compiled on your system, you will need to specify the path to your VTK build:
cmake -DVTK_DIR:PATH=/home/me/vtk_build ..
Build the project:
make
and run it:
./PBR_Skybox_Texturing
WINDOWS USERS
Be sure to add the VTK bin directory to your path. This will resolve the VTK dll's at run time.