做了一个VR的自由涂鸦画板,需要判断是否在指定位置涂鸦。

1.效果

Unity 模型Static unity 模型画笔_Unity 模型Static

2.思路

网上找了好多资料,最后缝合起来的。 (:з」∠)

自由涂鸦画板实现思路:

使用Texture.GetPixels32() 获取纹理的像素数组(Color32[]),将画笔与画板碰撞点的像素改为画笔的颜色,最后将修改后的像素数组用Texture.SetPixels32()设置给纹理。

判断是否按规定轨迹涂鸦思路:

涂鸦过程实际是操作的一个像素数组(Color32[]),数组内储存着纹理每个像素的颜色。将指定的轨迹用N个单位为一像素的点标记,在绘制结束后判断这几个像素是否被修改成了画笔的颜色,如果轨迹内点的颜色都被修改,说明是按轨迹涂鸦的。

3.实现步骤

涂鸦画板:

新建一个Quad作为画板,复制一份Y轴降低一点作为画板背景。新建一个材质,渲染模式改为Transparent,PS里做一张背景为透明的图片作为纹理,别做太大会很卡,我做的是512*512,必须是方形的,不然涂鸦时位置对不上,可能是我缝合时坐标转换没处理好。导入Unity后将图片的Read/Write Enable打勾。设置画板Tag为Blackboard,挂载脚本。

public class Blackboard : MonoBehaviour
{
    //画板尺寸
    private int m_TextureWidth;
    private int m_TextureHeight;

    //当前画板图片
    private Texture2D m_CurrentTexture;

    //当前编辑的颜色数组
    private Color32[] m_CurrentColors;

    //重置画板的颜色数组
    private Color[] m_CleanColorsArray;

    //当前画板的绘制颜色
    private Color m_CurrentBrushColor;

    //用于检测笔位置的Plane
    private Plane m_BoardPlane;

    public Plane BoardPlane
    {
        get => m_BoardPlane;
    }

    //先前拖拽的位置
    private Vector2 previous_drag_position;

    //用于获取HDRP Lit Shader主帖图的字段
    private static readonly int m_BaseColorMap = Shader.PropertyToID("_BaseColorMap");

    private void Awake()
    {
        m_BoardPlane = new Plane(transform.forward, transform.position);
        //获取画板尺寸
        var l_originTexture = GetComponent<MeshRenderer>().material.mainTexture as Texture2D;
        m_TextureWidth = l_originTexture.width;
        m_TextureHeight = l_originTexture.height;
        var l_originColor = l_originTexture.GetPixels32();
        //新建一个纹理赋予材质
        m_CurrentTexture = new Texture2D(l_originTexture.width, l_originTexture.height);
        m_CurrentTexture.SetPixels32(l_originColor);
        m_CurrentTexture.Apply();
        //HDRP使用这种方式给材质设置纹理,普通项目使用注释掉的方法赋值
        //GetComponent<MeshRenderer>().material.mainTexture = m_CurrentTexture;
        GetComponent<MeshRenderer>().material.SetTexture(m_BaseColorMap, m_CurrentTexture);
    }

    public void DrawStop()
    {
        previous_drag_position = Vector2.zero;
    }

    public void UpdateDisplay(Vector3 _worldPoint, Vector2 _uvPoint, int _brushWidth, Color32 _brushColor)
    {
        Vector2 pixel_pos = UVToPixelCoordinates(_uvPoint);

        m_CurrentColors = m_CurrentTexture.GetPixels32();

        if (previous_drag_position == Vector2.zero)
        {
            // 如果这是我们第一次在该图像上拖动,只需在鼠标位置上为像素着色
            MarkPixelsToColour(pixel_pos, _brushWidth, _brushColor);
        }
        else
        {
            // 在上次更新呼叫所在的行中显示颜色
            ColourBetween(previous_drag_position, pixel_pos, _brushWidth, _brushColor);
        }

        ApplyMarkedPixelChanges();

        previous_drag_position = pixel_pos;
    }

    /// <summary>
    /// UV坐标转像素坐标
    /// </summary>
    private Vector2 UVToPixelCoordinates(Vector2 _vector2)
    {
        // 需要以我们的坐标为中心
        float centered_x = _vector2.x * m_TextureWidth;
        float centered_y = _vector2.y * m_TextureHeight;

        // 将当前鼠标位置四舍五入到最近的像素
        Vector2 pixel_pos = new Vector2(Mathf.RoundToInt(centered_x), Mathf.RoundToInt(centered_y));

        return pixel_pos;
    }
    /// <summary>
    /// 计算需要绘制的像素数量
    /// </summary>
    public void MarkPixelsToColour(Vector2 center_pixel, int pen_thickness, Color color_of_pen)
    {
        //找出每个方向(x和y)需要着色的像素数量
        int center_x = (int) center_pixel.x;
        int center_y = (int) center_pixel.y;
        //int extra_radius = Mathf.Min(0, pen_thickness - 2);

        for (int x = center_x - pen_thickness; x <= center_x + pen_thickness; x++)
        {
            // 检查X是否环绕图像,因此我们不在图像的另一侧绘制像素
            if (x >= m_TextureWidth || x < 0)
                continue;

            for (int y = center_y - pen_thickness; y <= center_y + pen_thickness; y++)
            {
                MarkPixelToChange(x, y, color_of_pen);
            }
        }
    }

    /// <summary>
    /// 俩点之间插入过渡点
    /// </summary>
    private void ColourBetween(Vector2 start_point, Vector2 end_point, int width, Color color)
    {
        // 获取从头到尾的距离
        float distance = Vector2.Distance(start_point, end_point);
        Vector2 direction = (start_point - end_point).normalized;

        Vector2 cur_position = start_point;

        // 根据自上次更新以来经过的时间,计算在start_point和end_point之间进行插值的次数
        float lerp_steps = 1 / distance;

        for (float lerp = 0; lerp <= 1; lerp += lerp_steps)
        {
            cur_position = Vector2.Lerp(start_point, end_point, lerp);
            MarkPixelsToColour(cur_position, width, color);
        }
    }

    /// <summary>
    /// 修改像素数组信息
    /// </summary>
    public void MarkPixelToChange(int x, int y, Color color)
    {
        // 需要将x和y坐标转换为数组的平面坐标
        int array_pos = y * m_TextureHeight + x;

        // 检查这是一个有效的位置
        if (array_pos > m_CurrentColors.Length || array_pos < 0)
            return;

        m_CurrentColors[array_pos] = color;
    }

    /// <summary>
    /// 将新的像素数组赋值给纹理
    /// </summary>
    public void ApplyMarkedPixelChanges()
    {
        m_CurrentTexture.SetPixels32(m_CurrentColors);
        m_CurrentTexture.Apply();
    }

    /// <summary>
    /// 笔尖在画板正面还是背面
    /// </summary>
    /// <param name="_point">笔尖的位置</param>
    /// <returns>>当在正面的时候返回正值,当在背面的时候返回负值</returns>
    public bool GetSideOfBoardPlane(Vector3 _point)
    {
        return m_BoardPlane.GetSide(_point);
    }

    /// <summary>
    /// 笔尖距画板的距离
    /// </summary>
    /// <param name="_point"></param>
    /// <returns></returns>
    public float GetDistanceFromBoardPlane(Vector3 _point)
    {
        return m_BoardPlane.GetDistanceToPoint(_point);
    }

    /// <summary>
    /// 矫正后的笔尖应该在的位置
    /// </summary>
    /// <param name="point">笔尖的位置</param>
    /// <returns>矫正后的笔尖位置</returns>
    public Vector3 ProjectPointOnBoardPlane(Vector3 point)
    {
        float d = -Vector3.Dot(m_BoardPlane.normal, point - transform.position);
        return point + m_BoardPlane.normal * d;
    }
}

用一些基础模型组装一支笔。

Unity 模型Static unity 模型画笔_设计模式_02


TransformModify是用来调整握持位置的,需要赋值给抓取脚本的Snap Handle,RayOrigin是射线起点,BrushHead是一个空物体放在笔的最前端。

Unity 模型Static unity 模型画笔_unity_03

VRTK_SDKTransformModify的使用:

在需要调整握持位置物体下创建空物体挂载VRTK_SDKTransformModify脚本;
将GameObject拖拽给Target,SdkOverrides添加要修正握持位置的平台,我的是HTC Vive;
将创建的空物体赋值给握持物体GrabAttach脚本(有不同的握持类型,我使用的是ChildOfControllerGrabAttach)的snap handle;
在编辑器运行状态下调整握持物体的位置,调整完毕后记录Transform信息填入对应的平台中。

笔调整好之后挂载脚本:

public class PenBrush : MonoBehaviour
{
    [SerializeField] private Color32 m_BrushColor;
    [SerializeField] private int m_BrushWidth;
    [SerializeField] private Transform m_RayOrigin;
    [SerializeField] private float m_RayLength;
   [SerializeField] private Blackboard m_Blackboard;

    private bool m_IsGrab;
    private RaycastHit m_HitInfo;

    private void Awake()
    {
        //扩展的物体抓取脚本 
        GetComponent<BrushGrabAttach>().OnStartGrad += OnStartGrab;
        GetComponent<BrushGrabAttach>().OnStopGrad += OnStopGrad;
    }

    private void OnStartGrab()
    {
        m_IsGrab = true;
    }
    private void OnStopGrad()
    {
        m_IsGrab = false;
        m_Blackboard.DrawStop();
    }

    private bool m_PreviousHaveHit;

    private void Update()
    {
        if (!m_IsGrab) return;
        var l_ray = new Ray(m_RayOrigin.position, m_RayOrigin.forward);
        
        Vector3 forward = m_RayOrigin.transform.TransformDirection(Vector3.forward) * m_RayLength;
        //绘制射线,调试用的。需要打开Gizmos开关,不然不显示。
        Debug.DrawRay(m_RayOrigin.position, forward, Color.magenta);
        
        if (Physics.Raycast(l_ray, out m_HitInfo, m_RayLength))
        {
            if (m_HitInfo.collider.CompareTag("Blackboard"))
            {
                m_Blackboard.UpdateDisplay(m_HitInfo.point,m_HitInfo.textureCoord,m_BrushWidth,m_BrushColor);
                m_PreviousHaveHit = true;
            }
        }
        else if(m_PreviousHaveHit)
        {
            m_Blackboard.DrawStop();
            m_PreviousHaveHit = false;
        }
    }

    private void OnDestroy()
    {
        GetComponent<BrushGrabAttach>().OnStartGrad -= OnStartGrab;        
        GetComponent<BrushGrabAttach>().OnStopGrad -= OnStopGrad;
    }
}

为例避免出现笔穿透画板的情况,扩展一下VRTK的抓取脚本:

public class BrushGrabAttach : VRTK_BaseGrabAttach
{

    [Header("Painter Options")]

    [SerializeField]
    private Transform tips;//笔尖
    [SerializeField] protected Blackboard board;//画板

    public event UnityAction OnStartGrad; 
    public event UnityAction OnStopGrad; 
    
    #region 重写的父类方法
    protected override void Initialise()
    {
        //初始化父类的一些字段,这些字段只是标识这个抓附机制的作用
        tracked = false;
        kinematic = false;
        climbable = false;

        //初始化自定义的属性
        if (precisionGrab)//最好不要用精确抓取,因为这样很有可能会让笔处于一个不合理的位置,这样使用的时候,会很变扭(比如必须手腕旋转一个角度,笔才是正的) 
        {
            Debug.LogError("PrecisionGrab cant't be true in case of PainterGrabAttach Mechanic");
        }
    }

    public override bool StartGrab(GameObject grabbingObject, GameObject givenGrabbedObject, Rigidbody givenControllerAttachPoint)
    {
        if (base.StartGrab(grabbingObject, givenGrabbedObject, givenControllerAttachPoint))
        {
            SnapObjectToGrabToController(givenGrabbedObject);
            grabbedObjectScript.isKinematic = true;
            OnStartGrad?.Invoke();
            return true;
        }
        return false;
    }

    public override void StopGrab(bool applyGrabbingObjectVelocity)
    {
        ReleaseObject(applyGrabbingObjectVelocity);
        OnStopGrad?.Invoke();
        base.StopGrab(applyGrabbingObjectVelocity);
    }

    public override void ProcessFixedUpdate()
    {
        if (grabbedObject)//只有抓住物体后,grabbedObject才不会
        {
            grabbedObject.transform.rotation = controllerAttachPoint.transform.rotation * Quaternion.Euler(grabbedSnapHandle.transform.localEulerAngles);
            grabbedObject.transform.position = controllerAttachPoint.transform.position - (grabbedSnapHandle.transform.position - grabbedObject.transform.position);
            float distance = board.GetDistanceFromBoardPlane(tips.position);//笔尖距离平面的距离
            bool isPositiveOfBoardPlane = board.GetSideOfBoardPlane(tips.position);//笔尖是不是在笔尖的正面
            Vector3 direction = grabbedObject.transform.position - tips.position;//笔尖位置指向笔的位置的差向量
            //当笔尖穿透的时候,需要矫正笔的位置 
            if (isPositiveOfBoardPlane || distance > 0.0001f)
            {
                Vector3 pos = board.ProjectPointOnBoardPlane(tips.position);
                grabbedObject.transform.position = pos - board.BoardPlane.normal * 0.001f + direction;//pos是笔尖的位置,而不是笔的位置,加上direction后才是笔的位置 
            }
        }
    }

    #endregion

    //让手柄抓住物体
    private void SnapObjectToGrabToController(GameObject obj)
    {
        if (!precisionGrab)
        {
            SetSnappedObjectPosition(obj);
        }
    }

    //设置物体和手柄连接的位置 
    private void SetSnappedObjectPosition(GameObject obj)
    {
        if (grabbedSnapHandle == null)
        {
            obj.transform.position = controllerAttachPoint.transform.position;
        }
        else
        {
            //设置旋转,controllerAttachPoint是手柄上的一个与物体的连接点
            obj.transform.rotation = controllerAttachPoint.transform.rotation * Quaternion.Euler(grabbedSnapHandle.transform.localEulerAngles);
            //因为grabbedSnapHandle和obj.transform之间可能不是同一个点,所以为了让手柄抓的位置是grabbedSnapHandle,需要减去括号中代表的向量
            obj.transform.position = controllerAttachPoint.transform.position - (grabbedSnapHandle.transform.position - obj.transform.position);
        }
    }
}

现在画板和画笔挂载的脚本:

Unity 模型Static unity 模型画笔_unity_04


Unity 模型Static unity 模型画笔_数组_05


效果:

Unity 模型Static unity 模型画笔_unity_06

到此,普通的涂鸦功能已经实现,接下来加入轨迹识别。

轨迹识别:

在PS里做一张用于识别轨迹的图片。就是将轨迹用N个1像素的点绘制出来。

Unity 模型Static unity 模型画笔_数组_07

为了避免颜色干扰和减少计算量,新建一个透明图层,在这个图层上做标记。

Unity 模型Static unity 模型画笔_Unity 模型Static_08


绘图层就是有很多1像素点的图片。

Unity 模型Static unity 模型画笔_数组_09

绘制轨迹标记点时最好用铅笔工具,1像素100%硬度。每条轨迹的标记颜色色差最好大一点,太接近容易搞混,这里每条轨迹标记颜色需要记一下颜色数据,代码里是用颜色识别每一条轨迹的。将背景层和绘图层分别导出存为PNG格式。

图片导入Unity,背景层图片拖给背景的材质,绘图层托给画板的材质,记得将绘图层图片的Read/Write Enable打勾。

Unity 模型Static unity 模型画笔_设计模式_10

因为轨迹识别是分区域的,只有在A字母区域绘图时才识别A区域的轨迹是否被覆盖,所以要新建一个类来描述这个区域。需要在面板上编辑所以加上[Serializable]。

using System;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class IdentifyAreas
{
    public Transform upperLeft;

    public Transform lowerRight;

    //区域内的轨迹点颜色
    public Color32 trackColor;
    //提示完成的图片
    public GameObject show;

    //轨迹在绘图像素数组内的下标
    public List<int> track = new List<int>();

    /// <summary>
    /// 检测传入的点是否在区域内
    /// </summary>
    public bool CheckPointInArea(Vector3 _point)
    {
        var l_pointX = _point.x;
        var l_pointZ = _point.z;

        var l_x = l_pointX > upperLeft.position.x && l_pointX < lowerRight.position.x;
        var l_y = l_pointZ > lowerRight.position.z && l_pointZ < upperLeft.position.z;
        return l_x && l_y;
    }

    /// <summary>
    /// 颜色比对
    /// </summary>
    public bool CheckColor(Color32 _color32)
    {
        var l_resultR = Mathf.Abs(trackColor.r - _color32.r) <= 5;
        var l_resultG = Mathf.Abs(trackColor.g - _color32.g) <= 5;
        var l_resultB = Mathf.Abs(trackColor.b - _color32.b) <= 5;
        return l_resultR && l_resultG && l_resultB;
    }
}

使用俩个点的坐标来确定一个区域。

Unity 模型Static unity 模型画笔_游戏开发_11


现在需要修改一下Blackboard脚本,添加区域数组,在Awake阶段读取绘图层的标记点,根据点的颜色将标记点的位置信息加到IdentifyAreas脚本的下标List中。在UpdateDisplay加入绘图区域的检测。绘制结束后添加判断区域内的轨迹点是否被覆盖的逻辑。

修改后的Blackboard:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;

public class Blackboard : MonoBehaviour
{
    //画板尺寸
    private int m_TextureWidth;
    private int m_TextureHeight;

    //当前画板图片
    private Texture2D m_CurrentTexture;

    //当前编辑的颜色数组
    private Color32[] m_CurrentColors;

    //重置画板的颜色数组
    private Color[] m_CleanColorsArray;

    //当前画板的绘制颜色
    private Color m_CurrentBrushColor;

    //用于检测笔位置的Plane
    private Plane m_BoardPlane;

    public Plane BoardPlane
    {
        get => m_BoardPlane;
    }

    //先前拖拽的位置
    private Vector2 previous_drag_position;

    //用于获取HDRP Lit Shader主帖图的字段
    private static readonly int m_BaseColorMap = Shader.PropertyToID("_BaseColorMap");

    //目前绘制的区域(新加)
    private IdentifyAreas m_CurrentArea;

    //所有识别区(新加)
    public IdentifyAreas[] areas;

    private void Awake()
    {
        m_BoardPlane = new Plane(transform.forward, transform.position);
        //获取画板尺寸
        var l_originTexture = GetComponent<MeshRenderer>().material.mainTexture as Texture2D;
        m_TextureWidth = l_originTexture.width;
        m_TextureHeight = l_originTexture.height;
        var l_originColor = l_originTexture.GetPixels32();
        //新建一个纹理赋予材质
        m_CurrentTexture = new Texture2D(l_originTexture.width, l_originTexture.height);
        m_CurrentTexture.SetPixels32(l_originColor);
        m_CurrentTexture.Apply();
        GetComponent<MeshRenderer>().material.SetTexture(m_BaseColorMap, m_CurrentTexture);

		//读取所有的标记点(新加)
        for (int i = 0; i < l_originColor.Length; i++)
        {
        	//过滤掉透明像素
            if (l_originColor[i].a == 0) continue;
            foreach (var l_area in areas)
            {
            	//从Unity读取出来的颜色有色差(不会太大),不能直接比较,在IdentifyAreas新建了一个模糊比对方法。
                if (l_area.CheckColor(l_originColor[i]))
                    l_area.track.Add(i);
            }
        }
    }

    public void DrawStop()
    {
        previous_drag_position = Vector2.zero;
        //(新加)
        if (m_CurrentArea != null)
            CheckTrack();
    }
	//检查轨迹覆盖(新加)
    private void CheckTrack()
    {
        foreach (var l_track in m_CurrentArea.track)
        {
            if (m_CurrentColors[l_track] != m_CurrentBrushColor)
                return;
        }
		m_CurrentArea.show.SetActive(true);
        Debug.Log("完成");
    }

    public void UpdateDisplay(Vector3 _worldPoint, Vector2 _uvPoint, int _brushWidth, Color32 _brushColor)
    {
        Vector2 pixel_pos = UVToPixelCoordinates(_uvPoint);

        m_CurrentColors = m_CurrentTexture.GetPixels32();

        if (previous_drag_position == Vector2.zero)
        {
        	//(新加)
            m_CurrentBrushColor = _brushColor;
            //检测在哪个区域开始
            foreach (var l_area in areas)
            {
                if (l_area.CheckPointInArea(_worldPoint))
                    m_CurrentArea = l_area;
            }

            // 如果这是我们第一次在该图像上拖动,只需在鼠标位置上为像素着色
            MarkPixelsToColour(pixel_pos, _brushWidth, _brushColor);
        }
        else
        {
            // 在上次更新呼叫所在的行中显示颜色
            ColourBetween(previous_drag_position, pixel_pos, _brushWidth, _brushColor);
        }

        ApplyMarkedPixelChanges();

        previous_drag_position = pixel_pos;
    }

    /// <summary>
    /// UV坐标转像素坐标
    /// </summary>
    private Vector2 UVToPixelCoordinates(Vector2 _vector2)
    {
        // 需要以我们的坐标为中心
        float centered_x = _vector2.x * m_TextureWidth;
        float centered_y = _vector2.y * m_TextureHeight;

        // 将当前鼠标位置四舍五入到最近的像素
        Vector2 pixel_pos = new Vector2(Mathf.RoundToInt(centered_x), Mathf.RoundToInt(centered_y));

        return pixel_pos;
    }
    /// <summary>
    /// 计算需要绘制的像素数量
    /// </summary>
    public void MarkPixelsToColour(Vector2 center_pixel, int pen_thickness, Color color_of_pen)
    {
        //找出每个方向(x和y)需要着色的像素数量
        int center_x = (int) center_pixel.x;
        int center_y = (int) center_pixel.y;
        //int extra_radius = Mathf.Min(0, pen_thickness - 2);

        for (int x = center_x - pen_thickness; x <= center_x + pen_thickness; x++)
        {
            // 检查X是否环绕图像,因此我们不在图像的另一侧绘制像素
            if (x >= m_TextureWidth || x < 0)
                continue;

            for (int y = center_y - pen_thickness; y <= center_y + pen_thickness; y++)
            {
                MarkPixelToChange(x, y, color_of_pen);
            }
        }
    }

    /// <summary>
    /// 俩点之间插入过渡点
    /// </summary>
    private void ColourBetween(Vector2 start_point, Vector2 end_point, int width, Color color)
    {
        // 获取从头到尾的距离
        float distance = Vector2.Distance(start_point, end_point);
        Vector2 direction = (start_point - end_point).normalized;

        Vector2 cur_position = start_point;

        // 根据自上次更新以来经过的时间,计算在start_point和end_point之间进行插值的次数
        float lerp_steps = 1 / distance;

        for (float lerp = 0; lerp <= 1; lerp += lerp_steps)
        {
            cur_position = Vector2.Lerp(start_point, end_point, lerp);
            MarkPixelsToColour(cur_position, width, color);
        }
    }

    /// <summary>
    /// 修改像素数组信息
    /// </summary>
    public void MarkPixelToChange(int x, int y, Color color)
    {
        // 需要将x和y坐标转换为数组的平面坐标
        int array_pos = y * m_TextureHeight + x;

        // 检查这是一个有效的位置
        if (array_pos > m_CurrentColors.Length || array_pos < 0)
            return;

        m_CurrentColors[array_pos] = color;
    }

    /// <summary>
    /// 将新的像素数组赋值给纹理
    /// </summary>
    public void ApplyMarkedPixelChanges()
    {
        m_CurrentTexture.SetPixels32(m_CurrentColors);
        m_CurrentTexture.Apply();
    }

    /// <summary>
    /// 笔尖在画板正面还是背面
    /// </summary>
    /// <param name="_point">笔尖的位置</param>
    /// <returns>>当在正面的时候返回正值,当在背面的时候返回负值</returns>
    public bool GetSideOfBoardPlane(Vector3 _point)
    {
        return m_BoardPlane.GetSide(_point);
    }

    /// <summary>
    /// 笔尖距画板的距离
    /// </summary>
    /// <param name="_point"></param>
    /// <returns></returns>
    public float GetDistanceFromBoardPlane(Vector3 _point)
    {
        return m_BoardPlane.GetDistanceToPoint(_point);
    }

    /// <summary>
    /// 矫正后的笔尖应该在的位置
    /// </summary>
    /// <param name="point">笔尖的位置</param>
    /// <returns>矫正后的笔尖位置</returns>
    public Vector3 ProjectPointOnBoardPlane(Vector3 point)
    {
        float d = -Vector3.Dot(m_BoardPlane.normal, point - transform.position);
        return point + m_BoardPlane.normal * d;
    }
}

接下来到面板上编辑IdentifyAreas信息:

Unity 模型Static unity 模型画笔_游戏开发_12

Unity 模型Static unity 模型画笔_数组_13


到此,文章开头的效果已经实现。

4.其他功能

板檫:

板擦和画笔的功能一样,板擦的颜色是画板的原始色。
画笔和板擦的抓取方式不同,重写一下之前抓取脚本里的方法。

using UnityEngine;

public class EraserGrabAttach : BrushGrabAttach
{
    public override void ProcessFixedUpdate()
    {
        if (grabbedObject)//只有抓住物体后,grabbedObject才不会
        {
            grabbedObject.transform.rotation = controllerAttachPoint.transform.rotation * Quaternion.Euler(grabbedSnapHandle.transform.localEulerAngles);
            grabbedObject.transform.position = controllerAttachPoint.transform.position - (grabbedSnapHandle.transform.position - grabbedObject.transform.position);
            float distance = board.GetDistanceFromBoardPlane(transform.position);//黑板檫距离画板的距离
            if (distance > -0.096f)//当黑板檫离画板足够近的时候
            {
                float percentOfDistance = Helper.RemapNumberClamped(distance, -0.096f, -0.05f, 0f, 1f);//映射后,得到插值系数
                Quaternion q = Quaternion.FromToRotation(grabbedObject.transform.up, -board.transform.forward);//最终的目的是:黑板擦的transform.up指向-transform.forward
                q *= grabbedObject.transform.rotation;//得到黑板檫达到最终目的时的旋转
                grabbedObject.transform.rotation = Quaternion.Slerp(grabbedObject.transform.rotation, q, percentOfDistance);//通过插值,得到当前黑板檫的旋转
                if (distance > 0.01f)//如果黑板檫穿透了画板,需要进行矫正
                {
                    Vector3 pos = board.ProjectPointOnBoardPlane(grabbedObject.transform.position);
                    grabbedObject.transform.position = pos - board.transform.forward * 0.01f;
                }
            }                     
        }
    }
}

5.存在的问题

Unity 模型Static unity 模型画笔_unity_14

判断的是标记点的颜色是否被修改成画笔颜色,要是将区域内全部涂鸦也会触发完成逻辑。暂时想到的解决办法是在区域内用其他颜色标记几个辅助点,做判断时这几个点的颜色不能被修改。