在Unity中实时绘制纹理总共需要两个东西, 一个是RenderTexture, 而如何确定绘制的区域则需要用到Raycast到MeshCollider从而获取到指定的textureCoord, 然后使用GL调用底层函数把一个Material绘制在RenderTexture上.
需要注意的是:一个有效的可绘制纹理模型,它的Mesh必须有有效的uv数据,而Unity自带的各个预制模型是没有正常的UV数据,从而无法在它们身上绘制纹理.在试验的使用请不要使用类似Unity自带类似Cube,Sphere等模型.*
第一步: 准备
定义一个画笔纹理, 比如一个白色32x32的圆.
然后为画笔纹理创建一个Shader:
Shader "Painting"
{
Properties
{
_MainTex("MainTex (RGB) Trans (A)", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Cull Off
Lighting Off
ZWrite Off
Fog{ Mode Off }
Blend One OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
};
fixed4 _Color;
v2f vert(appdata_base IN)
{
v2f OUT;
OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
OUT.texcoord = IN.texcoord;
return OUT;
}
sampler2D _MainTex;
fixed4 frag(v2f IN) : SV_Target
{
float4 col = _Color * tex2D(_MainTex, IN.texcoord);
col.rgb *= col.a;
return col;
}
ENDCG
}
}
}
最后创建一个画笔Material, 将纹理和shader应用上. 最终成为这样
被画的模型也应当应用一个至少有一个Texture参数的Shader,比如Unlit/Texture
,这样我们才能够将它原来的纹理替换成我们在运行时创建的RenderTexture.
场景搭建完毕以后是这样:
第二步: 编写绘画代码
一切尽在代码中
using UnityEngine;
public class Painter : MonoBehaviour
{
public const int SIZE = 512;
public Material mat;
public float scale;
public Color col;
private Renderer rend;
private RenderTexture rendTex;
private Texture brushTex;
private void Start()
{
//使用ARGB32的格式创建一个RenderTexture
rendTex = new RenderTexture(SIZE, SIZE, 24, RenderTextureFormat.ARGB32);
rendTex.isPowerOfTwo = true;
rendTex.useMipMap = false;
rendTex.Create();
//清空画布
Clear();
//获取当前模型的Renderer把它的纹理替换成刚建立的RenderTexture
rend = GetComponent<Renderer>();
rend.material.SetTexture("_MainTex", rendTex);
//获取画笔纹理
brushTex = mat.mainTexture;
//设置我们想要的颜色到画笔材质上.
mat.SetColor("_Color", col);
}
private void Update()
{
//当鼠标按下时发射射线碰撞模型
if (Input.GetMouseButton(0))
{
var mp = Input.mousePosition;
var ray = Camera.main.ScreenPointToRay(mp);
RaycastHit rayHit;
if (Physics.Raycast(ray, out rayHit))
{
//uv坐标是0~1,而我们要的是它纹理上的坐标是0~SIZE,于是乘以纹理的SIZE
DrawBrush((int)(rayHit.textureCoord.x * SIZE), (int)(rayHit.textureCoord.y * SIZE), col, scale);
}
}
}
private void Clear()
{
Graphics.SetRenderTarget(rendTex);
GL.PushMatrix();
GL.Clear(true, true, Color.white);
GL.PopMatrix();
}
private void DrawBrush(int x, int y, Color col, float scale)
{
//计算画笔居中当前位置以后的四个角的坐标
var left = x - brushTex.width * scale / 2f;
var right = x + brushTex.width * scale / 2f;
var top = y + brushTex.height * scale / 2f;
var bot = y - brushTex.height * scale / 2f;
//将GPU的绘制目标转移到当前RenderTexture上
Graphics.SetRenderTarget(rendTex);
//使用GL图像库绘制一个四边形
GL.PushMatrix();
GL.LoadOrtho();
mat.SetPass(0);
GL.Begin(GL.QUADS);
GL.TexCoord2(0, 0);
GL.Vertex3(left / SIZE, bot / SIZE, 0);
GL.TexCoord2(0, 1);
GL.Vertex3(left / SIZE, top / SIZE, 0);
GL.TexCoord2(1, 1);
GL.Vertex3(right / SIZE, top / SIZE, 0);
GL.TexCoord2(1, 0);
GL.Vertex3(right / SIZE, bot / SIZE, 0);
GL.End();
GL.PopMatrix();
//搞定
}
}
最后将Painter脚本添加到刚才的模型身上, 运行游戏,点击鼠标左键就能在模型上画画了.是不是很溜!!!