Table of Contents
理解TouchDesigner播放系统01_mesh projection
- Kyle Liu
- Touch designer
- March 6, 2025
Ndisplay的画面投射原理
TouchDesigner中,我们的核心目的就是模仿Ndisplay的Mesh Projection部分的功能。
这个功能能够从一个Default View Point观察虚拟世界,并且将看到的东西,投射在任何模型上,对于虚拟拍摄来说,就是将看到的东西,投射在代表屏幕的模型上,然后屏幕再将这个投射在自己身上的画面,按照UV坐标的方式显示出来。
下面我们用一个例子来详细理解下这个过程。
好比现实生活中,我们用一台摄像机去拍摄环境,在这个摄像机和环境之间,有一个透明的带有弧度的玻璃屏幕,人们从摄像机的角度看过去(也就是从Default View Point)看过去,完全不会感知到透明玻璃屏幕的存在(因为你看到的就是真是的环境),但是想象一下,如果我们在某个位置固定下来,用摄像机将那一个位置的画面捕捉下来,然后我们将那个透明的玻璃屏幕替换成,同样形状大小同样位置的一个投影幕布,然后此时用一个投影仪将刚才拍摄的画面,完美按照摄像机的视角投射到幕布上。此时我们从刚才相同的Default View Point看过去,我们还是感知不到屏幕的存在,我们还是看到的环境本身。
接着,我们将投影幕布换成同样的形状大小和位置的led曲面显示屏,这时候就不能使用投影仪了,而是需要windows系统输出一个画面给显示屏。这里就要提到我们为什么要用曲面显示屏来举例子了,因为这个时候你可能会意识到,把刚才摄像机捕捉的画面,也就是投影仪投出的画面直接设置成windows桌面的话,显示是不对的,首先,摄像机拍摄的时候,不可能画面范围和显示器画面范围完全重合,想象一下,摄像机的画面肯定是,中间有个透明玻璃屏幕但没有沾满整个画面,然后边缘还是有多余的画面的,这是其一。其次投影的画面投在曲面的显示屏上的时候,会因为屏幕曲面的原因而画面有所扭曲。而对于windows系统来说,它不管你的屏幕是否有弧度,它只是把屏幕当作一个矩形来显示内容。所以直接把摄像机拍摄的图像,直接显示在屏幕上是不对的。
那么我们就需要一个步骤,将摄像机当时捕捉的画面中,属于屏幕范围的那部分,进行某种裁切反向扭曲后,得到一个平整的windows壁纸,然后当曲面屏幕显示这个壁纸的时候,又因为其本身物理的弧度,将画面扭曲,从而确保当从摄像机的位置观察出去的时候,看到的和摄像机拍摄到的画面一样。
那么这个反扭曲该如何理解呢?我们可以将其理解为喷绘,想象一下,我们将曲面屏再换成同样形状大小位置的一个画布,假设有种喷涂技术,可以从摄像机的位置,用喷绘的方式将画面喷到这个画布上,当然喷涂的时候,画布之外也有颜料喷出,但是没有落在画布上,就消失了(也就是裁切了画面)。然后我们将画布取下,平铺在地上,便得到了这个壁纸。这个过程让我们联想到了,3d模型的UV展开的过程。
所以,总结下,如果要实现Ndisplay方式的mesh projection,我们需要:
一个相机从看着mesh的方向捕捉一个画面,确保画面中能够拍摄到mesh的全部
将这个画面以相机的位置和视角,投射在mesh上
将投射在mesh上的画面,使用UV进行展开
展开得到的画面,就是需要输出给我们现实中显示屏的画面
下面我们就在TouchDesigner中,根据这个原理,实现核心的mesh projection的方案。
简单的TD mesh projection方案
不需要按照步骤操作,只是说明原理用途
1 创建代表视频播放的3d卡片
这里用两个transform的作用是,第一个transform控制mesh距离中心多远,下一个transform则是沿着中心旋转,类似于unreal ndisplay中,lightcard的逻辑,第一个transform是distance to center, 第二个是控制longitude 和 latitude。我在最终的项目中也是使用这两个节点来控制每个视频卡片显示的位置的。
2 导入两个代表led屏幕节点的mesh
这里使用温哥华Node1 和 Node2 的mesh,也就是 Wall_1_2 和 Wall_3_4来展示,总之这里每个节点代表一个windows桌面。
3 将代表视频播放的mesh,放置到合适的位置
这里我把这个视频播放的mesh放在node1和node2的mesh后面,这里没什么特别的,只是说我想稍后能让node1和2有东西可以显示,并且验证画面是相连的。
4 进入对于每个led mesh的fbx节点,创建一个用于投射的camera,并且让camrea是看向这个led 屏幕mesh的
这里的这个camera就相当于是,之前我们例子里提到的那个摄像机。而这个camera的位置就代表了default view point的位置。
这里你可能会有疑问,比如说相机的fov该设置为多少,因为毕竟fov的大小决定画面的大小。
在我最终的项目中,我有一个逻辑来根据每个mesh的四个顶点来计算其相机的fov,不过原则就是相机需要能够看到完整的mesh。
注意:为了能够在TouchDesigner中,使用相机look at功能,我们需要对LED的mesh进行处理。
默认的,工作流程制作出的用于unreal ndisplay的led mesh,每个mesh的中心点都是一样的,在坐标中心。这样当在TouchDesigner中,设置look at 到mesh的时候,其实是look at在(0,0,0)坐标。
我们需要讲每个mesh的中心设置为其本身的中心,blender中可以通过设置 Origin to geometry 来实现。
这样TouchDesigner中,当Look At一个mesh的时候,才是看向这个mesh的位置。
5 创建一个render,使用mesh的camera,渲染播放视频的3d mesh,这代表了前面我们例子中提到的,摄像机捕捉的真实世界的画面。
注意我这里render的分辨率设置成为了,每个led屏幕的分辨率2430x2160,其实这是在开发早期,我对于这个部分的理解误区,后来我经过测试,其实任何分辨率和比例都可以,但是要记住这个比例因为后面会用于投射到mesh的步骤。比如你可以用1:1的比例,渲染2k的分辨率,都可以。
其实严格来讲,应当更加精细的去计算这分辨率,因为此时相机拍摄到的画面,投射到led mesh上,肯定有损耗,因为可以看到周围黑色的部分,不在led mesh上的区域就被浪费掉了。我在最终项目中,通过计算每个相机的fov,来尽可能的找到一个小的fov来框住全部的mesh,所以损耗可以忽略。而且其次,播放视频和渲染Unreal场景不同,视频本身的分辨率就不大,那么落在每个led pannel上的部分就更加分辨率低了,所以其实也无所谓。
这里你只要记住,确保这个相机拍摄到的画面够用,就可以了。什么是够用?
1.画面范围包含led mesh
2.画面分辨率足够到你裁切和投射到led mesh所覆盖的区域
6 创建一个constant 材质,将这个render1的画面作为贴图,显示到led mesh上
这里就是我们之前提到的,直接将相机画面,以贴图的形式给到led mesh,肯定是不对的。
7 改变贴图的应用方式,变为从相机投射贴图
这里进入Wall_1_2的geometry节点,本身这里面只有一个 mesh节点,我们添加一个texture 和 geo节点,在texture节点里,选择texture type为 perspective from camera, 并且设置camera name 为 ../cam1,也就是刚才放置在Wall_1_2 fbx节点中的那个投射相机。
注意,此时texture节点中,还需要设置camera aspect要完全符合之前我们提到的那个投射相机画面比例,需要和那个一样。例如我的流程就是设置成了led屏幕的分辨率比例,前面投射相机的画面比例为2430x2160,所以这里我也设置成了9:8。但如果你那里用了1:1的2k分辨率,这里就保持为1:1比例。
同时记得设置geo1的材质为刚才我们创建的材质。
此时我们能看出来,这两种贴图应用的方式的区别了。从下图看出,上面的画面代表是,投射在mesh上的,正确的画面,而下面代表是相机原本拍摄到的画面。也就是我们从相机拍摄的画面中,只选择led mesh范围内的内容,然后展示在led mesh上。
可以这样理解,如下图,这时相机拍摄到的画面,而图中绿色代表的是led mesh的位置和范围,我们其实只需要绿色框框中的内容,将其投射在led mesh上。
为了验证我们的理论是对的,这时候,我无论如何改变相机的fov,虽然相机拍摄的画面会变,但是投射在mesh上的部分永远是一样的。当然你要确保fov的数值能够使得相机的视窗包含整个led mesh,否则led mesh范围内的画面都没拍到,投射的时候就没有这个信息了。
8 创建用于输出用于windows显示的画面
这时候,其实我们所需要的信息已经得到了,也就是上一步图中的上半部分的画面。我们需要得到那个画面以led mesh uv展开的一个结果,从而给windows显示。
这时,创建一个renderer,将其render mode设置为 UV Unwrap模式。
Geometry我使用的是,Wall_1_2中我们刚才自己创建的一个新的geo1节点,这个节点连接这我们创建的texture节点,也就是使用的从camera 投射的方式应用贴图。此时我们能看到,这个Wall_1_2_render画面得到的就是前面我们看到的,投在led mesh上的画面以UV展开的结果。
Camera我使用的是一个,随便的camera,这个camera无所谓的因为uv unwrap的渲染方式不需要相机,但是TouchDesigner的render节点必须要一个camera才能工作,所以起到占位的作用。
注意,这个render的uv unwrap coord 要选择 Texture Layer 1 (uv[3-5]),而不是默认的Texture Layer 0 。也就是说,要使用mesh里的第二个UV map进行unwrap的方式渲染。所以其实前面提到的LED mesh处理,除了设置中心到mesh本身,还有一步,是要复制一个uv map,也就是每个mesh需要有两个相同的uv。 这里我的理解是,在TouchDesigner中,每个mesh的第一个uv用于前面我们提到的,从相机投射贴图,可能已经被占用了或者被改写了。所以需要用第二个uv来进行uv unwrap的渲染。
在Blender中,只要复制一份同样的uv map就行,确保每个mesh有两个uv就好。
9 测试搭建完成,验证结果
此时,我们将得到两个画面:
其实,仔细看可以看到我们前面讨论的,有关于画面反扭曲,和扭曲的过程:
比如,对于Wall_3_4这个led mesh来说,视频播放的那个mesh是个矩形,是没有弧度的,而通过我们刚才那么一顿操作,这个视频播放的mesh的边缘,竟然变弯曲了。这是因为每个节点的led屏幕,是自带弧度的,向内凹进去的。
所以可以先想象一下,如果把这个画面,显示在一个,向内凹进去的led 屏幕上,那么看过去,这个视频的上边就被扭曲回平的了。这也间接证明了,我们这套方法的正确性。
接下来,我搭建了一个测试用的,用于预览的节点组。其实就是把得到的画面贴图,应用在led mesh上,然后渲染出来。可以看到画面完美拼接起来了。并且我大概讲预览相机放置到和投射相机相同的位置,也可以看到,刚才我们提到的那个视频上边缘的弧度,会在最终大屏幕上变平的现象。
结论
至此,我们得到了一个简单的,具有Ndisplay那样的投射方式的,TouchDesigner视频播放系统。
其核心就是,有关投射和uv unwrap渲染的部分。
而这部分为什么这么重要呢?其实也就是和为什么Ndisplay显示重要的答案一样。
我们能够在LED屏幕上,得到,从相机视角的正确透视的画面。通常在温哥华,拍摄车戏的时候,我们会讲default view point,设置到当前摄影机的位置(使用stype追踪),从而得到正确的透视。
我们能够跨越墙屏和顶屏的显示一个整体的画面,这和Unreal的ndisplay逻辑是一样的。
如果理解了这部分的逻辑,那么其他在我完整的项目中的功能,相比而言,更加直观和易于理解。例如,lightCard 功能,调色功能,视频文件索引功能,视频同步播放功能,控制参数保存管理功能等等。