效果:
教程链接:传送门:)
原理
基本
unityshader可以对材质贴图进行编辑,在在片元着色器中检测到顶点BrushPos小于BrushSize的顶点颜色修改位BrushColor。
笔尖位置发出一条射线,检测碰撞点,将点渲染到贴图上(将笔刷有关信息传入shader)
保存上一帧、当前帧、画布材质,在三者之间进行切换更新。
优化
移速过快会造成点阵距离过大不够流畅,于是在相距大于BrushSize的两个点之间进行插值补足空隙。
Shader
Shader "Brush/MarkPenEffect"
{
properties{
_MainTex("Texture",2D) = "white"{}
_BrushPos("BrushPos",Vector) = (0,0,0,0)
_BrushColor("Brush Color",Color) = (1,1,1,1)
_BrushSize("Brush Size",float) = 0.01
}
Subshader{
Tags{"RenderType" = "Opaque"}
pass {
CGPROGRAM
#pragma vertex vert;
#pragma fragment frag;
#include "UnityCg.cginc"
struct appdata {
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
};
struct v2f {
float2 uv:TEXCOORD0;
float4 vertex:SV_POSITION;
};
sampler2D _MainTex;
float4 _BrushPos;
float4 _BrushColor;
float _BrushSize;
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) :SV_Target{
fixed4 col = tex2D(_MainTex,i.uv);
if (length(i.uv - _BrushPos.xy) < _BrushSize) {
col = _BrushColor;
}
return col;
}
ENDCG
}
}
}
脚本代码
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class MarkPen : MonoBehaviour
{
private Texture tex;//这张图是板子的原图
public RenderTexture cacheTex;//缓存上一帧的图
RenderTexture currentTex;//当前帧操作的图
public float brushSize = 0.01f;
public Color brushCol = Color.red;
private Material effectMat;//用来处理图像的材质
private Material renderMat;//原始面板的材质
public Transform penHead;
public Transform board;
private Vector2 lastuv;
private bool isDown;
// Start is called before the first frame update
void Start()
{
Initialized();
}
// Update is called once per frame
void Update()
{
Ray ray = new Ray(penHead.position, transform.forward);
if(Physics.Raycast(ray,out RaycastHit raycastHit, 1, LayerMask.GetMask("Board")))
{
if (!isDown)
{
isDown = true;
lastuv = raycastHit.textureCoord2;
}
RenderBrushToBoard(raycastHit);
lastuv = raycastHit.textureCoord2;
}
else
{
isDown= false;
}
}
private void RenderBrushToBoard(RaycastHit hit)
{
Debug.Log("1");
Vector2 dir = hit.textureCoord2 - lastuv;
if (Vector3.SqrMagnitude(dir) > brushSize * brushSize)
{
int length = Mathf.CeilToInt(dir.magnitude / brushSize);
for (int i = 0; i < length; i++)
{
RenderToMatTex(lastuv + dir.normalized * i * brushSize);
}
}
else
{
RenderToMatTex(hit.textureCoord2);
}
Debug.Log("2");
}
/* private void RenderBrushToBoard(RaycastHit hit)
{
RenderToMatTex(hit.textureCoord2);
}*/
private void RenderToMatTex(Vector2 uv)
{
effectMat.SetVector("_BrushPos",new Vector4(uv.x,uv.y,lastuv.x,lastuv.y));
effectMat.SetColor("_BrushColor",brushCol);
effectMat.SetFloat("_BrushSize", brushSize);
Graphics.Blit(cacheTex,currentTex,effectMat);
renderMat.SetTexture("_MainText",currentTex);
Graphics.Blit(currentTex,cacheTex);
}
private void Initialized()
{
effectMat = new Material(Shader.Find("Brush/MarkPenEffect"));
Material boardMat = board.GetComponent<MeshRenderer>().material;
tex =boardMat.mainTexture;
renderMat = boardMat;
cacheTex = new RenderTexture(tex.width,tex.height,0,RenderTextureFormat.ARGB32);
Graphics.Blit(tex,cacheTex);
renderMat.SetTexture("_MainTex",cacheTex);
currentTex= new RenderTexture(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
}
}