簡介
根據(jù)個人需要,想在unity3D中實現(xiàn)在3D模型上繪制紋理的功能椎扬,經(jīng)過多方查找資料和一些個人的思考颅停,寫了一個粗略的版本出來谓晌,在此總結(jié)一點(diǎn)個人的淺見,方便后時遺忘時癞揉,有址可尋纸肉,由于個人經(jīng)驗善淺,如若存在不足喊熟,歡迎大佬指正柏肪。
實現(xiàn)
<pre>
using UnityEngine;
using System.Collections;
// 由于想做紋理的旋轉(zhuǎn)操作,在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;
}
// 旋轉(zhuǎn)矩陣
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;
// 傷口模型的碰撞層級(和其他的模型區(qū)分開來谬俄,防止誤操作)
public LayerMask woundMask;
// 傷口模型的初始化貼圖(初始化的時候賦予傷口模型,確保傷口模型完全透明)
public Texture2D saveTexture;
// 光標(biāo)位置
Vector2 cursPos;
// 紋理坐標(biāo)的上方向
Vector2 text_UP = new Vector2(0, -1);
// 紋理矩陣
Matrix3x3 my_Matrix = new Matrix3x3();
// 筆刷半徑
public float brush_Radius = 8;
// 筆刷流量(此處使用了時間做為間隔的判斷弃理,主要是個人對于筆刷流量的具體控制形勢了解的不甚明朗^_^)
public float flow = 0.2f;
float count;
// 傷口模型材質(zhì)的原始貼圖
Texture2D oring_Texture = null;
// 繪制傷口的貼圖
Texture2D editTexture = null;
// 前一次紋理采樣的uv位置
Vector2 last_UV;
// 當(dāng)前紋理采樣的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);
// 初始化當(dāng)前采樣的uv位置
current_UV = new Vector2(-1, -1);
// 根據(jù)筆刷半徑和筆刷紋理大小計算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<MeshRenderer>();
if (myMeshRender)
{
// 如果當(dāng)前原始圖像為空
if (oring_Texture == null)
{
// 獲取原始圖像
oring_Texture = myMeshRender.sharedMaterial.mainTexture as Texture2D;
// 如果當(dāng)前模型的材質(zhì)沒有貼圖溃论,則創(chuàng)建一張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);
// 設(shè)置貼圖為全白透明
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;
// 根據(jù)原始貼圖案铺,創(chuàng)建新的紋理
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();
}
// 使用可編輯的新圖替換材質(zhì)
myMeshRender.sharedMaterial.mainTexture = editTexture;
}
// 當(dāng)前光標(biāo)點(diǎn)對應(yīng)的uv坐標(biāo)
current_UV = myHit.textureCoord;
// 如果是第一次進(jìn)入,更新上一次的uv采樣為當(dāng)前采樣
if (last_UV.x < 0 || last_UV.y < 0)
{
last_UV = current_UV;
}
// 如果不是第一次進(jìn)入
else if (last_UV.x >= 0 && last_UV.y >= 0)
{
// 如果當(dāng)前采樣和前一次采樣相同梆靖,
if (current_UV.x == last_UV.x && current_UV.y == last_UV.y)
return;
// 當(dāng)前朝向
Vector2 current_Dir = current_UV - last_UV;
// 圖片的旋轉(zhuǎn)量
float angle = Vector2.Angle(text_UP, current_Dir);
// 左旋或者右旋
if (current_UV.x < last_UV.x)
angle *= -1;
// 繪制筆刷范圍的像素
DrawArea(current_UV, angle);
// 更新前一次采樣為當(dāng)前采樣
last_UV = current_UV;
editTexture.Apply();
}
}
// 蒙皮模型
else
{
SkinnedMeshRenderer mySkinRender = myHit.transform.GetComponent<SkinnedMeshRenderer>();
if (mySkinRender)
{
// 如果當(dāng)前原始圖像為空(即第一次對當(dāng)前模型進(jìn)行繪制)
if (oring_Texture == null)
{
// 獲取原始圖像
oring_Texture = mySkinRender.sharedMaterial.mainTexture as Texture2D;
// 如果當(dāng)前模型的材質(zhì)沒有貼圖控汉,則創(chuàng)建一張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);
// 設(shè)置貼圖為全白透明
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;
// 根據(jù)原始貼圖返吻,創(chuàng)建新的紋理
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();
}
// 使用可編輯的新圖替換材質(zhì)
mySkinRender.sharedMaterial.mainTexture = editTexture;
}
// 當(dāng)前光標(biāo)點(diǎn)對應(yīng)的uv坐標(biāo)
current_UV = myHit.textureCoord;
// 如果是第一次進(jìn)入姑子,更新上一次的uv采樣為當(dāng)前采樣
if (last_UV.x < 0 || last_UV.y < 0)
{
last_UV = current_UV;
}
// 如果不是第一次進(jìn)入
else if (last_UV.x >= 0 && last_UV.y >= 0)
{
// 如果當(dāng)前采樣和前一次采樣相同,
if (current_UV.x == last_UV.x && current_UV.y == last_UV.y)
return;
// 當(dāng)前朝向
Vector2 current_Dir = current_UV - last_UV;
// 圖片的旋轉(zhuǎn)量
float angle = Vector2.Angle(text_UP, current_Dir);
// 左旋或者右旋
if (current_UV.x < last_UV.x)
angle *= -1;
// 繪制筆刷范圍的像素
DrawArea(current_UV, angle);
// 更新前一次采樣為當(dāng)前采樣
last_UV = current_UV;
editTexture.Apply();
}
}
}
}
}
// 繪制筆刷區(qū)域
void DrawArea(Vector2 currentUV, float a)
{
// 設(shè)置旋轉(zhuǎn)
my_Matrix.Rotation(a * Mathf.Deg2Rad);
// 當(dāng)前uv點(diǎn)在圖片中的像素位置
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)
{
// 當(dāng)前像素位置
Vector2 cur_Pix = new Vector2(x, y);
// 旋轉(zhuǎn)當(dāng)前像素
Vector2 rot_Pix = my_Matrix.MxV(cur_Pix);
// 平移像素
rot_Pix.x += myPixle_X;
rot_Pix.y += myPixle_Y;
// 繪制當(dāng)前像素
DrawSinglePixel(rot_Pix, brush_Sample);
brush_Sample.x += sample_W;
}
brush_Sample.y += sample_H;
}
}
// 繪制單個像素數(shù)
void DrawSinglePixel(Vector2 destPos,Vector2 brushPixelPos)
{
// 計算當(dāng)前uv點(diǎn)在像素中的位置
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);
// 根據(jù)筆刷的透明度街佑,計算新的填充色
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;
}
// 設(shè)置圖片
editTexture.SetPixel(destlx, destly, new_c);
}
// 結(jié)束繪制
public void DrawEnd()
{
last_UV = new Vector2(-1, -1);
current_UV = new Vector2(-1, -1);
}
</pre>
附注
editTexture.alphaIsTransparency = true; 在unity編輯階段沒有問題谢翎,編譯的時候會報錯,所以在設(shè)置texture2D透明的時候沐旨,直接在創(chuàng)建的時候設(shè)置森逮,使用如下語句
editTexture = new Texture2D(oring_Texture.width, oring_Texture.height, TextureFormat.ARGB32 | TextureFormat.Alpha8, false);