326 lines
12 KiB
C#
326 lines
12 KiB
C#
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);
|
||
}
|
||
}
|
||
} |