2023-12-04 16:49:54 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
2026-01-13 13:20:29 +08:00
|
|
|
|
using PhxhSDK;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEditor;
|
2023-12-19 14:28:32 +08:00
|
|
|
|
using UnityEngine.Rendering.Universal;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
using Object = UnityEngine.Object;
|
|
|
|
|
|
|
2023-12-13 13:51:06 +08:00
|
|
|
|
public static class TwoDimensionRenderUtil
|
2023-12-04 16:49:54 +08:00
|
|
|
|
{
|
|
|
|
|
|
private static Camera _camera;
|
|
|
|
|
|
private static GameObject _gameObject;
|
2023-12-12 20:03:44 +08:00
|
|
|
|
private const float WIDTH = 1920f;
|
|
|
|
|
|
private const float HEIGHT = 1080f;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
|
|
|
|
|
|
//camera old value
|
|
|
|
|
|
private static float _oldAspect;
|
|
|
|
|
|
private static float _oldSize;
|
|
|
|
|
|
private static Vector3 _oldPosition;
|
|
|
|
|
|
private static RenderTexture _oldRT;
|
|
|
|
|
|
private static LayerMask _oldCullingMask;
|
|
|
|
|
|
private static CameraClearFlags _oldClearFlags;
|
|
|
|
|
|
private static Color _oldBkgColor;
|
2023-12-19 14:28:32 +08:00
|
|
|
|
private static bool _enablePostProcess;
|
2023-12-19 14:49:47 +08:00
|
|
|
|
private static string _cameraTag;
|
|
|
|
|
|
|
2023-12-04 16:49:54 +08:00
|
|
|
|
//obj old value
|
|
|
|
|
|
private static int _oldLayer;
|
2023-12-13 13:51:06 +08:00
|
|
|
|
private static Vector3 _oldObjPosition;
|
|
|
|
|
|
private static Vector3 _oldObjScale;
|
|
|
|
|
|
private static Transform _oldObjParent;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
|
|
|
|
|
|
//RT old value
|
|
|
|
|
|
private static RenderTexture _oldActiveRT;
|
2023-12-14 12:21:48 +08:00
|
|
|
|
private static RenderTexture _currRT;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
|
|
|
|
|
|
private static void SaveCameraValue()
|
|
|
|
|
|
{
|
|
|
|
|
|
_oldAspect = _camera.aspect;
|
|
|
|
|
|
_oldSize = _camera.orthographicSize;
|
|
|
|
|
|
_oldPosition = _camera.transform.position;
|
|
|
|
|
|
_oldRT = _camera.targetTexture;
|
|
|
|
|
|
_oldCullingMask = _camera.cullingMask;
|
|
|
|
|
|
_oldClearFlags = _camera.clearFlags;
|
|
|
|
|
|
_oldBkgColor = _camera.backgroundColor;
|
2023-12-19 14:28:32 +08:00
|
|
|
|
_enablePostProcess = _camera.GetUniversalAdditionalCameraData().renderPostProcessing;
|
2023-12-19 14:49:47 +08:00
|
|
|
|
_cameraTag = _camera.gameObject.tag;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void RevertCameraValue()
|
|
|
|
|
|
{
|
|
|
|
|
|
_camera.aspect = _oldAspect;
|
|
|
|
|
|
_camera.orthographicSize = _oldSize;
|
|
|
|
|
|
_camera.transform.position = _oldPosition;
|
|
|
|
|
|
_camera.targetTexture = _oldRT;
|
|
|
|
|
|
_camera.cullingMask = _oldCullingMask;
|
|
|
|
|
|
_camera.clearFlags = _oldClearFlags;
|
|
|
|
|
|
_camera.backgroundColor = _oldBkgColor;
|
2023-12-19 14:28:32 +08:00
|
|
|
|
_camera.GetUniversalAdditionalCameraData().renderPostProcessing = _enablePostProcess;
|
2023-12-19 14:49:47 +08:00
|
|
|
|
_camera.gameObject.tag = _cameraTag;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void SaveObjValue()
|
|
|
|
|
|
{
|
|
|
|
|
|
_oldLayer = _gameObject.layer;
|
2023-12-13 13:51:06 +08:00
|
|
|
|
_oldObjParent = _gameObject.transform.parent;
|
|
|
|
|
|
_oldObjPosition = _gameObject.transform.position;
|
|
|
|
|
|
_oldObjScale = _gameObject.transform.lossyScale;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-12 20:03:44 +08:00
|
|
|
|
private static void RevertObject(bool isPrefab)
|
2023-12-04 16:49:54 +08:00
|
|
|
|
{
|
2023-12-12 20:03:44 +08:00
|
|
|
|
if (isPrefab)
|
|
|
|
|
|
{
|
|
|
|
|
|
Object.DestroyImmediate(_gameObject);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-14 12:21:48 +08:00
|
|
|
|
SetGameObjectLayer(_gameObject, _oldLayer);
|
2023-12-13 13:51:06 +08:00
|
|
|
|
_gameObject.transform.position = _oldObjPosition;
|
|
|
|
|
|
_gameObject.transform.localScale = _oldObjScale;
|
|
|
|
|
|
_gameObject.transform.parent = _oldObjParent;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void SaveRT()
|
|
|
|
|
|
{
|
|
|
|
|
|
_oldActiveRT = RenderTexture.active;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void RevertRT()
|
|
|
|
|
|
{
|
|
|
|
|
|
RenderTexture.active = _oldActiveRT;
|
2023-12-14 12:21:48 +08:00
|
|
|
|
RenderTexture.ReleaseTemporary(_currRT);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MenuItem("GameObject/RenderToTexture")]
|
|
|
|
|
|
private static void RenderToTexture()
|
|
|
|
|
|
{
|
|
|
|
|
|
var activeGo = Selection.activeGameObject;
|
|
|
|
|
|
if (activeGo != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 offset = Vector3.zero;
|
|
|
|
|
|
Vector3 scale = Vector3.zero;
|
2023-12-14 14:33:45 +08:00
|
|
|
|
Quaternion rotation = Quaternion.identity;
|
|
|
|
|
|
activeGo.RenderToTexture(Camera.main, "Assets/test.jpg", ref offset, ref scale, ref rotation);
|
2023-12-14 12:21:48 +08:00
|
|
|
|
}
|
2023-12-04 16:49:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-13 13:51:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将gameObject渲染到一张贴图上
|
|
|
|
|
|
/// 注:下面的所有计算都是基于scale值为1的情况,如果要在scale不为1的情况下使用positionOffset和textureScale,则需要自行乘上scale
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="gameObject"></param>
|
|
|
|
|
|
/// <param name="camera">需要用来渲染的相机,必须是正交的,且旋转已经设置好了</param>
|
|
|
|
|
|
/// <param name="path">贴图存放的路径</param>
|
|
|
|
|
|
/// <param name="positionOffset">三维到二维的偏移量,当三维转二维的时候用三维position加上该值可保证三维和二维刚好重合</param>
|
|
|
|
|
|
/// <param name="textureScale">转换二维后的缩放值,条件是二维的mesh长宽都为1</param>
|
2023-12-14 14:33:45 +08:00
|
|
|
|
/// <param name="rotation">转换二维后的旋转值</param>
|
2023-12-13 13:51:06 +08:00
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Texture2D RenderToTexture(this GameObject gameObject, Camera camera, string path,
|
2023-12-14 14:33:45 +08:00
|
|
|
|
ref Vector3 positionOffset, ref Vector3 textureScale, ref Quaternion rotation)
|
2023-12-04 16:49:54 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (gameObject == null || camera == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-13 13:51:06 +08:00
|
|
|
|
if (!camera.orthographic)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("current camera is not orthographic");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-12 20:03:44 +08:00
|
|
|
|
bool isPrefab = false;
|
|
|
|
|
|
if (EditorUtility.IsPersistent(gameObject))
|
|
|
|
|
|
{
|
|
|
|
|
|
isPrefab = true;
|
|
|
|
|
|
gameObject = PrefabUtility.InstantiatePrefab(gameObject) as GameObject;
|
|
|
|
|
|
if (gameObject == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-13 13:51:06 +08:00
|
|
|
|
var mesh = gameObject.GetComponentInChildren<MeshFilter>().sharedMesh;
|
|
|
|
|
|
if (mesh == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
2023-12-04 16:49:54 +08:00
|
|
|
|
_gameObject = gameObject;
|
|
|
|
|
|
_camera = camera;
|
|
|
|
|
|
SaveCameraValue();
|
|
|
|
|
|
SaveObjValue();
|
|
|
|
|
|
SaveRT();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2023-12-19 14:28:32 +08:00
|
|
|
|
_camera.GetUniversalAdditionalCameraData().renderPostProcessing = false;
|
2023-12-19 14:49:47 +08:00
|
|
|
|
_camera.gameObject.tag = "RenderTwoDimension";
|
|
|
|
|
|
|
2023-12-12 20:03:44 +08:00
|
|
|
|
var newLayer = LayerMask.NameToLayer("RenderTool");
|
2023-12-14 14:33:45 +08:00
|
|
|
|
SetGameObjectLayer(gameObject, newLayer);
|
|
|
|
|
|
gameObject.transform.parent = null;
|
|
|
|
|
|
gameObject.transform.localScale = Vector3.one;
|
|
|
|
|
|
gameObject.transform.position = Vector3.zero;
|
2023-12-13 13:51:06 +08:00
|
|
|
|
//设置position y为0不一定物体就刚好位于地面上,需要调整一下
|
2023-12-14 14:33:45 +08:00
|
|
|
|
var msr = gameObject.GetComponentInChildren<MeshRenderer>();
|
|
|
|
|
|
var meshTrans = msr.transform;
|
2023-12-13 13:51:06 +08:00
|
|
|
|
var yOffset = 0 - msr.bounds.min.y;
|
2023-12-14 14:33:45 +08:00
|
|
|
|
gameObject.transform.position = new Vector3(0, yOffset, 0);
|
2023-12-13 13:51:06 +08:00
|
|
|
|
|
2023-12-12 20:03:44 +08:00
|
|
|
|
camera.aspect = WIDTH / HEIGHT;
|
2023-12-14 14:33:45 +08:00
|
|
|
|
var viewPortPoints = GetMeshBorder(mesh, meshTrans,
|
2023-12-04 16:49:54 +08:00
|
|
|
|
camera);
|
|
|
|
|
|
var left = viewPortPoints[0].x;
|
|
|
|
|
|
var right = viewPortPoints[1].x;
|
|
|
|
|
|
var bottom = viewPortPoints[2].y;
|
|
|
|
|
|
var top = viewPortPoints[3].y;
|
|
|
|
|
|
camera.transform.position = camera.ViewportToWorldPoint(viewPortPoints[4]);
|
|
|
|
|
|
camera.cullingMask = 1 << newLayer;
|
2023-12-12 20:03:44 +08:00
|
|
|
|
var rtWidth = Mathf.FloorToInt(WIDTH * (right - left));
|
|
|
|
|
|
var rtHeight = Mathf.FloorToInt(HEIGHT * (top - bottom));
|
|
|
|
|
|
var pixelHeightRatio = top - bottom;
|
2023-12-14 14:33:45 +08:00
|
|
|
|
var imageHeight = camera.orthographicSize * pixelHeightRatio * 2;
|
2023-12-12 20:03:44 +08:00
|
|
|
|
var imageWidth = imageHeight * rtWidth / rtHeight;
|
2023-12-13 13:51:06 +08:00
|
|
|
|
textureScale = new Vector3(imageWidth, imageHeight, 1);
|
2023-12-14 14:33:45 +08:00
|
|
|
|
rotation = camera.transform.rotation;
|
2023-12-14 12:21:48 +08:00
|
|
|
|
_currRT = RenderTexture.GetTemporary(rtWidth, rtHeight, 0, RenderTextureFormat.ARGB32);
|
2023-12-04 16:49:54 +08:00
|
|
|
|
camera.aspect = (float)rtWidth / rtHeight;
|
|
|
|
|
|
camera.orthographicSize *= (top - bottom);
|
2023-12-14 12:21:48 +08:00
|
|
|
|
camera.targetTexture = _currRT;
|
2023-12-04 16:49:54 +08:00
|
|
|
|
camera.clearFlags = CameraClearFlags.Color;
|
|
|
|
|
|
camera.backgroundColor = Color.clear;
|
|
|
|
|
|
camera.Render();
|
2023-12-12 20:03:44 +08:00
|
|
|
|
var twoDimensionPositionWS = TwoDimensionToWorldPosition(camera);
|
2023-12-14 14:33:45 +08:00
|
|
|
|
positionOffset = twoDimensionPositionWS - meshTrans.position;
|
2023-12-14 12:21:48 +08:00
|
|
|
|
RenderTexture.active = _currRT;
|
2026-01-13 13:20:29 +08:00
|
|
|
|
Texture2D tex = TextureUtils.Create(gameObject.name, rtWidth, rtHeight);
|
2023-12-04 16:49:54 +08:00
|
|
|
|
tex.ReadPixels(new Rect(0, 0, rtWidth, rtHeight), 0, 0);
|
|
|
|
|
|
tex.Apply();
|
2023-12-25 15:38:15 +08:00
|
|
|
|
Byte[] bytes = tex.EncodeToJPG();
|
2023-12-04 16:49:54 +08:00
|
|
|
|
FileStream fs = File.Open(path, FileMode.Create);
|
|
|
|
|
|
BinaryWriter writer = new BinaryWriter(fs);
|
|
|
|
|
|
writer.Write(bytes);
|
|
|
|
|
|
writer.Flush();
|
|
|
|
|
|
writer.Close();
|
|
|
|
|
|
fs.Close();
|
|
|
|
|
|
Object.DestroyImmediate(tex);
|
|
|
|
|
|
AssetDatabase.Refresh();
|
|
|
|
|
|
var savedTex = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
|
|
|
|
|
|
if (savedTex != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Log("保存成功!" + path);
|
|
|
|
|
|
}
|
2023-12-13 13:51:06 +08:00
|
|
|
|
|
2023-12-04 16:49:54 +08:00
|
|
|
|
return savedTex;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
RevertCameraValue();
|
2023-12-12 20:03:44 +08:00
|
|
|
|
RevertObject(isPrefab);
|
2023-12-04 16:49:54 +08:00
|
|
|
|
RevertRT();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2023-12-13 13:51:06 +08:00
|
|
|
|
/// 获取mesh在当前相机视角下的边界视口坐标,一定刚好可以包围住mesh
|
2023-12-04 16:49:54 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="mesh"></param>
|
|
|
|
|
|
/// <param name="trans"></param>
|
|
|
|
|
|
/// <param name="cam"></param>
|
|
|
|
|
|
/// <returns>前四个值是左右下上,最后一个值是中心点</returns>
|
|
|
|
|
|
private static Vector3[] GetMeshBorder(Mesh mesh, Transform trans, Camera cam)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3[] result = new Vector3[5];
|
|
|
|
|
|
float left = float.PositiveInfinity;
|
|
|
|
|
|
float right = float.NegativeInfinity;
|
|
|
|
|
|
float top = float.NegativeInfinity;
|
|
|
|
|
|
float bottom = float.PositiveInfinity;
|
|
|
|
|
|
Vector3 leftPosSS = Vector3.zero;
|
|
|
|
|
|
Vector3 rightPosSS = Vector3.zero;
|
|
|
|
|
|
Vector3 bottomPosSS = Vector3.zero;
|
|
|
|
|
|
Vector3 topPosSS = Vector3.zero;
|
|
|
|
|
|
for (int i = 0; i < mesh.vertexCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var vertex = mesh.vertices[i];
|
|
|
|
|
|
var positionWS = trans.TransformPoint(vertex);
|
|
|
|
|
|
var positionSS = cam.WorldToViewportPoint(positionWS);
|
|
|
|
|
|
if (positionSS.x < left)
|
|
|
|
|
|
{
|
|
|
|
|
|
left = positionSS.x;
|
|
|
|
|
|
leftPosSS = positionSS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (positionSS.x > right)
|
|
|
|
|
|
{
|
|
|
|
|
|
right = positionSS.x;
|
|
|
|
|
|
rightPosSS = positionSS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (positionSS.y < bottom)
|
|
|
|
|
|
{
|
|
|
|
|
|
bottom = positionSS.y;
|
|
|
|
|
|
bottomPosSS = positionSS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (positionSS.y > top)
|
|
|
|
|
|
{
|
|
|
|
|
|
top = positionSS.y;
|
|
|
|
|
|
topPosSS = positionSS;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Vector2 centerSS = new Vector3((left + right) / 2, (top + bottom) / 2, 0);
|
|
|
|
|
|
result[0] = leftPosSS;
|
|
|
|
|
|
result[1] = rightPosSS;
|
|
|
|
|
|
result[2] = bottomPosSS;
|
|
|
|
|
|
result[3] = topPosSS;
|
|
|
|
|
|
result[4] = centerSS;
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-12 20:03:44 +08:00
|
|
|
|
/// <summary>
|
2023-12-13 13:51:06 +08:00
|
|
|
|
/// 将当前相机(必须是正交的)近裁剪面投射到地面上(底部刚好和地面相切,故Z轴不能有旋转),中心点的世界坐标
|
2023-12-12 20:03:44 +08:00
|
|
|
|
/// 此时的相机已经完成拍摄作业,即已经将镜头对准被拍摄物体且aspect刚好包围被拍摄物
|
2023-12-13 13:51:06 +08:00
|
|
|
|
/// 原理:面片的高度是确定的,等于相机halfSize*cos(x轴旋转)
|
|
|
|
|
|
/// 假设中心点在相机局部坐标系中的坐标为(0,0,z),获得相机的M矩阵,M矩阵右乘(0,0,z)的y值即为刚刚计算出来的高度值,可建立方程:
|
|
|
|
|
|
/// height = z*M(1,2)+M(1,3)
|
|
|
|
|
|
/// 由此可以算出z值,再将(0,0,z)从局部坐标转换到世界坐标即可
|
2023-12-12 20:03:44 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="camera"></param>
|
2023-12-13 13:51:06 +08:00
|
|
|
|
/// <returns>中心点世界坐标,用该值和被拍摄物件的世界坐标相减可以得到偏移量,用于将物体从三维转二维后坐标依然可以对的上</returns>
|
2023-12-12 20:03:44 +08:00
|
|
|
|
private static Vector3 TwoDimensionToWorldPosition(Camera camera)
|
|
|
|
|
|
{
|
|
|
|
|
|
var halfSize = camera.orthographicSize;
|
|
|
|
|
|
var xAngle = camera.transform.localEulerAngles.x * Mathf.Deg2Rad;
|
|
|
|
|
|
var height = Mathf.Cos(xAngle) * halfSize;
|
|
|
|
|
|
var mMatrix = camera.transform.localToWorldMatrix;
|
|
|
|
|
|
var localZ = (height - mMatrix[1, 3]) / mMatrix[1, 2];
|
|
|
|
|
|
var localPos = new Vector3(0, 0, localZ);
|
|
|
|
|
|
var worldPos = camera.transform.TransformPoint(localPos);
|
|
|
|
|
|
return worldPos;
|
|
|
|
|
|
}
|
2023-12-14 12:21:48 +08:00
|
|
|
|
|
2023-12-14 14:33:45 +08:00
|
|
|
|
// private static Quaternion GetRootGameObjectRotation(Transform rootTrans, Transform currTrans,
|
|
|
|
|
|
// Quaternion currQuaternion)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// if (currTrans == rootTrans || currTrans == null)
|
|
|
|
|
|
// return currQuaternion;
|
|
|
|
|
|
// currQuaternion *= Quaternion.Inverse(currTrans.localRotation);
|
|
|
|
|
|
// return GetRootGameObjectRotation(rootTrans, currTrans.parent, currQuaternion);
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-12-14 12:21:48 +08:00
|
|
|
|
public static void SetGameObjectLayer(GameObject obj, int layer)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (obj == null) return;
|
|
|
|
|
|
obj.layer = layer;
|
|
|
|
|
|
var trans = obj.transform;
|
|
|
|
|
|
for (var i = 0; i < trans.childCount; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
var child = trans.GetChild(i);
|
|
|
|
|
|
SetGameObjectLayer(child.gameObject, layer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-12-04 16:49:54 +08:00
|
|
|
|
}
|