Unity截图调用OpenGL绘制
这次研究的是一个屏幕截取并保存的功能。先上图。
1.Ctrl + Alt + X 开始截图。
2.Enter键执行截屏并弹出保存框。
3.截取后保存的图片。
大致的思路是这样:
1.GL类执行界面选框的显示。
2.用Texture2D类设置新的像素块颜色。
3.System.Windows.forms.dll弹出保存对话框,保存图片文件并打开。
难点在于GL框的绘制逻辑以及新的图片生成时屏幕坐标的计算。
代码如下:
ScreenShot类
using UnityEngine;
using System.Collections;
using System.Windows.Forms;
using System.IO;
using System.Text;
//截屏工具
public class ScreenShot : MonoBehaviour
{
//选框材质
public Material mat;
//初始位置
Vector3 startPos = Vector3.zero;
//结束位置
Vector3 endPos = Vector3.zero;
//是否绘制
bool isDraw = false;
//是否清除
bool isClear = true;
void Update ()
{
//左键按下开始绘制
if(Input.GetMouseButtonDown(0))
{
isDraw = true;
isClear = true;
startPos = Input.mousePosition;
}
//左键松开GL框悬停
if(Input.GetMouseButtonUp(0))
{
//鼠标原位置点击不做GL绘制
if(endPos == startPos)
{
isDraw = false;
}
isClear = false;
}
//右键按下重新绘制
if(Input.GetMouseButtonDown(1))
{
isDraw = false;
enabled = false;
}
//Enter键按下执行截屏
if(Input.GetKeyDown(KeyCode.Return) && isDraw && !isClear)
{
isDraw = false;
StartCoroutine(DoScreenShot(new Vector2(startPos.x,UnityEngine.Screen.height - startPos.y),new Vector2(endPos.x,UnityEngine.Screen.height - endPos.y)));
enabled = false;
}
}
void OnGUI ()
{
//文本提示
if(!isDraw)
{
GUI.Label(new Rect(Input.mousePosition.x,UnityEngine.Screen.height - Input.mousePosition.y,100,100)," 请框选截图范围");
}
if(isDraw && !isClear && endPos != startPos)
{
GUI.Label(new Rect(Input.mousePosition.x,UnityEngine.Screen.height - Input.mousePosition.y,100,100)," 按下Enter键截屏");
}
}
void OnPostRender ()
{
//绘制GL线框
if(!isDraw)return;
if(isClear)
endPos = Input.mousePosition;
GL.PushMatrix();
if(!mat)return;
mat.SetPass(0);
GL.LoadPixelMatrix();
//绘制GL填充面
GL.Begin(GL.QUADS);
GL.Color(new Color(Color.yellow.r,Color.yellow.g,Color.yellow.b,0.1f));
GL.Vertex3(startPos.x,startPos.y,0);
GL.Vertex3(endPos.x,startPos.y,0);
GL.Vertex3(endPos.x,endPos.y,0);
GL.Vertex3(startPos.x,endPos.y,0);
GL.End();
//绘制GL边框线
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex3(startPos.x,startPos.y,0);
GL.Vertex3(endPos.x,startPos.y,0);
GL.Vertex3(endPos.x,startPos.y,0);
GL.Vertex3(endPos.x,endPos.y,0);
GL.Vertex3(endPos.x,endPos.y,0);
GL.Vertex3(startPos.x,endPos.y,0);
GL.Vertex3(startPos.x,endPos.y,0);
GL.Vertex3(startPos.x,startPos.y,0);
GL.End();
GL.PopMatrix();
}
//截屏
IEnumerator DoScreenShot (Vector2 startPos,Vector2 endPos)
{
Debug.Log(new Rect(startPos.x < endPos.x ? startPos.x : endPos.x,startPos.y < endPos.y ? UnityEngine.Screen.height - endPos.y : UnityEngine.Screen.height - startPos.y,Mathf.Abs(endPos.x - startPos.x - 2),Mathf.Abs(endPos.y - startPos.y - 2)));
yield return new WaitForSeconds(1);
Texture2D tex = new Texture2D((int)Mathf.Abs(endPos.x - startPos.x - 2),(int)Mathf.Abs(endPos.y - startPos.y - 2),TextureFormat.RGB24,false);
tex.ReadPixels (new Rect(startPos.x < endPos.x ? startPos.x : endPos.x,startPos.y < endPos.y ? UnityEngine.Screen.height - endPos.y : UnityEngine.Screen.height - startPos.y,Mathf.Abs(endPos.x - startPos.x - 2),Mathf.Abs(endPos.y - startPos.y - 2)),0,0);
tex.Apply();
Destroy(tex);
SaveScreenShot(tex);
}
//保存文件
void SaveScreenShot (Texture2D tex)
{
//先将文件保存在工程目录下(其实可以不用这么做)
byte[] bytes = tex.EncodeToPNG();
string filename = System.DateTime.Now.Year.ToString("0000") + System.DateTime.Now.Month.ToString("00") + System.DateTime.Now.Day.ToString("00") + System.DateTime.Now.Hour.ToString("00") + System.DateTime.Now.Minute.ToString("00") + System.DateTime.Now.Second.ToString("00") + Random.Range(0,9999).ToString("0000");
string oldFilePath = UnityEngine.Application.dataPath + "/" + filename + ".png";
FileStream f =new FileStream(oldFilePath,FileMode.Create);
f.Write(bytes,0,bytes.Length);
f.Flush();
f.Close();
System.GC.Collect();
Resources.UnloadUnusedAssets();
//弹出文件保存对话框,调用的是WINDOWS的DLL.
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "png|*.png";
string newFilePath = "";
if (dialog.ShowDialog() == DialogResult.OK)
{
newFilePath = dialog.FileName;
}
if(newFilePath != "")
{
if(File.Exists(newFilePath))
{
File.Delete(newFilePath);
}
File.Move(oldFilePath, newFilePath);
UnityEngine.Application.OpenURL(newFilePath);
}
else
{
File.Delete(oldFilePath);
}
}
}
ScreenShotControl类
using UnityEngine;
using System.Collections;
//截屏控制
public class ScreenShotControl : MonoBehaviour
{
ScreenShot screenshot;
// Use this for initialization
void Start ()
{
screenshot = GetComponent<ScreenShot>();
}
// Update is called once per frame
void Update ()
{
}
void OnGUI ()
{
//Ctrl + Alt + X 执行截屏
if(Event.current.Equals(Event.KeyboardEvent("^&X")))
{
screenshot.enabled = true;
}
}
}
阐述几个开发中的问题:
1.调用Windows的DLL,需要在Unity里更改一下设置。
File -> Build Settings -> Player Settings ->Other Settings -> Optimization -> Api Compatibility Level 更改为 .NET 2.0。
2.在执行完截屏操作后,虽然能够成功生成图片,但是会报一个错。
ReadPixels was called to read pixels from system frame buffer, while not inside drawing frame.
如果有高手知道具体的解决办法,请指教一下。
3.程序的代码结构写的不是太好,请高手指正。