This page has been translated automatically.
UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Programming
Fundamentals
Setting Up Development Environment
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine 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
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
Warning! This version of documentation is OUTDATED, as it describes an older SDK version! Please switch to the documentation for the latest SDK version.
Warning! This version of documentation describes an old SDK version which is no longer supported! Please upgrade to the latest SDK version.

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 RenderTarget 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 screen-space effects should be disabled to avoid artefacts (See the render_screen_space_effects 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 describe our Car class and implement all methods using the approaches described above.

Source code (C#)
// 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 bool 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.Position = new dvec3(0.0f, 0.73f, 1.60f);

            // passing the node to the Editor and adding it as a child to the car frame
            car_frame.AddChild(material_mirror);

            // creating a new mirror material named "planar_reflector"
			Material mesh_base = Materials.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)
            Console.Run("render_reflection_dynamic 1");

            return true;
        }

        public bool Init()
        {
            // setting texture width and height
           	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.SetMaterialParameterFloat4("albedo_color", new vec4(1.0f, 0.0f, 0.0f, 1.0f), 0);

            // 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.Transform = 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.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);

                // creating a camera for the current mirror and setting its parameters
        		mirrors[i].camera = new PlayerDummy();
                mirrors[i].camera.Projection = MathLib.Perspective(VFOV, 2.0f, 0.05f, 10000.0f) * MathLib.Scale(-1.0f, 1.0f, 1.0f);
	          	mirrors[i].camera.Position = 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);
	        	car_frame.AddChild(mirrors[i].camera);
		
        		// 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();
        	car_frame.AddChild(player);
        	player.WorldPosition = new dvec3(0.0f, -0.5f, 1.1f);
        	player.Controlled = false;
        	Game.Player = player;
        	Game.Enabled = true;
        	player.Fov = 60.0f;
	        player.SetDirection(new vec3(0.0f, 1.0f, 0.0f), new vec3(0.0f, 0.0f, -1.0f));
            player.Collision = 0;
	        controls = player.Controls;

            car_frame.WorldPosition = car_frame.WorldPosition + new dvec3(0.0f, 4.0f, 0.0f);

            return true;
        }
        public bool Update()
        {
          	ifps = Engine.ifps;
	        // get the current world transformation matrix of the mesh
	        dmat4 transform = car_frame.WorldTransform;

        	// 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.WorldPosition = car_frame.WorldPosition + direction * MOVING_SPEED*ifps;
        	}
        	if ((controls.GetState(Controls.STATE_BACKWARD) == 1) || (controls.GetState(Controls.STATE_TURN_DOWN) == 1))
        	{
        		car_frame.WorldPosition = car_frame.WorldPosition - 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 true;
        }
        public bool 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;
                render_state.SaveState();
                render_state.ClearStates();
                
                // enabling polygon front mode to correct camera flipping
                render_state.PolygonFront = 1;

                // rendering and image from the camera of the current mirror to the texture
                viewport.RenderTexture2D(mirrors[i].camera.Camera, mirrors[i].texture);

                // restoring back render state
                render_state.PolygonFront = 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 true;
        }

    }
}

And now let us integrate our car to the AppWorldLogic class.

Source code (C#)
// 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 bool Init()
		{

            // creating and initializing a car
            car = new Car();
            car.Init();

			return true;
		}

		// start of the main loop
		public override bool Update()
		{
            car.Update();
			
			return true;
		}

		public override bool Render()
		{
            car.Render();
			
			return true;
		}

		/* .. */
		
	}
}
Last update: 2020-06-02
Build: ()