简介
根据个人需要,想在unity3D中实现在3D模型上绘制纹理的功能,经过多方查找资料和一些个人的思考,写了一个粗略的版本出来,在此总结一点个人的浅见,方便后时遗忘时,有址可寻,由于个人经验善浅,如若存在不足,欢迎大佬指正。
实现
using UnityEngine;
using System.Collections;// 由于想做纹理的旋转操作,在unity3D中没有找到3阶矩阵,所以自己动手写了一个,也算是自己动手丰衣足食。
class Matrix3x3
{
float m11, m21, m31,
m12, m22, m32,
m13, m23, m33;
public Matrix3x3()
{
Identity();
}// 单位化矩阵 void Identity() { m11 = 1.0f; m21 = 0.0f; m31 = 0.0f; m12 = 0.0f; m22 = 1.0f; m32 = 0.0f; m13 = 0.0f; m23 = 0.0f; m33 = 1.0f; } // 平移矩阵 public void Translation(float x, float y) { m11 = 1.0f; m21 = 0.0f; m31 = 0.0f; m12 = 0.0f; m22 = 1.0f; m32 = 0.0f; m13 = x; m23 = y; m33 = 1.0f; } // 旋转矩阵 public void Rotation(float a) { m11 = Mathf.Cos(a); m21 = Mathf.Sin(a); m31 = 0.0f; m12 = -m21; m22 = m11; m32 = 0.0f; m13 = 0.0f; m23 = 0.0f; m33 = 1.0f; } // 缩放矩阵 public void Scale(float x, float y) { m11 = x; m21 = 0.0f; m31 = 0.0f; m12 = 0.0f; m22 = y; m32 = 0.0f; m13 = 0.0f; m23 = 0.0f; m33 = 1.0f; }
// 矩阵乘二位向量
public Vector2 MxV(Vector2 v)
{
Vector3 sv = new Vector3(v.x, v.y, 1);
Vector3 rv;
rv.x = sv.x * m11 + sv.y * m12 + sv.z * m13;
rv.y = sv.x * m21 + sv.y * m22 + sv.z * m23;
rv.z = sv.x * m31 + sv.y * m32 + sv.z * m33;
return new Vector2(rv.x / rv.z, rv.y / rv.z);
}
}public class Wound : MonoBehaviour
{
// 笔刷贴图(选择比较小的,在取像素是会较为方便)
public Texture2D brush_Texture;
// 伤口模型的碰撞层级(和其他的模型区分开来,防止误操作)
public LayerMask woundMask;
// 伤口模型的初始化贴图(初始化的时候赋予伤口模型,确保伤口模型完全透明)
public Texture2D saveTexture;// 光标位置 Vector2 cursPos; // 纹理坐标的上方向 Vector2 text_UP = new Vector2(0, -1); // 纹理矩阵 Matrix3x3 my_Matrix = new Matrix3x3(); // 笔刷半径 public float brush_Radius = 8; // 笔刷流量(此处使用了时间做为间隔的判断,主要是个人对于笔刷流量的具体控制形势了解的不甚明朗^_^) public float flow = 0.2f; float count; // 伤口模型材质的原始贴图 Texture2D oring_Texture = null; // 绘制伤口的贴图 Texture2D editTexture = null; // 前一次纹理采样的uv位置 Vector2 last_UV; // 当前纹理采样的uv位置 Vector2 current_UV; // UV的比例值 float u_per_Pixel = -1; float v_per_Pixel = -1; // brush纹理采样 float sample_W; float sample_H; // Use this for initialization void Start() { count = 0; // 初始化上一次采样的uv位置 last_UV = new Vector2(-1, -1); // 初始化当前采样的uv位置 current_UV = new Vector2(-1, -1); // 根据笔刷半径和笔刷纹理大小计算brush纹理采样值 sample_W = brush_Texture.width / (2 * brush_Radius); sample_H = brush_Texture.height / (2 * brush_Radius); } // Update is called once per frame void Update() { } // 绘制伤口 public void DrawTexture(Vector2 cursorPos) { if(count > 0) { count -= Time.deltaTime; return; } count = flow; // 射线碰撞检测 Ray myRay = Camera.main.ScreenPointToRay(cursorPos); RaycastHit myHit; if (Physics.Raycast(myRay, out myHit, 1000f, woundMask)) { // 普通模型 MeshRenderer myMeshRender = myHit.transform.GetComponent
(); if (myMeshRender) { // 如果当前原始图像为空 if (oring_Texture == null) { // 获取原始图像 oring_Texture = myMeshRender.sharedMaterial.mainTexture as Texture2D; // 如果当前模型的材质没有贴图,则创建一张1024*1024的图像, if (oring_Texture == null) { u_per_Pixel = 1.0f / 1024; v_per_Pixel = 1.0f / 1024; editTexture = new Texture2D(1024, 1024, TextureFormat.ARGB32, false); // 设置贴图为全白透明 Color cT = new Color(1, 1, 1, 0); for (int y = 0; y < 1024; ++y) { for (int x = 0; x < 1024; ++x) { editTexture.SetPixel(x, y, cT); } } } // 如果有原始图像 else { // 计算uv的比重 u_per_Pixel = 1.0f / oring_Texture.width; v_per_Pixel = 1.0f / oring_Texture.height; // 根据原始贴图,创建新的纹理 editTexture = new Texture2D(oring_Texture.width, oring_Texture.height, TextureFormat.ARGB32, false); editTexture.alphaIsTransparency = true; // 将原始图像的像素赋予给新图片 for (int y = 0; y < oring_Texture.height; ++y) { for (int x = 0; x < oring_Texture.width; ++x) { editTexture.SetPixel(x, y, oring_Texture.GetPixel(x, y)); } } // 更新伤口绘制贴图 editTexture.Apply(); } // 使用可编辑的新图替换材质 myMeshRender.sharedMaterial.mainTexture = editTexture; } // 当前光标点对应的uv坐标 current_UV = myHit.textureCoord; // 如果是第一次进入,更新上一次的uv采样为当前采样 if (last_UV.x < 0 || last_UV.y < 0) { last_UV = current_UV; } // 如果不是第一次进入 else if (last_UV.x >= 0 && last_UV.y >= 0) { // 如果当前采样和前一次采样相同, if (current_UV.x == last_UV.x && current_UV.y == last_UV.y) return; // 当前朝向 Vector2 current_Dir = current_UV - last_UV; // 图片的旋转量 float angle = Vector2.Angle(text_UP, current_Dir); // 左旋或者右旋 if (current_UV.x < last_UV.x) angle *= -1; // 绘制笔刷范围的像素 DrawArea(current_UV, angle); // 更新前一次采样为当前采样 last_UV = current_UV; editTexture.Apply(); } } // 蒙皮模型 else { SkinnedMeshRenderer mySkinRender = myHit.transform.GetComponent (); if (mySkinRender) { // 如果当前原始图像为空(即第一次对当前模型进行绘制) if (oring_Texture == null) { // 获取原始图像 oring_Texture = mySkinRender.sharedMaterial.mainTexture as Texture2D; // 如果当前模型的材质没有贴图,则创建一张1024*1024的图像, if (oring_Texture == null) { u_per_Pixel = 1.0f / 1024; v_per_Pixel = 1.0f / 1024; editTexture = new Texture2D(1024, 1024, TextureFormat.ARGB32, false); // 设置贴图为全白透明 Color cT = new Color(1, 1, 1, 0); for (int y = 0; y < 1024; ++y) { for (int x = 0; x < 1024; ++x) { editTexture.SetPixel(x, y, cT); } } } // 如果有原始图像 else { // 计算uv的比重 u_per_Pixel = 1.0f / oring_Texture.width; v_per_Pixel = 1.0f / oring_Texture.height; // 根据原始贴图,创建新的纹理 editTexture = new Texture2D(oring_Texture.width, oring_Texture.height, TextureFormat.ARGB32, false); // 将原始图像的像素赋予给新图片 for (int y = 0; y < oring_Texture.height; ++y) { for (int x = 0; x < oring_Texture.width; ++x) { editTexture.SetPixel(x, y, oring_Texture.GetPixel(x, y)); } } // 更新伤口绘制贴图 editTexture.Apply(); } // 使用可编辑的新图替换材质 mySkinRender.sharedMaterial.mainTexture = editTexture; } // 当前光标点对应的uv坐标 current_UV = myHit.textureCoord; // 如果是第一次进入,更新上一次的uv采样为当前采样 if (last_UV.x < 0 || last_UV.y < 0) { last_UV = current_UV; } // 如果不是第一次进入 else if (last_UV.x >= 0 && last_UV.y >= 0) { // 如果当前采样和前一次采样相同, if (current_UV.x == last_UV.x && current_UV.y == last_UV.y) return; // 当前朝向 Vector2 current_Dir = current_UV - last_UV; // 图片的旋转量 float angle = Vector2.Angle(text_UP, current_Dir); // 左旋或者右旋 if (current_UV.x < last_UV.x) angle *= -1; // 绘制笔刷范围的像素 DrawArea(current_UV, angle); // 更新前一次采样为当前采样 last_UV = current_UV; editTexture.Apply(); } } } } } // 绘制笔刷区域 void DrawArea(Vector2 currentUV, float a) { // 设置旋转 my_Matrix.Rotation(a * Mathf.Deg2Rad); // 当前uv点在图片中的像素位置 int myPixle_X = Mathf.FloorToInt(currentUV.x * editTexture.width); int myPixle_Y = Mathf.FloorToInt(currentUV.y * editTexture.height); // 绘制 int r = Mathf.FloorToInt(brush_Radius); // 初始化笔刷纹理采样位置 Vector2 brush_Sample = Vector2.zero; for (int y = -r; y < r; ++y) { // 笔刷纹理X方向的采样回到本行开头 brush_Sample.x = 0; for(int x = -r; x < r; ++x) { // 当前像素位置 Vector2 cur_Pix = new Vector2(x, y); // 旋转当前像素 Vector2 rot_Pix = my_Matrix.MxV(cur_Pix); // 平移像素 rot_Pix.x += myPixle_X; rot_Pix.y += myPixle_Y; // 绘制当前像素 DrawSinglePixel(rot_Pix, brush_Sample); brush_Sample.x += sample_W; } brush_Sample.y += sample_H; } } // 绘制单个像素数 void DrawSinglePixel(Vector2 destPos,Vector2 brushPixelPos) { // 计算当前uv点在像素中的位置 int destlx = Mathf.FloorToInt(destPos.x); int destly = Mathf.FloorToInt(destPos.y); int brushlx = Mathf.FloorToInt(brushPixelPos.x); int brushly = Mathf.FloorToInt(brushPixelPos.y); // 获取背景色的颜色,和笔刷的颜色 Color brush_c = brush_Texture.GetPixel(brushlx, brushly); Color edit_c = editTexture.GetPixel(destlx, destly); // 根据笔刷的透明度,计算新的填充色 Color new_c; if (edit_c.a != 0) { new_c = new Color((1 - brush_c.a) * edit_c.r + brush_c.a * brush_c.r, (1 - brush_c.a) * edit_c.g + brush_c.a * brush_c.g, (1 - brush_c.a) * edit_c.b + brush_c.a * brush_c.b); } else { new_c = brush_c; } // 设置图片 editTexture.SetPixel(destlx, destly, new_c); } // 结束绘制 public void DrawEnd() { last_UV = new Vector2(-1, -1); current_UV = new Vector2(-1, -1); }
附注
editTexture.alphaIsTransparency = true; 在unity编辑阶段没有问题,编译的时候会报错,所以在设置texture2D透明的时候,直接在创建的时候设置,使用如下语句
editTexture = new Texture2D(oring_Texture.width, oring_Texture.height, TextureFormat.ARGB32 | TextureFormat.Alpha8, false);