Maya C++ API create MImage from viewport and camera

Hello,

I am developing a tool for Maya with the OpenMaya API in C++.
My goal is to retrieve an image from a specific camera and specific objects, for testing purposes I would like to have a viewport image.

My first question is to know if there is a way to do this through the Maya API only ? Or should I pass by lets say OpenGL to render my own image ?

If it is preferable for me to use OpenGL, is there a way to retrieve Vertex Buffer from a specific object, so I can use this, its World Matrix and the camera’s World Matrix to generate the image ?

Thanks a lot for your help, please let me know if that’s unclear :slight_smile:

Cheers !

Greetings!
To begin with, you can get acquainted with the functionality of the M3dView class
This class contains functions that meet many of your needs:
OpenMayaUI.M3dView Class Reference

Tiny demo:

import maya.cmds as cmds
import maya.api.OpenMaya as om2
import maya.api.OpenMayaUI as omui2

def maya_useNewAPI():
    pass

# or:
# maya_useNewAPI = True


image_format = "jpg"
image_snapshot_01 = r'c:\temp\shapshot_01.jpg'
image_snapshot_02 = r'c:\temp\shapshot_02.jpg'

cmds.setFocus('modelPanel4')
cmds.toggleAxis(origin = false)

current_view = omui2.M3dView.active3dView()
current_image = om2.MImage()
current_image.create(current_view.portWidth(), current_view.portHeight())

cmds.modelEditor('modelPanel4',
                  edit = True,
                  grid = False,
    useDefaultMaterial = True,
     wireframeOnShaded = True)

current_view.refresh(force = True, all = False)
current_view.readColorBuffer(current_image, True)
current_image.writeToFile(image_snapshot_01, outputFormat = image_format)


cmds.modelEditor('modelPanel4',
                  edit = True,
    useDefaultMaterial = False,
     wireframeOnShaded = False)

current_view.refresh(force = True, all = False)
current_view.readColorBuffer(current_image, True)
current_image.writeToFile(image_snapshot_02, outputFormat = image_format)

cmds.modelEditor('modelPanel4', edit = True, grid = True)
current_view.refresh(force = True, all = False)

Depending on the goals and conditions (Viewport 2.0. / OpenGL\DX / legacy default viewport), for effective processing you will have to use both OpenMayaUI, and OpenMayaRender, and OpenGL.GL :slight_smile:

Here are links to great examples from the documentation for maya.api.OpenMaya.*:

python/api2/py2ViewRenderOverride.py

python/api2/py2UiDrawManager.py

python/api2/py2FootPrintNode.py

python/api2/py2FootPrintNode_GeometryOverride.py

python/api2/py2FootPrintNode_GeometryOverride_AnimatedMaterial.py

python/api2/py2DrawFootPrintbyRenderUtilities.py

N.B.

Almost all examples (directly or indirectly) use the Viewport 2.0 API.
A good starting point is this documentation:

Viewport 2.0 API

Harmony and good luck!

1 Like

Thanks a lot for your answer.

I have tried to use this method with C++ and the M3dView.readColorBuffer is deprecated since Maya 2019. I am supposed to be using MHWRender::MRenderTargetManager::acquireRenderTarget instead, but I am having a really hard time to set this up.

At the moment I have something like this, but I am probably missing something because it fails to create the MRenderOverride and it displays the error “Failed to find viewport render override.” :

void captureViewportImage(const MString& filePath) {
	MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer();
	if (!renderer) {
		MGlobal::displayError("Viewport 2.0 renderer not available.");
		return;
	}

	const MString& overrideName = renderer->activeRenderOverride();
	const MHWRender::MRenderOverride* override = renderer->findRenderOverride(overrideName);
	if (!override) {
		MGlobal::displayError("Failed to find viewport render override.");
		return;
	}

	const MHWRender::MRenderTargetManager* targetManager = renderer->getRenderTargetManager();
	MHWRender::MRenderTargetDescription targetDesc;
	MHWRender::MRenderTarget* renderTarget = targetManager->acquireRenderTarget(targetDesc);
	if (!renderTarget) {
		MGlobal::displayError("Failed to acquire render target.");
		return;
	}

	MHWRender::MDrawContext* context = MHWRender::MRenderUtilities::acquireSwatchDrawContext(renderTarget);
	MHWRender::MTextureManager* textureManager = renderer->getTextureManager();
	MHWRender::MTexture* colorTexture = context->copyCurrentColorRenderTargetToTexture();
	textureManager->saveTexture(colorTexture, filePath);

	targetManager->releaseRenderTarget(renderTarget);
}

I am new to all this viewport stuff so I am probably missing something, and not understanding how it actually works.

Who would have thought… (s)
Yes, this is true for OpenMaya-1/C++.
But in maya.api.OpenMaya (OpenMaya-2) this works correctly with Viewport 2, the function is automatically completed correctly.
Here’s a recommendation from the Maya developers themselves:
Get image from M3dView::readColorBuffer in Viewport 2

import maya.OpenMaya as openmaya
import maya.OpenMayaUI as openmayaUI

view = openmayaUI.M3dView.active3dView()
img = openmaya.MImage()
if view.getRendererName() == view.kViewport2Renderer:       
    img .create(view.portWidth(), view.portHeight(), 4, openmaya.MImage.kFloat)
    view.readColorBuffer(img)
    img.convertPixelFormat(openmaya.MImage.kByte)
else:
    view.readColorBuffer(img)

Despite the fact that the entire M3dView class (in the latest Viewport 2.0 API documentation) is positioned as “deprecated”, in the examples (the latest Viewport 2.0 API documentation) this class is used very actively. :slight_smile:
Because for simple needs, this method is simpler and just as effective.

To my great regret, I cannot help you with checking the C++ code :frowning:
Maybe something like this:

_OPENMAYA_DEPRECATION_PUSH_AND_DISABLE_WARNING
view.readColorBuffer(img);
_OPENMAYA_POP_WARNING
1 Like

I managed to do the first step that I wanted to begin with, the previous messaged helped a lot so I got this bit of code:

M3dView active_view = M3dView::active3dView();

MImage img;
active_view.pushViewport(0, 0, active_view.portWidth(), active_view.portHeight());
active_view.refresh();
_OPENMAYA_DEPRECATION_PUSH_AND_DISABLE_WARNING
active_view.readColorBuffer(img);
_OPENMAYA_POP_WARNING
active_view.popViewport();
img.verticalFlip();

Now the next I am trying to do is to get the image from another camera, a camera that I am not looking from. I am not sure the viewport 2.0 has function that allows for rendering a viewport image from a specific camera, so I decided to render my own image through OpenGL and so far this the code I have:

MImage img;

_OPENMAYA_DEPRECATION_PUSH_AND_DISABLE_WARNING
MGLFunctionTable *gGLFT = MHardwareRenderer::theRenderer()->glFunctionTable();
  
if (!gGLFT) {
    return;
}

int size = 512;

MGLuint texID, fboID, rboID;

gGLFT->glDisable(MGL_BLEND);

gGLFT->glGenTextures(1, &texID);
gGLFT->glBindTexture(MGL_TEXTURE_2D, texID);
gGLFT->glTexImage2D(MGL_TEXTURE_2D, 0, MGL_RGBA8, size, size, 0, MGL_RGBA, MGL_UNSIGNED_BYTE, 0);
gGLFT->glBindTexture(MGL_TEXTURE_2D, 0);

gGLFT->glGenFramebuffersEXT(1, &fboID);
gGLFT->glBindFramebufferEXT(MGL_FRAMEBUFFER, fboID);
gGLFT->glFramebufferTexture2DEXT(MGL_FRAMEBUFFER, MGL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0);

gGLFT->glViewport(0, 0, size, size);

gGLFT->glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
gGLFT->glClear(MGL_COLOR_BUFFER_BIT | MGL_DEPTH_BUFFER_BIT);

gGLFT->glBegin(MGL_TRIANGLES);
gGLFT->glColor4f(1.0f, 0.0f, 0.0f, 1.0f);

gGLFT->glVertex2f(-0.5f, -0.5f);
gGLFT->glVertex2f(0.5f, -0.5f, 0.0f);
gGLFT->glVertex2f(0.0f, 0.5f, 0.0f);

gGLFT->glEnd();
 
unsigned char *image_buffer = new unsigned char[size * size * 4];
gGLFT->glReadPixels(0, 0, size, size, MGL_RGBA, MGL_UNSIGNED_BYTE, image_buffer);

img.setPixels(image_buffer, size, size);
img.writeToFile("......../test.jpg", "jpg");

gGLFT->glBindFramebufferEXT(MGL_FRAMEBUFFER, MGL_NONE);
_OPENMAYA_POP_WARNING

This code is executed inside a node that I am creating, but the output image changes if I have the hypershade open:

It looks like it picks up an already existing viewport with its coordinates system, so my question is, is that possible to create another, invisible, OpenGL viewport with default projection, modelview… matrices so I can render the image from it ?

Thank you for your help :slight_smile:
Cheers !