Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make particle emitter invisible in thermal camera image #240

Merged
merged 16 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

### Ignition Rendering 4.X

### Ignition Rendering 4.x.x (202x-xx-xx)

1. Add support for 8 bit thermal camera image format
* [Pull Request #235](https://github.com/ignitionrobotics/ign-rendering/pull/235)

### Ignition Rendering 4.3.1 (2021-02-03)

1. Fix converting Pbs to Unlit material conversion (#230)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,15 @@ namespace ignition
public: virtual void SetColorRangeImage(const std::string &_image)
override;

/// \brief Particle system visibility flags
public: static uint32_t kParticleVisibilityFlags;
adlarkin marked this conversation as resolved.
Show resolved Hide resolved

// Documentation inherited.
protected: virtual void Init() override;

/// \brief Create the particle system
private: void CreateParticleSystem();
iche033 marked this conversation as resolved.
Show resolved Hide resolved

/// \brief Only the ogre scene can instanstiate this class
private: friend class Ogre2Scene;

Expand Down
4 changes: 4 additions & 0 deletions ogre2/src/Ogre2ParticleEmitter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
using namespace ignition;
using namespace rendering;

uint32_t Ogre2ParticleEmitter::kParticleVisibilityFlags = 0x00100000;

class ignition::rendering::Ogre2ParticleEmitterPrivate
{
/// \brief Internal material name.
Expand Down Expand Up @@ -420,6 +422,8 @@ void Ogre2ParticleEmitter::Init()
this->dataPtr->ps->setParticleQuota(500);
this->dataPtr->ps->setSortingEnabled(true);

this->dataPtr->ps->setVisibilityFlags(kParticleVisibilityFlags);

IGN_ASSERT(kOgreEmitterTypes.size() == EmitterType::EM_NUM_EMITTERS,
"The nummer of supported emitters does not match the number of "
"Ogre emitter types.");
Expand Down
130 changes: 106 additions & 24 deletions ogre2/src/Ogre2ThermalCamera.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "ignition/rendering/ogre2/Ogre2Conversions.hh"
#include "ignition/rendering/ogre2/Ogre2Includes.hh"
#include "ignition/rendering/ogre2/Ogre2Material.hh"
#include "ignition/rendering/ogre2/Ogre2ParticleEmitter.hh"
#include "ignition/rendering/ogre2/Ogre2RenderEngine.hh"
#include "ignition/rendering/ogre2/Ogre2RenderTarget.hh"
#include "ignition/rendering/ogre2/Ogre2RenderTypes.hh"
Expand Down Expand Up @@ -75,6 +76,14 @@ class Ogre2ThermalCameraMaterialSwitcher : public Ogre::RenderTargetListener
/// \brief destructor
public: ~Ogre2ThermalCameraMaterialSwitcher() = default;

/// \brief Set image format
/// \param[in] _format Image format
public: void SetFormat(PixelFormat _format);

/// \brief Set temperature linear resolution
/// \param[in] _resolution Temperature linear resolution
public: void SetLinearResolution(double _resolution);

/// \brief Callback when a render target is about to be rendered
/// \param[in] _evt Ogre render target event containing information about
/// the source render target.
Expand Down Expand Up @@ -119,6 +128,15 @@ class Ogre2ThermalCameraMaterialSwitcher : public Ogre::RenderTargetListener
/// \brief A map of ogre sub item pointer to their original hlms material
private: std::unordered_map<Ogre::SubItem *, Ogre::HlmsDatablock *>
datablockMap;

/// \brief linear temperature resolution. Defaults to 10mK
private: double resolution = 0.01;

/// \brief thermal camera image format
private: PixelFormat format = PF_L16;

/// \brief thermal camera image bit depth
private: unsigned int bitDepth = 16u;
};
}
}
Expand All @@ -130,7 +148,7 @@ class Ogre2ThermalCameraMaterialSwitcher : public Ogre::RenderTargetListener
class ignition::rendering::Ogre2ThermalCameraPrivate
{
/// \brief The thermal buffer
public: uint16_t *thermalBuffer = nullptr;
public: unsigned char *thermalBuffer = nullptr;

/// \brief Outgoing thermal data, used by newThermalFrame event.
public: uint16_t *thermalImage = nullptr;
Expand Down Expand Up @@ -172,6 +190,9 @@ class ignition::rendering::Ogre2ThermalCameraPrivate
/// This only affects objects that are not heat sources
/// TODO(anyone) add API for setting this value?
public: bool rgbToTemp = true;

/// \brief bit depth of each pixel
public: unsigned int bitDepth = 16u;
};

using namespace ignition;
Expand All @@ -196,6 +217,18 @@ Ogre2ThermalCameraMaterialSwitcher::Ogre2ThermalCameraMaterialSwitcher(
this->ogreCamera = this->scene->OgreSceneManager()->findCamera(this->name);
}

//////////////////////////////////////////////////
void Ogre2ThermalCameraMaterialSwitcher::SetFormat(PixelFormat _format)
{
this->format = _format;
this->bitDepth = 8u * PixelUtil::BytesPerChannel(format);
}

//////////////////////////////////////////////////
void Ogre2ThermalCameraMaterialSwitcher::SetLinearResolution(double _resolution)
{
this->resolution = _resolution;
}
//////////////////////////////////////////////////
void Ogre2ThermalCameraMaterialSwitcher::preRenderTargetUpdate(
const Ogre::RenderTargetEvent & /*_evt*/)
Expand Down Expand Up @@ -263,18 +296,15 @@ void Ogre2ThermalCameraMaterialSwitcher::preRenderTargetUpdate(
for (unsigned int i = 0; i < item->getNumSubItems(); ++i)
{
Ogre::SubItem *subItem = item->getSubItem(i);
if (!subItem->hasCustomParameter(this->customParamIdx))
{
// normalize temperature value
float color = temp * 100.0 /
static_cast<float>(std::numeric_limits<uint16_t>::max());

// set g, b, a to 0. This will be used by shaders to determine
// if particular fragment is a heat source or not
// see media/materials/programs/thermal_camera_fs.glsl
subItem->setCustomParameter(this->customParamIdx,
Ogre::Vector4(color, 0, 0, 0.0));
}

// normalize temperature value
float color = (temp / this->resolution) / ((1 << bitDepth) - 1.0);

// set g, b, a to 0. This will be used by shaders to determine
// if particular fragment is a heat source or not
// see media/materials/programs/thermal_camera_fs.glsl
subItem->setCustomParameter(this->customParamIdx,
Ogre::Vector4(color, 0, 0, 0.0));
Ogre::HlmsDatablock *datablock = subItem->getDatablock();
this->datablockMap[subItem] = datablock;

Expand Down Expand Up @@ -318,14 +348,20 @@ void Ogre2ThermalCameraMaterialSwitcher::preRenderTargetUpdate(
auto maxTemperature = std::get_if<float>(&maxTempVariant);
if (minTemperature && maxTemperature)
{
// make sure the temperature range is between [0, 655.35] kelvin
// make sure the temperature range is between [min, max] kelvin
// for the given pixel format and camera resolution
float maxTemp = ((1 << bitDepth) - 1.0) * this->resolution;
Ogre::GpuProgramParametersSharedPtr params =
heatSignatureMaterial->getTechnique(0)->getPass(0)->
getFragmentProgramParameters();
params->setNamedConstant("minTemp",
std::max(static_cast<float>(*minTemperature), 0.0f));
params->setNamedConstant("maxTemp",
std::min(static_cast<float>(*maxTemperature), 655.35f));
std::min(static_cast<float>(*maxTemperature), maxTemp));
params->setNamedConstant("bitDepth",
static_cast<int>(this->bitDepth));
params->setNamedConstant("resolution",
static_cast<float>(this->resolution));
}
heatSignatureMaterial->load();
this->heatSignatureMaterials[item->getId()] = heatSignatureMaterial;
Expand Down Expand Up @@ -552,6 +588,22 @@ void Ogre2ThermalCamera::CreateThermalTexture()
this->ogreCamera->setNearClipDistance(nearPlane);
this->ogreCamera->setFarClipDistance(farPlane);

// only support 8 bit and 16 bit formats for now.
// default to 16 bit
Ogre::PixelFormat ogrePF;
if (this->ImageFormat() == PF_L8)
{
ogrePF = Ogre::PF_L8;
}
else
{
this->SetImageFormat(PF_L16);
ogrePF = Ogre::PF_L16;
}

PixelFormat format = this->ImageFormat();
this->dataPtr->bitDepth = 8u * PixelUtil::BytesPerChannel(format);

// Set the uniform variables (thermal_camera_fs.glsl).
// The projectParams is used to linearize thermal buffer data
// The other params are used to clamp the range output
Expand Down Expand Up @@ -582,6 +634,8 @@ void Ogre2ThermalCamera::CreateThermalTexture()
static_cast<float>(this->heatSourceTempRange));
psParams->setNamedConstant("rgbToTemp",
static_cast<int>(this->dataPtr->rgbToTemp));
psParams->setNamedConstant("bitDepth",
static_cast<int>(this->dataPtr->bitDepth));

// Create thermal camera compositor
auto engine = Ogre2RenderEngine::Instance();
Expand Down Expand Up @@ -684,7 +738,12 @@ void Ogre2ThermalCamera::CreateThermalTexture()
colorTargetDef->addPass(Ogre::PASS_CLEAR));
passClear->mColourValue = Ogre::ColourValue(0, 0, 0);
// scene pass
colorTargetDef->addPass(Ogre::PASS_SCENE);
Ogre::CompositorPassSceneDef *passScene =
static_cast<Ogre::CompositorPassSceneDef *>(
colorTargetDef->addPass(Ogre::PASS_SCENE));
iche033 marked this conversation as resolved.
Show resolved Hide resolved
// thermal camera should not see particles
passScene->mVisibilityMask = IGN_VISIBILITY_ALL &
~Ogre2ParticleEmitter::kParticleVisibilityFlags;
}

// rt_input target - converts to thermal
Expand Down Expand Up @@ -726,7 +785,7 @@ void Ogre2ThermalCamera::CreateThermalTexture()
Ogre::TextureManager::getSingleton().createManual(
this->Name() + "_thermal", "General", Ogre::TEX_TYPE_2D,
this->ImageWidth(), this->ImageHeight(), 1, 0,
Ogre::PF_L16, Ogre::TU_RENDERTARGET,
ogrePF, Ogre::TU_RENDERTARGET,
0, false, 0, Ogre::BLANKSTRING, false, true);

Ogre::RenderTarget *rt =
Expand All @@ -748,6 +807,9 @@ void Ogre2ThermalCamera::CreateThermalTexture()
{
this->dataPtr->thermalMaterialSwitcher.reset(
new Ogre2ThermalCameraMaterialSwitcher(this->scene, this->Name()));
this->dataPtr->thermalMaterialSwitcher->SetFormat(this->ImageFormat());
this->dataPtr->thermalMaterialSwitcher->SetLinearResolution(
this->resolution);
c.target->addListener(this->dataPtr->thermalMaterialSwitcher.get());
break;
}
Expand Down Expand Up @@ -779,8 +841,7 @@ void Ogre2ThermalCamera::PostRender()

unsigned int width = this->ImageWidth();
unsigned int height = this->ImageHeight();

PixelFormat format = PF_L16;
PixelFormat format = this->ImageFormat();
Ogre::PixelFormat imageFormat = Ogre2Conversions::Convert(format);

int len = width * height;
Expand All @@ -789,7 +850,8 @@ void Ogre2ThermalCamera::PostRender()

if (!this->dataPtr->thermalBuffer)
{
this->dataPtr->thermalBuffer = new uint16_t[len * channelCount];
this->dataPtr->thermalBuffer =
new unsigned char[len * channelCount * bytesPerChannel];
}
Ogre::PixelBox dstBox(width, height,
1, imageFormat, this->dataPtr->thermalBuffer);
Expand All @@ -803,12 +865,32 @@ void Ogre2ThermalCamera::PostRender()
this->dataPtr->thermalImage = new uint16_t[len];
}

// fill thermal data
memcpy(this->dataPtr->thermalImage, this->dataPtr->thermalBuffer,
height*width*channelCount*bytesPerChannel);
if (format == PF_L8)
{
// workaround for populating a 16bit image buffer with 8bit data
// \todo(anyone) add a new ConnectNewThermalFrame function that accepts
// a generic unsigned char array instead of uint16_t so we can do a direct
// memcpy of the data
for (unsigned int i = 0u; i < height; ++i)
{
for (unsigned int j = 0u; j < width; ++j)
{
unsigned int idx = (i * width) + j;
this->dataPtr->thermalImage[idx] = static_cast<uint16_t>(
this->dataPtr->thermalBuffer[idx]);
}
}
}
else
{
// fill thermal data
memcpy(this->dataPtr->thermalImage, this->dataPtr->thermalBuffer,
height * width * channelCount * bytesPerChannel);
}

this->dataPtr->newThermalFrame(
this->dataPtr->thermalImage, width, height, 1, "L16");
this->dataPtr->thermalImage, width, height, 1,
PixelUtil::Name(format));

// Uncomment to debug thermal output
// std::cout << "wxh: " << width << " x " << height << std::endl;
Expand Down
9 changes: 6 additions & 3 deletions ogre2/src/media/materials/programs/heat_signature_fs.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ out vec4 fragColor;
uniform float minTemp = 0.0;
uniform float maxTemp = 100.0;

// map a temperature from the [0, 655.35] range to the [minTemp, maxTemp]
// range (655.35 is the largest Kelvin value allowed)
uniform int bitDepth;
uniform float resolution;

// map a temperature from the [min, max] range to the user defined
// [minTemp, maxTemp] range
float mapNormalized(float num)
{
float mappedKelvin = ((maxTemp - minTemp) * num) + minTemp;
return mappedKelvin / 655.35;
return mappedKelvin / (((1 << bitDepth) - 1.0) * resolution);
}

void main()
Expand Down
9 changes: 7 additions & 2 deletions ogre2/src/media/materials/programs/thermal_camera_fs.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ uniform float resolution;
uniform float heatSourceTempRange;
uniform float ambient;
uniform int rgbToTemp;
uniform int bitDepth;

float getDepth(vec2 uv)
{
Expand All @@ -51,6 +52,7 @@ void main()
// temperature defaults to ambient
float temp = ambient;
float heatRange = range;
int bitMaxValue = (1 << bitDepth) - 1;

// dNorm value is between [0, 1]. It is used to for varying temperature
// value of a fragment.
Expand All @@ -74,7 +76,9 @@ void main()
if (isHeatSource)
{
// heat is normalized so convert back to work in kelvin
temp = heat * 655.35;
// for 16 bit camera and 0.01 resolution:
// ((1 << bitDepth) - 1) * resolution = 655.35
temp = heat * bitMaxValue * resolution;

// set temperature variation for heat source
heatRange = heatSourceTempRange;
Expand Down Expand Up @@ -115,7 +119,8 @@ void main()
// apply resolution factor
temp /= resolution;
// normalize
temp /= 65535.0;
float denorm = float(bitMaxValue);
temp /= denorm;

fragColor = vec4(temp, 0, 0, 1.0);
}
Loading