using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using PhxhSDK;
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);
}
}
///
/// 将gameObject渲染到一张贴图上
/// 注:下面的所有计算都是基于scale值为1的情况,如果要在scale不为1的情况下使用positionOffset和textureScale,则需要自行乘上scale
///
///
/// 需要用来渲染的相机,必须是正交的,且旋转已经设置好了
/// 贴图存放的路径
/// 三维到二维的偏移量,当三维转二维的时候用三维position加上该值可保证三维和二维刚好重合
/// 转换二维后的缩放值,条件是二维的mesh长宽都为1
/// 转换二维后的旋转值
///
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().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();
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 = TextureUtils.Create(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(path);
if (savedTex != null)
{
Debug.Log("保存成功!" + path);
}
return savedTex;
}
finally
{
RevertCameraValue();
RevertObject(isPrefab);
RevertRT();
}
}
///
/// 获取mesh在当前相机视角下的边界视口坐标,一定刚好可以包围住mesh
///
///
///
///
/// 前四个值是左右下上,最后一个值是中心点
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;
}
///
/// 将当前相机(必须是正交的)近裁剪面投射到地面上(底部刚好和地面相切,故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)从局部坐标转换到世界坐标即可
///
///
/// 中心点世界坐标,用该值和被拍摄物件的世界坐标相减可以得到偏移量,用于将物体从三维转二维后坐标依然可以对的上
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);
}
}
}