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

326 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
using UnityEngine.Rendering.Universal;
using Object = UnityEngine.Object;
public static class TwoDimensionRenderUtil
{
private static Camera _camera;
private static GameObject _gameObject;
private const float WIDTH = 1920f;
private const float HEIGHT = 1080f;
//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;
private static bool _enablePostProcess;
private static string _cameraTag;
//obj old value
private static int _oldLayer;
private static Vector3 _oldObjPosition;
private static Vector3 _oldObjScale;
private static Transform _oldObjParent;
//RT old value
private static RenderTexture _oldActiveRT;
private static RenderTexture _currRT;
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;
_enablePostProcess = _camera.GetUniversalAdditionalCameraData().renderPostProcessing;
_cameraTag = _camera.gameObject.tag;
}
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;
_camera.GetUniversalAdditionalCameraData().renderPostProcessing = _enablePostProcess;
_camera.gameObject.tag = _cameraTag;
}
private static void SaveObjValue()
{
_oldLayer = _gameObject.layer;
_oldObjParent = _gameObject.transform.parent;
_oldObjPosition = _gameObject.transform.position;
_oldObjScale = _gameObject.transform.lossyScale;
}
private static void RevertObject(bool isPrefab)
{
if (isPrefab)
{
Object.DestroyImmediate(_gameObject);
return;
}
SetGameObjectLayer(_gameObject, _oldLayer);
_gameObject.transform.position = _oldObjPosition;
_gameObject.transform.localScale = _oldObjScale;
_gameObject.transform.parent = _oldObjParent;
}
private static void SaveRT()
{
_oldActiveRT = RenderTexture.active;
}
private static void RevertRT()
{
RenderTexture.active = _oldActiveRT;
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;
Quaternion rotation = Quaternion.identity;
activeGo.RenderToTexture(Camera.main, "Assets/test.jpg", ref offset, ref scale, ref rotation);
}
}
/// <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>
/// <param name="rotation">转换二维后的旋转值</param>
/// <returns></returns>
public static Texture2D RenderToTexture(this GameObject gameObject, Camera camera, string path,
ref Vector3 positionOffset, ref Vector3 textureScale, ref Quaternion rotation)
{
if (gameObject == null || camera == null)
{
return null;
}
if (!camera.orthographic)
{
Debug.LogError("current camera is not orthographic");
return null;
}
bool isPrefab = false;
if (EditorUtility.IsPersistent(gameObject))
{
isPrefab = true;
gameObject = PrefabUtility.InstantiatePrefab(gameObject) as GameObject;
if (gameObject == null)
{
return null;
}
}
var mesh = gameObject.GetComponentInChildren<MeshFilter>().sharedMesh;
if (mesh == null)
return null;
_gameObject = gameObject;
_camera = camera;
SaveCameraValue();
SaveObjValue();
SaveRT();
try
{
_camera.GetUniversalAdditionalCameraData().renderPostProcessing = false;
_camera.gameObject.tag = "RenderTwoDimension";
var newLayer = LayerMask.NameToLayer("RenderTool");
SetGameObjectLayer(gameObject, newLayer);
gameObject.transform.parent = null;
gameObject.transform.localScale = Vector3.one;
gameObject.transform.position = Vector3.zero;
//设置position y为0不一定物体就刚好位于地面上需要调整一下
var msr = gameObject.GetComponentInChildren<MeshRenderer>();
var meshTrans = msr.transform;
var yOffset = 0 - msr.bounds.min.y;
gameObject.transform.position = new Vector3(0, yOffset, 0);
camera.aspect = WIDTH / HEIGHT;
var viewPortPoints = GetMeshBorder(mesh, meshTrans,
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;
var rtWidth = Mathf.FloorToInt(WIDTH * (right - left));
var rtHeight = Mathf.FloorToInt(HEIGHT * (top - bottom));
var pixelHeightRatio = top - bottom;
var imageHeight = camera.orthographicSize * pixelHeightRatio * 2;
var imageWidth = imageHeight * rtWidth / rtHeight;
textureScale = new Vector3(imageWidth, imageHeight, 1);
rotation = camera.transform.rotation;
_currRT = RenderTexture.GetTemporary(rtWidth, rtHeight, 0, RenderTextureFormat.ARGB32);
camera.aspect = (float)rtWidth / rtHeight;
camera.orthographicSize *= (top - bottom);
camera.targetTexture = _currRT;
camera.clearFlags = CameraClearFlags.Color;
camera.backgroundColor = Color.clear;
camera.Render();
var twoDimensionPositionWS = TwoDimensionToWorldPosition(camera);
positionOffset = twoDimensionPositionWS - meshTrans.position;
RenderTexture.active = _currRT;
Texture2D tex = PhxhSDK.Utils.CreateTexture(gameObject.name, rtWidth, rtHeight);
tex.ReadPixels(new Rect(0, 0, rtWidth, rtHeight), 0, 0);
tex.Apply();
Byte[] bytes = tex.EncodeToJPG();
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);
}
return savedTex;
}
finally
{
RevertCameraValue();
RevertObject(isPrefab);
RevertRT();
}
}
/// <summary>
/// 获取mesh在当前相机视角下的边界视口坐标一定刚好可以包围住mesh
/// </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;
}
/// <summary>
/// 将当前相机(必须是正交的)近裁剪面投射到地面上(底部刚好和地面相切故Z轴不能有旋转),中心点的世界坐标
/// 此时的相机已经完成拍摄作业,即已经将镜头对准被拍摄物体且aspect刚好包围被拍摄物
/// 原理:面片的高度是确定的等于相机halfSize*cos(x轴旋转)
/// 假设中心点在相机局部坐标系中的坐标为(0,0,z),获得相机的M矩阵M矩阵右乘(0,0,z)的y值即为刚刚计算出来的高度值可建立方程:
/// height = z*M(1,2)+M(1,3)
/// 由此可以算出z值再将(0,0,z)从局部坐标转换到世界坐标即可
/// </summary>
/// <param name="camera"></param>
/// <returns>中心点世界坐标,用该值和被拍摄物件的世界坐标相减可以得到偏移量,用于将物体从三维转二维后坐标依然可以对的上</returns>
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;
}
// 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);
// }
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);
}
}
}