NLDClient-yudde/ProjectNLD/Assets/Editor/Scene/TwoDimensionRenderUtil.cs

327 lines
12 KiB
C#
Raw Normal View History

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
}