Unity沉浸式/360View/全景渲染

Unity沉浸式/360View/全景渲染

啥是沉浸式

想象一下,在一个房间里,前面、后面、地面、左面、右面、天花板分别安装了铺满墙面的LED显示屏,你坐在房间里观影,是一种什么体验。如果玩游戏呢??

最近就接到了这样的小活,当然,这个房间里并不是六面都有显示器,而是只有前面、地面、左面、右面四个显示器。

现在需要把Unity里的场景,沉浸式的渲染到这4个显示器上。

Unity沉浸式/360View/全景渲染

如何实现,排坑及最终实现历程

最先想到的,就是用4个摄像机。建个空物体,底下挂4个Camera,前面的向前,左边的朝左,右边的朝右,地面的朝下,相互垂直。看起来很简单,对吧。但是,问题就出现了,画面没法对其,死活调不好摄像机的参数。手动对了半天,对不上。

4个屏幕的分辨率如下:

  • 正面:3744 * 1560
  • 地面:3744 * 1664
  • 左面:1634 * 1872
  • 右面:1648 * 1872

发现了没,这分辨率很奇葩,地面的高度居然跟侧边的高度不一样,侧边的深度跟地面的深度也不一样,我靠,这怎么能对的上呢。。

然后深入研究了一下,发现摄像机可以自定义透视矩阵。

csharp 复制代码
// Unity API : Matrix4x4.Frustum
public static Matrix4x4 Frustum (float left, float right, float bottom, float top, float zNear, float zFar);
public static Matrix4x4 Frustum (FrustumPlanes fp);

这不就容易了么!!!

半小时就写了一个组件,让摄像机无视输出分辨率,直接分别调整水平和垂直的视野角度,核心的代码如下:

csharp 复制代码
// horizontalFOV : 水平视野角度
// verticalFOV  : 垂直视野角度

float near = targetCamera.nearClipPlane;
float far = targetCamera.farClipPlane;

// 计算水平和垂直方向的视锥体边界
float horizontalHalfAngle = horizontalFOV * 0.5f * Mathf.Deg2Rad;
float verticalHalfAngle = verticalFOV * 0.5f * Mathf.Deg2Rad;

float right = Mathf.Tan(horizontalHalfAngle) * near;
float left = -right;
float top = Mathf.Tan(verticalHalfAngle) * near;
float bottom = -top;

// 创建自定义投影矩阵
Matrix4x4 projectionMatrix = Matrix4x4.Frustum(left, right, bottom, top, near, far);
targetCamera.projectionMatrix = projectionMatrix;

当我把每个摄像机的视野参数做成可调整的,发给现场测试的哥们,他调了很久,告诉我还是对不上,主要是地面和侧面对不上。。然后认真分析了一下,主要原因是,他这个房间的地面,是倾斜的,两侧屏幕实际是个梯形。。说是为了更好的显示效果。这TM怎么能对的上。。

开始考虑怎么解决。

方案1:

两侧的摄像机,再增加一个斜压缩矩阵。

手工构造一个斜压缩矩阵,用现有的透视矩阵乘以这个矩阵,最终再赋予摄像机。然而尝试了几次,都失败了,弄出来的总是个斜切效果,可能是因为自己数学功底还是太差,对于矩阵的手工构造,一直停留在云雾状态,果断放弃。

方案2:

两边的相机不再直接输出到显示器,而是先渲染到一个RenderTexture,然后,再建一个RawImage铺满屏幕,把RenderTexture通过RawImage进行显示,这时候,就可以写一个Shader,对RawImage的UV采样进行修改:

c 复制代码
fixed4 frag (v2f i) : SV_Target
{
	float s;
	if(_CompressRate<0)
		s = (1 - i.uv.x) * -_CompressRate;
	else
		s = i.uv.x * _CompressRate;
	i.uv.y = clamp((i.uv.y-s)/(1-s), 0, 1);
	return tex2D(_MainTex, i.uv);
}

新大陆

至此其实基本上已经解决了,然而,就在这个时候,又发现一个新大陆:

csharp 复制代码
// Unity API : Camera.RenderToCubemap
public bool RenderToCubemap (Cubemap cubemap, int faceMask);

居然有现成的API,他可以以当前摄像机位置为视角,直接把360全景渲染到一个立方体纹理中。要是有了这个,那还要啥4个摄像机。。一个相机就够了。

于是尝试了一下。

csharp 复制代码
private void Awake()
{
	_cubeTexture = new RenderTexture( size, size, 24 )
	{
		name = "CubeRenderTexutre",
		dimension = TextureDimension.Cube
	};
}

private LateUpdate()
{
	_camera.RenderToCubemap(_cubeTexture);
}

然而,与想象中不一样的是,他渲染的时候,只参考相机的位置,并不会根据摄像机的朝向去渲染,而是按照世界坐标系进行渲染。也就是说,盒子里的前面始终是世界坐标系的z方向。

那么,要想把纹理从盒子里取出来,输出到屏幕上,就比较麻烦。主要是对立方体纹理的采样。经过研究,弄了一个shader出来,传入观察方向、视野角度,让他渲染到一个RawImage。

c 复制代码
Shader "Custom/CubemapProjector" {
    Properties {
        _Cubemap ("Cubemap", Cube) = "" {}
        _Direction ("World Direction", Vector) = (0,0,1,0)
        _FovFactor ("FOV Factor", Vector) = (0.25,0.25,0,0)
    }
    SubShader {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            samplerCUBE _Cubemap;
            float3 _Direction;
            float2 _FovFactor;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                // 计算实际视野角度(度)
                float horizontalFOV = _FovFactor.x * 360.0;
                float verticalFOV = _FovFactor.y * 360.0;
                
                // 转换为弧度并计算tan值
                float tanHalfH = tan(radians(horizontalFOV) * 0.5;
                float tanHalfV = tan(radians(verticalFOV)) * 0.5;
                
                // 映射UV到方向平面
                float2 uv = (i.uv - 0.5) * 2.0; // 转换到[-1,1]
                uv.x *= tanHalfH;
                uv.y *= tanHalfV;
                
                // 创建局部坐标系
                float3 forward = normalize(_Direction);
                float3 worldUp = abs(forward.y) > 0.999 ? float3(0,0,1) : float3(0,1,0);
                float3 right = normalize(cross(worldUp, forward));
                float3 up = cross(forward, right);
                
                // 计算采样方向
                float3 viewDir = normalize(uv.x * right + uv.y * up + forward);
                
                // 采样立方体贴图
                fixed4 col = texCUBE(_Cubemap, viewDir);
                return col;
            }
            ENDCG
        }
    }
}

研究到这里,发现,其实这个跟四个相机也没有什么差别,因为最关键的还是视野角度和观察方向,这俩个要素是不可能脱离的,然后,Cubemap的方案就没有再继续深入研究下去了,因为经过分析,其实4相机和cubemap的渲染方法各有优劣,在性能上,也相差不大。

这个项目目前还没有竣工,以后最终选取哪一种给方案也还没有定论,目前来看,决定暂时还是用4相机。

记录一下项目历程,主要是以自己以后查看,如果能有幸帮助到后人,那就更好了。

相关推荐
RReality11 小时前
【Unity Shader URP】Matcap 材质捕捉实战教程
java·ui·unity·游戏引擎·图形渲染·材质
魔士于安11 小时前
unity urp材质球大全
游戏·unity·游戏引擎·材质·贴图·模型
南無忘码至尊14 小时前
Unity学习90天 - 第 6 天 -学习物理 Material + 重力与阻力并实现弹跳球和冰面滑动效果
学习·unity·游戏引擎
mxwin16 小时前
Unity 单通道立体渲染(Single Pass Instanced)对 Shader 顶点布局的特殊要求
unity·游戏引擎·shader
魔士于安19 小时前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
RReality19 小时前
【Unity Shader URP】简易卡通着色(Simple Toon)实战教程
ui·unity·游戏引擎·图形渲染·材质
魔士于安19 小时前
unity 骷髅人 连招 武器 刀光 扭曲空气
游戏·unity·游戏引擎·贴图·模型
洛阳吕工21 小时前
从 micro-ROS 到 px4_ros2:ROS2 无人机集成开发实战指南
游戏引擎·无人机·cocos2d
风酥糖1 天前
Godot游戏练习01-第29节-游戏导出
游戏·游戏引擎·godot
瑞瑞小安1 天前
Unity功能篇:文本框随文字内容动态调整
ui·unity