空间人项目

来自CGTWiki
跳转至:导航搜索

一期程序编写:陈莹莹 郑健平 管延鑫 邢树军

一期硬件保障:李远航

二期程序编写:沈圣 邢树军


项目简介

与其他单位合作项目,本小组的工作主要是光栅调试软件的编写,多机同步程序的编写。

硬件系统构成

共包含一台主机和15台从机,每一台机连接一个光场立体显示器。 每台机器的配置如下:

操作系统 Windows 10 专业版 64位 ( 4.09.00.0904 )
	
处理器	英特尔 Core i9-9900K @ 3.60GHz 八核

主板	技嘉 Z390 AORUS ULTRA-CF ( 英特尔 PCI 标准主机 CPU 桥 - Z390 芯片组 )

内存	32 GB ( 金士顿 DDR4 3200MHz )

主硬盘	三星 MZVLB256HAHQ-00000 ( 256 GB / 固态硬盘 )

显卡	Nvidia GeForce RTX 2080 ( 8 GB / 技嘉 )

显示器	三星 SAM0C1A S24E390 ( 23.5 英寸  )

声卡	英特尔 High Definition Audio 控制器

网卡	英特尔 Wireless-AC 9462


光场显示器的LCD面板为32寸8k面板。

硬件供货商

李悦 13811731808

专用模型

程序及功能

一期程序及主要功能

控制程序

使用1台主机发送指令,控制15台从机打开、关闭指定程序,拷贝指定文件到指定路径和控制从机关机。

  • 主机端:
控制程序——主机端
OPEN:发送OPEN指令打开指定程序。

CLOSE:发送CLOSE指令关闭指定程序。

SHUTDOWN:发送SHUTDOWM指令关闭从机。

COPY:发送COPY指令拷贝指定文件到从机。


  • 从机端:
控制程序——从机端
本机IPV4地址:如192.168.1.1,程序可自动获取。

监听端口号:当前配置为24999。

程序运行路径:即打开指定程序的绝对路径,一般配合拷贝使用。

共享文件夹IP地址:此处使用主机的IP地址。

账号、密码:主机的Windows账号及密码或Microsoft账号及密码。

拷贝源路径:主机共享文件夹IP地址+待拷贝的文件夹路径。

拷贝目标路径:从机将文件拷贝到的指定路径。


控制程序——配置文件示例
软件配置

  方法一:可以在应用程序的目录下配置文件Config.ini中进行配置。

  方法二:可以在应用程序界面进行配置并保存。

开启启动:

  勾选程序右下角的开机启动,即可实现开启自启动。


控制程序——消息框
消息框用于显示当前从机接收并执行的指令情况,以及弹出程序报错信息。


关键代码
Winform开机启动
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UdpNetwork
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            //默认启动方式
            //Application.Run(new FormClient());

            /*
             * 当前用户是管理员的时候,直接启动应用程序
             * 如果不是管理员,则使用启动对象启动程序,以确保使用管理员身份运行
             */
            //获得当前登录的Windows用户标示
            System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
            System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity);
            //判断当前登录用户是否为管理员
            if (principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
            {
                //如果是管理员,则直接运行
                Application.Run(new FormClient());
            }
            else
            {
                //创建启动对象
                System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
                startInfo.UseShellExecute = true;
                startInfo.WorkingDirectory = Environment.CurrentDirectory;
                startInfo.FileName = Application.ExecutablePath;
                //设置启动动作,确保以管理员身份运行
                startInfo.Verb = "runas";
                try
                {
                    System.Diagnostics.Process.Start(startInfo);
                }
                catch
                {
                    return;
                }
                //退出
                Application.Exit();
            }
            
        }
    }
}
文件删除与拷贝
  • 拷贝
        //将源路径srcPath下的所有文件及文件夹拷贝到目标路径aimPath的文件夹下
        private void CopyDir(string srcPath, string aimPath)
        {
            try
            {
                // 检查目标目录是否以目录分割字符结束如果不是则添加之
                if (aimPath[aimPath.Length - 1] != Path.DirectorySeparatorChar)
                    aimPath += Path.DirectorySeparatorChar;
                // 判断目标目录是否存在如果不存在则新建之
                if (!Directory.Exists(aimPath))
                    Directory.CreateDirectory(aimPath);
                //得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组
                //如果你指向copy目标文件下面的文件而不包含目录请使用下面的方法
                //string[] fileList = Directory.GetFiles(srcPath);
                string[] fileList = Directory.GetFileSystemEntries(srcPath);
                //遍历所有的文件和目录
                foreach (string file in fileList)
                {
                    //先当作目录处理如果存在这个目录就递归Copy该目录下面的文件
                    if (Directory.Exists(file))
                        CopyDir(file, aimPath + Path.GetFileName(file));
                    //否则直接Copy文件
                    else
                        File.Copy(file, aimPath + Path.GetFileName(file), true);
                }
            }
            catch (Exception ee)
            {
                copySucceedFlag = false;
                //throw new Exception(ee.ToString());
            }
        }
  • 删除
        //删除目标路径文件夹下的所有文件及文件夹
        private void delectDir(string srcPath)
        {
            try
            {
                DirectoryInfo dir = new DirectoryInfo(srcPath);
                FileSystemInfo[] fileinfo = dir.GetFileSystemInfos();  //返回目录中所有文件和子目录
                foreach (FileSystemInfo i in fileinfo)
                {
                    if (i is DirectoryInfo)            //判断是否文件夹
                    {
                        DirectoryInfo subdir = new DirectoryInfo(i.FullName);
                        subdir.Delete(true);          //删除子目录和文件
                    }
                    else
                    {
                        File.Delete(i.FullName);      //删除指定文件
                    }
                }
            }
            catch (Exception delectException)
            {
                InformationShow(delectException.ToString(), "错误", 5);
            }
        }

附件:
文件:UdpServer&Client技术方案设计书.docx
文件:UdpServer&Client系统使用说明书.docx
控制程序及源码

同步程序

同步程序: 1台控制主机为server,15台显示从机为client。
本系统共15个场景。Server、client分别含有独立的15个场景,左侧为server场景,右侧为client场景,Server、client场景间通过UDP协议进行同步。
server场景 client场景

选择界面场景

choosescene、choose场景为选择界面场景

  • 以点击场景按钮的方式通过对场景号的选择对场景进行切换,通过UDP协议将server端的场景编号传到client端,client根据相应的场景编号切换到相应的场景,使得场景同步。
  • 可通过下拉菜单(server端按K呼出,按J隐藏;client端按G呼出,按F隐藏)对整个系统的显示行列数(server端)和单个从机对应屏幕编号(client端)进行选择,并将选择结果通过xml文件保存到本地。

4.png 3.png


光栅参数调节场景

Adjustscene、adjustplay场景为光栅参数调节场景。

  • 在server端对client屏幕进行选择(初始未经选择时client可为全白画面,屏幕编号选择后为正常调节画面)。
  • 通过键盘上下左右、F1、F2、F3、F4、F6键对倾角、线数、RGB参数进行调整,并通过UDP协议将参数传输到client端,client端对应编号的屏幕将使用接收到的参数值对光栅调节画面shader进行调整。
  • 当client端画面符合标准后,可通过点击保存按键,通过xml文件将光栅参数保存到各client本地,每次渲染时将直接从本地读取对应参数。

5.png 6.png

模型显示场景

Serverscene、clientscene为模型显示场景

  • 键盘上下左右可以调整模型位置,N、M键放大或缩小模型,鼠标左键调节模型朝向。
  • 可通过(K键显示,J键隐藏)shift、fadewidth调节界面,对各块client屏幕的shift、fadewidth进行调节(fadewidth阴差阳错不需要了),具体调节方法与adjust类似。

shift、fadewidth值是通过xml文件保存在server本地,通过数组形式实时发送给client的。

通过UDP协议将sever模型的transform传到client端,并通过对client端模型赋值实现同步。

7.png 8.png 9.png

imagescene场景

其余的imagescene场景与上述模型场景操作基本一致。

在imagescene界面,点击小键盘1键和3键可切换imagescene。

添加、更换模型

要增加新模型场景的只需要:

  1. 在server端和client端各复制一个imagescene场景
  2. 将场景中原本的model删掉
  3. 加入新的model,给新model挂上New Animator Controller
    server端操作:将Main Camera下的UDP Server component的model1、animator1位置拖入新模型
    client端操作:将listenner下的脚本model、animator位置拖入新模型。
  4. 在build settings中加入新场景,并使得两个新场景在build settings中的场景编号一致。
关键代码
struct的封装与byte流的转化
    [Serializable]
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct sysinfo
    {
        public int close;
        public Matrix4x4 WTCMatrix;
        public Matrix4x4 projMatrix;
        public int action1;
        public int VNUM;
        public int HNUM
        public Vector3 moposition;
        public Vector3 moscale;
        public Vector3 morotation;
        public bool ShiftFlag;
        public bool fadeWidthFlag;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
        public float[] shiftarray;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
        public float[] fadeWidtharray;
    };

    public byte[] StructToBytes(object structObj)
    {
        int size = Marshal.SizeOf(structObj);
        IntPtr buffer = Marshal.AllocHGlobal(size);
        try
        {
            Marshal.StructureToPtr(structObj, buffer, false);
            byte[] bytes = new byte[size];
            Marshal.Copy(buffer, bytes, 0, size);
            return bytes;
        }

        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }

    public object BytesToStruct(byte[] bytes, Type strcutType)
    {
        int size = Marshal.SizeOf(strcutType);
        IntPtr buffer = Marshal.AllocHGlobal(size);
        try
        {
            Marshal.Copy(bytes, 0, buffer, size);
            return Marshal.PtrToStructure(buffer, strcutType);
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }
UDP server
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System;
using System.IO;
using UnityEngine.Experimental.UIElements;
using UnityEngine.UI;
using System.Xml;


public class UDPServer : MonoBehaviour
{
    Socket socket; //目标socket
    IPEndPoint clientend;
    Thread connectThread; //连接线程
    private Camera _servercam;
    Matrix4x4 _WTCMatrix;//view matrix 
    Matrix4x4 _projMatrix;//projection matrix
    //Transform Camtransform;
    Transform modeltransform;
    float speed = 0.1f; 
    Vector3 modelscale;
    Vector3 modelpos;
    Vector3 modelrotation;
    int actcommand1 = 0;//动作指令
    public GameObject model1;
    public Animator _animator1;//模型animator
    int ScreenNum = 0;
    //具体调节客户端编号
    int closecommand;//判断是否关闭客户端
    float shift;
    float[] shiftarray = new float[15];
    bool shiftflag = false;
    public InputField ShiftInputField;
    float[] fadeWidtharray = new float[15];
    float fadeWidth;
    bool fadeWidthflag = false;
    public InputField fadeWidthInputField;
    //float[] shiftarray = new float[15];
    //float[] fadewidtharray = new float[15];
    int _vNum;
    int _hNum;
    private static readonly object locker = new object();
    Converter converter = new Converter();

    void Start()
    {
        if (System.IO.File.Exists("D:\\" + "shiftarray.xml"))
        {
            readshiftarrayXML();
        }
        if (System.IO.File.Exists("D:\\" + "fadeWidtharray.xml"))
        {
            readfadeWidtharrayXML();
        }
        _servercam = GetComponent<Camera>();
        InitSocket();
    }


    //初始化
    void InitSocket()
    {
        //udp连接
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        clientend = new IPEndPoint(IPAddress.Parse("255.255.255.255"), 25000);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
        //开启一个线程连接,必须的,否则主线程卡死
        connectThread = new Thread(new ThreadStart(SocketReceive));
        connectThread.Start();
    }
    void Update()
    {
        ShiftInputField.text = shift.ToString();
        fadeWidthInputField.text = fadeWidth.ToString();
        //改变模型参数
        modelchange();
        fadeWidthChange();
        for(int i=0;i<15;i++)
        {
            if (ScreenNum == i)
            {
                shift = shiftarray[i];
                ShiftChange();
                fadeWidth = fadeWidtharray[i];
                fadeWidthChange();
                shiftarray[i] = shift;
                fadeWidtharray[i] = fadeWidth;
            }
        }
        //发送参数
        SocketSend();
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Application.Quit();
        }
    }

    public void shiftbutton()
    {
        shiftflag = true;
        saveshiftarrayXML();
    }

    public void fadeWidthbutton()
    {
        fadeWidthflag = true;
        savefadeWidtharrayXML();
    }

    //服务器发送
    void SocketSend()
    {
        Converter.sysinfo serverinfo = new Converter.sysinfo();
        //输入1则发送关闭客户端指令
        if (Input.GetKeyDown(KeyCode.Keypad1))
        {
            closecommand = 1;
        }
        else
        {
            closecommand = 0;
        }
        //获取view matrix 和projection matrix
        _WTCMatrix = _servercam.worldToCameraMatrix;
        _projMatrix = _servercam.projectionMatrix;
        //Camtransform = _servercam.transform;
        modeltransform = model1.transform;
        //发送分屏行列数,打开自动应用上次关闭时应用的行列数
        if (System.IO.File.Exists("D:\\" + "vNum.xml"))
        {
            readvNumXML();
        }
      
        if (System.IO.File.Exists("D:\\" + "hNum.xml"))
        {
            readhNumXML();
        }

        if (System.IO.File.Exists("D:\\" + "ScreenNum.xml"))
        {
            readScreenNumXML();
        }
        print(shiftarray[7]);
       
        lock (locker)
        {
            serverinfo.close = closecommand;
            serverinfo.WTCMatrix = _WTCMatrix;
            serverinfo.projMatrix = _projMatrix;
            serverinfo.action1 = actcommand1;
            serverinfo.VNUM = _vNum;
            serverinfo.HNUM = _hNum;
            serverinfo.moposition = modelpos;
            serverinfo.moscale = modelscale;
            serverinfo.morotation = modelrotation;
            serverinfo.shiftarray = shiftarray;
            serverinfo.fadeWidtharray = fadeWidtharray;
            serverinfo.ShiftFlag = shiftflag;
            serverinfo.fadeWidthFlag = fadeWidthflag;
        }
        byte[] byteArray = converter.StructToBytes(serverinfo);
        socket.SendTo(byteArray, clientend);
        shiftflag = false;
        fadeWidthflag = false;
        actcommand1 = 0;
    }

    public void squatbutton()
    {
        actcommand1 = 12;
        _animator1.Play("squat", 0, 0f);
    }

    public void salutebutton()
    {
        actcommand1 = 13;
        _animator1.Play("salute", 0, 0f);
    }

    //服务器接收
    void SocketReceive()
    {
        //进入接收循环
        while (true)
        {
            Thread.Sleep(2000);
            //将当前线程休眠2秒
        }
    }

    public void buttonnum1()
    {
        ScreenNum = 0;
        saveScreenNumXML();
    }

    public void buttonnum2()
    {
        ScreenNum = 1;
        saveScreenNumXML();
    }
    public void buttonnum3()
    {
        ScreenNum = 2;
        saveScreenNumXML();
    }
    public void buttonnum4()
    {
        ScreenNum = 3;
        saveScreenNumXML();
    }
    public void buttonnum5()
    {
        ScreenNum = 4;
        saveScreenNumXML();
    }
    public void buttonnum6()
    {
        ScreenNum = 5;
        saveScreenNumXML();
    }
    public void buttonnum7()
    {
        ScreenNum = 6;
        saveScreenNumXML();
    }
    public void buttonnum8()
    {
        ScreenNum = 7;
        saveScreenNumXML();
    }
    public void buttonnum9()
    {
        ScreenNum = 8;
        saveScreenNumXML();
    }
    public void buttonnum10()
    {
        ScreenNum = 9;
        saveScreenNumXML();
    }
    public void buttonnum11()
    {
        ScreenNum = 10;
        saveScreenNumXML();
    }
    public void buttonnum12()
    {
        ScreenNum = 11;
        saveScreenNumXML();
    }
    public void buttonnum13()
    {
        ScreenNum = 12;
        saveScreenNumXML();
    }
    public void buttonnum14()
    {
        ScreenNum = 13;
        saveScreenNumXML();
    }
    public void buttonnum15()
    {
        ScreenNum = 14;
        saveScreenNumXML();

    }

    void ShiftChange()
    {
        if (Input.GetKeyDown(KeyCode.Keypad4))
        {
            if (shift >= 1f)
            {
                shift -= 1f;  //调整shift
            }
        }
        if (Input.GetKeyDown(KeyCode.Keypad6))
        {
            shift +=1f;  //调整shift
        }
        if (Input.GetKeyDown(KeyCode.Keypad2))
        {
            if (shift >= 10f)
            {
                shift -= 10f;  //调整shift
            }
        }

        if (Input.GetKeyDown(KeyCode.Keypad8))
        {
            shift += 10f;  //调整shift
        }
    }

    void fadeWidthChange()
    {
        if (Input.GetKey(KeyCode.Keypad7))
        {
            if (fadeWidth >= 1f)
            {
                fadeWidth -= 1f;  //调整shift
            }
        }

        if (Input.GetKey(KeyCode.Keypad9))
        {
            fadeWidth += 1f;  //调整shift
        }
    }

    //连接关闭
    void SocketQuit()
    {
        //关闭线程
        if (connectThread != null)
        {
            connectThread.Interrupt();
            connectThread.Abort();
        }
        //最后关闭socket
        if (socket != null)
        {
            socket.Close();
        }
        print("disconnect");
    }


    //更改模型transform
    void modelchange()
    {
        float movex = 0;
        float movey = 0;
        float scale = 0;
        float rotationx = 0;
        float rotationy = 0;
        if (Input.GetKey(KeyCode.A))
        {
            movex += speed * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.D))
        {
            movex -= speed * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.S))
        {
            movey -= speed * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.W))
        {
            movey += speed * Time.deltaTime;
        }

        if (Input.GetKey(KeyCode.N))
        {
            scale += speed * Time.deltaTime;
        }
        if (Input.GetKey(KeyCode.M))
        {
            scale -= speed * Time.deltaTime;
        }
        if (Input.GetMouseButton(0))
        {
            rotationy = -Input.GetAxis("Mouse X") * Time.deltaTime * speed;
            rotationx = -Input.GetAxis("Mouse Y") * Time.deltaTime * speed;
        }
        else
        {
            rotationx = rotationy = 0;
        }
        if(model1 != null)
        {
            model1.transform.Rotate(new Vector3(rotationx, rotationy, 0));
            model1.transform.Translate(new Vector3(movex, movey, 0));
            model1.transform.localScale += new Vector3(scale, scale, scale);
            modelpos = model1.transform.position;
            modelscale = model1.transform.localScale;
            modelrotation = model1.transform.eulerAngles;
        }
    }

    void saveshiftarrayXML()
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));
        XmlElement shiftarrayInfoXML = xmlDoc.CreateElement("shiftarrayINFO");
        xmlDoc.AppendChild(shiftarrayInfoXML);
        for (int i = 0; i < 15; i++)
        {
            shiftarrayInfoXML.SetAttribute("_shift" + i.ToString(), shiftarray[i].ToString());
        }
        xmlDoc.Save("D:\\" + "shiftarray.xml");
    }
    void readshiftarrayXML()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("D:\\" + "shiftarray.xml");
        for (int i = 0; i < 15; i++)
        {
            shiftarray[i] = float.Parse(doc.DocumentElement.GetAttribute("_shift" + i.ToString()));
            //print(i.ToString() +"   "+shiftarray[i]);
        }
    }


    void savefadeWidtharrayXML()
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));
        XmlElement fadeWidtharrayInfoXML = xmlDoc.CreateElement("fadeWidtharrayINFO");
        xmlDoc.AppendChild(fadeWidtharrayInfoXML);
        for (int i = 0; i < 15; i++)
        {
            fadeWidtharrayInfoXML.SetAttribute("_fadeWidth" + i.ToString(), fadeWidtharray[i].ToString());
        }
        xmlDoc.Save("D:\\" + "fadeWidtharray.xml");
    }

    void readfadeWidtharrayXML()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("D:\\" + "fadeWidtharray.xml");
        for (int i = 0; i < 15; i++)
        {
            fadeWidtharray[i] = float.Parse(doc.DocumentElement.GetAttribute("_fadeWidth" + i.ToString()));
        }
    }

    void saveScreenNumXML()
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));
        XmlElement ScreenNumInfoXML = xmlDoc.CreateElement("ScreenNumINFO");
        xmlDoc.AppendChild(ScreenNumInfoXML);
        ScreenNumInfoXML.SetAttribute("ScreenNum", ScreenNum.ToString());
        xmlDoc.Save("D:\\" + "ScreenNum.xml");
    }

    void readScreenNumXML()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("D:\\" + "ScreenNum.xml");
        ScreenNum = int.Parse(doc.DocumentElement.GetAttribute("ScreenNum"));
    }

    void readvNumXML()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("D:\\" + "vNum.xml");
        _vNum = int.Parse(doc.DocumentElement.GetAttribute("vNum"));
    }

    void readhNumXML()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("D:\\" + "hNum.xml");
        _hNum = int.Parse(doc.DocumentElement.GetAttribute("hNum"));
    }

    void OnApplicationQuit()
    {
        SocketQuit();
    }
}
UDP client
using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using System.IO;
using UnityEngine.Events;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Xml;
using System.Timers;
using System.Runtime.InteropServices;


namespace MultiView
{
    public class listenner : MonoBehaviour
    {
        Socket socket; //目标socket
        IPEndPoint ipEnd; //服务端端口
        EndPoint ep;
        Thread connectThread; //连接线程
        byte[] recvData; //接收的数据,必须为字节
        int recvLen; //接收的数据长度
        public GameObject model;
        public Animator _animator;//animator controller
        int _actcommand;//动作指令
        //给viewmatrix、projectionmatirx设定初始值,防止报错
        Matrix4x4 initwtcmatrix;
        Matrix4x4 initpromatrix;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
        //public float[] shiftarray;
        float[] shiftarray = new float[15];
        public float[] fadeWidtharray = new float[15];
        int ScreenNum;
        bool shiftFlag = false;
        //float shift ;
        public float _shift;
        //float fadeWidth;
        //public float _fadeWidth = 0;
        bool fadeWidthFlag = true;
        [HideInInspector]
        public int vnum;
        [HideInInspector]
        public int hnum;
        [HideInInspector]
        public Matrix4x4 projMatrix;
        [HideInInspector]
        public Matrix4x4 WTCmatrix;
        string str = "shift";
        //设定屏幕编号
        //int ip;
        //[HideInInspector]
        public int screenip =0;
        Converter.sysinfo _clientinfo;//提取结构体 
        Stack<Converter.sysinfo> clientstack = new Stack<Converter.sysinfo>();//结构体栈
        //Queue<Converter.sysinfo> clientqueue = new Queue<Converter.sysinfo>();//结构体队列
        private static readonly object locker = new object();
        Converter converter = new Converter();

        //初始化
        void InitSocket()
        {
            //socket
            ipEnd = new IPEndPoint(IPAddress.Any, 25000);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            ep = ipEnd as EndPoint;
            socket.Bind(ipEnd);

            //开启一个线程连接,必须的,否则主线程卡死
            //lock(locker)
            //{
                connectThread = new Thread(new ThreadStart(SocketReceive));
                connectThread.Start();
            //}
            
        }

        
        //根据动作指令改变模型动作
        void stackOPT()
        {
            _actcommand = _clientinfo.action1;
            //print(_actcommand);
            if (_actcommand == 12)
            {
                _animator.Play("squat", 0, 0f);
            }
            if (_actcommand == 13)
            {
                _animator.Play("salute", 0, 0f);
            }
        }


        //socket接收  并将结构体加入队列
        void SocketReceive()
        {
            while (true)
            {
                //print(" 111");
                //接收字节
                recvData = new byte[1024];
                recvLen = socket.ReceiveFrom(recvData, ref ep);
                //将字节转换为结构体
                Converter.sysinfo clientinfo = new Converter.sysinfo();
                clientinfo = (Converter.sysinfo)converter.BytesToStruct(recvData, typeof(Converter.sysinfo));
                //print(clientinfo.shiftarray[7])/*;*/
                //将结构体加入队列
                //clientqueue.Enqueue(clientinfo);
                //将结构体加入栈
                clientstack.Push(clientinfo);
            }
        }

        //设置初始矩阵
        void Initmatrix()
        {
            initwtcmatrix = Matrix4x4.identity;
            initwtcmatrix.m22 = -1;
            initpromatrix = Matrix4x4.identity;
            initpromatrix.m00 = 0.11250f;
            initpromatrix.m11 = 0.2f;
            initpromatrix.m22 = -0.002f;
            initpromatrix.m23 = -1.0006f;
        }
        

        //设定参数初始值
        void Awake()
        {
            Initmatrix();
            vnum = 1;
            hnum = 1;
            WTCmatrix = initwtcmatrix;
            projMatrix = initpromatrix;
            InitSocket();
            if (System.IO.File.Exists("D:\\" + "ip.xml"))
            {
                readIPXML();
            }
        }

        void Start()
        {

             //在这里初始化
            if (System.IO.File.Exists("D:\\" + "listenshiftarray.xml"))
            {
                readshiftarrayXML();
            }
            if (System.IO.File.Exists("D:\\" + "listenfadeWidtharray.xml"))
            {
                readfadeWidtharrayXML();
            }
            _shift = shiftarray[screenip];

            //for (int i=0;i<15;i++)
            //{
            //    if (screenip == i)
            //    {
            //        _shift = shiftarray[i];
            //        print(i+" "shiftarray[7]);
            //        //_fadeWidth = fadeWidtharray[i];
            //    }
            //}

            //print(shiftarray[screenip]);
        }


        void Update()
        {
            if (System.IO.File.Exists("D:\\" + "ip.xml"))
            {
                readIPXML();
            }
            else
            {
                screenip = 15;
            }

            //获取屏幕编号
        
            lock (locker)
            {
                //if (clientqueue.Count > 0)
                //Debug.Log(clientstack.Count);
                if (clientstack.Count > 0)
                {
                    _clientinfo = clientstack.Pop();
                    //_clientinfo = clientqueue.Dequeue();
                    stackOPT();
                    //系统行列数
                    vnum = _clientinfo.VNUM;
                    hnum = _clientinfo.HNUM;
                    //模型transform
                    model.transform.position = _clientinfo.moposition;
                    model.transform.localScale = _clientinfo.moscale;
                    model.transform.eulerAngles = _clientinfo.morotation;
                    //Debug.Log(model.transform.position);
                    //相机矩阵
                    projMatrix = _clientinfo.projMatrix;
                    WTCmatrix = _clientinfo.WTCMatrix;
                    //shift、fadeWidth调整值
                    shiftFlag = _clientinfo.ShiftFlag;
                    fadeWidthFlag = _clientinfo.fadeWidthFlag;
                    for(int i=0;i<15;i++)
                    {
                        shiftarray[i] = _clientinfo.shiftarray[i];
                        //print(i+" "+shiftarray[i].ToString());
                    }
                    shiftarray = _clientinfo.shiftarray;
                    fadeWidtharray = _clientinfo.fadeWidtharray;
                   _shift = shiftarray[screenip];
                    //print(_clientinfo.shiftarray[7].ToString() + "screenip");
                  //fadeWidth = fadewidtharray[screenip];
                }
            }

            //判断是否在本客户端进行调整


            if (shiftFlag == true)
            {
                saveshiftarrayXML();
            }
            //保存fadeWidth值
            if (fadeWidthFlag == true)
            {
                savefadeWidtharrayXML();
            }



            if (Input.GetKeyDown(KeyCode.Escape))
            {
                Application.Quit();
            }
            //print( shift0 + "shiftarray[0]");
            //print( shift1  + "shiftarray[1]");
            //print( shift2  + "shiftarray[2]");
            //print(fadeWidth0 + "fadeWidth0[12]");
            //print(fadeWidth1 + "fadeWidth0[13]");
            //print(fadeWidth2 + "fadeWidth0[14]");
            shiftFlag = false;
            fadeWidthFlag = false;
            clientstack.Clear();
        }


        void OnApplicationQuit()
        {
            SocketQuit();
        }

        //场景unload时断开连接
        void OnDestroy()
        {
            SocketQuit();
        }





        //连接关闭
        void SocketQuit()
        {
            //关闭线程
            if (connectThread != null)
            {
                connectThread.Interrupt();
                connectThread.Abort();
            }
            //最后关闭socket
            if (socket != null)
            {
                socket.Close();
            }
        }

        void saveshiftarrayXML()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));
            XmlElement listenshiftarrayInfoXML = xmlDoc.CreateElement("LISTENshiftarrayINFO");
            xmlDoc.AppendChild(listenshiftarrayInfoXML);
            for (int i =0;i<15; i++)
            {
                listenshiftarrayInfoXML.SetAttribute("_shift"+i.ToString(), shiftarray[i].ToString());
                print(i+" "+ shiftarray[i]);
                print(i + " test" + shiftarray[i]);
            }
            xmlDoc.Save("D:\\"+ "listenshiftarray.xml");
        }

        void readshiftarrayXML()
        {
            XmlDocument doc = new XmlDocument();
            doc.Load("D:\\" + "listenshiftarray.xml");
            for (int i = 0; i < 15; i++)
            {
                shiftarray[i] = float.Parse(doc.DocumentElement.GetAttribute("_shift" + i.ToString())); 
            }
        }

        void savefadeWidtharrayXML()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));
            XmlElement listenfadeWidtharrayInfoXML = xmlDoc.CreateElement("LISTENfadeWidtharrayINFO");
            xmlDoc.AppendChild(listenfadeWidtharrayInfoXML);
            for (int i = 0; i < 15; i++)
            {
                listenfadeWidtharrayInfoXML.SetAttribute("_fadeWidth" + i.ToString(), fadeWidtharray[i].ToString());
            }
            xmlDoc.Save("D:\\" + "listenfadeWidtharray.xml");
        }



        void readfadeWidtharrayXML()
        {
            XmlDocument doc = new XmlDocument();
            doc.Load("D:\\" + "listenfadeWidtharray.xml");
            for (int i = 0; i < 15; i++)
            {
                fadeWidtharray[i] = float.Parse(doc.DocumentElement.GetAttribute("_fadeWidth" + i.ToString()));
            }
        }

        
        void readIPXML()
        {
            XmlDocument doc = new XmlDocument();
            doc.Load("D:\\" + "ip.xml");
            screenip = int.Parse(doc.DocumentElement.GetAttribute("ip"));
        }
    }

}
光线衰减函数

[math]f(x)=3x^2-2x^3[/math]

使用原因为:此函数为一种平滑变化的核函数,当它从1减小时,从1平滑地过渡到0,当它向1增加时,从0平滑过渡到1。
合理怀疑使用别的能类似变换的函数也可以。

附件:

server端client端源代码

二期程序及主要功能

同步程序

同步程序: 1台控制主机为server,3台显示从机为client,每台从机两张显卡,每张显卡四个DP口,每个DP口接入一个一输入六输出的多屏宝,然后接入3×2×4×6=144个投影仪。
主机端为普通渲染模式,从机端为光场渲染模式,并将渲染的144个视点纹理图分别由144个投影仪投影到屏幕上。根据投影仪的排布方式调整视点纹理的顺序,并利用校正矩阵将纹理校正到正确位置。
主机端控制旋转、移动、缩放,将变化的相机投影矩阵通过UDP发送到从机端,从而实现从机端的旋转、移动、缩放。实现同步。
还可同步切换模型、GPS同步。

主机端控制界面

空间人二期 01.png

鼠标左键控制相机旋转、右键控制相机移动、滚轮控制缩放。
通过鼠标可以移动红、绿两个标定点,确定GPS的移动范围,并将两个标定点的真实经纬度输入,通过经纬度和Unity世界坐标的相互转换,可以根据读取的GPS的经纬度坐标获得标定区域的Unity世界坐标,并将此坐标发送至从机端,实现GPS同步。

从机端控制界面

空间人二期 02.png

每一台从机渲染48个子纹理,通过读取计算好的校正矩阵将渲染的纹理进行校正,并根据投影仪的排布,调整每个子纹理的渲染顺序,使其渲染在正确位置。

程序源代码

空间人二期

VNC远程控制

VNC程序的下载地址:

链接:https://pan.baidu.com/s/15FRFOy7ylGVTqD7UwUegOQ 提取码:s8wb


在15台从机上安装REALVNC SERVER,在主机上安装VNC VIEWER。使用vnc密码,密码为88888888

15台从机的ip从192.168.2.1到192.168.2.15。

主机ip地址为192.168.2.30。

VNC VIEWER


项目集成

地点

联系人

徐文宇

集成过程中遇到的问题汇总

  1. 不应当让装机工程师早走,导致让远航做网线,这些事确实应当委托装机公司来做,一个不够两个人来做。可以假想一下,如果没有学生会做网线,这件事应当怎么解决。
  2. 中午吃饭时间不固定,有时拖到三四点,这个也应该反省。
  3. 约好的九点,我们应当在九点之前到,不应该让老师们等着。
  4. 吃饭时,稍微注意一下,留意水土不服的问题。