Creating Mirrors Using Viewports (Rendering to Texture) or a Standard Material
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.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.
- 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:
- Create a mesh, that is going to represent a mirror.
- Inherit a material from the mesh_base.
- For the new inherited material perform the following actions:
- Enable the planar reflection option for the new inherited material.
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.
- Tune the reflection_pivot_rotation parameter to compensate rotation of the surface if any.
- Assign the new inherited material to the reflecting surface.
- Enable rendering of planar reflections using the render_reflection_dynamic console command.
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:
- Create a mesh, that is going to represent a mirror.
- Inherit a material from the mesh_base.
- 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).
- Create a 2D texture to render the image from the camera to.
- Create a viewport to render the image from the camera to the texture.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).
- Save current render state and change necessary settings.
- Render an image from the camera to the texture using the renderTexture2D() method of the Viewport class.
- Restore the previously saved render state.
- Set the texture as albedo texture for the material of the reflecting surface.
Using Both Approaches to Create Mirrors for a Car
First let us describe our Car class and implement all methods using the approaches described above.
// Car.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Unigine;
enum VMode
{
MODE_VIEWPORT_SINGLE, // use a single viewport for multiple cameras
MODE_VIEWPORT_MULTIPLE // use a separate viewport for each camera
};
namespace UnigineApp
{
// rearview mirror
struct Mirror {
public ObjectMeshStatic mesh; // mirror mesh
public String m_type; // mirror type (left/right/mid)
public Texture texture; // mirror texture
public PlayerDummy camera; // mirror camera
public Viewport viewport; // mirror viewport
public dvec3 position; // mirror position
};
class Car
{
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 = 1; // aspect ratio
const float HFOV = 60; // horizontal field of view
const float VFOV = HFOV / ASPECT_RATIO; // vertical field of view
VMode MODE = VMode.MODE_VIEWPORT_MULTIPLE; // set viewport mode (single/multiple)
ObjectMeshStatic car_frame;
ObjectMeshStatic material_mirror;
Mirror[] mirrors = new Mirror[3];
Controls controls;
float ifps;
/// method creating a mirror using the mesh_base "planar reflection" option
public int create_top_mirror()
{
// creating a mesh with a plane surface for the top mirror
Mesh mesh = new Mesh();
mesh.addPlaneSurface("plane", 1.97f, 0.3f, 1.0f);
// rotating the mesh surface by 108 degrees around the X-axis
mesh.setSurfaceTransform(MathLib.rotateX(108.0f));
// creating a top mirror and setting its position
material_mirror = new ObjectMeshStatic(mesh);
material_mirror.setPosition(new dvec3(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"
Material mesh_base = Materials.get().findMaterial("mesh_base");
Material 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"), new 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)
Unigine.Console.get().run("render_reflection_dynamic 1");
return 1;
}
public int init()
{
// setting texture width and hight
int width = 512;
int height = width / ASPECT_RATIO;
// preparing a mesh for the car body
Mesh bodymesh = new Mesh();
bodymesh.addBoxSurface("body", new vec3(2.0f, 5.0f, 1.2f));
bodymesh.addBoxSurface("body", new vec3(0.05f, 0.05f, 1.1f));
bodymesh.setSurfaceTransform(MathLib.translate(new vec3(0.96f, 0.8f, 0.91f)), 1);
bodymesh.addBoxSurface("body", new vec3(0.05f, 0.05f, 1.1f));
bodymesh.setSurfaceTransform(MathLib.translate(new vec3(-0.96f, 0.8f, 0.91f)), 2);
bodymesh.addBoxSurface("body", new vec3(1.88f, 0.05f, 0.05f));
bodymesh.setSurfaceTransform(MathLib.translate(new vec3(0.0f, 0.8f, 1.435f)), 3);
// creating car body using a mesh and passing it to the Editor
car_frame = new ObjectMeshStatic(bodymesh);
car_frame.setMaterial("mesh_base", "*");
car_frame.setMaterialParameter("albedo_color", new 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 = new dvec3(-1.08f, 0.77f, 0.9f);
mirrors[1].position = new dvec3(0.0f, 0.35f, 1.25f);
mirrors[2].position = new dvec3(1.08f, 0.77f, 0.9f);
// initializing mirrors
for (int i = 0; i < 3; i++)
{
// creating a mesh for the current mirror
Mesh mesh = new Mesh();
mesh.addPlaneSurface("mirror_plane", 0.2f, 0.1f, 1);
// creating the current mirror and setting its transform
mirrors[i].mesh = new ObjectMeshStatic(mesh);
mirrors[i].mesh.setTransform(new dmat4(MathLib.translate(new vec3(mirrors[i].position))*MathLib.rotateX(90.0f)));
// creating and setting materials for the current mirror mesh
Material mesh_base = Materials.get().findMaterial("mesh_base");
mesh_base.inherit(String.Concat("car_mirror_", mirrors[i].m_type));
mirrors[i].mesh.setMaterial(String.Concat("car_mirror_", mirrors[i].m_type), 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 = new PlayerDummy();
mirrors[i].camera.setProjection(MathLib.perspective(VFOV, 2.0f, 0.05f, 10000.0f) * MathLib.scale(-1.0f, 1.0f, 1.0f));
mirrors[i].camera.setPosition(mirrors[i].position);
mirrors[i].camera.setDirection(new 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 (MODE == VMode.MODE_VIEWPORT_MULTIPLE)
{
mirrors[i].viewport = new Viewport();
}
else if ((MODE == VMode.MODE_VIEWPORT_SINGLE) && (i == 1))
{
mirrors[i].viewport = new Viewport();
}
}
// setting up player and controls
PlayerSpectator player = new PlayerSpectator();
player.release();
car_frame.addChild(player.getNode());
player.setWorldPosition(new dvec3(0.0f, -0.5f, 1.1f));
player.setControlled(0);
Game.get().setPlayer(player.getPlayer());
Game.get().setEnabled(1);
player.setFov(60.0f);
player.setDirection(new vec3(0.0f, 1.0f, 0.0f), new vec3(0.0f, 0.0f, -1.0f));
player.setCollision(0);
controls = player.getControls();
car_frame.setWorldPosition(car_frame.getWorldPosition() + new dvec3(0.0f, 4.0f, 0.0f));
return 1;
}
public int update()
{
ifps = Game.get().getIFps();
// get the current world transformation matrix of the mesh
dmat4 transform = car_frame.getWorldTransform();
// get the direction vector of the mesh from the second column of the transformation matrix
dvec3 direction = transform.getColumn3(1);
// checking controls states and changing car frame transformation
if ((controls.getState(Controls.STATE_FORWARD) == 1) || (controls.getState(Controls.STATE_TURN_UP) == 1))
{
car_frame.setWorldPosition(car_frame.getWorldPosition() + direction * MOVING_SPEED*ifps);
}
if ((controls.getState(Controls.STATE_BACKWARD) == 1) || (controls.getState(Controls.STATE_TURN_DOWN) == 1))
{
car_frame.setWorldPosition(car_frame.getWorldPosition() - direction * MOVING_SPEED*ifps);
}
if ((controls.getState(Controls.STATE_MOVE_LEFT) == 1) || (controls.getState(Controls.STATE_TURN_LEFT) == 1))
{
car_frame.setWorldRotation(car_frame.getWorldRotation() * new quat(0.0f, 0.0f, DELTA_ANGLE*ifps));
direction.z += DELTA_ANGLE*ifps;
}
if ((controls.getState(Controls.STATE_MOVE_RIGHT) == 1) || (controls.getState(Controls.STATE_TURN_RIGHT) == 1))
{
car_frame.setWorldRotation(car_frame.getWorldRotation() * new quat(0.0f, 0.0f, -DELTA_ANGLE*ifps));
direction.z -= DELTA_ANGLE*ifps;
}
return 1;
}
public int render()
{
Viewport viewport;
for (int i = 0; i < 3; i++)
{
if (MODE == VMode.MODE_VIEWPORT_MULTIPLE)
{
// using a separate viewport for each camera
viewport = mirrors[i].viewport;
}
else
{
// using a single viewport for all cameras
viewport = mirrors[1].viewport;
// skipping post effects when using a single viewport for multiple cameras to avoid artefacts
viewport.appendSkipFlags(Viewport.SKIP_POSTEFFECTS);
}
// saving current render state and clearing it
RenderState render_state = RenderState.get();
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();
Material material = mirrors[i].mesh.getMaterialInherit(0);
// setting rendered texture as albedo texture for the material of the current mirror
material.setTexture(material.findTexture("albedo"), mirrors[i].texture);
}
return 1;
}
}
}
And now let us integrate our car to the AppWorldLogic class.
// AppWorldLogic.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Unigine;
namespace UnigineApp
{
class AppWorldLogic : WorldLogic
{
Car car;
public override int init()
{
// creating and initializing a car
car = new Car();
car.init();
return 1;
}
// start of the main loop
public override int update()
{
car.update();
return 1;
}
public override int render()
{
car.render();
return 1;
}
/* .. */
}
}