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); } } }