Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Meshのローカル座標系における法線情報を指定サイズのテクスチャに焼いて保存するUnityEditor拡張。InkPainter( https://github.com/EsProgram/InkPainter ) を利用する。この法線テクスチャ情報を使うサンプルのスクリプトとShaderもついでに乗っけとく
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.IO;
using Es.InkPainter;
public class LocalNormalMapCreator : EditorWindow
{
private static Mesh mesh;
private static Texture2D tex;
private static int textureSize = 256;
private static bool debugLine;
[MenuItem("Window/InkPainter/NormalMap Creator")]
public static void Open()
{
EditorWindow.GetWindow<NormalMapCreator>();
}
private void OnGUI()
{
mesh = EditorGUILayout.ObjectField("Mesh", mesh, typeof(Mesh), false) as Mesh;
textureSize = EditorGUILayout.IntField("Texture size", textureSize);
debugLine = EditorGUILayout.Toggle("Debug line draw", debugLine);
if (mesh != null && GUILayout.Button("Create NormalTexture"))
{
var tri = mesh.triangles;
var normals = mesh.normals;
var uv = mesh.uv;
tex = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false);
for (int i = 0; i < tri.Length; i += 3)
{
var n1 = normals[tri[i]];
var n2 = normals[tri[i + 1]];
var n3 = normals[tri[i + 2]];
var p1 = uv[tri[i]];
var p2 = uv[tri[i + 1]];
var p3 = uv[tri[i + 2]];
//三角形の各点
var a = new Vector2Int(Mathf.FloorToInt(textureSize * p1.x), Mathf.FloorToInt(textureSize * p1.y));
var b = new Vector2Int(Mathf.FloorToInt(textureSize * p2.x), Mathf.FloorToInt(textureSize * p2.y));
var c = new Vector2Int(Mathf.FloorToInt(textureSize * p3.x), Mathf.FloorToInt(textureSize * p3.y));
//三角形の各法線カラー
var an = new Color(n1.x * 0.5f + 0.5f, n1.y * 0.5f + 0.5f, n1.z * 0.5f + 0.5f, 1);
var bn = new Color(n2.x * 0.5f + 0.5f, n2.y * 0.5f + 0.5f, n2.z * 0.5f + 0.5f, 1);
var cn = new Color(n3.x * 0.5f + 0.5f, n3.y * 0.5f + 0.5f, n3.z * 0.5f + 0.5f, 1);
//描画処理
FillTriangles(a, b, c, an, bn, cn);
//デバッグ用描画処理
if (debugLine)
{
DrawLine(a.x, a.y, b.x, b.y, Color.black);
DrawLine(b.x, b.y, c.x, c.y, Color.black);
DrawLine(c.x, c.y, a.x, a.y, Color.black);
}
}
tex.Apply();
}
if (tex != null)
{
GUILayout.Box(tex);
if (GUILayout.Button("Save file"))
{
string path = EditorUtility.SaveFilePanel("Save to png", Application.dataPath, $"{mesh.name}_LN.png", "png");
byte[] pngData = tex.EncodeToPNG();
if (pngData != null)
{
File.WriteAllBytes(path, pngData);
AssetDatabase.Refresh();
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
}
}
}
}
/// <summary>
/// スキャンラインで三角形範囲内なら塗りつぶしを行う
/// </summary>
private static void FillTriangles(Vector2Int a, Vector2Int b, Vector2Int c, Color an, Color bn, Color cn)
{
//三角形を包含する最小の四角形を算出する
var rect = TriangleInclusionRect(a, b, c);
//スキャンライン法式でピクセルカラーを埋めていく
for (int x = rect.xMin; x <= rect.xMax; ++x)
for (int y = rect.yMin; y <= rect.yMax; ++y)
{
var tp = new Vector2(x, y);
var ta = new Vector2(a.x, a.y);
var tb = new Vector2(b.x, b.y);
var tc = new Vector2(c.x, c.y);
if (Math.ExistPointOnTriangleEdge(tp, ta, tb, tc) || Math.ExistPointInTriangle(tp, ta, tb, tc))
tex.SetPixel(x, y, PixcelNormalColor(new Vector2Int(x, y), a, b, c, an, bn, cn));
}
}
private static RectInt TriangleInclusionRect(Vector2Int a, Vector2Int b, Vector2Int c)
{
var tris = new List<Vector2Int>() { a, b, c };
var minX = tris.Min(p => p.x);
var minY = tris.Min(p => p.y);
var maxX = tris.Max(p => p.x);
var maxY = tris.Max(p => p.y);
//矩形のwidth/heightを計算
return new RectInt(minX, minY, maxX - minX, maxY - minY);
}
private static Color PixcelNormalColor(Vector2Int p, Vector2Int a, Vector2Int b, Vector2Int c, Color an, Color bn, Color cn)
{
//a-bでの割合
var tmp1 = Color.Lerp(an, bn, Vector2Int.Distance(a, p) / Vector2Int.Distance(a, b));
//a-cでの割合
var tmp2 = Color.Lerp(an, cn, Vector2Int.Distance(a, p) / Vector2Int.Distance(a, c));
//色の合成返却
return Color.Lerp(tmp1, tmp2, 0.5f);
}
private static void DrawLine(int x0, int y0, int x1, int y1, Color col)
{
int dy = y1 - y0;
int dx = x1 - x0;
int stepx, stepy;
if (dy < 0)
{ dy = -dy; stepy = -1; }
else
{ stepy = 1; }
if (dx < 0)
{ dx = -dx; stepx = -1; }
else
{ stepx = 1; }
dy <<= 1;
dx <<= 1;
float fraction = 0;
tex.SetPixel(x0, y0, col);
if (dx > dy)
{
fraction = dy - (dx >> 1);
while (Mathf.Abs(x0 - x1) > 1)
{
if (fraction >= 0)
{
y0 += stepy;
fraction -= dx;
}
x0 += stepx;
fraction += dy;
tex.SetPixel(x0, y0, col);
}
}
else
{
fraction = dx - (dy >> 1);
while (Mathf.Abs(y0 - y1) > 1)
{
if (fraction >= 0)
{
x0 += stepx;
fraction -= dy;
}
y0 += stepy;
fraction += dx;
tex.SetPixel(x0, y0, col);
}
}
}
}
Shader "LocalNormalToWorldNormal"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Rotate("q", VECTOR) = (0,0,0,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Rotate;
float4 rotate(float4 p, float4 q)
{
return float4(p.w*q.xyz + q.w*p.xyz + cross(p.xyz, q.xyz), p.w*q.w - dot(p.xyz, q.xyz));
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 localNormal = tex2D(_MainTex, i.uv) * 2 - 1;
float4 q = _Rotate;
float4 n = float4(localNormal.xyz, 0);
float4 r = float4(-q.xyz, q.w);
float4 normal = rotate(rotate(q, n), r);
return float4(normal.xyz, 1);
}
ENDCG
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SetQuaternionToShader : MonoBehaviour {
private void OnWillRenderObject()
{
var t = transform.rotation;
var rot = new Vector4(t.x, t.y, t.z, t.w);
GetComponent<Renderer>().material.SetVector("_Rotate", rot);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment