UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
FAQ
Programming
Fundamentals
Setting Up Development Environment
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
CIGI Client Plugin
Rendering-Related Classes

Creating Mirrors Using Viewports (Rendering to Texture) or a Standard Material

Car Rearview Mirrors

Car Rearview Mirrors

This example covers some aspects of using viewports and cameras as well as demonstrates 2 different ways of implementing mirrors:

  • Using the planar reflection option of the standard mesh_base material.
    Notice
    Planar reflections don't reflect each other and miss post-effects.
  • Using the Viewport class to render an image from a certain camera to the TextureRender interface and then setting the texture as albedo texture of the mirror material.
Key features:
  • 3 rearview mirrors implemented using viewport texture rendering
  • large top rearview tilted mirror implemented using the planar reflection option of the mesh_base material.
  • 2 modes:
    • Single viewport for multiple cameras
    • Separate viewport for each camera
  • Keyboard control of car movement.

Using the Standard mesh_base Material

You can create mirrors by simply using the planar reflection option the mesh_base material. The basic workflow here is as follows:

  1. Create a mesh, that is going to represent a mirror.
  2. Inherit a material from the mesh_base.
  3. For the new inherited material perform the following actions:

    For metalness workflow:

    • Set the value of the metallness parameter to 1.
    • Set the value of the roughness parameter to 0.

    For specular workflow:

    • Set the value of the gloss parameter to 1.
    • Set the value of the microfiber parameter to 0.
  4. Tune the reflection_pivot_rotation parameter to compensate rotation of the surface if any.
  5. Assign the new inherited material to the reflecting surface.
  6. Enable rendering of planar reflections using the render_reflection_dynamic console command.
This approach is implemented here in the create_top_mirror() method, that creates the top rearview tilted mirror.

Using Viewports (Rendering to Texture)

Another way of creating mirrors is to use a viewport to render an image from a camera placed behind the mirror to a texture and set it as albedo texture for the material of the reflecting surface. This approach can also be used to create various portals or monitors showing an image from a certain camera etc. The basic workflow here is as follows:

  1. Create a mesh, that is going to represent a mirror.
  2. Inherit a material from the mesh_base.
  3. Create a camera and set its projection and modelview matrices (in case of implementing a mirror the camera should be placed behind the mirror looking in the direction of the normal to the reflecting surface).
  4. Create a 2D texture to render the image from the camera to.
  5. Create a viewport to render the image from the camera to the texture.
    Notice
    It is recommended to use a separate viewport for each camera. In case of using several cameras with a single viewport rendering of post effects should be skipped to avoid artefacts (See the render_skip_post_materials console command).
  6. Save current render state and change necessary settings.
  7. Render an image from the camera to the texture using the renderTexture2D() method of the Viewport class.
  8. Restore the previously saved render state.
  9. Set the texture as albedo texture for the material of the reflecting surface.
This approach is implemented here for all three small rearview mirrors.

Using Both Approaches to Create Mirrors for a Car

First let us declare our Car class and all necessary variables and constants.

Source code (C++)
// Car.h
#ifndef __CAR_H__
#define __CAR_H__
#include <UnigineEngine.h>
#include <UnigineObjects.h>
#include <UnigineMaterials.h>
#include <UnigineViewport.h>
#include <UnigineEditor.h>
#include <UnigineGame.h>
#include <UnigineRenderState.h>

enum
{
	MODE_VIEWPORT_SINGLE = 0,				// use a single viewport for multiple cameras
	MODE_VIEWPORT_MULTIPLE					// use a separate viewport for each camera
};

const float MOVING_SPEED = 10.0f;			// speed of objects movement
const float DELTA_ANGLE = 60.0f;			// delta angle of objects rotation
const int ASPECT_RATIO = 2;					// aspect ratio
const double HFOV = 60;						// horizontal field of view for mirror cameras
const double VFOV = HFOV / ASPECT_RATIO;	// vertical field of view for mirror cameras




// rearview mirror 
struct Mirror {
	Unigine::ObjectMeshStaticPtr mesh;		// mirror mesh
	Unigine::String m_type;					// mirror type (left/right/mid)
	Unigine::TexturePtr texture;			// mirror texture
	Unigine::PlayerDummyPtr camera;			// mirror camera
	Unigine::ViewportPtr viewport;			// mirror viewport
	Unigine::Math::Vec3 position;			// mirror position
};

class Car 
{
public:
	Car()	{	}
	~Car()	{	}

	int create_top_mirror();
	int init();
	int update();
	int render();
	int shutdown();

	Unigine::ObjectMeshStaticPtr car_frame;
	Unigine::ObjectMeshStaticPtr material_mirror;

	Mirror mirrors[3];
	Unigine::ControlsPtr controls;
};
#endif // __CAR_H__

And implement all methods using the approaches described above.

Source code (C++)
// Car.cpp

#include "Car.h"
#include <UnigineConsole.h>

using namespace Unigine;
using namespace Math;

// setting viewport mode (single/multiple)
int VMODE = MODE_VIEWPORT_MULTIPLE;

///  method creating a mirror using the mesh_base "planar reflection" option
int Car::create_top_mirror() {

	// creating a mesh with a plane surface for the top mirror
	MeshPtr mesh = Mesh::create();
	mesh->addPlaneSurface("plane", 1.97f, 0.3f, 1.0f);

	// rotating the mesh surface by 108 degrees around the X-axis
	mesh->setSurfaceTransform(rotateX(108.0f));

	// creating a top mirror and setting its position
	material_mirror = ObjectMeshStatic::create(mesh);
	material_mirror->setPosition(Vec3(0.0f, 0.73f, 1.60f));

	// passing the node to the Editor and adding it as a child to the car frame
	material_mirror->release();
	Editor::get()->addNode(material_mirror->getNode());
	car_frame->addChild(material_mirror->getNode());

	// creating a new mirror material named "planar_reflector"
	MaterialPtr mesh_base = Materials::get()->findMaterial("mesh_base");
    MaterialPtr reflector_material = mesh_base->inherit("planar_reflector");

	// enabling planar reflections for the mirror material
	reflector_material->setState("planar_reflection", 1);

	// setting metallness and roughness parameters to make the surface look like a mirror
	reflector_material->setParameterSlider(reflector_material->findParameter("metalness"), 1.0f);
	reflector_material->setParameterSlider(reflector_material->findParameter("roughness"), 0.0f);

	// compensating the 108-degree rotation angle of the mesh surface
	reflector_material->setParameter(reflector_material->findParameter("reflection_pivot_rotation"), vec4(108.0f, 0.0f, 0.0f, 0.0f));

	// assigning new mirror material to the surface of the mirror
	material_mirror->setMaterial("planar_reflector", 0);

	// enabling planar reflections rendering using the corresponding console command (render_reflection_dynamic)
	Console::get()->run("render_reflection_dynamic 1");

	return 1;
}

int Car::init()
{
	int width = 512;
	int height = width / ASPECT_RATIO;

	// preparing a mesh for the car body
	MeshPtr bodymesh = Mesh::create();
	bodymesh->addBoxSurface("body", vec3(2.0f, 5.0f, 1.2f));
	bodymesh->addBoxSurface("body", vec3(0.05f, 0.05f, 1.1f));
	bodymesh->setSurfaceTransform(translate(vec3(0.96f, 0.8f, 0.91f)), 1);
	bodymesh->addBoxSurface("body", vec3(0.05f, 0.05f, 1.1f));
	bodymesh->setSurfaceTransform(translate(vec3(-0.96f, 0.8f, 0.91f)), 2);
	bodymesh->addBoxSurface("body", vec3(1.88f, 0.05f, 0.05f));
	bodymesh->setSurfaceTransform(translate(vec3(0.0f, 0.8f, 1.435f)), 3);

	// creating car body using a mesh and passing it to the Editor
	car_frame = ObjectMeshStatic::create(bodymesh);
	car_frame->setMaterial("mesh_base", "*");
	car_frame->setMaterialParameter("albedo_color", vec4(1.0f, 0.0f, 0.0f, 1.0f), 0);
	car_frame->release();
	Editor::get()->addNode(car_frame->getNode(), 1);

	// creating a top rearview mirror using the mesh_base material
	create_top_mirror();

	// setting type and position for all mirrors
	mirrors[0].m_type = "left";
	mirrors[1].m_type = "mid";
	mirrors[2].m_type = "right";
	mirrors[0].position = Vec3(-1.08f, 0.77f, 0.9f);
	mirrors[1].position = Vec3(0.0f, 0.35f, 1.25f);
	mirrors[2].position = Vec3(1.08f, 0.77f, 0.9f);

	// initializing mirrors
	for (int i = 0; i < 3; i++)
	{
		// creating a mesh for the current mirror
		MeshPtr mesh = Mesh::create();
		mesh->addPlaneSurface("mirror_plane", 0.2f, 0.1f, 1);

		// creating the current mirror and setting its transform
		mirrors[i].mesh = ObjectMeshStatic::create(mesh);
		mirrors[i].mesh->setTransform(Mat4(translate(vec3(mirrors[i].position))*rotateX(90.0f)));

		// creating and setting materials for the current mirror mesh
		String str = String::format("car_mirror_%s", mirrors[i].m_type.get());
		MaterialPtr mesh_base = Materials::get()->findMaterial("mesh_base");
		mesh_base->inherit(str.get());
		mirrors[i].mesh->setMaterial(str.get(), 0);

		// adding mirror mesh to the Editor as a runtime node
		mirrors[i].mesh->release();
		Editor::get()->addNode(mirrors[i].mesh->getNode(), 1);

		// creating a camera for the current mirror and setting its parameters
		mirrors[i].camera = PlayerDummy::create();
		mirrors[i].camera->setProjection(perspective(VFOV, 2.0, 0.05, 10000) * scale(-1.0f, 1.0f, 1.0f));
		mirrors[i].camera->setPosition(mirrors[i].position);
		mirrors[i].camera->setDirection(vec3(0.0f, -1.0f, 0.0f), vec3::UP);

		// adding mirror mesh and camera as children to the car frame 
		car_frame->addChild(mirrors[i].mesh->getNode());
		car_frame->addChild(mirrors[i].camera->getNode());
		
		// creating a 2D texture to be set for the mirror material
		mirrors[i].texture = Texture::create();
		mirrors[i].texture->create2D(width, height, Texture::FORMAT_RGBA8, Texture::USAGE_RENDER);

		// checking viewport mode and creating necessary number of viewports
		if (VMODE == MODE_VIEWPORT_MULTIPLE)
		{
			mirrors[i].viewport = Viewport::create();
		}
		else if ((VMODE == MODE_VIEWPORT_SINGLE) && (i == 1))
		{
			mirrors[i].viewport = Viewport::create();
		}
	}
	
	// setting up player and controls
	PlayerSpectatorPtr player = PlayerSpectator::create();
	player->release();
	car_frame->addChild(player->getNode());
	player->setWorldPosition(Vec3(0.0f, -0.5f, 1.1f));
	player->setControlled(0);
	Game::get()->setPlayer(player->getPlayer());
	Game::get()->setEnabled(1);
	player->setFov(60.0f);
	player->setDirection(vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, -1.0f));
	player->setCollision(0);
	controls = player->getControls();

	return 1;
}

float ifps;

int Car::update()
{
	ifps = Game::get()->getIFps();
	// get the current world transformation matrix of the mesh
	Mat4 transform = car_frame->getWorldTransform();

	// get the direction vector of the mesh from the second column of the transformation matrix
	Vec3 direction = transform.getColumn3(1);

	// checking controls states and changing car frame transformation
	if (controls->getState(Controls::STATE_FORWARD) || controls->getState(Controls::STATE_TURN_UP))
	{
		car_frame->setWorldPosition(car_frame->getWorldPosition() + direction * MOVING_SPEED*ifps);
	}
	if (controls->getState(Controls::STATE_BACKWARD) || controls->getState(Controls::STATE_TURN_DOWN))
	{
		car_frame->setWorldPosition(car_frame->getWorldPosition() - direction * MOVING_SPEED*ifps);
	}
	if (controls->getState(Controls::STATE_MOVE_LEFT) || controls->getState(Controls::STATE_TURN_LEFT))
	{
		car_frame->setWorldRotation(car_frame->getWorldRotation() * quat(0.0f, 0.0f, DELTA_ANGLE*ifps));
		direction.z += DELTA_ANGLE*ifps;
	}
	if (controls->getState(Controls::STATE_MOVE_RIGHT) || controls->getState(Controls::STATE_TURN_RIGHT))
	{
		car_frame->setWorldRotation(car_frame->getWorldRotation() * quat(0.0f, 0.0f, -DELTA_ANGLE*ifps));
		direction.z -= DELTA_ANGLE*ifps;
	}

	return 1;
}

int Car::render()
{
	ViewportPtr viewport;

	for (int i = 0; i < 3; i++)
	{
		MaterialPtr material = mirrors[i].mesh->getMaterialInherit(0);
		
		if (VMODE == MODE_VIEWPORT_MULTIPLE)
		{
			// using a separate viewport for each camera
			viewport = mirrors[i].viewport;
		}
		else if (VMODE == MODE_VIEWPORT_SINGLE)
		{
			// using a single viewport for all cameras
			viewport = mirrors[1].viewport;

			// skipping post effects when using a single viewport for multiple cameras
			viewport->appendSkipFlags(Viewport::SKIP_POSTEFFECTS);
		}

		// rendering
		RenderState *render_state = RenderState::get();

		// saving current render state and clearing it 
		render_state->saveState();
		render_state->clearStates();
		
		// enabling polygon front mode to correct camera flipping
		render_state->setPolygonFront(1);
		
		// rendering and image from the camera of the current mirror to the texture
		viewport->renderTexture2D(mirrors[i].camera->getCamera(), mirrors[i].texture);

		// restoring back render state
		render_state->setPolygonFront(0);
		render_state->restoreState();

		// setting rendered texture as albedo texture for the material of the current mirror
		material->setTexture(material->findTexture("albedo"), mirrors[i].texture);

	}

	return 1;
}

int Car::shutdown()
{
	car_frame.clear();

	for (int i = 0; i < 3; i++)
	{
		mirrors[i].mesh.clear();
		mirrors[i].texture.clear();
		mirrors[i].camera.clear();
	}

	return 1;
}

Let us integrate our car with the AppWorldLogic class.

Source code (C++)
// AppWorldLogic.h
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include "car.h"

class AppWorldLogic : public Unigine::WorldLogic {
	
public:

	/* .. */

	Car car;
};

#endif // __APP_WORLD_LOGIC_H__

And let us insert the following code into the AppWorldLogic.cpp file.

Source code (C++)
// AppWorldLogic.cpp

#include "AppWorldLogic.h"

/* .. */


int AppWorldLogic::init() {
	
	// creating and initializing a car
	car.init();
    
	return 1;
}

// start of the main loop
int AppWorldLogic::update() {

	// calling car update
	car.update();

	return 1;
}

int AppWorldLogic::render() {
	
	// calling car render
	car.render();

	return 1;
}

int AppWorldLogic::shutdown() {

	// calling car shutdown
	car.shutdown();

	return 1;
}

/* .. */
Last update: 2018-06-04