diff --git a/BatchRenderer/Renderers/Mitsuba/rtbRenderMitsubaFactoids.m b/BatchRenderer/Renderers/Mitsuba/rtbRenderMitsubaFactoids.m
new file mode 100644
index 0000000000000000000000000000000000000000..283a1af1e41f1df6742b431402715601ab96c445
--- /dev/null
+++ b/BatchRenderer/Renderers/Mitsuba/rtbRenderMitsubaFactoids.m
@@ -0,0 +1,76 @@
+function [factoids, exrOutput] = rtbRenderMitsubaFactoids(sceneFile, varargin)
+% Obtain ground truth "factoids" about a Mitsuba scene.
+%
+% [factoids, exrOutput] = rtbRenderMitsubaFactoids(sceneFile) invokes
+% Mitsuba to obtain ground truth scene "factoids".  Returns a struct array
+% of ground truth images, with one field per ground truth factoid.
+%
+% The given sceneFile must specify a "multichannel" integrator with one or
+% more nested "field" integrators.  You can create such scenes with
+% rtbWriteMitsubaFactoidScene().
+%
+% rtbRenderMitsubaFactoids( ... 'mitsuba', mitsuba) specify a struct of
+% info about the installed Mitsuba renderer.  For some factoids, this must
+% be a version of Mistuba compiled for RGB rendering, not spectral
+% rendering.  The default is taken from getpref('Mitsuba').
+%
+% rtbRenderMitsubaFactoids(... 'hints', hints)
+% Specify a struct of RenderToolbox options.  If hints is omitted, values
+% are taken from rtbDefaultHints().
+%
+%%% RenderToolbox4 Copyright (c) 2012-2017 The RenderToolbox Team.
+%%% About Us://github.com/RenderToolbox/RenderToolbox4/wiki/About-Us
+%%% RenderToolbox4 is released under the MIT License.  See LICENSE file.
+
+parser = inputParser();
+parser.addRequired('sceneFile', @ischar);
+parser.addParameter('mitsuba', [], @(m) isempty(m) || isstruct(m));
+parser.addParameter('hints', rtbDefaultHints(), @isstruct);
+parser.parse(sceneFile, varargin{:});
+sceneFile = parser.Results.sceneFile;
+mitsuba = parser.Results.mitsuba;
+hints = rtbDefaultHints(parser.Results.hints);
+
+if isempty(mitsuba)
+    % modify default mitsuba config to look for rgb
+    mitsuba = getpref('Mitsuba');
+    mitsuba.dockerImage = 'ninjaben/mitsuba-rgb';
+    mitsuba.kubernetesPodSelector = 'app=mitsuba-spectral';
+    if ismac()
+        mitsuba.app = '/Applications/Mitsuba-RGB.app';
+    else
+        mitsuba.executable = 'mitusba-rgb';
+    end
+end
+
+
+%% Render the factoid scene.renderer = RtbMitsubaRenderer(hints);
+renderer = RtbMitsubaRenderer(hints);
+renderer.mitsuba = mitsuba;
+
+[~, ~, exrOutput] = renderer.renderToExr(sceneFile);
+[sliceInfo, data] = ReadMultichannelEXR(exrOutput);
+
+
+%% Group data slices by factoid name.
+factoids = struct();
+factoidSize = size(data);
+for ii = 1:numel(sliceInfo)
+    % factoid channels have names like albedo.R, albedo.G, albedo.B
+    split = find(sliceInfo(ii).name == '.');
+    factoidName = sliceInfo(ii).name(1:split-1);
+    channelName = sliceInfo(ii).name(split+1:end);
+    
+    % initialize factoid output with data array and channel names
+    if ~isfield(factoids, factoidName)
+        factoids.(factoidName).data = ...
+            zeros(factoidSize(1), factoidSize(2), 0);
+        factoids.(factoidName).channels = {};
+    end
+    
+    % insert data and channel name into output for this factoid
+    slice = data(:,:,ii);
+    factoids.(factoidName).data(:,:,end+1) = slice;
+    factoids.(factoidName).channels{end+1} = channelName;
+end
+
diff --git a/BatchRenderer/Renderers/Mitsuba/rtbWriteMitsubaFactoidScene.m b/BatchRenderer/Renderers/Mitsuba/rtbWriteMitsubaFactoidScene.m
new file mode 100644
index 0000000000000000000000000000000000000000..aee2c716bff9dd87fcd663a9db8ce57c0ba4e150
--- /dev/null
+++ b/BatchRenderer/Renderers/Mitsuba/rtbWriteMitsubaFactoidScene.m
@@ -0,0 +1,128 @@
+function factoidFile = rtbWriteMitsubaFactoidScene(originalFile, varargin)
+% Convert the given scene to get factoids instead of ray tracing.
+%
+% factoidFile = rtbWriteMitsubaFactoidScene(originalFile) copies and
+% modifies the given originalFile so that it will produce Mitsuba ground
+% truth "factoids" instead of ray tracing data.  Returns a new Mitsuba
+% scene file based on the given originalFile.
+%
+% The returned sceneFile woill specify a "multichannel" integrator with one
+% or more nested "field" integrators.  You can pass this file to
+% rtbRenderMitsubaFactoids() to obtain the factoid data.
+%
+% rtbWriteMitsubaFactoidScene( ... 'factoids', factoids) specify a cell
+% array of ground truth factoid names to be obtained.  The default includes
+% all available factoids:
+%   - 'position' - absolute position of the object under each pixel
+%   - 'relPosition' - camera-relative position of the object under each pixel
+%   - 'distance' - distance to camera of the object under each pixel
+%   - 'geoNormal' - surface normal at the surface under each pixel
+%   - 'shNormal' - surface normal at the surface under each pixel, interpolated for shading
+%   - 'uv' - texture mapping UV coordinates at the surface under each pixel
+%   - 'albedo' - diffuse reflectance of the object under each pixel
+%   - 'shapeIndex' - integer identifier for the object under each pixel
+%   - 'primIndex' - integer identifier for the triangle or other primitive under each pixel
+%
+% rtbWriteMitsubaFactoidScene( ... 'factoidFormat', factoidFormat) specify
+% a mitsuba pixel format to use for formatting the output, like 'rgb' or
+% 'spectrum'.  The default is 'rgb'.
+%
+% rtbWriteMitsubaFactoidScene( ... 'singleSampling', singleSampling)
+% whether or not to do a simplified rendering with one sample per pixel and
+% a narrow "box" reconstruction filder.  This is useful for labeling
+% factoids like shapeIndex, where it doesn't make sense to average across
+% multiple ray samples.  The default is true, do a simplified rendering.
+%
+%%% RenderToolbox4 Copyright (c) 2012-2017 The RenderToolbox Team.
+%%% About Us://github.com/RenderToolbox/RenderToolbox4/wiki/About-Us
+%%% RenderToolbox4 is released under the MIT License.  See LICENSE file.
+
+parser = inputParser();
+parser.addRequired('originalFile', @ischar);
+parser.addParameter('factoidFile', '', @ischar);
+parser.addParameter('factoids', ...
+    {'position', 'relPosition', 'distance', 'geoNormal', 'shNormal', ...
+    'uv', 'albedo', 'shapeIndex', 'primIndex'}, ...
+    @iscellstr);
+parser.addParameter('factoidFormat', 'rgb', @ischar);
+parser.addParameter('singleSampling', true, @islogical);
+parser.parse(originalFile, varargin{:});
+originalFile = parser.Results.originalFile;
+factoidFile = parser.Results.factoidFile;
+factoids = parser.Results.factoids;
+factoidFormat = parser.Results.factoidFormat;
+singleSampling = parser.Results.singleSampling;
+
+% default output like the input
+if isempty(factoidFile)
+    [factoidPath, factoidBase] = fileparts(originalFile);
+    factoidFile = fullfile(factoidPath, [factoidBase '-factoids.xml']);
+end
+
+
+%% Read the in the scene xml document.
+sceneDocument = xml2struct(originalFile);
+
+
+%% Replace the integrator for multiple "fields".
+
+% "multichannel" integrator to hold several "fields"
+integrator.Attributes.id = 'integrator';
+integrator.Attributes.type = 'multichannel';
+sceneDocument.scene.integrator = integrator;
+
+% nested "field" for each factoid
+nFactoids = numel(factoids);
+fieldIntegrators = cell(1, nFactoids);
+for ff = 1:nFactoids
+    factoidName = factoids{ff};
+    
+    fieldIntegrator = struct();
+    fieldIntegrator.Attributes.name = factoidName;
+    fieldIntegrator.Attributes.type = 'field';
+    fieldIntegrator.string.Attributes.name = 'field';
+    fieldIntegrator.string.Attributes.value = factoidName;
+    
+    fieldIntegrators{ff} = fieldIntegrator;
+end
+sceneDocument.scene.integrator.integrator = fieldIntegrators;
+
+
+%% Replace the film for exr and factoid formats.
+sceneDocument.scene.sensor.film.Attributes.type = 'hdrfilm';
+filmStrings = cell(1, 4);
+filmStrings{1}.Attributes.name = 'componentFormat';
+filmStrings{1}.Attributes.value = 'float16';
+filmStrings{2}.Attributes.name = 'fileFormat';
+filmStrings{2}.Attributes.value = 'openexr';
+
+[formatCell{1:nFactoids}] = deal(factoidFormat);
+formatList = sprintf('%s, ', formatCell{:});
+filmStrings{3}.Attributes.name = 'pixelFormat';
+filmStrings{3}.Attributes.value = formatList(1:end-2);
+
+nameList = sprintf('%s, ', factoids{:});
+filmStrings{4}.Attributes.name = 'channelNames';
+filmStrings{4}.Attributes.value = nameList(1:end-2);
+
+sceneDocument.scene.sensor.film.string = filmStrings;
+
+
+%% Replace the filter and sampler for simplified rendering?
+if singleSampling
+    sampler.Attributes.id = 'sampler';
+    sampler.Attributes.type = 'ldsampler';
+    sampler.integer.Attributes.name = 'sampleCount';
+    sampler.integer.Attributes.value = 1;
+    sceneDocument.scene.sensor.sampler = sampler;
+    
+    rfilter.Attributes.id = 'rfilter';
+    rfilter.Attributes.type = 'box';
+    rfilter.float.Attributes.name = 'radius';
+    rfilter.float.Attributes.value= 0.5;
+    sceneDocument.scene.sensor.film.rfilter = rfilter;
+end
+
+
+%% Write back the scene document.
+struct2xml(sceneDocument, factoidFile);