VideoTexture: new ImageRender class for Render To Texture

The new class VideoTexture.ImageRender() is available to perform
render to texture in the GE.

Constructor:

  VideoTexture.ImageRender(scene,cam)
    cam  : camera object that will be used for the render.
           It must be an inactive camera.
    scene: reference to the scene that will be rendered.
           The camera must be part of that scene.
  Returns an object that can be used as a source of a VideoTexture.Texture object

Methods: none

Attributes:

  background: 
     4-tuple representing the background color of the rendering
     as RGBA color components, each component being an integer 
     between 0 and 255. 
     Default value = [0,0,255,255] (=saturated blue)
     Note: athough the alpha component can be specified, it is not
           supported at the moment, the alpha channel of the rendered
           texture will always be 255. You can however introduce an
           alpha channel by appending a FilterBlueScreen() filter, it
           will set the alpha to 0 (transparent) on all pixels that were
           not rendered.

  capsize:
     2-tuple representing the size of the render area as [x,y] number of pixels.
     Default value = largest rectangle with power of 2 dimensions that fits in the canvas 
     You may want to reduce the render area to increase performance. For example,
     a render area of [256,128] is probably sufficient to implement a car inner mirror.
     For best performance, use power of 2 dimensions and don't set any filter: this
     allows direct transfer between the GPU frame buffer and texture memory
     without going through the host.

  alpha: 
     Boolean indicating if the render alpha channel should be copied to the texture.
     Default value: False
     Experimental, do not use.

  whole:
     Boolean indicating if the entire canvas should be used for the rendering. 
     Default value: False
     Note: There is no reason to set this attribute to True: the rendering will
           in any case be scaled down to the largest rectangle with power of 2
           dimensions before transfering to the texture.

Attributes inherited from the ImageBase class:

  image : image binary data, read-only
  size  : [x,y] size of the texture, read-only
  scale : set to True for fast scale down in case the render area dimensions are not power of 2
  flip  : set to True for vertical flip. 
  filter: set a post-processing filter on the render.

Notes:

* Aspect Ratio
For consistent results in Blender and Blenderplayer, the same aspect ratio used
by Blender to draw the camera viewport (Scene(F10)->Format tab->Size X/Size Y) 
is also used during the rendering. You can control the portion of the scene that
will be rendered by "looking through the camera": the zone inside the outer dotted 
rectangle will be rendered to the texture.
In order to reproduce the scene without X/Y distortion, you must apply the texture
on an object or portion of object that has the same aspect ratio.

* Order of rendering
The rendereing is performed when you call the refresh() method of the parent 
Texture object. This happens outside the normal frame rendering and will have no 
effect on it.
However, if you want to use ImageViewport and ImageRender at the same time, be 
sure to refresh the viewport texture before the render texture because the latter
will destroy the frame buffer that is used by the former to update the texture.

* Scene status
The meshes are not updated during the render to texture: the rendered texture
is one frame late to the rendered frame with regards to mesh deformation.

* Example:

  cont = GameLogic.getCurrentController()
  # object that receives the texture
  obj = contr.getOwner()
  scene = GameLogic.getCurrentScene()
  # camera used for the render
  tvcam = scene.getObjectList()['OBtvcam']
  # assume obj has some faces UV assigned to tv.png
  matID = VideoTexture.materialID(obj, 'IMtv.png')
  GameLogic.tv = VideoTexture.Texture(obj, matID)
  GameLogic.tv.source = VideoTexture.ImageRender(scene,tvcam)
  GameLogic.tv.source.capsize = [256,256]
  # to render the texture, just call GameLogic.tv.refresh(True) on each frame.

You can download a demo game (with a video file) here:

  http://home.scarlet.be/~tsi46445/blender/VideoTextureDemo.zip

For those who have already downloaded the demo, you can just update the blend file:

  http://home.scarlet.be/~tsi46445/blender/VideoTextureDemo.blend
This commit is contained in:
Benoit Bolsee 2008-11-26 17:47:42 +00:00
parent 15e193f253
commit 6a51ba54cd
5 changed files with 188 additions and 92 deletions

@ -25,15 +25,12 @@ http://www.gnu.org/copyleft/lesser.txt.
#include <PyObjectPlus.h>
#include <structmember.h>
#include <KX_BlenderCanvas.h>
#include <KX_BlenderRenderTools.h>
#include <RAS_IRasterizer.h>
#include <RAS_OpenGLRasterizer.h>
#include <KX_WorldInfo.h>
#include <KX_Light.h>
#include <BIF_gl.h>
#include "KX_PythonInit.h"
#include "DNA_scene_types.h"
#include "ImageRender.h"
#include "ImageBase.h"
#include "BlendType.h"
#include "Exception.h"
@ -42,78 +39,126 @@ ExceptionID SceneInvalid, CameraInvalid;
ExpDesc SceneInvalidDesc (SceneInvalid, "Scene object is invalid");
ExpDesc CameraInvalidDesc (CameraInvalid, "Camera object is invalid");
#if 0 // not yet supported
// constructor
ImageRender::ImageRender (KX_Scene * scene, KX_Camera * camera) : m_scene(scene),
m_camera(camera)
ImageRender::ImageRender (KX_Scene * scene, KX_Camera * camera) :
ImageViewport(),
m_scene(scene),
m_camera(camera)
{
// create screen area
m_area.winrct.xmin = m_upLeft[0];
m_area.winrct.ymin = m_upLeft[1];
m_area.winx = m_size[0];
m_area.winy = m_size[1];
// create canvas
m_canvas = new KX_BlenderCanvas(&m_area);
// create render tools
m_rendertools = new KX_BlenderRenderTools();
// create rasterizer
m_rasterizer = new RAS_OpenGLRasterizer(m_canvas);
m_rasterizer->Init();
// initialize background colour
setBackground(0, 0, 255);
// refresh lights
refreshLights();
setBackground(0, 0, 255, 255);
// retrieve rendering objects
m_engine = KX_GetActiveEngine();
m_rasterizer = m_engine->GetRasterizer();
m_canvas = m_engine->GetCanvas();
m_rendertools = m_engine->GetRenderTools();
}
// destructor
ImageRender::~ImageRender (void)
{
// release allocated objects
delete m_rasterizer;
delete m_rendertools;
delete m_canvas;
}
// set background color
void ImageRender::setBackground (unsigned char red, unsigned char green, unsigned char blue)
void ImageRender::setBackground (int red, int green, int blue, int alpha)
{
m_background[0] = red;
m_background[1] = green;
m_background[2] = blue;
m_rasterizer->SetBackColor(m_background[0], m_background[1], m_background[2], 1.0);
m_background[0] = (red < 0) ? 0.f : (red > 255) ? 1.f : float(red)/255.f;
m_background[1] = (green < 0) ? 0.f : (green > 255) ? 1.f : float(green)/255.f;
m_background[2] = (blue < 0) ? 0.f : (blue > 255) ? 1.f : float(blue)/255.f;
m_background[3] = (alpha < 0) ? 0.f : (alpha > 255) ? 1.f : float(alpha)/255.f;
}
// capture image from viewport
void ImageRender::calcImage (unsigned int texId)
{
// setup camera
bool cameraPasive = !m_camera->GetViewport();
// render scene
Render();
// reset camera
if (cameraPasive) m_camera->EnableViewport(false);
if (m_rasterizer->GetDrawingMode() != RAS_IRasterizer::KX_TEXTURED || // no need for texture
m_camera->GetViewport() || // camera must be inactive
m_camera == m_scene->GetActiveCamera())
{
// no need to compute texture in non texture rendering
m_avail = false;
return;
}
// render the scene from the camera
Render();
// get image from viewport
ImageViewport::calcImage(texId);
// restore OpenGL state
m_canvas->EndFrame();
}
void ImageRender::Render()
{
//
}
const float ortho = 100.0;
const RAS_IRasterizer::StereoMode stereomode = m_rasterizer->GetStereoMode();
// refresh lights
void ImageRender::refreshLights (void)
{
// clear lights list
//m_rendertools->RemoveAllLights();
// set lights
//for (int idx = 0; idx < scene->GetLightList()->GetCount(); ++idx)
// m_rendertools->AddLight(((KX_LightObject*)(scene->GetLightList()->GetValue(idx)))->GetLightData());
}
// The screen area that ImageViewport will copy is also the rendering zone
m_canvas->SetViewPort(m_position[0], m_position[1], m_position[0]+m_capSize[0]-1, m_position[1]+m_capSize[1]-1);
m_canvas->ClearColor(m_background[0], m_background[1], m_background[2], m_background[3]);
m_canvas->ClearBuffer(RAS_ICanvas::COLOR_BUFFER|RAS_ICanvas::DEPTH_BUFFER);
m_rasterizer->BeginFrame(RAS_IRasterizer::KX_TEXTURED,m_engine->GetClockTime());
m_rendertools->BeginFrame(m_rasterizer);
m_engine->SetWorldSettings(m_scene->GetWorldInfo());
m_rendertools->SetAuxilaryClientInfo(m_scene);
m_rasterizer->DisplayFog();
// matrix calculation, don't apply any of the stereo mode
m_rasterizer->SetStereoMode(RAS_IRasterizer::RAS_STEREO_NOSTEREO);
if (m_camera->hasValidProjectionMatrix())
{
m_rasterizer->SetProjectionMatrix(m_camera->GetProjectionMatrix());
} else
{
RAS_FrameFrustum frustrum;
float lens = m_camera->GetLens();
bool orthographic = !m_camera->GetCameraData()->m_perspective;
float nearfrust = m_camera->GetCameraNear();
float farfrust = m_camera->GetCameraFar();
float aspect_ratio = 1.0f;
Scene *blenderScene = m_scene->GetBlenderScene();
if (orthographic) {
lens *= ortho;
nearfrust = (nearfrust + 1.0)*ortho;
farfrust *= ortho;
}
// compute the aspect ratio from frame blender scene settings so that render to texture
// works the same in Blender and in Blender player
if (blenderScene->r.ysch != 0)
aspect_ratio = float(blenderScene->r.xsch) / float(blenderScene->r.ysch);
RAS_FramingManager::ComputeDefaultFrustum(
nearfrust,
farfrust,
lens,
aspect_ratio,
frustrum);
MT_Matrix4x4 projmat = m_rasterizer->GetFrustumMatrix(
frustrum.x1, frustrum.x2, frustrum.y1, frustrum.y2, frustrum.camnear, frustrum.camfar);
m_camera->SetProjectionMatrix(projmat);
}
MT_Transform camtrans(m_camera->GetWorldToCamera());
if (!m_camera->GetCameraData()->m_perspective)
camtrans.getOrigin()[2] *= ortho;
MT_Matrix4x4 viewmat(camtrans);
m_rasterizer->SetViewMatrix(viewmat, m_camera->NodeGetWorldPosition(),
m_camera->GetCameraLocation(), m_camera->GetCameraOrientation());
m_camera->SetModelviewMatrix(viewmat);
// restore the stereo mode now that the matrix is computed
m_rasterizer->SetStereoMode(stereomode);
// do not update the mesh, we don't want to do it more than once per frame
//m_scene->UpdateMeshTransformations();
m_scene->CalculateVisibleMeshes(m_rasterizer,m_camera);
m_scene->RenderBuckets(camtrans, m_rasterizer, m_rendertools);
}
// cast Image pointer to ImageRender
@ -174,26 +219,31 @@ static int ImageRender_init (PyObject * pySelf, PyObject * args, PyObject * kwds
// get background color
PyObject * getBackground (PyImage * self, void * closure)
{
return Py_BuildValue("[BBB]", getImageRender(self)->getBackground()[0],
getImageRender(self)->getBackground()[1], getImageRender(self)->getBackground()[2]);
return Py_BuildValue("[BBBB]",
getImageRender(self)->getBackground(0),
getImageRender(self)->getBackground(1),
getImageRender(self)->getBackground(2),
getImageRender(self)->getBackground(3));
}
// set color
static int setBackground (PyImage * self, PyObject * value, void * closure)
{
// check validity of parameter
if (value == NULL || !PySequence_Check(value) || PySequence_Length(value) != 3
if (value == NULL || !PySequence_Check(value) || PySequence_Length(value) != 4
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 0))
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 1))
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 2)))
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 2))
|| !PyInt_Check(PySequence_Fast_GET_ITEM(value, 3)))
{
PyErr_SetString(PyExc_TypeError, "The value must be a sequence of 3 ints");
PyErr_SetString(PyExc_TypeError, "The value must be a sequence of 4 integer between 0 and 255");
return -1;
}
// set background color
getImageRender(self)->setBackground((unsigned char)(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 0))),
(unsigned char)(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 1))),
(unsigned char)(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 2))));
(unsigned char)(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 2))),
(unsigned char)(PyInt_AsLong(PySequence_Fast_GET_ITEM(value, 3))));
// success
return 0;
}
@ -209,6 +259,10 @@ static PyMethodDef imageRenderMethods[] =
static PyGetSetDef imageRenderGetSets[] =
{
{(char*)"background", (getter)getBackground, (setter)setBackground, (char*)"background color", NULL},
// attribute from ImageViewport
{(char*)"capsize", (getter)ImageViewport_getCaptureSize, (setter)ImageViewport_setCaptureSize, (char*)"size of render area", NULL},
{(char*)"alpha", (getter)ImageViewport_getAlpha, (setter)ImageViewport_setAlpha, (char*)"use alpha in texture", NULL},
{(char*)"whole", (getter)ImageViewport_getWhole, (setter)ImageViewport_setWhole, (char*)"use whole viewport to render", NULL},
// attributes from ImageBase class
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
@ -264,4 +318,3 @@ PyTypeObject ImageRenderType =
};
#endif // #if 0

@ -47,9 +47,9 @@ public:
virtual ~ImageRender (void);
/// get background color
unsigned char * getBackground (void) { return m_background; }
int getBackground (int idx) { return (idx < 0 || idx > 3) ? 0 : int(m_background[idx]*255.f); }
/// set background color
void setBackground (unsigned char red, unsigned char green, unsigned char blue);
void setBackground (int red, int green, int blue, int alpha);
protected:
/// rendered scene
@ -57,27 +57,22 @@ protected:
/// camera for render
KX_Camera * m_camera;
/// screen area for rendering
ScrArea m_area;
/// rendering device
RAS_ICanvas * m_canvas;
/// rasterizer
RAS_IRasterizer * m_rasterizer;
/// render tools
RAS_IRenderTools * m_rendertools;
/// canvas
RAS_ICanvas* m_canvas;
/// rasterizer
RAS_IRasterizer* m_rasterizer;
/// render tools
RAS_IRenderTools* m_rendertools;
/// engine
KX_KetsjiEngine* m_engine;
/// background colour
unsigned char m_background[3];
float m_background[4];
/// render 3d scene to image
virtual void calcImage (unsigned int texId);
/// refresh lights
void refreshLights (void);
/// methods from KX_KetsjiEngine
bool BeginFrame();
void EndFrame();
void Render();
void SetupRenderFrame(KX_Scene *scene, KX_Camera* cam);
void RenderFrame(KX_Scene* scene, KX_Camera* cam);

@ -34,12 +34,12 @@ http://www.gnu.org/copyleft/lesser.txt.
// constructor
ImageViewport::ImageViewport (void) : m_texInit(false)
ImageViewport::ImageViewport (void) : m_alpha(false), m_texInit(false)
{
// get viewport rectangle
glGetIntegerv(GL_VIEWPORT, m_viewport);
// create buffer for viewport image
m_viewportImage = new BYTE [3 * getViewportSize()[0] * getViewportSize()[1]];
m_viewportImage = new BYTE [4 * getViewportSize()[0] * getViewportSize()[1]];
// set attributes
setWhole(false);
}
@ -62,7 +62,7 @@ void ImageViewport::setWhole (bool whole)
m_capSize[idx] = whole ? short(getViewportSize()[idx])
: calcSize(short(getViewportSize()[idx]));
// position
m_position[idx] = whole ? 0 : (getViewportSize()[idx] - m_capSize[idx]) >> 1;
m_position[idx] = whole ? 0 : ((getViewportSize()[idx] - m_capSize[idx]) >> 1);
}
// init image
init(m_capSize[0], m_capSize[1]);
@ -123,20 +123,31 @@ void ImageViewport::calcImage (unsigned int texId)
&& m_capSize[1] == calcSize(m_capSize[1]) && !m_flip)
{
// just copy current viewport to texture
glBindTexture(GL_TEXTURE_2D, texId);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1]);
// image is not available
m_avail = false;
glBindTexture(GL_TEXTURE_2D, texId);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1]);
// image is not available
m_avail = false;
}
// otherwise copy viewport to buffer, if image is not available
else if (!m_avail)
{
// get frame buffer data
glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGB,
GL_UNSIGNED_BYTE, m_viewportImage);
// filter loaded data
FilterRGB24 filt;
filterImage(filt, m_viewportImage, m_capSize);
if (m_alpha)
{
glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGBA,
GL_UNSIGNED_BYTE, m_viewportImage);
// filter loaded data
FilterRGBA32 filt;
filterImage(filt, m_viewportImage, m_capSize);
}
else
{
glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGB,
GL_UNSIGNED_BYTE, m_viewportImage);
// filter loaded data
FilterRGB24 filt;
filterImage(filt, m_viewportImage, m_capSize);
}
}
}
@ -151,14 +162,14 @@ inline ImageViewport * getImageViewport (PyImage * self)
// get whole
static PyObject * ImageViewport_getWhole (PyImage * self, void * closure)
PyObject * ImageViewport_getWhole (PyImage * self, void * closure)
{
if (self->m_image != NULL && getImageViewport(self)->getWhole()) Py_RETURN_TRUE;
else Py_RETURN_FALSE;
}
// set whole
static int ImageViewport_setWhole (PyImage * self, PyObject * value, void * closure)
int ImageViewport_setWhole (PyImage * self, PyObject * value, void * closure)
{
// check parameter, report failure
if (value == NULL || !PyBool_Check(value))
@ -172,6 +183,28 @@ static int ImageViewport_setWhole (PyImage * self, PyObject * value, void * clos
return 0;
}
// get alpha
PyObject * ImageViewport_getAlpha (PyImage * self, void * closure)
{
if (self->m_image != NULL && getImageViewport(self)->getAlpha()) Py_RETURN_TRUE;
else Py_RETURN_FALSE;
}
// set whole
int ImageViewport_setAlpha (PyImage * self, PyObject * value, void * closure)
{
// check parameter, report failure
if (value == NULL || !PyBool_Check(value))
{
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
return -1;
}
// set alpha
if (self->m_image != NULL) getImageViewport(self)->setAlpha(value == Py_True);
// success
return 0;
}
// get position
static PyObject * ImageViewport_getPosition (PyImage * self, void * closure)
@ -202,14 +235,14 @@ static int ImageViewport_setPosition (PyImage * self, PyObject * value, void * c
}
// get capture size
static PyObject * ImageViewport_getCaptureSize (PyImage * self, void * closure)
PyObject * ImageViewport_getCaptureSize (PyImage * self, void * closure)
{
return Py_BuildValue("(ii)", getImageViewport(self)->getCaptureSize()[0],
getImageViewport(self)->getCaptureSize()[1]);
}
// set capture size
static int ImageViewport_setCaptureSize (PyImage * self, PyObject * value, void * closure)
int ImageViewport_setCaptureSize (PyImage * self, PyObject * value, void * closure)
{
// check validity of parameter
if (value == NULL || !PySequence_Check(value) || PySequence_Length(value) != 2
@ -242,6 +275,7 @@ static PyGetSetDef imageViewportGetSets[] =
{(char*)"whole", (getter)ImageViewport_getWhole, (setter)ImageViewport_setWhole, (char*)"use whole viewport to capture", NULL},
{(char*)"position", (getter)ImageViewport_getPosition, (setter)ImageViewport_setPosition, (char*)"upper left corner of captured area", NULL},
{(char*)"capsize", (getter)ImageViewport_getCaptureSize, (setter)ImageViewport_setCaptureSize, (char*)"size of viewport area being captured", NULL},
{(char*)"alpha", (getter)ImageViewport_getAlpha, (setter)ImageViewport_setAlpha, (char*)"use alpha in texture", NULL},
// attributes from ImageBase class
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},

@ -43,6 +43,12 @@ public:
bool getWhole (void) { return m_whole; }
/// set whole buffer use
void setWhole (bool whole);
/// is alpha channel used
bool getAlpha (void) { return m_alpha; }
/// set whole buffer use
void setAlpha (bool alpha) { m_alpha = alpha; }
/// get capture size in viewport
short * getCaptureSize (void) { return m_capSize; }
/// set capture size in viewport
@ -61,6 +67,8 @@ protected:
short m_capSize[2];
/// use whole viewport
bool m_whole;
/// use alpha channel
bool m_alpha;
/// position of capture rectangle in viewport
GLint m_position[2];
@ -79,6 +87,12 @@ protected:
GLint * getViewportSize (void) { return m_viewport + 2; }
};
PyObject * ImageViewport_getCaptureSize (PyImage * self, void * closure);
int ImageViewport_setCaptureSize (PyImage * self, PyObject * value, void * closure);
PyObject * ImageViewport_getWhole (PyImage * self, void * closure);
int ImageViewport_setWhole (PyImage * self, PyObject * value, void * closure);
PyObject * ImageViewport_getAlpha (PyImage * self, void * closure);
int ImageViewport_setAlpha (PyImage * self, PyObject * value, void * closure);
#endif

@ -144,7 +144,7 @@ static void registerAllTypes(void)
#endif
pyImageTypes.add(&ImageBuffType, "ImageBuff");
pyImageTypes.add(&ImageMixType, "ImageMix");
//pyImageTypes.add(&ImageRenderType, "ImageRender");
pyImageTypes.add(&ImageRenderType, "ImageRender");
pyImageTypes.add(&ImageViewportType, "ImageViewport");
pyFilterTypes.add(&FilterBlueScreenType, "FilterBlueScreen");