最近、スプレーと描画の一連の機能を実現したいと思っていますが、これはかなり便利だと感じています。
1 つ目は、次のように描画操作にマテリアル テクスチャを使用する通常のカラー描画です。
上記がテクスチャ uv であると仮定すると、マウス レイが muv に当たり、半径に従って描画された外接四角形を計算し、円弧から円の中心までの距離に従って円の着色を判断します。ここで注意すべき唯一のことは、テクスチャ ピクセルの境界の問題です。次のように境界を越えないでください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestColorSpray : MonoBehaviour
{
public Color color; //颜色
[Range(0, 1)]
public float alpha; //透明度
public int radius; //半径像素
private Camera cam;
private bool isStart = false;
private Material mat;
private Texture2D tex;
private int texWidth;
private int texHeight;
void Start()
{
cam = Camera.main;
}
void FixedUpdate()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
StartSpray(hit.transform);
}
}
if (Input.GetMouseButton(0))
{
if (isStart)
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
#if UNITY_EDITOR
Debug.DrawLine(cam.transform.position, hit.point, Color.black);
#endif
//鼠标圆心
Vector2 muv = new Vector2(hit.textureCoord.x * texWidth, hit.textureCoord.y * texHeight);
int mx = Mathf.Clamp((int)muv.x, 0, texWidth - 1);
int my = Mathf.Clamp((int)muv.y, 0, texHeight - 1);
//外接矩形(不规则)
int left = Mathf.Clamp(mx - radius, 0, texWidth - 1);
int right = Mathf.Clamp(mx + radius, 0, texWidth - 1);
int bottom = Mathf.Clamp(my - radius, 0, texHeight - 1);
int top = Mathf.Clamp(my + radius, 0, texHeight - 1);
int rectwid = right - left;
int recthei = top - bottom;
//绘制
Color[] ogcols = tex.GetPixels(left, bottom, rectwid, recthei);
//逐行扫描
for (int y = 0; y < recthei; y++)
{
for (int x = 0; x < rectwid; x++)
{
int index = y * rectwid + x;
int px = x + left - mx;
int py = y + bottom - my;
float alphainten;
if (CheckInCircle(radius, px, py, out alphainten))
{
Color ocol = ogcols[index];
Color scol = color;
float inten = alphainten * alpha;
ogcols[index] = BlendColor(ocol, scol, inten);
}
}
}
tex.SetPixels(left, bottom, rectwid, recthei, ogcols);
tex.Apply();
}
}
}
if (Input.GetMouseButtonUp(0))
{
StopSpray();
}
}
private void StartSpray(Transform obj)
{
isStart = true;
mat = obj.GetComponent<MeshRenderer>().sharedMaterial;
tex = (Texture2D)mat.GetTexture("_MainTex");
texWidth = tex.width;
texHeight = tex.height;
}
private void StopSpray()
{
isStart = false;
}
/// <summary>
/// 检测xy在rad圆形中
/// 相对坐标,圆心(0,0)
/// </summary>
/// <param name="rad"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="inten"></param>
/// <returns></returns>
private bool CheckInCircle(int rad, int x, int y, out float inten)
{
float rad2 = rad * rad;
float len2 = x * x + y * y;
inten = 1f - len2 / rad2;
return len2 <= rad2;
}
/// <summary>
/// 混合颜色
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private Color BlendColor(Color a, Color b, float k)
{
Color c = Color.Lerp(a, b, k);
return c;
}
}
テストしてみると、マテリアルのメインテックスは次のように readwrite/rgba32 に設定されています。
ただし、小さな問題があり、素材画像をコピーすることをお勧めします。そうしないと、次のように適用によって元のデータが上書きされます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SprayObject : MonoBehaviour
{
void Start()
{
MeshRenderer render = gameObject.GetComponent<MeshRenderer>();
Material mat = render.material;
Texture2D tex = (Texture2D)mat.GetTexture("_MainTex");
Texture2D copytex = new Texture2D(tex.width, tex.height, tex.format, false);
copytex.SetPixels(tex.GetPixels());
copytex.Apply();
mat.SetTexture("_MainTex", copytex);
}
}
このようにして、スプレーするオブジェクトは最初にマテリアルとテクスチャのクローンを作成します。
同時に、この解決策には依然として最大の問題が残っています。それは、次のように UV シームを処理できないことです。
モデル メッシュの継ぎ目の UV は不連続かつランダムであるため (アーティストの UVMapping によると)、スプレーの連続性を扱うことは不可能です。より現実的な場合は、別のスプレー スキームを考える必要があります。
もちろん、現在のスキームにも地形やオブジェクトのブラシなど、使用する特定の場所があり、それを使用しています。
次のようなブラシ パターン テクスチャもあります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestGraphSpray : MonoBehaviour
{
public Texture2D graphTex; //图案
[Range(0, 1)]
public float alpha; //透明度
private Camera cam;
private bool isStart = false;
private Material mat;
private Texture2D tex;
private int texWidth;
private int texHeight;
private int gWid; //图案宽
private int gHei; //图案高
private int ghWid; //图案半宽
private int ghHei; //图案半高
void Start()
{
cam = Camera.main;
gWid = graphTex.width;
gHei = graphTex.height;
ghWid = (int)(gWid * 0.5f);
ghHei = (int)(graphTex.height * 0.5f);
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
StartSpray(hit.transform);
}
}
if (Input.GetMouseButton(0))
{
if (isStart)
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
#if UNITY_EDITOR
Debug.DrawLine(cam.transform.position, hit.point, Color.black);
#endif
//鼠标圆心
Vector2 muv = new Vector2(hit.textureCoord.x * texWidth, hit.textureCoord.y * texHeight);
int mx = Mathf.Clamp((int)muv.x, 0, texWidth - 1);
int my = Mathf.Clamp((int)muv.y, 0, texHeight - 1);
//外接矩形(不规则)
int left = Mathf.Clamp(mx - ghWid, 0, texWidth - 1);
int right = Mathf.Clamp(mx + ghWid, 0, texWidth - 1);
int bottom = Mathf.Clamp(my - ghHei, 0, texHeight - 1);
int top = Mathf.Clamp(my + ghHei, 0, texHeight - 1);
int rectwid = right - left;
int recthei = top - bottom;
//绘制
Color[] ocols = tex.GetPixels(left, bottom, rectwid, recthei);
//根据矩形是否越界,判断graphTex采样的起始点
int gx = mx > ghWid ? 0 : ghWid - mx;
int gy = my > ghHei ? 0 : ghHei - my;
Color[] gcols = graphTex.GetPixels(gx, gy, rectwid, recthei);
//逐行扫描
for (int y = 0; y < recthei; y++)
{
for (int x = 0; x < rectwid; x++)
{
int index = y * rectwid + x;
Color ocol = ocols[index];
Color gcol = gcols[index];
float k = alpha * gcol.a;
ocols[index] = BlendColor(ocol, gcol, k);
}
}
tex.SetPixels(left, bottom, rectwid, recthei, ocols);
tex.Apply();
}
}
}
if (Input.GetMouseButtonUp(0))
{
StopSpray();
}
}
private void StartSpray(Transform obj)
{
isStart = true;
mat = obj.GetComponent<MeshRenderer>().sharedMaterial;
tex = (Texture2D)mat.GetTexture("_MainTex");
texWidth = tex.width;
texHeight = tex.height;
}
private void StopSpray()
{
isStart = false;
}
private Color BlendColor(Color a, Color b, float k)
{
Color c = Color.Lerp(a, b, k);
return c;
}
}
中心となるのは、描画四角形のサンプリング パターンと背景色の配列を混合することです。その効果は次のとおりです。
四角形が境界を「突破」するときのパターン サンプリング座標の問題に特に注意してください。
もちろん、次のような、さらに深く掘り下げる必要がある多くの質問がまだあります。
1. モデルの UV ジョイントとモデル間のジョイントのスプレーをどのように処理するか?
2. スプレー + グリッド計算は zbrush 操作と同様に連動しますか?
3. 3D テクスチャとモデルのカラーリングを処理できますか?
ゆっくりと気づく時が来るでしょう。
ちなみに、この手の描画操作は ComputeShader には向いていないのでテストを書いてみましたが、ComputeShader はカラーマトリクスの計算は CPU よりも速いのですが、データバスのやり取りに時間がかかりすぎて、この手の吹き付け操作には向いていません。