0% found this document useful (0 votes)
22 views7 pages

Unity Volume Rendering Script Guide

Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
22 views7 pages

Unity Volume Rendering Script Guide

Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

using System.

Threading;
using [Link];
using UnityEditor;
using UnityEngine;

namespace UnityVolumeRendering
{
[ExecuteInEditMode]
public class VolumeRenderedObject : MonoBehaviour
{
[SerializeField, HideInInspector]
public TransferFunction transferFunction;

[SerializeField, HideInInspector]
public TransferFunction2D transferFunction2D;

[SerializeField, HideInInspector]
public VolumeDataset dataset;

[SerializeField, HideInInspector]
public MeshRenderer meshRenderer;

[SerializeField, HideInInspector]
public GameObject volumeContainerObject;

[SerializeField, HideInInspector]
private RenderMode renderMode;
[SerializeField, HideInInspector]
private TFRenderMode tfRenderMode;
[SerializeField, HideInInspector]
private bool lightingEnabled;
[SerializeField, HideInInspector]
private LightSource lightSource;

[SerializeField, HideInInspector]
private Vector2 visibilityWindow = new Vector2(0.0f, 1.0f);
[SerializeField, HideInInspector]
private bool rayTerminationEnabled = true;
[SerializeField, HideInInspector]
private bool cubicInterpolationEnabled = false;

private CrossSectionManager crossSectionManager;

private SemaphoreSlim updateMatLock = new SemaphoreSlim(1, 1);

public SlicingPlane CreateSlicingPlane()


{
GameObject sliceRenderingPlane =
[Link]([Link]<GameObject>("SlicingPlane"));
[Link] =
[Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link] * 0.1f; // TODO:
Change the plane mesh instead and use [Link]
MeshRenderer sliceMeshRend =
[Link]<MeshRenderer>();
[Link] = new Material([Link]);
Material sliceMat =
[Link]<MeshRenderer>().sharedMaterial;
[Link]("_DataTex", [Link]());
[Link]("_TFTex", [Link]());
[Link]("_parentInverseMat", [Link]);
[Link]("_planeMat",
[Link]([Link],
[Link], [Link])); // TODO: allow
changing scale

SlicingPlane slicingPlaneComp =
[Link]<SlicingPlane>();
[Link] = this;
return slicingPlaneComp;
}

public void SetRenderMode(RenderMode mode)


{
Task task = SetRenderModeAsync(mode);
}

public async Task SetRenderModeAsync(RenderMode mode, IProgressHandler


progressHandler = null)
{
if (renderMode != mode)
{
renderMode = mode;
SetVisibilityWindow(0.0f, 1.0f); // reset visibility window
}
await UpdateMaterialPropertiesAsync(progressHandler);
}

public void SetTransferFunctionMode(TFRenderMode mode)


{
Task task = SetTransferFunctionModeAsync(mode);
}

public async Task SetTransferFunctionModeAsync(TFRenderMode mode,


IProgressHandler progressHandler = null)
{
if (progressHandler == null)
progressHandler = [Link];

[Link](0.3f, "Generating transfer function


texture");
tfRenderMode = mode;
if (tfRenderMode == TFRenderMode.TF1D && transferFunction != null)
[Link]();
else if (transferFunction2D != null)
[Link]();
[Link]();

[Link](0.7f, "Updating material properties");


await UpdateMaterialPropertiesAsync(progressHandler);
[Link]();
}

public TFRenderMode GetTransferFunctionMode()


{
return tfRenderMode;
}

public RenderMode GetRenderMode()


{
return renderMode;
}

public bool GetLightingEnabled()


{
return lightingEnabled;
}

public LightSource GetLightSource()


{
return lightSource;
}

public CrossSectionManager GetCrossSectionManager()


{
if (crossSectionManager == null)
crossSectionManager = GetComponent<CrossSectionManager>();
if (crossSectionManager == null)
crossSectionManager =
[Link]<CrossSectionManager>();
return crossSectionManager;
}

public void SetLightingEnabled(bool enable)


{
if (enable != lightingEnabled)
{
lightingEnabled = enable;
UpdateMaterialProperties();
}
}

public async Task SetLightingEnabledAsync(bool enable, IProgressHandler


progressHandler = null)
{
if (enable != lightingEnabled)
{
lightingEnabled = enable;
await UpdateMaterialPropertiesAsync(progressHandler);
}
}

public void SetLightSource(LightSource source)


{
lightSource = source;
UpdateMaterialProperties();
}

public void SetVisibilityWindow(float min, float max)


{
SetVisibilityWindow(new Vector2(min, max));
}

public void SetVisibilityWindow(Vector2 window)


{
if (window != visibilityWindow)
{
visibilityWindow = window;
UpdateMaterialProperties();
}
}

public Vector2 GetVisibilityWindow()


{
return visibilityWindow;
}

public bool GetRayTerminationEnabled()


{
return rayTerminationEnabled;
}

public void SetRayTerminationEnabled(bool enable)


{
if (enable != rayTerminationEnabled)
{
rayTerminationEnabled = enable;
UpdateMaterialProperties();
}
}

[[Link]("Back-to-front rendering no longer supported")]


public bool GetDVRBackwardEnabled()
{
return false;
}

[[Link]("Back-to-front rendering no longer supported")]


public void SetDVRBackwardEnabled(bool enable)
{
[Link]("Back-to-front rendering no longer supported");
}

public bool GetCubicInterpolationEnabled()


{
return cubicInterpolationEnabled;
}

public void SetCubicInterpolationEnabled(bool enable)


{
if (enable != cubicInterpolationEnabled)
{
cubicInterpolationEnabled = enable;
UpdateMaterialProperties();
}
}

public void SetTransferFunction(TransferFunction tf)


{
[Link] = tf;
UpdateMaterialProperties();
}

public async Task SetTransferFunctionAsync(TransferFunction tf,


IProgressHandler progressHandler = null)
{
if ([Link] == null)
{
[Link] = new
Material([Link]("VolumeRendering/DirectVolumeRenderingShader"));
[Link]("_DataTex",
[Link]());
}
if (transferFunction == null)
{
transferFunction =
[Link]();
}

[Link] = tf;
await UpdateMaterialPropertiesAsync(progressHandler);
}

private void UpdateMaterialProperties(IProgressHandler progressHandler =


null)
{
Task task = UpdateMaterialPropertiesAsync(progressHandler);
}

private async Task UpdateMaterialPropertiesAsync(IProgressHandler


progressHandler = null)
{
await [Link]();

try
{
bool useGradientTexture = tfRenderMode == TFRenderMode.TF2D ||
renderMode == [Link] || lightingEnabled;
Texture3D texture = useGradientTexture ? await
[Link](progressHandler) : null;
[Link]("_GradientTex", texture);
UpdateMatInternal();
}
finally
{
[Link]();
}
}

private void UpdateMatInternal()


{
if (tfRenderMode == TFRenderMode.TF2D)
{
[Link]("_TFTex",
[Link]());
[Link]("TF2D_ON");
}
else
{
[Link]("_TFTex",
[Link]());
[Link]("TF2D_ON");
}
if (lightingEnabled)
[Link]("LIGHTING_ON");
else
[Link]("LIGHTING_ON");

if (lightSource == [Link])
[Link]("USE_MAIN_LIGHT");
else
[Link]("USE_MAIN_LIGHT");

switch (renderMode)
{
case [Link]:
{
[Link]("MODE_DVR");
[Link]("MODE_MIP");
[Link]("MODE_SURF");
break;
}
case [Link]:
{
[Link]("MODE_DVR");
[Link]("MODE_MIP");
[Link]("MODE_SURF");
break;
}
case [Link]:
{
[Link]("MODE_DVR");
[Link]("MODE_MIP");
[Link]("MODE_SURF");
break;
}
}

[Link]("_MinVal", visibilityWindow.x);
[Link]("_MaxVal", visibilityWindow.y);
[Link]("_TextureSize", new
Vector3([Link], [Link], [Link]));

if (rayTerminationEnabled)
[Link]("RAY_TERMINATE_ON");
else
[Link]("RAY_TERMINATE_ON");

if (cubicInterpolationEnabled)

[Link]("CUBIC_INTERPOLATION_ON");
else

[Link]("CUBIC_INTERPOLATION_ON");
}

private void Awake()


{
// TODO: Remove this after some time. This is to avoid breaking old
serialised objects from before volumeContainerObject was added.
EnsureVolumeContainerRef();
}

private void Start()


{
UpdateMaterialProperties();
}

public void OnValidate()


{
// TODO: Remove this after some time. This is to avoid breaking old
serialised objects from before volumeContainerObject was added.
EnsureVolumeContainerRef();
}

private void EnsureVolumeContainerRef()


{
if (volumeContainerObject == null)
{
[Link]("VolumeContainer missing. This is expected if the
object was saved with an old version of the plugin. Please re-save it.");
Transform trans = [Link]("VolumeContainer");
if (trans == null)
trans =
[Link]<MeshRenderer>(true)?.transform;
if (trans)
volumeContainerObject = [Link];
}
}
}
}

Common questions

Powered by AI

The VolumeRenderedObject class uses a SemaphoreSlim named updateMatLock to manage concurrency when updating material properties asynchronously. This ensures that only one asynchronous operation can update the material properties at a time, preventing race conditions and inconsistent states .

The CreateSlicingPlane method instantiates a GameObject from a resource template named "SlicingPlane." It sets the transform properties to effectively integrate the plane into the volume container, and adjusts the material properties by using the GetDataTexture and GetTexture methods to pass data and transfer function textures to the shader. This configuration allows the plane to act as a slicer in the volumetric dataset, offering exploratory visual dissection capabilities .

Ray termination, controlled by the rayTerminationEnabled boolean, allows for optimized rendering by stopping rays early once certain conditions are met, which can improve performance and reduce unnecessary calculations. Cubic interpolation, enabled through the cubicInterpolationEnabled flag, ensures smoother transitions between voxel samples, enhancing the visual quality by reducing aliasing and providing softer edges in the rendered output .

The VolumeRenderedObject class utilizes placeholder methods such as EnsureVolumeContainerRef() in Awake() and OnValidate() to maintain backward compatibility with older serialized data. It checks for missing references due to prior plugin versions, issues warnings, and attempts to auto-resolve these references if possible. These strategies help prevent breaking changes when updates introduce new fields or structure changes in serialized GameObjects .

The VolumeRenderedObject class makes extensive use of the Unity MeshRenderer to apply and configure shaders for volumetric rendering. It sets materials dynamically with textures derived from volume dataset data and transfer functions. Keywords in materials are toggled to accommodate rendering techniques such as transfer function variations, lighting conditions, and rendering modes, thus enabling a seamless transition between different visualizations .

The VolumeRenderedObject class leverages asynchronous methods such as SetTransferFunctionAsync to handle transfer function updates, employing the async/await pattern for non-blocking operations. UpdateMaterialPropertiesAsync is tasked with property updates, coordinating with the sync context using SemaphoreSlim for concurrency control. This approach integrates well with Unity’s event-driven model, ensuring responsive UI while maintaining data integrity during updates .

Using a progress handler in asynchronous methods provides vital user feedback during potentially lengthy operations. In VolumeRenderedObject, operations like SetRenderModeAsync and SetTransferFunctionModeAsync leverage IProgressHandler to report stages of progress to the user. This UI feedback prevents user confusion during stalls, improves perceived performance, and enhances the usability of the application by maintaining engagement and awareness of ongoing processes .

The VolumeRenderedObject class includes a boolean field lightingEnabled to determine whether lighting is applied in the rendering process. When lighting is enabled, specific keywords such as LIGHTING_ON are set on the material, allowing it to react to lighting in the scene. This affects the visual output by introducing light and shadow effects, enhancing the realism of the rendered volume .

Changing the render mode in VolumeRenderedObject affects how data is visually represented. Each mode, such as Direct Volume Rendering or Isosurface Rendering, activates different shader keywords and toggles visual aspects like depth cueing and occlusion. The render mode sets asynchronously with the SetRenderModeAsync method, allowing for UI progress feedback and non-blocking execution. It resets the visibility window to default as part of its mode transition procedure .

In the VolumeRenderedObject class, transfer functions define how voxel data maps to colors and opacities, significantly influencing the appearance of the rendered volume. The mode of the transfer function, which can be either 1D or 2D, is determined by the tfRenderMode property. Functions are generated using GenerateTexture() methods contingent on the selected TFRenderMode, influencing aspects like shading and texture representation in the rendering process .

You might also like