unity3D 中实现在模型上绘制纹理

简介

根据个人需要,想在unity3D中实现在3D模型上绘制纹理的功能,经过多方查找资料和一些个人的思考,写了一个粗略的版本出来,在此总结一点个人的浅见,方便后时遗忘时,有址可寻,由于个人经验善浅,如若存在不足,欢迎大佬指正。

实现

<pre>
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<MeshRenderer>();
        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<SkinnedMeshRenderer>();
            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);
}

</pre>

附注

editTexture.alphaIsTransparency = true; 在unity编辑阶段没有问题,编译的时候会报错,所以在设置texture2D透明的时候,直接在创建的时候设置,使用如下语句
editTexture = new Texture2D(oring_Texture.width, oring_Texture.height, TextureFormat.ARGB32 | TextureFormat.Alpha8, false);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容