Decal Render Order

When working with mesh decals render order can become important as it comes to user controlled decorations as seen in popular racing games. 

The code example below shows a possible implementation of a simple component that controls the render order using material queues.

Code

using ch.sycoforge.Decal;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent(typeof(EasyDecal))]
public class RenderOrder : MonoBehaviour
{
    //------------------------
    // Exposed Fields
    //------------------------

    [Range(0, 100)]
    public int ZIndex;

    //------------------------
    // Fields
    //------------------------
    private EasyDecal decal;
    private float lastZIndex = int.MinValue;

    private int defaultQueue;

    //------------------------
    // unity Methods
    //------------------------
    private void Start()
    {
        decal = GetComponent<EasyDecal>();

        defaultQueue = decal.DecalMaterial.renderQueue;
    }

    private void Update()
    {
        // Did the value change?
        if (lastZIndex != ZIndex)
        {
            decal.DecalMaterial.renderQueue = defaultQueue + ZIndex;

            lastZIndex = ZIndex;
        }
    }
}

The component above can just be added to the same game object as the EasyDecal script.

Simple Runtime Placement

The attached Unity package contains an example of a simple decal placement on a car.


Code

using UnityEngine;
using System.Collections;
using ch.sycoforge.Decal;

public class DecalPlacement : MonoBehaviour 
{
    //---------------------------------
    // Exposed Fields
    //---------------------------------
    
    public EasyDecal Decal;
    public float PlacementOffset = 4;

    public float RotationSpeed = 6;
    public float Rotation;

    public float ScaleSpeed = 1;
    public Vector2 Scale = Vector2.one;
    public bool Editable;

    //---------------------------------
    // Fields
    //---------------------------------
    private Camera ActiveCamera;

    //---------------------------------
    // Constants
    //---------------------------------
    private const string ROT_INPUT_NAME = "Mouse ScrollWheel";
    private const string VER_INPUT_NAME = "Vertical";
    private const string HOR_INPUT_NAME = "Horizontal";

    //---------------------------------
    // Mehods
    //---------------------------------

    private void Start() 
    {
        // Take main camera if no camera specified
        if(ActiveCamera == null)
        {
            ActiveCamera = Camera.main;
        }
    }


    private void Update () 
    {
        ChangeAtlasIndex();

        if(Input.GetKeyDown(KeyCode.E))
        {
            Editable = !Editable;

            Decal.Baked = !Editable;
        }

        if (!Editable) { return; }

        MoveRotateDecal();
        ScaleDecal();
        
    }

    private void ChangeAtlasIndex()
    {
        
        int dir = 0;
        if (Input.GetKeyDown(KeyCode.Y)) { dir = 1; }
        if (Input.GetKeyDown(KeyCode.X)) { dir = -1; }

        if(dir != 0)
        {
            Decal.Baked = false;
            Decal.AtlasRegionIndex += dir;
            Decal.LateBake();
        }
    }

    private void MoveRotateDecal()
    {
        float delta = Input.GetAxis(ROT_INPUT_NAME);

        // Scroll event received
        if (Mathf.Abs(delta) > 0f)
        {
            // Sum delta rotation
            Rotation += delta * RotationSpeed * Time.deltaTime * 1000.0f;
        }

        Vector2 screenPoint = Input.mousePosition;

        // Ray from camera's origin
        Ray ray = ActiveCamera.ScreenPointToRay(screenPoint);

        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, ActiveCamera.farClipPlane))
        {
            Vector3 avgNormal = AverageNormal(screenPoint, PlacementOffset);
            Quaternion quat = Quaternion.AngleAxis(Rotation, avgNormal) * Quaternion.FromToRotation(Vector3.up, avgNormal);

            // Apply decal transformations
            Decal.CachedTransform.position = hit.point + avgNormal * 0.2f;
            Decal.CachedTransform.rotation = quat;
        }
    }

    private void ScaleDecal()
    {
        float v = Input.GetAxis(VER_INPUT_NAME) * ScaleSpeed;
        float h = Input.GetAxis(HOR_INPUT_NAME) * ScaleSpeed;

        // Return if no input changes
        if (!(Mathf.Abs(v) > 0f || Mathf.Abs(h) > 0))
        {
            return;
        }

        // Sum delta scale
        Scale += new Vector2(h, v) * Time.deltaTime;

        Decal.CachedTransform.localScale = new Vector3(Scale.x, 1.0f, Scale.y);
    }

    private Vector3 AverageNormal(Vector2 screenPoint, float offset)
    {
        Vector3 normal = Vector3.zero;
        Camera cam = Camera.current;
        int hits = 0;

        Vector2[] offsets = new Vector2[]
            {
                new Vector2(0, offset), 
                new Vector2(offset, offset), 

                new Vector2(offset, 0), 
                new Vector2(offset, -offset), 

                new Vector2(0, -offset), 
                new Vector2(-offset, -offset),
 
                new Vector2(-offset, 0),
                new Vector2(-offset, offset)
            };

        for (int i = 0; i < offsets.Length; i++)
        {
            Ray ray = ActiveCamera.ScreenPointToRay(screenPoint + offsets[i]);

            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 1000.0f))
            {
                normal += hit.normal;
                hits++;
            }
        }

        return normal / hits;
    }
}

Download

Page 4 of 4

Copyright © 2020 by Sycoforge Technologies. All Rights Reserved.