# SOLVEDSample a shader in 3D space

Hello,

this seems to be an old topic, and I have found several search results for this, but none really helped. I'm trying to sample a shader from within `ObjectData::GetVirtualObjects()`.

It works fine with shaders that are calculated in UV space (e.g. Bitmap Shader, and most of the "Surface" shaders, like Galaxy, Planet, Tiles, Sunburst, etc...), but it does not seem to work with shaders that need a VolumeData (e.g. Surface/Earth, Wood, Marble, etc...). The Noise shader works, regardless of what space I set it to, but as the result does not change when I set it to "World" or "UV", I suspect it's only calculating in UV space.

I tried making up a mock `VolumeData`, but it does not work yet. What am I missing? Is this the preferred way of doing it anyway?

Here is the code:

``````	// Prepare InitRenderStruct
InitRenderStruct irs;
irs.doc = doc;
irs.document_colorprofile = DOCUMENT_COLORPROFILE_SRGB;
irs.flags = INITRENDERFLAG::TEXTURES;
irs.time = doc->GetTime();

return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Could not initialize shader!"_s);

const Float gridWidthI = Inverse((Float)gridWidth);
const Float gridHeightI = Inverse((Float)gridHeight);

// Create an array with as many ChannelDatas as we have threads available
maxon::BaseArray<ChannelData> channelData;

const Float time = doc->GetTime().Get();

// Prepare ChannelDatas, and VolumeDatas for each thread
auto shaderInit = [&channelData, &generationParameters, &time] (maxon::ParallelFor::BreakContext& context) -> maxon::Result<void>
{

// Create mock ChannelData
channelDataRef.n = Vector(0.0, 1.0, 0.0);
channelDataRef.d = Vector(0.001);
channelDataRef.t = time;
channelDataRef.texflag = 0;
channelDataRef.off = 0.0;
channelDataRef.scale = 0.01;

// Create mock VolumeData
VolumeData *volumeData = VolumeData::Alloc();
if (!volumeData)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Could not allocate VolumeData for shader sampling!"_s);
channelDataRef.vd = volumeData;

TexData *texData = TexData::Alloc();
if (!texData)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Could not allocate TexData for shader sampling!"_s);
texData->m = generationParameters.parentGeneratorMg;
texData->im = generationParameters.parentGeneratorMgI;
texData->Init();

volumeData->tex = texData;
volumeData->orign = channelDataRef.n;
volumeData->bumpn = channelDataRef.n;
volumeData->dispn = channelDataRef.n;
volumeData->n = channelDataRef.n;
volumeData->time = time;
volumeData->delta = channelDataRef.d;

return maxon::OK;
};

auto shaderWorker = [&gridRef, &shader, &channelData, &generationParameters, &thread, gridWidth, gridWidthI, gridHeightI] (Int y, maxon::ParallelFor::BreakContext& context) -> maxon::Result<void>
{

for (Int x = 0; x < gridWidth; ++x)
{
sampleCd.p = Vector((Float)x * gridWidthI, (Float)y * gridHeightI, 0.0);
if (sampleCd.vd)
sampleCd.vd->p = Vector(x, y, 0.0);

/*
do something with sampledColor...
*/
}

return maxon::OK;
};

// Free the allocated structures
auto shaderFinalize = [&channelData] (maxon::ParallelFor::BreakContext& context)
{
for (maxon::BaseArray<ChannelData>::Iterator cdIt = channelData.Begin(); cdIt != channelData.End(); ++cdIt)
{
ChannelData &cd = *cdIt;
// TexData::Free(cd.vd->tex):
VolumeData::Free(cd.vd);
}
};

``````

Thanks for any help!

Cheers,
Frank

P.S.: By the way, using this on an "Effects -> Spectral" shader crashes Cinema.

Sorry for the delay, this took me a bit more time than expected.

I would say @zipit is completely right here, but since you are using multi threads you have to attach a FakeVolumeData for each thread since some shader really needs and output their result according to this VolumeData.

Here a complete example.

``````// class that hold per thread sampling data
{
public:

BaseContainer _rdc;
BaseObject* _camera = nullptr;

ChannelData _channelData;
::Ray _ray;
VolumeData* _volumeData = nullptr;

{
if (_camera)
BaseObject::Free(_camera);

if (_volumeData)
VolumeData::Free(_volumeData);
};

void SetSamplingContext(const Vector& uv, const Vector& delta)
{
VolumeData& volumeData = *_volumeData;

const Vector d = Vector(delta.x, delta.y, delta.z);
_channelData.d = d;
volumeData.delta = d;

const Vector p = Vector(uv.x, uv.y, uv.z);

_channelData.p = p;
volumeData.uvw = p;
volumeData.p = p;
volumeData.pp = p;
volumeData.pp = p;
volumeData.pp = p;

_ray.p = p;
_ray.pp = p;
_ray.pp = p;
_ray.pp = p;
}

};

static maxon::Result<void> SampleMaterialBis(BaseDocument* doc)
{
iferr_scope;

MAXON_SCOPE
{
// Get the first material and check its a Cinema 4D default material
BaseMaterial* firstMat = doc->GetFirstMaterial();
CheckState(firstMat != nullptr, "Failed to get the first material");
CheckState(firstMat->IsInstanceOf(Mmaterial), "material is not a default c4d material");

// look for the material BaseContainer and the BaseList2D associated to the color param
BaseContainer* matBC = firstMat->GetDataInstance();
CheckState(matBC != nullptr, "Failed to get the BaseContainer");

CheckState(matColorBL != nullptr, "Failed to get the Color Shader");

}

// Allocate and init the InitRenderStruct
// This is important to init with valid FakeVolumeData since some shaders needs them.
InitRenderStruct irs;
VolumeData* volumeData = VolumeData::Alloc();
BaseObject* camera = BaseObject::Alloc(Ocamera);
CheckState(volumeData != nullptr, "Failed to initialize volume data");
CheckState(camera != nullptr, "Failed to initialize camera");
MAXON_SCOPE
{
BaseContainer renderData;

const Bool attached = C4DOS.Sh->AttachVolumeDataFake(volumeData, camera, renderData, nullptr);
CheckState(attached == true, "Failed to attach volume data");

irs.vd = volumeData;
irs.flags = INITRENDERFLAG::TEXTURES;
irs.time = doc->GetTime();
irs.docpath = doc->GetDocumentPath();

COLORSPACETRANSFORMATION transform = COLORSPACETRANSFORMATION::NONE;
// check if linear workflow is enabled
if (irs.linear_workflow)
transform = COLORSPACETRANSFORMATION::LINEAR_TO_SRGB;

CheckState(initResult == INITRENDERRESULT::OK, "Shader init failed");
}

// Allocate a list of ShaderSampler to store VolumeData and ChannelData per thread
MAXON_SCOPE
{
for (Int32 i = 0; i < GeGetCurrentThreadCount(); i++)
{

sampler->_camera = BaseObject::Alloc(Ocamera);
CheckState(sampler->_camera != nullptr, "Failed to initialize camera");

sampler->_volumeData = VolumeData::Alloc();
CheckState(sampler->_volumeData != nullptr, "Failed to initialize volume data");

const Bool attached = C4DOS.Sh->AttachVolumeDataFake(sampler->_volumeData, sampler->_camera, sampler->_rdc, nullptr);
CheckState(attached == true, "Failed to attach volume data");

samplers.Append(sampler) iferr_return;
}
}

// Worker lambda to build the grid
Int gridSize = 64;

// Create a BaseArray of matrices and colors (to store the results of our sampling, and visualize them in an instanceObject)
maxon::BaseArray<Matrix>	matrices;
maxon::BaseArray<maxon::Color64> colors;

// * 3 because we store the data for x,y,z axis in one big array
matrices.Resize(gridSize * gridSize * gridSize) iferr_return;
colors.Resize(gridSize * gridSize * gridSize) iferr_return;

auto worker = [&samplers, &shader, &matrices, &colors, &gridSize](UInt y)
{
// Retrieve the current sampler data for the current thread
if (sampler == nullptr)
return;

for (Int32 z = 0; z < gridSize; ++z)
{
for (Int32 x = 0; x < gridSize; ++x)
{
// Map 3D array to a 1D array
Int32 arrayId = (z * gridSize * gridSize) + (y * gridSize) + x;
matrices[arrayId] = MatrixMove(Vector(x, y, z));

// Define where we want to sample
Vector uvw = Vector(
double(x) / double(gridSize),
double(y) / double(gridSize),
double(z) / double(gridSize)
);

Vector delta = Vector(0.01, 0.01, 0.01);

sampler->SetSamplingContext(uvw, delta);

// sample

// Store the color result of the sampling operation
colors[arrayId] = maxon::Color64(sampledValue.x, sampledValue.y, sampledValue.z);
}
}
};

// Execute worker

// Free samplers
MAXON_SCOPE
{
for (Int32 i = 0; i < GeGetCurrentThreadCount(); i++)
{
if (sampler != nullptr)
{
DeleteObj(sampler);
samplers[i] = nullptr;
}
}
}

// call the FreeRender to release allocated memory used for sampling

VolumeData::Free(volumeData);
BaseObject::Free(camera);

// Create a Multi-Instance object with the matrix and the color information to visualize them
InstanceObject* const instance = InstanceObject::Alloc();
CheckState(instance != nullptr, "Failed to allocate an Instance Object");
MAXON_SCOPE
{
doc->InsertObject(instance, nullptr, nullptr);

// Set to multi instance + to Points mode
if (!instance->SetParameter(INSTANCEOBJECT_RENDERINSTANCE_MODE, INSTANCEOBJECT_RENDERINSTANCE_MODE_MULTIINSTANCE, DESCFLAGS_SET::NONE))
return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

if (!instance->SetParameter(INSTANCEOBJECT_DRAW_MODE, INSTANCEOBJECT_DRAW_MODE_POINTS, DESCFLAGS_SET::NONE))
return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

// Set data in the instance object
instance->SetInstanceMatrices(matrices) iferr_return;
instance->SetInstanceColors(colors) iferr_return;
}

return maxon::OK;
}
``````

Cheers,
Maxime.

No solution to your question, but there is information in the manuals: Sampling a Shader.

And also some hints regarding VolumeData here: InitRenderStruct::vd.

Yeah, I guess `Render::GetInitialVolumeData()`, which is mentioned in your second link, would be exactly what I need here... However, in `ObjectData::GetVirtualObjects()`, there is no such thing, so I have to make up my own data...

Hi,

I am not quite sure if I understand your question correctly and also am unclear on what you would consider "not working" in the context of sampling a volumetric shader.

However, what stood out to me in your code, is that you only do sample in a slice of the volume, basically treat the shader like a 2D-Shader, which might have side effects as your sampled shaders might simply only return the zero vector in that slice (here comes up the question of what is not working).

`````` sampleCd.p = Vector((Float)x * gridWidthI, (Float)y * gridHeightI, 0.0);
``````

Poking around in the Script Manager, I did not had any problems sampling the `Xwood` shader, at least the output looks meaningful.

Cheers,
zipit

``````import c4d

def main():
"""
"""
irs = c4d.modules.render.InitRenderStruct(doc)

for i in range(10):
n = i / 9.
cd = c4d.modules.render.ChannelData()
cd.p = c4d.Vector(0, 0, n)

if __name__=='__main__':
main()
``````
``````Vector(0.187, 0.077, 0.03)
Vector(0.171, 0.07, 0.028)
Vector(0.091, 0.038, 0.015)
Vector(0.163, 0.067, 0.027)
Vector(0.255, 0.105, 0.041)
Vector(0.275, 0.113, 0.044)
Vector(0.314, 0.13, 0.05)
Vector(0.249, 0.103, 0.04)
Vector(0.095, 0.039, 0.016)
Vector(0.318, 0.131, 0.05)
``````

@zipit said in Sample a shader in 3D space:

I am not quite sure if I understand your question correctly and also am unclear on what you would consider "not working" in the context of sampling a volumetric shader.

I get only one value, regardless of the sample coordinates.

However, what stood out to me in your code, is that you only do sample in a slice of the volume, basically treat the shader like a 2D-Shader, which might have side effects as your sampled shaders might simply only return the zero vector in that slice (here comes up the question of what is not working).

`````` sampleCd.p = Vector((Float)x * gridWidthI, (Float)y * gridHeightI, 0.0);
``````

`z` is `0.0` because I'm sampling at positions on a plane. `ChannelData::p` is, according to the SDK docs, only in UVW space, anyway (that's why I multiply the X and Y grid coordinate with the inverse X and Y size, so my actual sample values are in the range of `0.0` ... `1.0`). And sampling those positions works. For 3D shaders, as far as I always understood it, the `sampleCd.vd->p` is the one that counts, as that is actually a position in 3D space.

def main():
"""
"""
irs = c4d.modules.render.InitRenderStruct(doc)

``````for i in range(10):
n = i / 9.
cd = c4d.modules.render.ChannelData()
cd.p = c4d.Vector(0, 0, n)
``````

if name=='main':
main()
Vector(0.187, 0.077, 0.03)
Vector(0.171, 0.07, 0.028)
Vector(0.091, 0.038, 0.015)
Vector(0.163, 0.067, 0.027)
Vector(0.255, 0.105, 0.041)
Vector(0.275, 0.113, 0.044)
Vector(0.314, 0.13, 0.05)
Vector(0.249, 0.103, 0.04)
Vector(0.095, 0.039, 0.016)
Vector(0.318, 0.131, 0.05)

Hmm, that looks meaningful in deed. I still wonder why I get nothing out of the Wood shader and others.

Hm,

I might be wrong about this, but I always thought that `VolumeData.p` is a sample position in object space and `ChannelData.p` is the sample position in uvw space. The latter would lie in the half interval uv(w) coordinates are normally defined in, while the former would lie in an arbitrary interval, depending on the bounding box of the sampled geometry (i.e. `0 <= c <= 1` is not guaranteed to be met for the components of `VolumeData.p`). So a geometry-agnostic shader should not care about `VolumeData.p`.

Also note that I did deliberately only sample on the z-axis in my example and still get a varying output. If your premise would be true, I should get the same vector for all ten calls.

Cheers,
zipit

Hi sorry for not answering you earlier, I just wanted to inform you I reached the development team about it especially since you use multi-threading, and sampling a shader with Multiple thread is different (for example in my case your code does not work even on a 2D gradient).
I have a property but which doesn't support all shaders (especially Layer shader).

So I will keep you in touch when I have more news.
Cheers,
Maxime.

Sorry for the delay, this took me a bit more time than expected.

I would say @zipit is completely right here, but since you are using multi threads you have to attach a FakeVolumeData for each thread since some shader really needs and output their result according to this VolumeData.

Here a complete example.

``````// class that hold per thread sampling data
{
public:

BaseContainer _rdc;
BaseObject* _camera = nullptr;

ChannelData _channelData;
::Ray _ray;
VolumeData* _volumeData = nullptr;

{
if (_camera)
BaseObject::Free(_camera);

if (_volumeData)
VolumeData::Free(_volumeData);
};

void SetSamplingContext(const Vector& uv, const Vector& delta)
{
VolumeData& volumeData = *_volumeData;

const Vector d = Vector(delta.x, delta.y, delta.z);
_channelData.d = d;
volumeData.delta = d;

const Vector p = Vector(uv.x, uv.y, uv.z);

_channelData.p = p;
volumeData.uvw = p;
volumeData.p = p;
volumeData.pp = p;
volumeData.pp = p;
volumeData.pp = p;

_ray.p = p;
_ray.pp = p;
_ray.pp = p;
_ray.pp = p;
}

};

static maxon::Result<void> SampleMaterialBis(BaseDocument* doc)
{
iferr_scope;

MAXON_SCOPE
{
// Get the first material and check its a Cinema 4D default material
BaseMaterial* firstMat = doc->GetFirstMaterial();
CheckState(firstMat != nullptr, "Failed to get the first material");
CheckState(firstMat->IsInstanceOf(Mmaterial), "material is not a default c4d material");

// look for the material BaseContainer and the BaseList2D associated to the color param
BaseContainer* matBC = firstMat->GetDataInstance();
CheckState(matBC != nullptr, "Failed to get the BaseContainer");

CheckState(matColorBL != nullptr, "Failed to get the Color Shader");

}

// Allocate and init the InitRenderStruct
// This is important to init with valid FakeVolumeData since some shaders needs them.
InitRenderStruct irs;
VolumeData* volumeData = VolumeData::Alloc();
BaseObject* camera = BaseObject::Alloc(Ocamera);
CheckState(volumeData != nullptr, "Failed to initialize volume data");
CheckState(camera != nullptr, "Failed to initialize camera");
MAXON_SCOPE
{
BaseContainer renderData;

const Bool attached = C4DOS.Sh->AttachVolumeDataFake(volumeData, camera, renderData, nullptr);
CheckState(attached == true, "Failed to attach volume data");

irs.vd = volumeData;
irs.flags = INITRENDERFLAG::TEXTURES;
irs.time = doc->GetTime();
irs.docpath = doc->GetDocumentPath();

COLORSPACETRANSFORMATION transform = COLORSPACETRANSFORMATION::NONE;
// check if linear workflow is enabled
if (irs.linear_workflow)
transform = COLORSPACETRANSFORMATION::LINEAR_TO_SRGB;

CheckState(initResult == INITRENDERRESULT::OK, "Shader init failed");
}

// Allocate a list of ShaderSampler to store VolumeData and ChannelData per thread
MAXON_SCOPE
{
for (Int32 i = 0; i < GeGetCurrentThreadCount(); i++)
{

sampler->_camera = BaseObject::Alloc(Ocamera);
CheckState(sampler->_camera != nullptr, "Failed to initialize camera");

sampler->_volumeData = VolumeData::Alloc();
CheckState(sampler->_volumeData != nullptr, "Failed to initialize volume data");

const Bool attached = C4DOS.Sh->AttachVolumeDataFake(sampler->_volumeData, sampler->_camera, sampler->_rdc, nullptr);
CheckState(attached == true, "Failed to attach volume data");

samplers.Append(sampler) iferr_return;
}
}

// Worker lambda to build the grid
Int gridSize = 64;

// Create a BaseArray of matrices and colors (to store the results of our sampling, and visualize them in an instanceObject)
maxon::BaseArray<Matrix>	matrices;
maxon::BaseArray<maxon::Color64> colors;

// * 3 because we store the data for x,y,z axis in one big array
matrices.Resize(gridSize * gridSize * gridSize) iferr_return;
colors.Resize(gridSize * gridSize * gridSize) iferr_return;

auto worker = [&samplers, &shader, &matrices, &colors, &gridSize](UInt y)
{
// Retrieve the current sampler data for the current thread
if (sampler == nullptr)
return;

for (Int32 z = 0; z < gridSize; ++z)
{
for (Int32 x = 0; x < gridSize; ++x)
{
// Map 3D array to a 1D array
Int32 arrayId = (z * gridSize * gridSize) + (y * gridSize) + x;
matrices[arrayId] = MatrixMove(Vector(x, y, z));

// Define where we want to sample
Vector uvw = Vector(
double(x) / double(gridSize),
double(y) / double(gridSize),
double(z) / double(gridSize)
);

Vector delta = Vector(0.01, 0.01, 0.01);

sampler->SetSamplingContext(uvw, delta);

// sample

// Store the color result of the sampling operation
colors[arrayId] = maxon::Color64(sampledValue.x, sampledValue.y, sampledValue.z);
}
}
};

// Execute worker

// Free samplers
MAXON_SCOPE
{
for (Int32 i = 0; i < GeGetCurrentThreadCount(); i++)
{
if (sampler != nullptr)
{
DeleteObj(sampler);
samplers[i] = nullptr;
}
}
}

// call the FreeRender to release allocated memory used for sampling

VolumeData::Free(volumeData);
BaseObject::Free(camera);

// Create a Multi-Instance object with the matrix and the color information to visualize them
InstanceObject* const instance = InstanceObject::Alloc();
CheckState(instance != nullptr, "Failed to allocate an Instance Object");
MAXON_SCOPE
{
doc->InsertObject(instance, nullptr, nullptr);

// Set to multi instance + to Points mode
if (!instance->SetParameter(INSTANCEOBJECT_RENDERINSTANCE_MODE, INSTANCEOBJECT_RENDERINSTANCE_MODE_MULTIINSTANCE, DESCFLAGS_SET::NONE))
return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

if (!instance->SetParameter(INSTANCEOBJECT_DRAW_MODE, INSTANCEOBJECT_DRAW_MODE_POINTS, DESCFLAGS_SET::NONE))
return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

// Set data in the instance object
instance->SetInstanceMatrices(matrices) iferr_return;
instance->SetInstanceColors(colors) iferr_return;
}

return maxon::OK;
}
``````

Cheers,
Maxime.

Thank you very much! I'll try that out ASAP!

Cheers,
Frank

Almost perfect, thanks. It kinda only works half ok, I noticed.

The thing is, all shaders are always sampled in UV space. I tried adding the 3D position data for sampling, too, by setting `volumeData.p` to the global position of the point to sample, in `SetSamplingContext()`. but it is being ignored. For example, if the Shader is a Noise shader, I can set it to whatever space I want, the result is always the same.

With my old code, that worked. It just couldn't sample certain shaders (Layer Shader, Earth Shader, etc). Now, those shaders work, but sampling seems restricted to UV space. How can I sample those shaders in Texture Space, Object Space, and World Space (Camera, Screen, and the others can be neglected)?
{"ft_lang_label":"__label__en","ft_lang_prob":0.5933603,"math_prob":0.9422185,"size":17206,"snap":"2021-31-2021-39","text_gpt3_token_len":4656,"char_repetition_ratio":0.14719218,"word_repetition_ratio":0.1939694,"special_character_ratio":0.27205625,"punctuation_ratio":0.25874335,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9774532,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2021-07-24T09:09:00Z\",\"WARC-Record-ID\":\"<urn:uuid:e24e2f1f-af48-4ad0-828e-693c10292cd3>\",\"Content-Length\":\"153287\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:93383a62-db65-4eea-9344-1a4eab76b1de>\",\"WARC-Concurrent-To\":\"<urn:uuid:253f1d9f-4ead-4f49-87eb-90685b3b7dda>\",\"WARC-IP-Address\":\"172.67.14.129\",\"WARC-Target-URI\":\"https://plugincafe.maxon.net/topic/12521/sample-a-shader-in-3d-space\",\"WARC-Payload-Digest\":\"sha1:CAPDYH3VTEALZC3BVTDXYGIX3OZRNSZ4\",\"WARC-Block-Digest\":\"sha1:2ZAKN4BBZ2NMJ6RLMBUMWDFT3SYYJ3HF\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2021/CC-MAIN-2021-31/CC-MAIN-2021-31_segments_1627046150134.86_warc_CC-MAIN-20210724063259-20210724093259-00050.warc.gz\"}"}