CreatarGlobe实现多机立体显示方案(初稿)

时间:2021-09-24 03:19:48

CreatarGlobe实现多机立体显示方案(初稿)

关键字 : 集群渲染 立体显示 大屏幕 边缘融合 多机同步

多机同步显示

关键字: 大屏幕投影融合系统解决方案 集群渲染

多机3D同步显示又称“集群渲染”

目标

实现如下图的效果:

这个 3*2 的一个投影墙:(渲染节点)

下面是对应的主节点(master 节点)

下面是CAVE模式:(三面CAVE)- 环幕

网络编程相关知识

可使用的参考文档 :

C++_Socket网络编程大全

http://wenku.baidu.com/view/3f5ce1d5360cba1aa811da2b.html

Socket服务器与客户端双向通信实例

http://wenku.baidu.com/view/47a7877101f69e31433294a4.html?re=view

基础知识1

●  winsock APIs

网络连接函数
socket 创建套接字
bind 绑定本机端口
connect 建立连接
listen 监听端口
accept 接受连接
recv, recvfrom 数据接收
send, sendto 数据发送
close, shutdown 关闭套接字
转换函数
inet_addr() 点分十进制数表示的IP地址转换为网络字节序的IP地址
inet_ntoa() 网络字节序的IP地址转换为点分十进制数表示的IP地址
字节顺序转换函数
htonl 4字节主机字节序转换为网络字节序
ntohl  4字节网络字节序转换为主机字节序
htons 2字节主机字节序转换为网络字节序
ntohs 2字节网络字节序转换为主机字节序
网络信息检索函数
gethostname 获得主机名
getpeername 获得与套接口相连的远程协议地址
getsockname 获得套接口本地协议地址
gethostbyname 根据主机名取得主机信息
gethostbyaddr 根据主机地址取得主机信息
getprotobyname 根据协议名取得主机协议信息
getprotobynumber 根据协议号取得主机协议信息
getservbyname 根据服务名取得相关服务信息
getservbyport 根据端口号取得相关服务信息
getsockopt/setsockopt 获取/设置一个套接口选项
ioctlsocket 设置套接口的工作方式

参考
http://www.doc88.com/p-5929803397139.html

基础知识2

参考
http://www.cnblogs.com/uvsjoh/archive/2012/12/23/2830299.html

Windows网络编程使用winsock。Winsock是一个基于Socket模型的API,在Windows系统中广泛使用。
使用Winsock进行网络编程需要包含头文件Winsock2.h,需要使用库ws2_32.lib,包含方法:可以使用语句来告诉编译器连接该库
#pragma comment(lib, “ws2_32.lib”);
如果使用VS,可以通过“项目” --> “XX属性”--> “连接器”-->“输入”--> “附加依赖项”添加ws2_32.lib。 (XX为当前工程名)

面向连接的C/S程序工程流程图

使用Winsock API编制的网络应用程序中,在调用任何一个Winsock函数之前都必须检查协议栈安装情况,使用函数WSAStartup()完成操作。
一个服务端的例子

void server::startServer()
{
    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested=MAKEWORD(2,2);
    if(WSAStartup(wVersionRequested,&wsaData)!=0)
    {
        //Winsock初始化错误        msgBox.exec();
        return;
    }
    if(wsaData.wVersion!=wVersionRequested)
    {
        //Winsock版本不匹配                WSACleanup();
        return;
    }

    if ((m_sk = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
    {        
        WSACleanup();
        return;
    }
    bool ok;
    unsigned short port = ui.portLineEdit->text().toInt(&ok, 10);
    if (ok == false)
    {
        //端口输入错误        closesocket(m_sk);
        m_sk = -1;
        WSACleanup();
        return;
    }

    sockaddr_in addr;
    addr.sin_family = AF_INET; //使用互联网际协议,即IP协议
    addr.sin_port = htons(port);
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    if (bind(m_sk, (sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
    {
        //绑定端口失败
        closesocket(m_sk);
        m_sk = -1;
        WSACleanup();
        return;
    }

    if (listen(m_sk, 10) == SOCKET_ERROR)
    {
        //监听端口失败        closesocket(m_sk);
        m_sk = -1;
        WSACleanup();
        return;
    }
    updateMsgRecs(tr("等待连接..."));
    //创建线程去等待连接
    HANDLE h = CreateThread(NULL, 0, &server::acceptProc, (LPVOID)this, 0, 0);
    if (h == NULL)
    {        
        closesocket(m_sk);
        m_sk = -1;
        WSACleanup();
        return;        
    }
    CloseHandle(h);
}

监听线程函数

DWORD WINAPI server::acceptProc(LPVOID lpParamter)
{
    /*server *p_server = (server *)lpParamter;
    sockaddr_in client_addr;
    int len = sizeof(client_addr);
    char msgBuff[256];
    int sk;
    while (1)
    {
        if ((sk = ::accept(p_server->sk(), (sockaddr*)&client_addr, &len) )== SOCKET_ERROR)
        {
            emit p_server->haveNewMsg(QObject::tr("accept 出错"));
            break;
        }
        
        sprintf(msgBuff, "新连接来自%s", inet_ntoa(client_addr.sin_addr));
        emit p_server->haveNewMsg(msgBuff);
        emit p_server->newClient(inet_ntoa(client_addr.sin_addr), sk);
    }*/

    server *p_server = (server *)lpParamter;
    int client[FD_SETSIZE];
    fd_set allset, rset;
    sockaddr_in client_addr;
    int len;
    int clientfd;
    int sockfd;

    int i;
    for (i=0; i<FD_SETSIZE; i++)
        client[i] = -1;
    FD_ZERO(&allset);
    int listenfd = p_server->sk();
    FD_SET(listenfd, &allset);

    int nready;
    int maxfd = listenfd;
    int maxi = -1;
    char buff[MAX_LEN+1];

    while (1)
    {
        rset = allset;
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);
        if (FD_ISSET(listenfd, &rset)) //new connection        {
            len = sizeof(client_addr);
            if ( (clientfd = ::accept(p_server->sk(), (sockaddr*)&client_addr, &len)) == SOCKET_ERROR)
            {
                emit p_server->haveNewMsg(QObject::tr("accept 出错"));
                break;
            }
            //找出client数组中第一个为-1的单元存放已经连接的socket
            for (i=0; i<FD_SETSIZE; i++)
            {
                if (client[i] <0)
                {
                    client[i] = clientfd;
                    break;
                }
            }
            if (i == FD_SETSIZE)
            {
                emit p_server->haveNewMsg(QObject::tr("error: too many clients!"));
                break;
            }
            //sprintf(buff, "新连接来自%s", inet_ntoa(client_addr.sin_addr));
            //emit p_server->haveNewMsg(buff);
            emit p_server->newClient(inet_ntoa(client_addr.sin_addr), clientfd);
            FD_SET(clientfd, &allset);
            if (clientfd > maxfd)
                maxfd = clientfd;
            if (i>maxi)
                maxi = i;
            if (--nready <= 0)
                continue;
        }
        for (i=0; i<=maxi; i++)
        {
            if ( (sockfd = client[i]) < 0)
                continue;
            if (FD_ISSET(sockfd, &rset))
            {
                int n;
                //客户端已经关闭连接
                if ( (n = recv(sockfd, buff, MAX_LEN, 0 )) <= 0)
                {
                    closesocket(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                    emit p_server->haveNewMsg(QObject::tr("client closed"));
                    emit p_server->sClientClose(sockfd);
                }
                else //收到数据                {
                    buff[n] = 0;
                    emit p_server->haveNewMsg( buff, sockfd);
                    if (--nready <= 0)
                        break;
                }
            }
        } //for (i=0; i<=maxi; i++)    }
    return 0;
}

 一个客户端的例子

void client::connectServer()
{
    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested=MAKEWORD(2,2);
    if(WSAStartup(wVersionRequested,&wsaData)!=0)
    {
        //Winsock初始化错误
        QMessageBox msgBox(QMessageBox::Warning, tr("错误"), tr("Winsock初始化错误"), QMessageBox::Ok, 0);
        msgBox.exec();
        return;
    }
    if(wsaData.wVersion!=wVersionRequested)
    {
        //Winsock版本不匹配
        QMessageBox msgBox(QMessageBox::Warning, tr("错误"), tr("Winsock版本不匹配"), QMessageBox::Ok, 0);
        msgBox.exec();
        WSACleanup();
        return;
    }

    if ((m_sk = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
    {
        QMessageBox msgBox(QMessageBox::Warning, tr("错误"), tr("创建socket失败"), QMessageBox::Ok, 0);
        msgBox.exec();
        WSACleanup();
        return;
    }

    sockaddr_in addr;
    addr.sin_family = AF_INET;

    bool ok;
    unsigned short port = ui.portLineEdit->text().toInt(&ok, 10);
    if (ok == false)
    {
        QMessageBox msgBox(QMessageBox::Warning, tr("错误"), tr("端口输入错误"), QMessageBox::Ok, 0);
        msgBox.exec();
        closesocket(m_sk);
        m_sk = -1;
        WSACleanup();
        return;
    }
    addr.sin_port  = htons(port);
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    if (0 == ::connect(m_sk, (sockaddr *)&addr, sizeof(addr)))
    {
        updateMsgRecs(tr("连接成功..."));
        HANDLE h = CreateThread(NULL, 0, &client::recvProc, (LPVOID)this, 0, 0);
        CloseHandle(h);
    }
    else
    {
        QMessageBox msgBox(QMessageBox::Warning, tr("错误"), tr("连接出错"), QMessageBox::Ok, 0);
        msgBox.exec();
        closesocket(m_sk);
        m_sk = -1;
        WSACleanup();
        
    }
}

●  winsock APIs

网络连接函数
socket 创建套接字
bind 绑定本机端口
connect 建立连接
listen 监听端口
accept 接受连接
recv, recvfrom 数据接收
send, sendto 数据发送
close, shutdown 关闭套接字
转换函数
inet_addr() 点分十进制数表示的IP地址转换为网络字节序的IP地址
inet_ntoa() 网络字节序的IP地址转换为点分十进制数表示的IP地址
字节顺序转换函数
htonl 4字节主机字节序转换为网络字节序
ntohl  4字节网络字节序转换为主机字节序
htons 2字节主机字节序转换为网络字节序
ntohs 2字节网络字节序转换为主机字节序
网络信息检索函数
gethostname 获得主机名
getpeername 获得与套接口相连的远程协议地址
getsockname 获得套接口本地协议地址
gethostbyname 根据主机名取得主机信息
gethostbyaddr 根据主机地址取得主机信息
getprotobyname 根据协议名取得主机协议信息
getprotobynumber 根据协议号取得主机协议信息
getservbyname 根据服务名取得相关服务信息
getservbyport 根据端口号取得相关服务信息
getsockopt/setsockopt 获取/设置一个套接口选项
ioctlsocket 设置套接口的工作方式

winsock client
& server

分类: winsock
2013-05-07 21:36 489人阅读 评论(0) 收藏 举报

winsock

// winsock client.cpp : 定义控制台应用程序的入口点。
//
客户端实例

//vs2010编译通过

#include "stdafx.h"

#include
<winsock2.h>
#include <stdio.h>

#define SERVPORT   
5050             
//
端口为5050
#define MAXDATASIZE 100
#define SERVIP     
"127.0.0.1"      //
服务器IP地址为"127.0.0.1",注意使用inet_addrIP地址转换为网络格式
#pragma comment(lib,"ws2_32.lib")

void main(int argc, char *argv[])
{
 WSADATA             
wsaData;
 SOCKET              
sConnect;
 SOCKADDR_IN         
serverAddr; 
 int     recvbytes;
 char     buf[MAXDATASIZE];

 //初始化Windows Socket 2.2

 WSAStartup(MAKEWORD(2,2), &wsaData);

 // 创建一个新的Socket来连接服务器

 sConnect = socket(AF_INET, SOCK_STREAM,
IPPROTO_TCP);

 // 填写连接地址信息

 serverAddr.sin_family = AF_INET;
 serverAddr.sin_port = htons(SERVPORT);   
 serverAddr.sin_addr.s_addr = inet_addr(SERVIP);

 memset(&(serverAddr.sin_zero), 0,
sizeof(serverAddr.sin_zero));

 // 向服务器发出连接请求

 if (connect(sConnect, (SOCKADDR
*)&serverAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
 {
  printf("connect failed!\n");
  system("pause");
  return;
 }
 //
接受服务器的回应消息
 recvbytes = recv(sConnect, buf, MAXDATASIZE, 0);
 if (recvbytes == SOCKET_ERROR)
 {
  system("pause");
  printf("recv failed!\n");
 }
 else
 {
  buf[recvbytes] = '\0';
  printf("%s\n",buf);
 }

 closesocket(sConnect);

 // 释放Windows Socket DLL的相关资源

 system("pause");
 WSACleanup();
}

// winsock server.cpp : 定义控制台应用程序的入口点。
//
服务器端实例

//vs2010端编译通过

#include "stdafx.h"

#include
<winsock2.h>
#include <stdio.h>

#define SERVPORT    5050
#pragma comment(lib,"ws2_32.lib")

void main(void)
{
 WSADATA             
wsaData;
 SOCKET              
sListen;  //
监听socket
 SOCKET              
sClient;  //
连接socket
 SOCKADDR_IN         
serverAddr;  //
本机地址信息
 SOCKADDR_IN         
clientAddr;  //
客户端地址信息
 int             
clientAddrLen; //
地址结构的长度
 int                 
nResult;
 //
初始化Windows Socket 2.2

 WSAStartup(MAKEWORD(2,2), &wsaData);

 // 创建一个新的Socket来响应客户端的连接请求

 sListen = socket(AF_INET, SOCK_STREAM,
IPPROTO_TCP);

 // 填写服务器绑定的地址信息
 //
端口为5050
 // IP
地址为INADDR_ANY,响应每个网络接口的客户机活动
 //
注意使用htonlIP地址转换为网络格式

 serverAddr.sin_family = AF_INET;
 serverAddr.sin_port = htons(SERVPORT);   
 serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 memset(&(serverAddr.sin_zero), 0,
sizeof(serverAddr.sin_zero));  
 //
绑定监听端口

 nResult = bind(sListen, (SOCKADDR
*)&serverAddr, sizeof(SOCKADDR));
 if (nResult == SOCKET_ERROR)
 {
  printf("bind failed!\n");

  return;
 }

 // 开始监听,指定最大接受队列长度5,不是连接数的上限

 listen(sListen, 5);

 // 接受新的连接
 while(1)
 {
  clientAddrLen = sizeof (SOCKADDR);
  sClient = accept(sListen, (SOCKADDR *)&clientAddr,
&clientAddrLen);
  if(sClient == INVALID_SOCKET)
  {
   printf("Accept failed!");
  }
  else
  {
   printf("Accepted client: %s : %d\n",
inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
   //
向客户端发送信息
   nResult = send(sClient, "Connect success!", 16, 0);
   if (nResult == SOCKET_ERROR)
   {
    printf("send failed!");
   }
  }
  //
我们直接关闭连接,
  closesocket(sClient);
 } 

 // 并关闭监听Socket,然后退出应用程序

 closesocket(sListen);

 // 释放Windows Socket DLL的相关资源

 WSACleanup();
}

 

多机同步(多线程同步)

采用Event事件和等待waitfor...机制实现跨机同步。

WaitForSingleObject

等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。这些等待函数中最常用的是WaitForSingleObject:

DWORD
WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);

HANDLE经常使用
CEvent对象,在程序中可以通过调用SetEvent/ResetEvent分别将EVENT置为这两种状态分别是发信号与不发信号。

参考 :

WaitForSingleObject的用法

http://blog.sina.com.cn/s/blog_4b88ef57010009js.html

线程中CreateEvent和SetEvent及WaitForSingleObject的用法

http://chinaxyw.iteye.com/blog/548622

WaitForMultiObjects

用法类似于waitForSingleObjects只不过是多信号等待。

原理分析

多机同步显示示意图

多机同步的实现原理实际是控制master和slaver上的投影机姿态矩阵,将二者绑定在一起,就像是架在一根竹棍上的两个相机一样,一起做相同的运动,这样集群中的所有相机的相对位置相同,姿态也相对不变,投影画面也就能够始终保持无缝拼接了。

疑问: 重叠区域地物的调度需要怎么才能保证多台机器同时出现同时消失?不控制是否可以接受?

如果不控制的话在重叠区域就很容易出现建筑物的亮度比非重叠区亮度低的情况(因为重叠区做了融合处理削弱了亮度),尤其是在边缘处亮度接近于全黑。

流程设计

  1. 1.  启动集群所有计算机中的creatar软件,creatar通过网络广播反馈或者自定义配置文件方式
    确定集群中的 主控机master(一台)和从属机slaver(一台或多台)角色, 建立主控机master与从属机slaver之间的通讯通道(每台slaver和master之间都有一个通讯线程存在)。

主控机向从属机发送的信息为
“相机矩阵” 和 “翻屏指令”, 接受的信息为从属机发来的 “等待翻屏指令”。从属机向主控机发送的信息为 “等待翻屏指令”,接收的信息是“相机矩阵”。【细化】

一般这种多机显示应用都要设计一个独立的控制界面,放在主控机master的集成显卡显示区域。主控机的专业3D显卡和从属机3D显卡显示3D系统的3D视图画面。

  1. 2.  固定从属机slaver的相机与主控机master的相机之间的姿态差异,这样只要初始状态时的master和slavers的相机的投影画面能无缝拼合在一起,未来master的相机做了怎样的矩阵变换,slavers的相机也跟随做相应的矩阵变换就能保证投影画面能始终无缝拼合。这样就需要每帧master都要把它的“相机矩阵”发给slaver,或者把其变换矩阵发给slaver,slaver根据收到的矩阵做相同的姿态改变就可以了。

3. 流程图如下所示

会用到windows多线程编程的阻塞机制,master上建议使用WaitForMultiObjects(),slaver上建议使用WaitForSingleObject()来阻塞渲染线程。

实现细节

winsock广播

参考资料
http://www.cnblogs.com/uvsjoh/archive/2012/12/28/2837687.html

广播通信是无连接的通信,通信前不需要建立连接。不需要listen和accept,但需要绑定一个socket用来接收广播。

广播包的发送
创建socket
设置socket,例如设置超时、允许广播等
绑定socket。在使用广播前必须绑定一个socket。这一步可有可无,如果没有,系统自动绑定到一个未用端口。
发送广播。广播的端口号要和接收方绑定的端口号一致

#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
void autoCleanup()
{
    WSACleanup();
}
int _tmain(int argc, _TCHAR* argv[])
{

    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested=MAKEWORD(2,2);
    int ret;
    ret = WSAStartup(wVersionRequested,&wsaData);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    int bc = 1;
    //允许发送广播消息
    int so_broadcast = TRUE;
    ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&so_broadcast, sizeof(so_broadcast));
    
    //sockaddr_in addr;
    //addr.sin_family = AF_INET; //使用互联网际协议,即IP协议
    //addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); 
    //addr.sin_port = htons(2526);

    //如果仅仅是发送广播,这一步可有可无。没有绑定也能发送广播
    //ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
    struct sockaddr_in b_addr;
    b_addr.sin_family = AF_INET;
    b_addr.sin_addr.S_un.S_addr =htonl(INADDR_BROADCAST);
    b_addr.sin_port = htons(2527);


    char buff[50] = "Hello, world!";
    while (1)
    {
        ret = sendto(sock, buff, strlen(buff), 0, (struct sockaddr*)&b_addr, sizeof(b_addr));

        printf("send... %d\n", WSAGetLastError());
        Sleep(3000);
    }

    closesocket(sock);
    atexit(autoCleanup);
    return 0;
}

接收广播包
接收方一定要知道广播方的端口号,然后绑定同样的端口号才能正确接收。道理很简单,如果不绑定到一个端口,它不知道到哪里接收数据。

// send.cpp : Defines the entry point for the console application.//
#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
void autoCleanup()
{
    WSACleanup();
}
int _tmain(int argc, _TCHAR* argv[])
{
    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested=MAKEWORD(2,2);
    WSAStartup(wVersionRequested,&wsaData);

    SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    //这个端口要和广播方广播端口一致
    addr.sin_port = htons(2527);

    bind(sock, (struct sockaddr *)&addr, sizeof(addr));

    struct sockaddr_in from;
    int len = sizeof(from);
    int ret;
    char buff[50];

    while (1)
    {
        ret = recvfrom(sock, buff, 49, 0, (struct sockaddr *)&from, &len);
        if (ret > 0)
        {
            buff[ret] = 0;
            printf("%s\n", buff);
            printf("%s %d\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port));
        }
    }

    closesocket(sock);
    atexit(autoCleanup);
    return 0;
}

winsock网络通讯

利用winsock编写网络应用程序服务端的步骤简述如下
WSAStartup 初始化网络编程库
socket 创建套接字
bind 指定地址、端口,绑定套接字
listen 进入监听状态
accept 等待接收新连接
send/recv 收发数据
closesocket 关键套接字
WSAStartup 释放对动态库的使用

参考资料

C++_Socket网络编程大全

http://wenku.baidu.com/view/3f5ce1d5360cba1aa811da2b.html

Socket服务器与客户端双向通信实例

http://wenku.baidu.com/view/47a7877101f69e31433294a4.html?re=view

Windows网络编程

http://www.cnblogs.com/uvsjoh/archive/2012/12/23/2830299.html

MSDN上的客户端代码(Winsock
Client Source Code)

http://www.cnblogs.com/wwping/archive/2012/04/18/2454979.html

面向连接的C/S程序工程流程图

 

字节序转换函数
htons 把 unsigned short 类型从主机序转换到网络序
htonl 把 unsigned long 类型从主机序转换到网络序
ntohs 把 unsigned short 类型从网络序转换到主机序
ntohl 把 unsigned long 类型从网络序转换到主机序
这几个函数很好记,比如htons中hton代表host to network, s代表unsigned
short
char FAR * inet_ntoa( struct in_addr in);
将一个IP转换成一个互联网标准点分格式的字符串。
in_addr_t inet_addr(const char *cp);
将一个点分十进制的IP转换成一个长整数型数(u_long类型)。返回值已是网络字节顺序,可以直接作为internet 地址

Openscenegraph单机立体显示

参考资料

osg中立体显示的设置“最长的一帧”第三日

osg::DisplaySettings 这个类在OSG 的窗口显示中扮演了重要的地位:它保存了OSG 目前用到的,与图形显示,尤其是立体显示有关的所有信息,

主要包括:

_displayType:显示器类型,默认为MONITOR(监视器),此外还支持POWERWALL

(威力墙),REALITY_CENTER(虚拟实境中心)和HEAD_MOUNTED_DISPLAY(头盔

显示器)。

_stereoMode :
立体显示模式, 默认为ANAGLYPHIC ( 互补色), 此外还支持

QUAD_BUFFER(四路缓冲),HORIZONTAL_SPLIT(水平分割),VERTICAL_SPLIT(垂直分割),LEFT_EYE(左眼用),RIGHT_EYE(右眼用),HORIZONTAL_INTERLACE(水平交错),VERTICAL_INTERLACE(垂直交错),CHECKERBOARD(棋盘式交错,用于DLP 显示器)。

_eyeSeparation:双眼的物理距离,默认为0.05。

_screenWidth,_screenHeight:屏幕的实际宽度和高度,分别默认设置为0.325 和0.26,

目前它们影响的仅仅是视图采用透视投影时的宽高比。

_screenDistance:人眼到屏幕的距离,默认为0.5。

_splitStereoHorizontalEyeMapping:默认为LEFT_EYE_LEFT_VIEWPORT(左眼渲染左

视口),也可设为LEFT_EYE_RIGHT_VIEWPORT(左眼渲染右视口)。

_splitStereoHorizontalSeparation:左视口和右视口之间的距离(像素数),默认为0。

_splitStereoVerticalEyeMapping:默认为LEFT_EYE_TOP_VIEWPORT(左眼渲染顶视

口),也可设为LEFT_EYE_BOTTOM_VIEWPORT(左眼渲染底视口)。

_splitStereoVerticalSeparation:顶视口和底视口之间的距离(像素数),默认为0。

_splitStereoAutoAdjustAspectRatio:默认为true,用于屏幕分割之后对其宽高比进行补

偿。

_maxNumOfGraphicsContexts:用户程序中最多可用的GraphicsContext(图形设备上下

文)数目,默认为32 个。

_numMultiSamples:多重采样的子像素样本数,默认为0。如果显示卡支持的话,打开

多重采样可以大幅改善反走样(anti-aliasing)的效果。

此外还有很多可以设置的类变量,如_minimumNumberStencilBits(模板缓存的最小位

数)等,其默认设置均在osg::DisplaySettings::setDefaults
函数中完成,其中有些变量可能还

没有作用。要注意的是,DisplaySettings
的作用仅仅是保存所有可能在系统显示中用到的数

据,这个类本身并不会据此改变任何系统设置和渲染方式。

值得称道的是,DisplaySettings
可以很方便地从系统环境变量或者命令行参数中获取用

户对显示设备的设置,详细的调用方法可以参阅DisplaySettings::readEnvironmentalVariables

和DisplaySettings::readCommandLine
两个函数的内容,十分通俗易懂。

如果希望在用户程序中更改DisplaySettings
中的显示设置,请务必在执行视景器的

realize 函数之前,当然也就是仿真循环开始之前。这一点也是要切记的。

分析及方案设计

目前通用的立体实现模式有两种:主动式立体和被动式立体

主动式立体显示

主动立体显示方式也称快门式3D显示技术,主动快门式3D技术是目前3D投影设备市场上应用比较广泛的3D显示技术,其需要配合主动快门式眼镜使用,原理是这样的:屏幕会先显示给左眼看的画面,这时眼镜会同步将你的右眼遮住,有点像海盗戴的眼罩那样。接着,屏幕会快速切换到给右眼看的画面,这时眼镜就会转成将你的左眼遮住,确保你看到的画面是正确的。

主动快门式3D技术是通过交替左眼和右眼看到的图像以至于你的大脑将两幅图像融合成一体来实现,从而产生了单幅图像的3D立体感。画面交替的过程非常迅速,每秒可以到120次(120Hz刷新率),因此对人眼来说是无法看到这个左右转换的。

主动立体的主要技术特征:同步发射器及快门式眼镜、支持120Hz输出的融合器、支持120Hz投影机。

这种情况下osg::DisplaySettings应设置如下:

_displayType使用默认值 MONITOR(监视器),

_stereoMode 使用默认值 ANAGLYPHIC ( 互补色)

其它参数使用默认值即可。

评价 这种模式对3D程序的渲染速率要求很高(120帧每秒)一般的3D软件都难以达到,所以应用比较多的是3D视频播放或3D电影领域。听说这么高的频率下人眼也是很容易累的,而且这种3D影片的制作难度也比较大。

被动式立体显示

被动立体显示方式也称光学偏振显示技术,主要实现方式:通过两台显示设备(投影机),同时把两个经过特殊处理(立体处理)的图像或影片同步放映,使这略有差别的两幅图像(景深差别)重叠在银幕上(偏振光学幕)。这时如果用眼睛直接观看,看到的画面是重影模糊不清的,要看到立体影像,就要在每架投影机前装一块偏振片。从两架放映机投射出的光,通过偏振片后,就成了偏振光。左右两架投影机前的偏振片的偏振化方向互相垂直,因而产生的两束偏振光的偏振方向也互相垂直。

这两束偏振光投射到银幕上再反射到观众处,偏振光方向不改变。当观众带上偏振眼镜后,左右两片偏振镜的偏振轴互相垂直并与放映镜头前的偏振轴一致,所以每只眼睛只看到相应的偏振光图象,即左眼只能看到左机映出的画面,右眼只能看到右机映出的画面,这样就会像直接观看那样产生立体感觉。

被动立体显示的主要技术特征:双倍的投影机、配置偏振光片及眼镜、配置偏振屏幕、左右眼单独播放的融合器。

这种情况下osg::DisplaySettings应设置如下:

_displayType使用默认值 MONITOR(监视器),

_stereoMode 使用默认值 HORIZONTAL_SPLIT(水平分割)或者VERTICAL_SPLIT(垂直分割),具体使用哪种方式应视具体的设备环境来决定,一般水平分割的情况比较多。

_splitStereoHorizontalEyeMapping
如果使用水平分割可以设置此参数。

_splitStereoVerticalEyeMapping  如果使用垂直分割可以设置此参数。

其它参数默认值是否需要修改结合具体情况决定。

评价: 最简单的应用就是一台电脑+两台投影机的小规模场景应用,不需要融合器。如果是上下2*2或3*3的投影机布局的话就需要融合器和软件系统实现3D同步控制功能,相当于多机立体显示。

Openscenegraph多机同步立体显示

最好是选用支持Overlap边缘重叠的专业显卡 + 支持边缘融合的投影机 这样在程序级就不需要产生重叠带及对边缘区域进行融合处理。用硬件的解决方案简化系统实现复杂度。

对于单机输出多路信号的需求可以借助专业级多屏显卡来实现。

专业网站 :中国投影网 http://www.ty360.com/dp/

将多机同步与立体显示相结合可以实现多机立体显示。涉及的问题有重叠区对象调度!

OpenscenGraph中控制swapbuffer的方法(用于多机大屏幕同步显示机制)

*********************************************************************
osg多机同步swapbuffer的实现方式。

osg中真正调用opengl::swapbuffer的地方在
osg::GrapicsContext::swapBuffers()中调用的。
如果developer想干预的话 可以调用 osg::GrapicsContext::setSwapCallback(SwapCallback* rc)来设置自定义的缓存交换回调。
自定义的回调必须调用GraphicsContext::swapBuffersImplementation()函数.

typedef std::vector<osg::GraphicsContext*> Contexts;
osg::Viewer::getContexts(Contexts& contexts, bool onlyValid=true); 可以获取grapicscontext的数组
*********************************************************************

可以将同步这部份做成exe程序(精灵程序,隐藏窗口),用消息机制分发给目标机器的目标窗口。也可以直接集成到creatarglobe系统中做为可选模块。

 

 

方案

1. 在系统配置文件中写入角色信息。creatarGlobe系统启动时通过读取配置信息确定自身角色及集群中其它机器的角色,然后建立集群跨机通讯机制(网络监听和收发)。

2. 调整初始状态时的master和slaves的相机位置使其投影画面在重叠区能重合在一起。借助投影机和融合器实现产生重叠区和边缘融合功能。

3. 立体显示设置。根据实际需求设置osg::DisplaySettings中的参数实现立体效果。具体可以参考上面的openscenegragh单机立体显示中的描述。

4. 进入同步机制。

对于master,首先将自己“相机矩阵”通过网络通讯分发给每个slave,然后开始渲染,渲染完毕后等待slaver的“等待翻屏指令”的到来【waitformultiobjects】,当收到所有的“等待翻屏指令”后(每个指令对应一个事件Event),master通过网络向所有的slave发送“翻屏指令”,然后自己翻屏,重置事件信号ResetEvent。然后就是重复以上流程。

对于slave,首先接收master发来的“相机矩阵”,根据此矩阵调整自身相机使其相对master的相机位置姿态不变。然后开始渲染,渲染完毕后向master发送 “等待翻屏指令”,然后等待master发送的“翻屏指令”事件【waitforsingleobject】,当收到“翻屏指令”后设置翻屏事件信号Event,然后开始翻屏,重置事件信号ResetEvent。然后就是重复以上流程。

这里的翻屏swapbuffer通过osg::GrapicsContext::setSwapCallback(SwapCallback* rc),自己定义缓存交换回调函数来实现。

备注: 接收的命令最好是带时间戳的,这样可以保证同步的是同一帧的3D画面,避免画面撕裂。

5. 系统退出时,slave的网络线程先退出然后master的网络线程再退出以保证线程安全。

6. 伪代码:

需要实现的自定义类

CGlbGlobeSwapBufferCallback : public
osg::SwapCallback

{

CGlbGlobeSwapBufferCallback(CGlbGlobeView*
view)

{

mpr_view
= view;

}

virtual
void operater ()

{

.......

//
从view中获取自身角色master or slaver

bool
isMaster = mpr_view->GetRole();

if
(isMaster)

{//
如果自己角色是master,做以下工作: 收集网络传来的所有slaver绘制完毕消息,然后广播翻屏指令,翻屏

int
slaverCnt = mpr_view->GetSlaverCount();

WaitForMultiObjects(...);

{//
向所有的slaver发送翻屏指令

for(int
i = 0; i < slaverCnt; i++)

{

mpr_view->sendMessage(slaverIP,"drawcomplete");

}

}

swapBuffer();

}

else{//
如果自己角色是slaver,做以下工作: 通过网络向master发送绘制完毕消息,等待master通过网络传来的翻屏指令,翻屏

mpr_view->sendMessage(masterIP,"drawcomplete");

WaitForSingleObject();

swapBuffer();

}

}

private:

glbref_ptr<CGlbGlobeView>
mpr_view;

}

在view中要有处理TCP/IP网络发送和接收的类或接口。

CGlbClient

{

}

CGlbServer

{

}

设置相机矩阵由gluperspective->glufrustum