精华内容
下载资源
问答
  • Unity展厅FBX文件

    2018-01-24 14:14:42
    Unity展厅FBX文件Unity展厅FBX文件Unity展厅FBX文件Unity展厅FBX文件Unity展厅FBX文件
  • unity展厅场景Showroom Environment 1.1 要求Unity 5.5.1 或更高版本。
  • 1.项目时间不确定,有时候几天下来需求,修改完需要去现场测试,对立即处理能力要求高,辛苦  2.主要用到串口东西比较多,设备通讯一般用的是串口发送消息,串口服务器(继电器),可以对其发送命令。...

     1.项目时间不确定,有时候几天下来需求,修改完需要去现场测试,对立即处理能力要求高,辛苦

     2.主要用到串口东西比较多,设备通讯一般用的是串口发送消息,串口服务器(继电器),可以对其发送命令。然后转发出去

    展开全文
  • 记录自己独自完成的一个完整的展厅中控系统。整体实现思路:灯光线路接上网络继电器,网络继电器接上串口服务器,串口服务器接上路由器,电脑接上路由器,华为paid连接路由器无线网络,实现所有设备连接在一个局域网...


    前言

    记录自己独自完成的一个完整的展厅中控系统。整体实现思路:灯光线路接上网络继电器,网络继电器接上串口服务器,串口服务器接上路由器,电脑接上路由器,华为paid连接路由器无线网络,实现所有设备连接在一个局域网内。 中控的主要功能: 1.控制灯光的开关 2.控制电脑开关机 3.控制电视机的开机和关机 4.控制内容: a、PPT的上下翻页(有动态效果的) b、视频的暂停、播放、停止 c、系统声音的加减

    一、硬件环境

    交换机:光电信息转换
    路由器:创建局域网络,连接不同的网络设备
    AC控制器:管理所有AP设备(无线WIFI)
    串口服务器:可以直连传统串口设备(不带IP的模块),通过网络协议发送指令给模块
    时序电源:提送稳定可控的电源(可通过IP或串口控制各个电源开闭)
    网络继电器:通过网络控制灯光开关
    电脑:每台电脑作为单独的服务端
    电视机:这里用的是电视机作为显示器
    华为paid:安卓系统,作为客户端给每台电脑发送指令

    二、整体构造

    1.灯光控制模块

    灯光连接智能照明模块(其实就是一个继电器),智能照明模块再与串口服务器连接

    实现:串口服务器作为服务端,自己开发一个客户端,通过网络协议(我这里是UDP和端口号)连接到串口服务器上,发送指令,实现智能照明模块的开口 开闭合控制灯光的开关

    注意事项:智能照明模块的波特率要与串口服务器的波特率设置一致,网络协议(在串口服 务器内也可设置)要确定好(UDP或者是TCP);
    确定好智能照明模块的指令(每个模块都有自己的指令,说明书或者供应商提供)

    代码如下:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using UnityEngine;
    
    public class SendInstructionsToLamp : MonoBehaviour
    {
        public Socket socket;
    
        public int _port = 9600;
        public string _ip = "192.168.0.7";
        void Start()
        {
            ConnectLight();
        }
    
    
        /// <summary>
        /// 通过TCP协议连接智能照明模块(灯组控制部分)
        /// </summary>
        public void ConnectLight()
        {
            try
            {
    
    
                //创建客户端Socket,获得远程ip和端口号
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                IPAddress ip = IPAddress.Parse(_ip);
                IPEndPoint point = new IPEndPoint(ip, _port);
    
                socket.Connect(point);
                Debug.Log("连接成功!");
            }
            catch (Exception)
            {
                Debug.Log("IP或者端口号错误...");
            }
    
        }
    
        bool quankai;
        List<byte[]> instructions_temp;
        /// <summary>
        /// 给智能照明模块发送指令(灯组控制部分)
        /// </summary>
        /// <param name="instructions">发送指令的list集合</param>
        /// <param name="timeSpan">发送指令的时间间隔</param>
        public void SendInstructions(List<byte[]> instructions, float timeSpan) {
            StartCoroutine(SendInstructions_TimeSpan(instructions, timeSpan));
        }
    
        IEnumerator SendInstructions_TimeSpan(List<byte[]> instructions, float timeSpan) {
            for (int k = 0; k < 10; k++)//这里为什么要做一个循环发送指令,是因为在实际测试中发现,发送一两次指令不稳定,多发送几次确保指令能被网络继电器接收到
            {
                for (int i = 0; i < instructions.Count; i++)
                {
                    byte[] b = new byte[8];
                    socket.Send(instructions[i]);
                    yield return new WaitForSeconds(0.1f);
                }
    
            }
            
            yield return new WaitForSeconds(timeSpan);
            //StopAllCoroutines();
        }
    
        //连接关闭
        void SocketQuit() {
            socket.Close();
        }
    
        private void OnApplicationQuit()
        {
            Debug.Log(123456);
            SocketQuit();
        }
    
        /// <summary>
        /// 一键照明指令
        /// </summary>
        /// <param name="b">true为全开、false为全关</param>
        public void One_KeyLightSwitch(bool b) {
            if (b)
            {
                List<byte[]> list_byte1 = new List<byte[]>();
                byte[] bt1 = new byte[8] { 0x04, 0x06, 0x00, 0x02, 0x00, 0x01, 0xE9, 0x9F };
                list_byte1.Add(bt1);
                SendInstructions(list_byte1, 0);
                Debug.Log("照明全开!");
            }
            else {
                List<byte[]> list_byte2 = new List<byte[]>();
                byte[] bt1 = new byte[8] { 0x04, 0x06, 0x00, 0x01, 0x00, 0x00, 0xD8, 0x5F };
                list_byte2.Add(bt1);
                SendInstructions(list_byte2, 0);
                Debug.Log("照明全关!");
            }
        }
    }
    
    

    2.主中控模块

    代码如下:

    客户端

    using UnityEngine;
    using System;
    //using System.IO.Ports;
    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    using System.Text;
    using UnityEngine.UI;
    
    public class SpSend : MonoBehaviour
    {
        public static SpSend _instance;
    
        public Toggle[] toggles;
        public Toggle toggle1;
    
    
    
        public Socket socketSend;
        Socket socketSend_Tcp_Udp;
    
        string ip;
    
        private void Awake()
        {
            _instance = this;
        }
    
        private void Start()
        {
            bt_connect_Click("192.168.0.101");
        }
        public QiehuanVideoPPT_1 qiehuanVideoPPT_1;
    
        public void Send_Udp_String(string s)
        {
            byte[] b = Encoding.ASCII.GetBytes(s.ToCharArray());
            socketSend_Tcp_Udp.Send(b);
        }
    
    
    
        public void bt_connect_Click(string _ip)
        {
            if (socketSend != null)
                socketSend = null;
            try
            {
                int _port = 8888;
                socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                IPAddress ip = IPAddress.Parse(_ip);
                IPEndPoint point = new IPEndPoint(ip, _port);
    
                socketSend.Connect(point);
                Debug.Log("连接成功!");
            }
            catch (Exception)
            {
                Debug.Log("IP或者端口号错误...");
            }
    
        }
    
    
    
        public void SendMassageToClient(string s) {
            //string s = "u:video1.mp4";
            Debug.Log(s);
            byte[] b = Encoding.ASCII.GetBytes(s.ToCharArray());
    
            socketSend.Send(b);
            //socketSend.Close();
        }
    
    }
    
    

    服务端

    using UnityEngine;
    using System.Collections;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using UnityEngine.Video;
    using RenderHeads.Media.AVProVideo;
    
    public class UDPServer : MonoBehaviour
    {
        public static UDPServer _instance;
    
        private void Awake()
        {
            _instance = this;
        }
    
        /// <summary>
        /// 模拟按键  按键对应表:http://www.doc88.com/p-895906443391.html
        /// </summary>
        /// <param name="bvk">虚拟键值 </param>
        /// <param name="bScan">0</param>
        /// <param name="dwFlags">0为按下,1按住,2释放</param>
        /// <param name="dwExtraInfo">0</param>
        [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "keybd_event")]
        public static extern void Keybd_event(byte bvk, byte bScan, int dwFlags, int dwExtraInfo);
    
    
        //控制键盘事件
        public void KeybdWait(byte t)
        {
            Keybd_event(t, 0, 0, 0);
            Keybd_event(t, 0, 2, 0);
        }
    
        public string ipAddress = "192.168.0.10";
        public int ConnectPort = 8888;
        public string recvStr;
    
        public ReadConfigJson configJson;
        public VideoPlayer videoPlayer;
    
        public MediaPlayer mediaPlayer;
        public SoundManager soundManager;
    
        Socket socket;
        EndPoint clientEnd;
        IPEndPoint ipEnd;
        string sendStr;
        byte[] recvData = new byte[1024];
        byte[] sendData = new byte[1024];
        int recvLen;
        Thread connectThread;
        bool isPlayer,baohu=true;
        //初始化
        void InitSocket()
        {
            ipEnd = new IPEndPoint(IPAddress.Parse(ipAddress), ConnectPort);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.Bind(ipEnd);
    
            //定义客户端
            IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
            clientEnd = (EndPoint)sender;
            print("等待连接数据");
            //开启一个线程连接
            connectThread = new Thread(new ThreadStart(SocketReceive));
            connectThread.Start();
    
            
        }
       public void SocketSend(string sendStr)
        {
            sendData = new byte[1024];
            sendData = Encoding.UTF8.GetBytes(sendStr);
            socket.SendTo(sendData, sendData.Length, SocketFlags.None, clientEnd);
        }
        //服务器接收
        void SocketReceive()
        {
            while (true)
            {
                recvData = new byte[1024];
                recvLen = socket.ReceiveFrom(recvData, ref clientEnd);
                recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
                Debug.Log("收到得信息 " + recvStr);
                //控制object要放在主线程里  控制系统虚拟按键要放在线程里——切记切记
                if (recvStr == "F5")
                {
                    //recvStr = "";
                    KeybdWait(116);
                }
                else if (recvStr == "prve")
                {
                    //recvStr = "";
                    KeybdWait(37);
                }
                else if (recvStr == "next")
                {
                    //recvStr = "";
                    KeybdWait(39);
                }
                else if (recvStr == "frist")
                {
                   // recvStr = "";
                    WindowMod._instance.SetFrist("中控播放器");
                }
                //else if (recvStr == "stop"|| recvStr.Contains(".mp4"))
                //{
                //    KeybdWait(27);
                //}
            }
        }
    
        //连接关闭
        void SocketQuit()
        {
            //关闭线程
            if (connectThread != null)
            {
                connectThread.Interrupt();
                connectThread.Abort();
            }
            //最后关闭socket
            if (socket != null)
                socket.Close();
            Debug.LogWarning("断开连接");
        }
    
        // Use this for initialization
        void Start()
        {
            ipAddress = IPManager.GetIP(ADDRESSFAM.IPv4);
            ConnectPort = configJson.configInfo.port;
            InitSocket(); //在这里初始化server
            ToolControlTaskBar.HideTaskBar();
        }
    
        public void PlayerInstruction()
        {
           
            if (recvStr == "frist")
            {
                recvStr = "";
                WindowMod._instance.SetFrist("ZKPlayer");
                KillProcess("PowerPoint 幻灯片放映 - [ppt1.pptx]");
                
            }
            else if (recvStr.Contains(".ppsx")&&baohu)
            {
                baohu = false;
                isPlayer = false;
                KillProcess("POWERPNT.EXE");
                FunctionControl._instance.shiping.SetActive(false);
                //videoPlayer.Stop();
                //videoPlayer.targetTexture.Release();
                mediaPlayer.Stop();
                Application.OpenURL(Application.streamingAssetsPath + "/ppt/" + recvStr);
                //StartCoroutine(WaitLookForPPT());
                //WindowMod._instance.SetFrist(recvStr+" - PowerPoint");
                recvStr = "";
                baohu = true;
            }
            else if (recvStr.Contains(".mp4"))
            {
                isPlayer = true;
                mediaPlayer.m_VideoPath = Application.streamingAssetsPath + "/mp4/" + recvStr;
                //mediaPlayer.
                //videoPlayer.url = Application.streamingAssetsPath + "/mp4/" + recvStr;
                //videoPlayer.Play();
                mediaPlayer.OpenVideoFromFile(MediaPlayer.FileLocation.RelativeToStreamingAssetsFolder, mediaPlayer.m_VideoPath);
                mediaPlayer.Play();
                recvStr = "";
                FunctionControl._instance.shiping.SetActive(true);
                WindowMod._instance.SetFrist("ZKPlayer");
                KillProcess("POWERPNT.EXE");
            }
            else if (recvStr == "play"&&isPlayer)
            {
                recvStr = "";
                //videoPlayer.Play();
                mediaPlayer.Play();
            }
            else if (recvStr == "pause")
            {
                //videoPlayer.Pause();
                mediaPlayer.Pause();
                recvStr = "";
            }
            else if (recvStr == "stop")
            {
                isPlayer = false;
                //videoPlayer.Stop();
                //videoPlayer.targetTexture.Release();
                mediaPlayer.Stop();
                WindowMod._instance.SetFrist("ZKPlayer");
                FunctionControl._instance.shiping.SetActive(false);
                KillProcess("POWERPNT.EXE");
                recvStr = "";
            }
    
                //else if (recvStr!=null && recvStr.Contains(".mp4"))
                //{
                //    videoPlayer.url= Application.streamingAssetsPath + "/mp4/" + recvStr;
                //    videoPlayer.Stop();
                //    videoPlayer.Play();
                //    recvStr = "";
                //}
    
          
    
            if (recvStr == "volumeup")
            {
                soundManager.SystemVolumeUp();
                recvStr = "";
            }
            else if (recvStr == "volumedown")
            {
                soundManager.SystemVolumeDown();
                recvStr = "";
            }
            else if (recvStr == "close")
            {
                CloseComputer();
                recvStr = "";
            }
    
        }
    
        private void Update()
        {
            PlayerInstruction();
            if (Screen.width != ReadConfigJson._instance.configInfo.screenWidth)
            {
                Screen.SetResolution(ReadConfigJson._instance.configInfo.screenWidth, ReadConfigJson._instance.configInfo.screenHeigth, true);
            }
        }
    
        void OnApplicationQuit()
        {
            SocketQuit();
            ToolControlTaskBar.ShowTaskBar();
        }
    
        public void CloseComputer()
        {
            System.Diagnostics.Process p = new System.Diagnostics.Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.Arguments = "/c " + "shutdown -s -t 0";
            p.Start();
        }
    
        /// <summary>
        /// 杀死进程
        /// </summary>
        /// <param name="processName">应用程序名</param>
        void KillProcess(string processName)
        {
            System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
            foreach (System.Diagnostics.Process process in processes)
            {
                Debug.Log(process.ProcessName);
                try
                {
                    if (!process.HasExited)
                    {
                        if (process.ProcessName == processName)
                        {
                            process.Kill();
                            UnityEngine.Debug.Log("已杀死进程");
                        }
                    }
                }
                catch (System.InvalidOperationException)
                {
                    //UnityEngine.Debug.Log("Holy batman we've got an exception!");
                }
            }
        }
    
    
    }
    
    

    本来视频播放用的是unity自带组件videoplayer,但是展厅的主机没有独立显卡,视频播放起来会卡顿,于是便换成了AVpro。


    3.电脑主机的开关机控制

    开机:通过网络给电脑发开机指令
    注意事项:你的电脑允许网络唤醒,在boss主板设置勾选允许网络唤醒
    网卡驱动也要设置允许网络唤醒
    在这里插入图片描述

    如果你发现你的你没有电源管理,请更新网卡驱动

    网络唤醒代码

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Text.RegularExpressions;
    using UnityEngine;
    public class WakeUpComputer : MonoBehaviour
    {
        //通过正则表达式设定MAC地址筛选标准,关于正则表达式请自行百度
        const string macCheckRegexString = @"^([0-9a-fA-F]{2})(([/\s:-][0-9a-fA-F]{2}){5})$";
        private static readonly Regex MacCheckRegex = new Regex(macCheckRegexString);
        //唤醒主要逻辑方法
        public static bool WakeUp(string mac)
        {
            //查看该MAC地址是否匹配正则表达式定义,(mac,0)前一个参数是指mac地址,后一个是从指定位置开始查询,0即从头开始
            if (MacCheckRegex.IsMatch(mac, 0))
            {
                byte[] macByte = FormatMac(mac);
                WakeUpCore(macByte);
                return true;
            }
            return false;
        }
        private static void WakeUpCore(byte[] mac)
        {
            //发送方法是通过UDP
            UdpClient client = new UdpClient();
            //Broadcast内容为:255,255,255,255.广播形式,所以不需要IP
            client.Connect(IPAddress.Broadcast, 50000);
            //下方为发送内容的编制,6遍“FF”+17遍mac的byte类型字节。
            byte[] packet = new byte[17 * 6];
            for (int i = 0; i < 6; i++)
                packet[i] = 0xFF;
            for (int i = 1; i <= 16; i++)
                for (int j = 0; j < 6; j++)
                    packet[i * 6 + j] = mac[j];
            //唤醒动作
            client.Send(packet, packet.Length);
        }
        private static byte[] FormatMac(string macInput)
        {
            byte[] mac = new byte[6];
            string str = macInput;
            //消除MAC地址中的“-”符号
            string[] sArray = str.Split('-');
            //mac地址从string转换成byte
            for (var i = 0; i < 6; i++)
            {
                var byteValue = Convert.ToByte(sArray[i], 16);
                mac[i] = byteValue;
            }
            return mac;
        }
        public void Button_Click_WakeUp(string s)
        {
            WakeUp(s);
            //print(WakeUp("4A-BB-6D-61-75-AE"));
            //print(WakeUp("E4-3A-6E-36-38-AA"));
        }
    }
    

    关机:发送指令给电脑关机
    注意事项:关机没啥主意事项,很简单
    关机代码:

     public void CloseComputer()
        {
            System.Diagnostics.Process p = new System.Diagnostics.Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.Arguments = "/c " + "shutdown -s -t 0";
            p.Start();
    }
    

    4.控制电视机的开关

    开关机:RS232红外学习模块+串口服务器,
    实现:串口服务器做服务端,RS232红外学习模块与串口服务器直连,自己开发客户端通过网络协议(我这里是UDP和端口号)连接到串口服务器上,发送指令给服务端,从而控制RS232红外学习模块。
    (连接方式和设置同智能照明模块一样,接线可能有所区别)
    注意事项:红外学习模块学习电视机的开关机频率,同时设置指令;
    学习的时候,电视遥控器对准红外学习指示灯,按住遥控器开机键,直到红外学习指示灯 快速连闪3下,学习结束

    展开全文
  • 本案以一个用户的展厅VR场景为例,从模型在建模软件中的预处理说起,详细讲解Unity3d的VR开发在美工阶段的UV2拆分、贴图优化、光影烘焙、后处理及特效制作的基本流程,其中涉及Build-in RP(Build-in Rendering ...

      应很多读者的要求,本文小姐姐将以一个用户的展厅VR场景为例,详细讲解Unity3d的VR开发在美工阶段的模型预处理、UV2拆分、贴图优化、光影烘焙、后处理与特效制作以及最终作品优化的基本方法和流程,其中涉及Build-in RP(Build-in Rendering Pipeline-内置渲染管道)、URP(Universal Rendering Pipeline-通用渲染管道)、HDRP(High Definition Rendering Pipeline- 高清渲染管道)和PBR(Physically Based Rendering-基于物理渲染)材质的应用,希望本文对使用unity3d进行VR开发但没经验及致力于虚拟展厅制作童鞋们理清思路有所帮助。

      这里特别说明一下,小姐姐在CSDN里发表了很多Unity3d相关的文章,每天有五六百人因为技术障碍,搜索到小姐姐的文章阅读来解决实际项目中的问题,撰写技术文献实属不易,小姐姐在这里没有任何报酬,您的点赞或关注是小姐姐笔耕不辍的最大动力!如果在阅读过程中有什么疑问,可V联小姐姐,我将尽全力解答您的提问。

      我们先来看看本案例在内置渲染管道下的效果截图:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
      上面这些图片是一个用户跟随小姐姐的视频教程临摹几周后独自完成的实际项目的截图,相信一些童鞋看到这些图片一定很眼馋,因为很多童鞋没有项目实战经验,单凭对Unity3d的一点点了解,硬着头皮靠自己摸索着推进项目,障碍一个接一个,搞得精疲力尽,可最后完成的却作品黯然失色。大家都知道,Unity3d的美工是作品的颜值和卖相,如果美工不过关,即使作品功能再强大,别人是不给你机会的,更别说让用户认可了,下面小姐姐将带着大家一起来完成这个项目的美工部分。

      需要说明的是,由于一些建模师不熟悉unity3d的美工制作,常常被美工要求反复修改模型,另一方面,由于不合理的模型构建方法,给后续的光影烘焙和场景优化带来很多问题,因此,要做好Unity的美工,就得从建模阶段入手,做好一些模型的预处理。


    1. 场景结构简介

      本案例的3dmax模型如下图所示,这个场景的原始模型是另一用户的提供的,由于这个用户对Unity3d的美工的掌握和很多童鞋一样只停留在概念阶段,没有经验,基本也是一边摸索一边推进,结果发现困难重重,尽管他的机器很高端,但是制作的U3d场景苍白暗淡,没有美感,而且非常卡顿,最后做不下去了,向小姐姐求援。当小姐姐打开Unity工程的时候,发现材质、贴图、灯光非常多,很多模型穿插,重面、破面也很多,无奈之下,小姐姐只好放弃他的Unity工程,从3dmax场景开始,重新修改模型,下面的就是小姐姐重新修改后的场景截图。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
      当小姐姐打开用户提供的原始3dmax场景时,发现贴图有215张,材质有264个,而且很多顶点没有焊合。据这个用户讲,他们团队的模型师主要做影视动画的建模,难怪会有200多个贴图和材质,因为动画最终提交给用户的只是视频(图片序列),场景渲染的机器配置一般都比较高,建模时不需要过多考虑模型的面数和贴图与材质的数量,而VR和AR提交的是实时运行的3D场景,播放环境可能是电脑,也可能是手机,或者是网页或者3D眼镜等,这些环境下的播放设备不可能像开发者的机器一样配置很高,所以Unity对模型的要求非常苛刻,在建模阶段,必须做好场景的优化和预处理,否则会给后续的美工制作埋下灾难性的隐患。

      由上图可见,这是一个展厅场景,其中有大量的史料图片,按照现代展厅的风格,这些图片都会用背喷灯片做成灯箱,这个可作为后面布设灯光的依据。


    2. 建模阶段的预处理


      小姐姐在《 Unity3d办公场景灯光布设与光影烘焙及后处理》一文中详细讲解了建模阶段的预处理,其中包括建模方法的优化; 模型拓扑布线优化预处理;重叠、隐藏面优化清理;材质、贴图的优化预处理; 基于灯光的模型预处理;碰撞物优化预处理和拆分UV2的预处理,大家可移步此文去阅读,这里仅对其中的材质和贴图优化预处理、遮挡剔除和视锥剔除优化预处理、基于灯光的模型预处理、碰撞物预处理进行讨论。

    ⑴. 材质和贴图优化预处理

      前面小姐姐提到过,用户提供的原始模型贴图和材质多达200多个,如此多的贴图和材质,势必会增加机器开销,导致作品实时运行卡顿,尤其是在移动端和3D眼镜中浏览,这肯定是无法接受的,因此必须消减贴图和材质的数量。

      从前面的模型截图可以看到,贴图最多的是那些展牌,原始模型之所以材质数量太多,是因为建模师把每张贴图都做成了单独的材质,所以小姐姐考虑将所有的贴图分组合并,具体操作如下图步骤所示,之所以用拼音命名,是因为这些群组的名称会被传递到Unity中,为防止一些插件不支持中文,所以建议最好用拼音或英文。

    在这里插入图片描述
    在这里插入图片描述
      按照上述操作之后,就将所有刚才选择的那些展牌的贴图在max中烘焙成了一张4096x4096的贴图,如下图所示,需要说明的是,上面将目标通道设为3,是因为UV1被之前的物体贴图占用,UV2要给光影图用,默认是1,如果不改为3,在烘焙前会先展UV,并存放在通道1上,这样之前的贴图就会错乱,烘焙后的这张图也是错乱的,所以必须改为3。

    在这里插入图片描述
      下面我们要将刚才烘焙的这张贴图让上面这些展牌共用,接下来按下图所示创建一个新的标准材质:

    在这里插入图片描述
      默认情况下新创建的材质的贴图是在UV1通道上,如下图所示:

    在这里插入图片描述
      接下来我们将刚才烘焙这些展牌时展开的UV3移动到UV1上,如下图所示:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

      接下来点击下图所示的图标,让每个展牌的UV1各自独立,图中绿色壳材质线框即刻消失。

    在这里插入图片描述
      通过上面的图解步骤,我们将上述的展牌的材质和贴图就都合并成了一个,之前各自的材质即被空置,可以安全清除,这种方法也称之为多物体共享一个材质和贴图。

      另外我们注意到,展牌的贴图比之前的暗了很多,这是因为这张合并了的贴图是通过max的烘焙所得,其中多少会有灯光的因素,所以应该将这张贴用ps打开,将其中的图片用每个展牌贴图合并之前的原始贴图重新逐个替换一遍,保存之后,这张贴图的颜色就正常了。

      很多童鞋可能不习惯上面这种贴图方法,只习惯一个物体的不同部分贴同一贴图的不同部分(一般会将上述的展牌先合并为一个物体,这样做不是不可以,但是对于后续的遮挡剔除和视锥剔除非常不利),对于不同物体贴同一贴图的不同部分有点不太适应,但这是完全可以的,之所以这样做,正是为了满足后续Unity的遮挡剔除和视锥剔除之需。这里插句题外话,很多读者因为制作实际项目遇到技术障碍,急得团团转,搜索到我的文章,向小姐姐求助,把小姐姐当作最后一根救命稻草。还有一些读者学Unity几年了,花了很多时间和精力,始终搞不定Unity的美工制作,于是搜索网上免费资料自己拼凑教程,结果还是没能如愿以偿。小姐姐的文章,只能让大家理清思路,要真正掌握Unity的美工制作流程,必须学练结合,还需要前人的言传,有些东西毕竟还是没法用图文来表达的,有兴趣的童鞋可以关注小姐姐的《Unity模型预处理布光烘焙后处理特效美工场景优化实战视频教程》,在听讲的同时,通过多案例临摹和感悟,可能脱开教程,将所学的技术应用于实际项目,需要的话可去某宝关注或V小姐姐,好了,言归正传。

      上面这种方法是在每个展牌各自都贴好了材质和贴图之后为消减材质和贴图的数量的被动之举。我们也可以在开始建模的时候,用手工展UV1的方法来做,也就是先规划好展牌的分类,确定哪些展牌的贴图应该合并在一起,然后用ps将这些展牌需要用的贴图拼在一张大的图片中,假设下图是我们事先拼好的一张大图:

    在这里插入图片描述
      然后如下图所示将其指定给一个新建的材质的Diffuse:

    在这里插入图片描述
      接下来将这个新建的材质赋给需要贴这张图中小图片的对应物体,如下图所示:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
      通过上述的图示步骤,可将多张拼合在一起的大图中的小图,正确地贴到对应的物体上。接下来再选择另一个需要贴这张大图中小图的物体, 用同样的方法将对应的小图贴到其上,这种方法就叫手工展UV1,也称为手工拆分UV1,此法一样可以实现合并材质和贴图的目标,与前一种方法不同的是,此法无需通过烘焙合并贴图,所以贴图不会变暗。

      以上讨论了多个物体在不合并的情况下将其材质和贴图合并的两种方法。

      接下来将场景中较小的展牌全部分离成单个的物体,如下图所示,然后群组,并命名为“xiao_deng_xiang”,用上述的步骤同法泡制,将材质和贴图合并为一个。

    在这里插入图片描述
      最后将场景中所有的文字展牌全部分离成单个的物体,如下图所示,然后群组,并命名为“wen_zi_deng_xiang”,也用上述的步骤同法泡制,将材质和贴图合并为一个。

    在这里插入图片描述
      通过上述的操作,场景中的所有展牌就被合并成了三个材质和三张贴图,整个场景中的材质由264个骤减为50个,贴图由原来的的215张骤减为50几张,大幅度降低了机器负载,如果不做这样的材质、贴图优化预处理,导入Unity之后,会非常卡顿。

      这里特别强调一点,unity3d有自己的材质系统,不兼容建模软件中高级渲染器的材质,因此只需用标准材质贴基本的纹理即可。

    ⑵. 遮挡剔除和视锥剔除优化预处理

      Unity3d场景既要控制贴图、材质的数量,也要控制物体的数量和场景中模型的总面数,为此,一些童鞋常常将很多物体在建模软件中合并(Attach),这样一来,物体的数量减少了,但是模型面数却没法消减。Unity3d引入了遮挡剔除和视锥剔除技术来优化相机视图中模型的面数,如果为了减少物体数量而将很多物体合并,反倒不利于Unity3d的遮挡剔除和视锥剔除,例如前面的展牌,如果将所有展牌物体全部合并,相机无论如何移动和旋转,相机会认为这些展牌的面数始终在视野之内,无法将真正处于视野以外的模型面剔除,从而导致运行卡顿。这里说明一点,模型面数对机器造成的负载相对物体的数量来说,要大得多,因此控制模型的面数比控制物体数量要重要的多。

      基于上述的概念,对于场景中众多的物体,小姐姐倾向于对其分类群组,不建议随意合并,以方便在unity进行视锥剔除和遮挡剔除优化。

    ⑶. 基于灯光的模型预处理

    在这里插入图片描述
    在这里插入图片描述
    ⑷. 碰撞物预处理

      为加速Unity的相机渲染,可以依据场景的轮廓,构建如下图所示的简版模型作为场景的替身,来接受Unity中碰撞物或称障碍物(Colliders)的实时检测,以防止相机自由下落或穿墙而过。这个简版模型相对带有复杂贴图和光影图的众多场景模型的来说,计算量会小很多,因此会大幅度加速相机的实时渲染,这个简版模型必须在建模软件中预先构建好,而且应与场景模型重合,右下图左上角可见,该模型只有300多个面,远远小于场景的总面数(18万+),这个模型无需贴图,也无需烘焙和渲染,仅用于碰撞检测。

    在这里插入图片描述
    在这里插入图片描述
      很多童鞋完成的作品跑起来很卡,大部分情况是直接将场景中所有物体设为了碰撞物,实时运行时,每帧都要检测相机与所有物体的碰撞情况,机器负载非常巨大,如果改用上面的简版物体作为碰撞物,作品跑起来立即就流畅多了,因此在建模软件中的碰撞物预处理是非常必要的。

    ⑸. 拆分UV2

      前面和大家讨论过拆分UV1,UV1是物体的纹理贴图坐标,我们使用拆分UV1,合并了多个不同物体的材质和贴图,UV2是物体光影图的贴图坐标,拆分UV2,是为unity中烘焙物体的光影图拆分贴图坐标,小姐姐在别的文章中多次强调,Unity3d的UV2拆分功能有缺陷,烘焙简单的场景还行,对于实际的工程,一烘焙就出现黑斑、亮斑、硬边、鬼影等缺陷,这些都是UV2的拆分缺陷引起的,要消除这些问题,得经历无数次的参数修改和烘焙测试,耗时费力,所以小姐姐建议在建模软件中拆分UV2,因为建模软件中拆分的UV2在Unity中烘焙一般没什么问题,这个工作需要在模型导出前来完成,所以拆分UV2也属于模型的预处理,下面我们就来拆分这个案例模型的UV2。

      拆分UV2之前,得根据后期光影烘焙的需要,对整个场景进行一个规划,也就是先确定哪些物体需要烘焙,哪些不需要烘焙,不需要烘焙的物体就不需要拆分UV2了,需要烘焙的物体还得根据物体的重要性和面的大小进行权衡,并本着节省光影图的原则,尽量将更多的物体拆分在一个UV2里面。需要注意的是,拆分在一个UV2里的物体,与前面拆分在一个UV1里的物体没有任何关系,各管各的事。不同的多个物体可以共享一个纹理,不同的多个物体也可以共享一个光影图,这些都是因为不同的多个物体共享了一个UV1或UV2产生的,具体将哪些物体拆分在一个UV2里面,这需要一些实战经验,如果有条件,可跟着我的视频教程去临摹案例来感悟和积累经验。另外,作为建模师,对后期的光影烘焙和场景优化等美工各阶段必须有足够的了解,否则构建的模型不是需要反复修改,就是相互扯皮。

      因为这个场景中的墙体是最大也是最重要的,所以先来拆分墙体,但是如果把所有的墙体拆分在一个UV2里,光影图的分辨率需要设到4096X4096才可能看上去不虚,可是光影图太大,烘焙太费时、同时后期的实时运行负载太大,所以把墙体分为南墙和北墙分别拆分,这样南墙和北墙就可以分别用2048X2048的分辨率来拆分了,一张4096x4096的光影图相当于4张2048x2048的光影图,两张2048x2048的光影图比一张4096x4096的光影图的负载小一半。

      如下图所示,选中南墙(将墙体上的展牌(灯箱)和地台及3D文字等物体排除)所有物体,然后群组,并命名为“nan_qiang_2048”,请大家养成用英文或拼音命名的习惯,以防Unity中的某个插件不支持中文出问题,拼音用低划线隔开是为了好识别,因为有的人一看到字母就以为是英文,直接就懵了,模型是要转给美工的和程序的,得让他们好识别。2048是表示这个群组拆分UV2的分辨率,在群组名称里标识,也是为了传递到Unity中给美工烘焙时设定光影图的分辨率时参考。

    在这里插入图片描述
    在这里插入图片描述
      按下图所示步骤拆分“nan_qiang_2048”群组的UV2:

    在这里插入图片描述
      拆分的UV2如下图所示,注意,虽然这些物体的UV2拆分在一个UV里了,但是这些物体并没有也不需要合并成一个物体,理由前面已经说过了。

    在这里插入图片描述
      看到这里,相信童鞋们会用同样的方法来拆分北墙了,具体过程这里就不赘述了。接下来将选择所有的展牌,并命名为“deng_xiang_128”,有的人说展牌这么大,这么多,分辨率怎么才设128呢,因为这些展牌后续要在Unity里用发光材质作为光源来照亮场景,而自发光的物体是不接受阴影的,也就是自发光物体上是不会有光影图的,同时自发光物体的光照需要烘焙才会有效果,要烘焙得设分辨率,舍得太大又毫无意义,所以这里就设为128。

    在这里插入图片描述
    在这里插入图片描述
      按下图所示的参数拆分后的结果如下,需要注意的是,拆分完一个群组隐藏一个,一方面对于选择物体方便,另一方面不至于丢掉哪个物体。

    在这里插入图片描述

      以此类推,将其他物体根据需要分别群组并拆分UV2,具体过程就不再赘述,有关拆分UV2的方法就讨论到这里。最后强调一点,要分批次烘焙,就得在建模软件中将物体群组拆分UV2,之所以分批次烘焙,一方面是因为整个场景烘焙太费时,一旦某个群组里的物体有问题或者需要修改,只需修改后导入unity重新烘焙这个群组就可以了,已经烘焙好的没有问题的物体是不受影响的,如果不分批次烘焙,每修改一次物体,就得把整个场景烘焙一遍,另一方面,分批次推进烘焙,不至于等的让人心急。


    3. 模型导出

      模型的导出在别的文章里面已经讨论过了,在这里再次讨论,是因为需要告诉大家,如果要分批次烘焙,就得将上面的群组一个一个导出成FBX文件,如下图所示:

    在这里插入图片描述
    在这里插入图片描述
      因为整个场景导出的FBX文件导入Unity后,其下的物体一旦有问题,是不能删除的,如下图所示,只有单独的FBX文件是可以删除的,所以为了能让有问题的物体所在的群组能单独删除,就得将建模软件中的群组分批次一个一个导出成FBX文件,这样导入Unity后,即使烘焙了,删除某个有问题的FBX文件,别的已经烘焙好的物体是不会受任何影响的,将有问题的物体在建模软件中修改后,重新导入Unity中,只需烘焙这一个群组就可以了,不知道大家明白我的意思不。

    在这里插入图片描述
    在这里插入图片描述

      模型导出需要注意的问题就说到这里。


    4. 模型导入Unity


      模型导入Unity在别的文章中也讨论过了,这里需要讨论的是当把分批次导出建模软件的物体群组FBX加载到场景时,有两种方式,一种是直接拖至层级面板,一种是直接拖至场景中,这是两种不同的方试,拖至层级面板,所有物体群组会按建模软件中的坐标原点对齐,这样每个物体都按建模软件中的相对位置定位,而直接拖至场景中,鼠标在哪,物体群组就会放在哪,这样整个场景就会散乱。

    在这里插入图片描述


    5. 灯光布设


      下图为所有模型导入Unity后搭建的场景,由图可见,没有任何层次,很多做程序的人和初学者,对美工没概念,做的场景就这样直接发布了,这显然是不行的,现代社会,任何产品都有美学的要求,对于Unity作品亦不例外,所以必须进行美工处理,美工的第一道工序就是场景布光。

    在这里插入图片描述
      和动画、电影、电视场景一样,Unity场景也需要采用经典的三点布光法(主光、辅光、背光)来照明,同时应根据场景的特点和实际情况来布设灯光。对于现代展厅,和舞厅、K厅一样,一般都是密闭空间,主要靠人工灯采光和营造气氛,无需自然光的参与,所以首先关闭默认的平行光和环境光,如下图所示:

    在这里插入图片描述
      在这个场景中,有几个比较大的落地展牌灯箱,我们在这些位置布设面光源,用这些灯光的综合作用作为主光源来照亮整个场景并产生阴影,当然也可以直接将这些灯箱设为发光材质来照明,但是考虑到面的发光材质的光照没有面光源效果好,所以我们还是用面光源来照明,如下图所示:

    展开全文
  • 学游戏引擎,有一个方面,可以做虚拟现实,比如做展厅,房地产导游等,看似高深的东西,其实原理很简单。当然要做得功能强大又酷炫,还需要积累很长时间。

    首发于游戏蛮牛论坛&&我的CSDN博客:http://blog.csdn.net/wowkk/  转载请说明出处,谢谢。
    本人现大学生,带师弟师妹开发,有合适的项目合作,请加QQ696619

    当知道怎样使用脚本功能之后,就可以学着应用了。Unity3d的逻辑很清晰,在场景里搭建好模型,用脚本控制需要被控制的模型,实现互动。

    首先搭建一个场景,在3D术语中,好像称之为建模。学习阶段,并不需要去使用专业3D模型处理软件或者找多漂亮的素材,我们直接使用Unity3d自带的简陋的建模功能,往后需要的话,只要把对应模型换成美工交给我们的模型即可。

    程序员虽然不要求掌握专业的美工软件,但若能熟练使用,那是大有帮助的。至少PhotoShop这种2D图片处理软件要懂得用,虽然不能处理3D模型,但做2D游戏也用得到,而且,游戏世界里也有“层”等概念,有些是息息相通的。

    下面请看本案例的步骤。

    1:组成场景。

    在场景文件面板Hierarchy中点击Create

    从上往下分别是:立方体(车身),圆柱(车轮),片(地面),摄像机,点光源

    进行拖动布局之后,得到下图效果:

    (PS:分别是控制“拖动视觉窗口”、“进行移动”、“旋转”、“缩放”。按下ctrl可以在Hierarchy和场景里选择多个物体。)

    2:实现车轮转动。

    创建一个JS脚本:Wheel.js,实现车轮转动。

    function Update () {

    gameObject.transform.Rotate(0,-35*Time.deltaTime,0);

    //每帧绕Y轴旋转-35度。gameObject表被绑定的对象,transform是它的变幻属性,调用旋转函数。

    //Update()函数,表每帧执行一次。此处我们要实现转动速度为35/秒,而不是35/帧,所以要乘以Time.deltaTime

    }

    然后把这个脚本绑定到4个车轮,运行之后,即可看到车轮转动效果。

    3:自由控制摄像机视角、创建父子关系。

    选中摄像机,GameObject – Align with view(Ctrl+Shift+F) 就可以把对象转到当前视点,借此灵活控制摄像机所要拍摄的内容。启动项目时,界面所看到的,就是摄像机所看到的内容。

    (GameObject里的菜单项:

    选中四个车轮,拖进车身,即可创建父子关系,这样就可以通过移动车身而顺带移动四个车轮。

    (这里已经将Cylinder重命名为Wheel)

    4:创建展台ExhibitionStand(圆柱),并让展台自动旋转

    创建一个ExhibitionStand.JS,代码跟前面车轮旋转的一样,绑定到ExhibitionStand。

    将车作为展台的子类。

    运行后,车轮自转,展台自转,车随展台转。

    5:添加材质

    Project—Create—Material

    以设置黑色车轮为例,将材质颜色设置为黑色。

    将材质拖进红色划线处(可以选择多个车轮一起操作)

    成品:

     

    //某物体转动时,默认是在以其中心部分作为旋转点。需要以特定部分为旋转点时,可以添加一个空物体,放到指定位置,然后作为某物体的父关系。这样,对父物体(空物体)进行旋转,即可灵活控制旋转点。

    展开全文
  • Unity — 影音视频播放中控系统(适用于展厅、影音厅等等),带有关机、音量控制、UDP控制切换!一:效果图二:所用插件三:项目说明!三:工程详解!四:Demo学习五:关于AB资源打包的学习和拓展 本文提供详细...
  • unity最新录屏插件

    2018-04-23 15:37:21
    Unity3D 录制视频插件 亲测pc上可用,最新的录制视频插件,有Dome案例,支持5.X各版本及最新2017版本,简单明了
  • unity 2019GDC is one of the biggest game development events of the year, and it happens right in Unity SF’s backyard! Join us for a week centered on our latest technologies and filled with exciting ...
  • TUIO结合Unity使用的案例,有利于更好的学习Unity与TUIO
  • unity 安卓串口接发代码:unity环境在android手机调用手机串口,该解压包内附详细图文教程,按教程来操作即可。
  • unity动画实现 Japanese animation studio Graphinica, Inc., creators of innovative TV and cinema animation productions, set out to discover whether it’s possible to produce theater-quality anime using ...
  • windowShades.unitypackage

    2021-08-14 21:18:33
    一种图片轮播显示的多种效果,适合展厅项目
  • unity3D科幻游戏场景

    2018-12-05 10:57:53
    unity3D科幻游戏场景
  • Unity3D android 拉起android软键盘 最近公司项目有一个手机VR展厅业务,用U3D做VR场景还可以做到,后面要加个2D的线下预定页面,就涉及到3D 2D界面转换,首先考虑u3d作为插件,不考虑升级客户端,更新U3D资源来升级...
  • unity 2017 我们的GDC 2017主题演讲不久前在旧金山结束了。 对于那些无法现场直播的人,您可以在这里观看录音: (Our GDC 2017 Keynote just ended a few moments ago here in San Francisco. For those who couldn...
  • 该资源内容包含多个Unity 3D的虚拟现实的作品,具有非常高的学习价值。仅用于学习借鉴和参考,不可用于商业用途!!!
  • 如何使用Unity制作虚拟导览(一)

    千次阅读 2015-07-09 13:47:26
    Unity用来制作游戏已经是目前市场上的一个发展趋势,而且有越来越多的公司与开发者不断的加入,那么Unity的应用是否能涵盖到各种领域?如果使用Unity制作建筑景观模拟?没错,这已是一个新时代的潮流,许多设计院的...
  • 安装Unity后,运行Unity,单击“ ADD”按钮,然后找到包含项目文件的文件夹。 将“ mmo-expo”添加到项目列表后,单击“ mmo-expo”以打开Unity编辑器并导入该项目所需的软件包。 光子引擎设置 转到并注册一个新的...
  • Unity_TUIO连接Object_viz

    2021-05-12 23:54:14
    说明: 展厅互动桌子常见的Maker识别模块,获取它的移动,旋转信息; (The common maker identification module of the interactive table in the exhibition hall can obtain its moving and rotating information;...
  • 游戏开发者大会是Unity的... 我们主要的Unity Technologies展位(#1402)紧挨着位于索尼和Nvidia展厅前面的朋友之间,而Unity Games展位(#1416)就在我们后面。 (The annual celebration of everything game develo...
  • unity 场景漫游

    千次阅读 2019-02-14 09:01:43
    public float zoomSensitivity = 2f;  public float mouseSensitivity = 2f;  public float speedSensitivity = 5f;  private float m_deltX = 0f;  private float m_deltY = 0f;  private Camera m...
  • unity中的生命周期函数We invited Daniel Keßelheim and Sebastian Rigling of Daimler Protics to share about the experiences they create with Unity as their development tool of choice. Learn how they ...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 145
精华内容 58
关键字:

unity展厅