CG在这里,是不是全部我不知道爱是什么,偶然搜到的

&img src=&/50/v2-714f34f665d6e7dd808a4335feb5c492_b.png& data-rawwidth=&1024& data-rawheight=&1024& class=&origin_image zh-lightbox-thumb& width=&1024& data-original=&/50/v2-714f34f665d6e7dd808a4335feb5c492_r.png&&&p&之前写过用 C 语言画&a href=&/question//answer/& class=&internal&&心形&/a&、&a href=&/question//answer/& class=&internal&&圣诞树&/a&、&a href=&/p/& class=&internal&&雪花&/a&、&a href=&/p/& class=&internal&&直线&/a&,这次我们试玩光学。&/p&&p&在这系列文章中,我们会在二维的画布上「绘画」一些基于物理的光。但作为趣味性的编程文章,我尽量用直觉、简单的方式去生成这些图像,仅介绍一些概念,和一般的正式计算机图形学内容不同。&/p&&p&本系列的源代码位于 &a href=&/?target=https%3A///miloyip/light2d& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&miloyip/light2d&i class=&icon-external&&&/i&&/a&。&/p&&h2&1. 光&/h2&&p&假设我们在一个二维的世界,这里有些会发光的二维形状,并暂时只考虑单色光。我们想知道的是,在这个空间中,每一点从 360 度各方向共有多少光经过。换成数学方式表示,我们想对每个二维坐标 &img src=&/equation?tex=%28x%2C+y%29& alt=&(x, y)& eeimg=&1&& 求积分:&/p&&p&&img src=&/equation?tex=F%28x%2C+y%29+%3D+%5Cint_0%5E%7B2%5Cpi%7D+L%28x%2C+y%2C+%5Ctheta%29%5Cmathrm%7Bd%7D%5Ctheta& alt=&F(x, y) = \int_0^{2\pi} L(x, y, \theta)\mathrm{d}\theta& eeimg=&1&&&/p&&p&当中 &img src=&/equation?tex=L%28x%2C+y%2C+%5Ctheta%29& alt=&L(x, y, \theta)& eeimg=&1&&
代表在二维坐标 &img src=&/equation?tex=%28x%2C+y%29& alt=&(x, y)& eeimg=&1&&
在 &img src=&/equation?tex=%5Ctheta& alt=&\theta& eeimg=&1&& 方向有多少光经过。&/p&&p&我们想采样这个 &img src=&/equation?tex=F%28x%2C+y%29& alt=&F(x, y)& eeimg=&1&& 存储在图像中,并利用 &a href=&/p/& class=&internal&&svpng()&/a& 输出结果,那么我们的 &img src=&/equation?tex=%5Ctexttt%7Bmain%28%29%7D& alt=&\texttt{main()}& eeimg=&1&& 函数简单为:&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&cp&&#include&/span& &span class=&cpf&&&svpng.inc&&/span&&span class=&cp&&&/span&
&span class=&cp&&#include&/span& &span class=&cpf&&&math.h&&/span&&span class=&cp&&&/span&
&span class=&cp&&#define W 512&/span&
&span class=&cp&&#define H 512&/span&
&span class=&kt&&unsigned&/span& &span class=&kt&&char&/span& &span class=&n&&img&/span&&span class=&p&&[&/span&&span class=&n&&W&/span& &span class=&o&&*&/span& &span class=&n&&H&/span& &span class=&o&&*&/span& &span class=&mi&&3&/span&&span class=&p&&];&/span&
&span class=&c1&&// ...&/span&
&span class=&kt&&int&/span& &span class=&nf&&main&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kt&&unsigned&/span& &span class=&kt&&char&/span&&span class=&o&&*&/span& &span class=&n&&p&/span& &span class=&o&&=&/span& &span class=&n&&img&/span&&span class=&p&&;&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&y&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&n&&y&/span& &span class=&o&&&&/span& &span class=&n&&H&/span&&span class=&p&&;&/span& &span class=&n&&y&/span&&span class=&o&&++&/span&&span class=&p&&)&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&x&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&n&&x&/span& &span class=&o&&&&/span& &span class=&n&&W&/span&&span class=&p&&;&/span& &span class=&n&&x&/span&&span class=&o&&++&/span&&span class=&p&&,&/span& &span class=&n&&p&/span& &span class=&o&&+=&/span& &span class=&mi&&3&/span&&span class=&p&&)&/span&
&span class=&n&&p&/span&&span class=&p&&[&/span&&span class=&mi&&0&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&p&/span&&span class=&p&&[&/span&&span class=&mi&&1&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&p&/span&&span class=&p&&[&/span&&span class=&mi&&2&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&p&&(&/span&&span class=&kt&&int&/span&&span class=&p&&)(&/span&&span class=&n&&fminf&/span&&span class=&p&&(&/span&&span class=&n&&sample&/span&&span class=&p&&(&/span&
&span class=&p&&(&/span&&span class=&kt&&float&/span&&span class=&p&&)&/span&&span class=&n&&x&/span& &span class=&o&&/&/span& &span class=&n&&W&/span&&span class=&p&&,&/span& &span class=&p&&(&/span&&span class=&kt&&float&/span&&span class=&p&&)&/span&&span class=&n&&y&/span& &span class=&o&&/&/span& &span class=&n&&H&/span&&span class=&p&&)&/span& &span class=&o&&*&/span& &span class=&mf&&255.0f&/span&&span class=&p&&,&/span& &span class=&mf&&255.0f&/span&&span class=&p&&));&/span&
&span class=&n&&svpng&/span&&span class=&p&&(&/span&&span class=&n&&fopen&/span&&span class=&p&&(&/span&&span class=&s&&&basic.png&&/span&&span class=&p&&,&/span& &span class=&s&&&wb&&/span&&span class=&p&&),&/span& &span class=&n&&W&/span&&span class=&p&&,&/span& &span class=&n&&H&/span&&span class=&p&&,&/span& &span class=&n&&img&/span&&span class=&p&&,&/span& &span class=&mi&&0&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&无论图像长宽是多少,这个二维空间的坐标范围都是 &img src=&/equation?tex=%28x%2C+y%29+%5Cin+%5B0%2C+1%5D+%5Ctimes+%5B0%2C+1%5D& alt=&(x, y) \in [0, 1] \times [0, 1]& eeimg=&1&& 。我们把结果映射至 &img src=&/equation?tex=%5C%7B0%2C+1%2C+%5Cldots%2C+255%5C%7D& alt=&\{0, 1, \ldots, 255\}& eeimg=&1&& 。&/p&&h2&2. 蒙地卡罗积分&/h2&&p&由于无法以解析式求解这个积分,我们使用蒙地卡罗积分法(Monte Carlo integration)。&/p&&p&在这个问题中,我们随机采样 &img src=&/equation?tex=N& alt=&N& eeimg=&1&& 个方向 &img src=&/equation?tex=%7B%5Ctheta_1%2C+%5Ctheta_2%2C+%5Cldots%2C+%5Ctheta_N%7D& alt=&{\theta_1, \theta_2, \ldots, \theta_N}& eeimg=&1&& ,然后计算 &img src=&/equation?tex=L%28x%2C+y%2C+%5Ctheta_i%29& alt=&L(x, y, \theta_i)& eeimg=&1&& 的平均值:&/p&&p&&img src=&/equation?tex=F%28x%2C+y%29+%5Capprox+%5Cfrac%7B2%5Cpi%7D%7BN%7D%5Csum_%7Bi%3D1%7D%5EN+L%28x%2C+y%2C+%5Ctheta_i%29& alt=&F(x, y) \approx \frac{2\pi}{N}\sum_{i=1}^N L(x, y, \theta_i)& eeimg=&1&&&/p&&p&代码实现也很浅白(设每像素向 64 个随机方向均匀采样/uniform sampling):&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&cp&&#define TWO_PI 6.f&/span&
&span class=&cp&&#define N 64&/span&
&span class=&kt&&float&/span& &span class=&nf&&sample&/span&&span class=&p&&(&/span&&span class=&kt&&float&/span& &span class=&n&&x&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&y&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kt&&float&/span& &span class=&n&&sum&/span& &span class=&o&&=&/span& &span class=&mf&&0.0f&/span&&span class=&p&&;&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&n&&i&/span& &span class=&o&&&&/span& &span class=&n&&N&/span&&span class=&p&&;&/span& &span class=&n&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kt&&float&/span& &span class=&n&&a&/span& &span class=&o&&=&/span& &span class=&n&&TWO_PI&/span& &span class=&o&&*&/span& &span class=&n&&rand&/span&&span class=&p&&()&/span& &span class=&o&&/&/span& &span class=&n&&RAND_MAX&/span&&span class=&p&&;&/span&
&span class=&n&&sum&/span& &span class=&o&&+=&/span& &span class=&n&&trace&/span&&span class=&p&&(&/span&&span class=&n&&x&/span&&span class=&p&&,&/span& &span class=&n&&y&/span&&span class=&p&&,&/span& &span class=&n&&cosf&/span&&span class=&p&&(&/span&&span class=&n&&a&/span&&span class=&p&&),&/span& &span class=&n&&sinf&/span&&span class=&p&&(&/span&&span class=&n&&a&/span&&span class=&p&&));&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&n&&sum&/span& &span class=&o&&/&/span& &span class=&n&&N&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&当中 &img src=&/equation?tex=%5Ctexttt%7Btrace%28ox%2C+oy%2C+dx%2C+dy%29%7D& alt=&\texttt{trace(ox, oy, dx, dy)}& eeimg=&1&& 函数代表从 &img src=&/equation?tex=%5Cmathbf%7Bo%7D& alt=&\mathbf{o}& eeimg=&1&& 位置从单位矢量 &img src=&/equation?tex=%5Chat%7B%5Cmathbf%7Bd%7D%7D& alt=&\hat{\mathbf{d}}& eeimg=&1&& 方向接收到的光。&/p&&p&(更新:本文不考虑实际单位,所以实现时把系数 &img src=&/equation?tex=2%5Cpi& alt=&2\pi& eeimg=&1&& 去掉了。感谢 &a class=&member_mention& href=&/people/8f986a85ee4d74237f65aedd10ba389d& data-hash=&8f986a85ee4d74237f65aedd10ba389d& data-hovercard=&p$b$8f986a85ee4d74237f65aedd10ba389d&&@Bimos&/a& 提醒)&/p&&h2&3. 光线步进&/h2&&p&通常,我们可以用光线追踪(ray tracing)方法,求出光线 &img src=&/equation?tex=%5Cmathbf%7Br%7D%28t%29+%3D+%5Cmathbf%7Bo%7D+%2B+%5Chat%7B%5Cmathbf%7Bd%7D%7Dt& alt=&\mathbf{r}(t) = \mathbf{o} + \hat{\mathbf{d}}t& eeimg=&1&& 与场景的最近点。&/p&&p&然而,我们需要为每种几何形状编写与光线的相交函数(通常比较复杂),之后做一些效果可能还要提供相交点的法线(normal vector)。&/p&&p&为简单起见,本文采用光线步进(ray marching)方法(又称为球体追踪/sphere tracing [1]),场景只需要以带符号距离场(signed distance field, SDF) &img src=&/equation?tex=%5Cphi+%3A+%5Cmathbb%7BR%7D%5E2+%5Crightarrow+%5Cmathbb%7BR%7D& alt=&\phi : \mathbb{R}^2 \rightarrow \mathbb{R}& eeimg=&1&&
表示&/p&&ol&&li&当 &img src=&/equation?tex=%5Cphi%28%5Cmathbf%7Bx%7D%29+%3E+0& alt=&\phi(\mathbf{x}) & 0& eeimg=&1&& ,表示坐标 &img src=&/equation?tex=%5Cmathbf%7Bx%7D& alt=&\mathbf{x}& eeimg=&1&& 位于场景形状之外,且 &img src=&/equation?tex=%5Cmathbf%7Bx%7D& alt=&\mathbf{x}& eeimg=&1&& 与最近形状边界的距离为 &img src=&/equation?tex=%5Cphi%28%5Cmathbf%7Bx%7D%29& alt=&\phi(\mathbf{x})& eeimg=&1&& ;&/li&&li&当 &img src=&/equation?tex=%5Cphi%28%5Cmathbf%7Bx%7D%29+%3C+0& alt=&\phi(\mathbf{x}) & 0& eeimg=&1&& ,表示坐标 &img src=&/equation?tex=%5Cmathbf%7Bx%7D& alt=&\mathbf{x}& eeimg=&1&& 位于场景形状之内,且 &img src=&/equation?tex=%5Cmathbf%7Bx%7D& alt=&\mathbf{x}& eeimg=&1&& 与最近形状边界的距离为 &img src=&/equation?tex=-%5Cphi%28%5Cmathbf%7Bx%7D%29& alt=&-\phi(\mathbf{x})& eeimg=&1&& ;&/li&&li&当 &img src=&/equation?tex=%5Cphi%28%5Cmathbf%7Bx%7D%29%3D+0& alt=&\phi(\mathbf{x})= 0& eeimg=&1&& ,说明 &img src=&/equation?tex=%5Cmathbf%7Bx%7D& alt=&\mathbf{x}& eeimg=&1&& 刚好在形状边界上。&/li&&/ol&&p&例如,圆心为 &img src=&/equation?tex=%5Cmathbf%7Bc%7D& alt=&\mathbf{c}& eeimg=&1&& 、半径为 &img src=&/equation?tex=r& alt=&r& eeimg=&1&& 的圆形 SDF 定义为(更精确的说法是圆盘/disk):&/p&&p&&img src=&/equation?tex=%5Cphi_%5Ctext%7Bcircle%7D%28%5Cmathbf%7Bx%7D%29+%3D+%5Cleft+%5C%7C+%5Cmathbf%7Bx%7D+-+%5Cmathbf%7Bc%7D+%5Cright+%5C%7C-r& alt=&\phi_\text{circle}(\mathbf{x}) = \left \| \mathbf{x} - \mathbf{c} \right \|-r& eeimg=&1&&&/p&&p&用代码表示:&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&kt&&float&/span& &span class=&nf&&circleSDF&/span&&span class=&p&&(&/span&&span class=&kt&&float&/span& &span class=&n&&x&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&y&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&cx&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&cy&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&r&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kt&&float&/span& &span class=&n&&ux&/span& &span class=&o&&=&/span& &span class=&n&&x&/span& &span class=&o&&-&/span& &span class=&n&&cx&/span&&span class=&p&&,&/span& &span class=&n&&uy&/span& &span class=&o&&=&/span& &span class=&n&&y&/span& &span class=&o&&-&/span& &span class=&n&&cy&/span&&span class=&p&&;&/span&
&span class=&k&&return&/span& &span class=&n&&sqrtf&/span&&span class=&p&&(&/span&&span class=&n&&ux&/span& &span class=&o&&*&/span& &span class=&n&&ux&/span& &span class=&o&&+&/span& &span class=&n&&uy&/span& &span class=&o&&*&/span& &span class=&n&&uy&/span&&span class=&p&&)&/span& &span class=&o&&-&/span& &span class=&n&&r&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&而所谓光线步进,就是我们从光线起始点 &img src=&/equation?tex=t+%3D+0& alt=&t = 0& eeimg=&1&& ,逐步增加 &img src=&/equation?tex=t& alt=&t& eeimg=&1&& ,当 &img src=&/equation?tex=%5Cphi%28%5Cmathbf%7Br%7D%28t%29%29+%5Cle+0& alt=&\phi(\mathbf{r}(t)) \le 0& eeimg=&1&& 代表我们到达到场景中某个形状的表面或内部。那么我们每次可以步进多远?由于 &img src=&/equation?tex=%5Cphi%28%5Cmathbf%7Bx%7D%29%3E0& alt=&\phi(\mathbf{x})&0& eeimg=&1&& 时,代表 &img src=&/equation?tex=%5Cmathbf%7Bx%7D& alt=&\mathbf{x}& eeimg=&1&& 距最近形状的距离,所以我们至少可以步进该距离而不会碰到任何形状!&/p&&figure&&img src=&/v2-17fd23c7b0af869f90bc2d374e56aa1e_b.jpg& data-rawwidth=&400& data-rawheight=&244& class=&content_image& width=&400&&&figcaption&图源 /gpugems/GPUGems2/gpugems2_chapter08.html&/figcaption&&/figure&&p&设场景只有一个发光的圆形,圆心为 &img src=&/equation?tex=%280.5%2C+0.5%29& alt=&(0.5, 0.5)& eeimg=&1&& ,半径为 &img src=&/equation?tex=0.1& alt=&0.1& eeimg=&1&& ,它每点都向各方向发射 &img src=&/equation?tex=2& alt=&2& eeimg=&1&& 个单位的光。那么光线步进可实现为:&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&cp&&#define MAX_STEP 10&/span&
&span class=&cp&&#define MAX_DISTANCE 2.0f&/span&
&span class=&cp&&#define EPSILON 1e-6f&/span&
&span class=&kt&&float&/span& &span class=&nf&&trace&/span&&span class=&p&&(&/span&&span class=&kt&&float&/span& &span class=&n&&ox&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&oy&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&dx&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&dy&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kt&&float&/span& &span class=&n&&t&/span& &span class=&o&&=&/span& &span class=&mf&&0.0f&/span&&span class=&p&&;&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&n&&i&/span& &span class=&o&&&&/span& &span class=&n&&MAX_STEP&/span& &span class=&o&&&&&/span& &span class=&n&&t&/span& &span class=&o&&&&/span& &span class=&n&&MAX_DISTANCE&/span&&span class=&p&&;&/span& &span class=&n&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kt&&float&/span& &span class=&n&&sd&/span& &span class=&o&&=&/span& &span class=&n&&circleSDF&/span&&span class=&p&&(&/span&&span class=&n&&ox&/span& &span class=&o&&+&/span& &span class=&n&&dx&/span& &span class=&o&&*&/span& &span class=&n&&t&/span&&span class=&p&&,&/span& &span class=&n&&oy&/span& &span class=&o&&+&/span& &span class=&n&&dy&/span& &span class=&o&&*&/span& &span class=&n&&t&/span&&span class=&p&&,&/span& &span class=&mf&&0.5f&/span&&span class=&p&&,&/span& &span class=&mf&&0.5f&/span&&span class=&p&&,&/span& &span class=&mf&&0.1f&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&sd&/span& &span class=&o&&&&/span& &span class=&n&&EPSILON&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&mf&&2.0f&/span&&span class=&p&&;&/span&
&span class=&n&&t&/span& &span class=&o&&+=&/span& &span class=&n&&sd&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&mf&&0.0f&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&如果光线超过指定距离,或是步数太多,都终止步进。注意我们只能尽量接近表面,所以用 &img src=&/equation?tex=%5Cepsilon+%3D+10%5E%7B-6%7D& alt=&\epsilon = 10^{-6}& eeimg=&1&& 表示足够近的阈值。整个程序完成,运算结果如下:&/p&&img src=&/v2-0a31e467fd69a2f08e0fcea6a1979ff2_b.jpg& data-caption=&& data-rawwidth=&512& data-rawheight=&512& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&/v2-0a31e467fd69a2f08e0fcea6a1979ff2_r.jpg&&&p&可以看到图像中有很多噪点,这是由于蒙地卡罗积分法具随机性,计算出来的估值与精确数值会有误差。增加采样数目 &img src=&/equation?tex=N& alt=&N& eeimg=&1&& ,就能令结果更精确(准确地说是减少方差/variance):&/p&&img src=&/v2-8d8ae3f9ac26ba_b.jpg& data-caption=&& data-rawwidth=&528& data-rawheight=&150& class=&origin_image zh-lightbox-thumb& width=&528& data-original=&/v2-8d8ae3f9ac26ba_r.jpg&&&p&然而,随着 &img src=&/equation?tex=N& alt=&N& eeimg=&1&& 上升,运算时间也线性上升。有没有方法可以改善?&/p&&h2&4. 分层、抖动采样&/h2&&p&形成噪点的原因在于,每个像素所得到的随机方向都很不一样。那么,如果我们不用随机方向,而是平分 360 度向 &img src=&/equation?tex=N& alt=&N& eeimg=&1&& 个方向采样,效果会如何?这种方式称为分层采样(stratified sampling):&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&kt&&float&/span& &span class=&nf&&sample&/span&&span class=&p&&(&/span&&span class=&kt&&float&/span& &span class=&n&&x&/span&&span class=&p&&,&/span& &span class=&kt&&float&/span& &span class=&n&&y&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kt&&float&/span& &span class=&n&&sum&/span& &span class=&o&&=&/span& &span class=&mf&&0.0f&/span&&span class=&p&&;&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&n&&i&/span& &span class=&o&&&&/span& &span class=&n&&N&/span&&span class=&p&&;&/span& &span class=&n&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// float a = TWO_PI * rand() / RAND_MAX; // 均匀采样&/span&
&span class=&kt&&float&/span& &span class=&n&&a&/span& &span class=&o&&=&/span& &span class=&n&&TWO_PI&/span& &span class=&o&&*&/span& &span class=&n&&i&/span& &span class=&o&&/&/span& &span class=&n&&N&/span&&span class=&p&&;&/span&
&span class=&c1&&// 分层采样&/span&
&span class=&c1&&// ...&/span&
&/code&&/pre&&/div&&p&改变这一行代码的结果是:&/p&&img src=&/v2-44cbaf9f10a_b.jpg& data-caption=&& data-rawwidth=&512& data-rawheight=&512& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&/v2-44cbaf9f10a_r.jpg&&&p&很好,没有噪点,但也太过规律了!&/p&&p&我们可以结合上面两种采样方式,就是先分为 &img src=&/equation?tex=N& alt=&N& eeimg=&1&& 个角度区域,再从区域中均匀采样,这称为抖动采样(jittered sampling)。&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&c1&&// float a = TWO_PI * rand() / RAND_MAX;
// 均匀采样&/span&
&span class=&c1&&// float a = TWO_PI * i / N;
// 分层采样&/span&
&span class=&kt&&float&/span& &span class=&n&&a&/span& &span class=&o&&=&/span& &span class=&n&&TWO_PI&/span& &span class=&o&&*&/span& &span class=&p&&(&/span&&span class=&n&&i&/span& &span class=&o&&+&/span& &span class=&p&&(&/span&&span class=&kt&&float&/span&&span class=&p&&)&/span&&span class=&n&&rand&/span&&span class=&p&&()&/span& &span class=&o&&/&/span& &span class=&n&&RAND_MAX&/span&&span class=&p&&)&/span& &span class=&o&&/&/span& &span class=&n&&N&/span&&span class=&p&&;&/span& &span class=&c1&&// 抖动采样&/span&
&/code&&/pre&&/div&&p&改变这一行代码的结果是:&/p&&img src=&/v2-6d1dba9ddb17d_b.jpg& data-caption=&& data-rawwidth=&512& data-rawheight=&512& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&/v2-6d1dba9ddb17d_r.jpg&&&p&同样使用每像素 &img src=&/equation?tex=N%3D64& alt=&N=64& eeimg=&1&& 次采样,仅仅改变采样方式(一行代码),效果就好很多:&/p&&img src=&/v2-f4e84db7dde6bdf383f4d47_b.jpg& data-caption=&& data-rawwidth=&396& data-rawheight=&150& class=&content_image& width=&396&&&h2&5. 结语&/h2&&p&这个完整程序位于 &a href=&/?target=https%3A///miloyip/light2d/blob/master/basic.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&basic.c&i class=&icon-external&&&/i&&/a&。如果不含加载头文件及常数定义,仅有 30 行代码。你可以改一下圆形位置、大小、光度,测试时可用较小的 W、H 和 N 加速运行过程。&/p&&p&本文简单介绍了蒙地卡罗积分、光线步进、分层采样等概念。下一篇《&a href=&/p/& class=&internal&&构造实体几何&/a&》会讲如何在场景中加入更多形状,将会显示出阴影。而之后也会尝试实现一些光学法则,生成更有趣的图形。&/p&&h2&参考&/h2&&p&[1] Hart, John C. &Sphere tracing: A geometric method for the antialiased ray tracing of implicit surfaces.& &i&The Visual Computer&/i&12.10 (1996): 527-545.&/p&
之前写过用 C 语言画、、、,这次我们试玩光学。在这系列文章中,我们会在二维的画布上「绘画」一些基于物理的光。但作为趣味性的编程文章,我尽量用直觉、简单的方式去生成这些图像,仅介绍一些概念,和一般的正式计算机图形学内容不同…
&img src=&/50/v2-047dfacf225e2e0cd5edef44bb5c687c_b.jpg& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&/50/v2-047dfacf225e2e0cd5edef44bb5c687c_r.jpg&&&p&使用虚幻4制作动画片这是我在2007年第一次接触虚幻3的时候完全没有想到的,而在现在的2017年已经有使用虚幻4制作完成的商业动画播出已经说明这件事在可行性上没有问题的了。&/p&&p&做一下简单介绍,薛红杰,04年进入游戏行业,至今有12年的游戏制作经验,1年动画制作经验。入行之初是一名角色原画师,从事原画工作3年后对3d发生兴趣,在06年学习资源极度缺乏的时候自学了zbrush,3ds max,softimage。07年的时候因为机缘巧合投简历到了当时在上海的epic games china studio。在epic games china&br&studio工作中第一次接触到了虚幻3,从此之后我的职业生涯基本都和这个引擎有关系。09年我在久游参与了《流星蝴蝶剑ol》的制作,也是虚幻3平台。13年到16年因为属于创业经历所以这段从事的工作和虚幻4没有关系,但是多年养成的学习习惯还是会关注虚幻4的变化。16年到17年这一年我以技术美术的身份在一家使用虚幻4制作动画的公司工作,后来转为美术总监,几乎是全流程的参与了动画制作(由于资金链出问题了,现在本人已经离开这家动画公司)。我就这次项目经历来说一下我在使用虚幻4制作动画上遇到了哪些问题和看法,以及我对动画制作和游戏制作不同的看法。&/p&&p&传统的3D动画制作流程是以maya为制作平台搭配其他dcc和渲染器辅助的流程,这个流程被大多数动画公司采用(皮克斯是个例外他们有自己的动画制作平台USD和渲染器Renderman)。这个流程简要来说是模型,绑定,动画,材质,灯光,渲染这样一个顺序流程,以Maya为主,其他DCC辅助,是一个线性非破坏流程。这个流程下的好处是工艺成熟,可控性高,人才储备丰富,在现有人才结构下运转良好,团队磨合好战斗力很强。缺点就是技术决定了团队环节多,规模大,渲染后的修改需要之前流程重复运转一次,修改成本大,团队的磨合成本高。&/p&&p&图示&/p&&p&典型的流水线&/p&&img src=&/50/v2-14eaabed081d39e74c5ecaa85eee79be_b.jpg& data-caption=&& data-rawwidth=&554& data-rawheight=&318& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/50/v2-14eaabed081d39e74c5ecaa85eee79be_r.jpg&&&p&传统动画流程&/p&&img src=&/50/v2-9bb1edb6c9fe07_b.jpg& data-caption=&& data-rawwidth=&554& data-rawheight=&318& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/50/v2-9bb1edb6c9fe07_r.jpg&&&p&虚幻4作为一个新的制作平台相应成规范的流程和人才储备还不够完善。虚幻4在整个流程里和Maya流程还是有很大区别,首先在DCC里创建完资产(模型,纹理,绑定,动画,模拟等),导入到虚幻4里,在虚幻4里然后搭建场景,编写材质,灯光,建立sequence,编辑素材然后输出这样一个流程。相比Maya的线性流程,虚幻流程是一个非线性流程,虚幻4这个流程中每个节点都可以在制作途中修改,可编辑性非常高,对其他环节影响小,最显见的就是灯光可以随时修改,随时输出。这样的环节对于创作人员来说可以说是释放了巨大的潜力,传统流程中创作团队几乎要到后期才能看到成品的模样,而虚幻4的流程完全没有这些限制,官方给出的定义是以导演可以纵观全局(原文是:Director-centric&br&during entire process)&/p&&p&不过虚幻的缺点也有,上手难度高,推广到团队里成本会不小,目前来说想要平滑的把虚幻4引入到Maya流水线还是个有难度的事情。人才储备方面,使用虚幻的人大多都在游戏行业,游戏行业里流程的概念并不强,没有动画行业那么严谨和多环节,过渡到动画行业有一定成本,已经非常成熟的动画团队在技术上接受虚幻的问题技术上有一部分,不过主要还是在制作平台的切换要丢弃之前很多积累,对新工具的学习也是一个可见成本。这点上来说,没有Maya流水线积累的新团队直接使用虚幻4会更加灵活,毕竟船小好掉头。可编辑性的提高同时也意味着管理复杂度的提高,这要求团队要有很强的管理水平。&/p&&p&非线性的工作流程&/p&&img src=&/50/v2-6ebb587d4f4b5e5dd172a0_b.jpg& data-caption=&& data-rawwidth=&554& data-rawheight=&318& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/50/v2-6ebb587d4f4b5e5dd172a0_r.jpg&&&p&虚幻4制作动画的流程&/p&&img src=&/50/v2-ca23c5f15003a_b.jpg& data-caption=&& data-rawwidth=&554& data-rawheight=&318& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/50/v2-ca23c5f15003a_r.jpg&&&p&以上只作为一个基本论调,毕竟虚幻4制作动画的技术爆炸阶段还没有到来,好处和坏处并存是正常状态。&/p&&p&我作为一个从游戏行业使用虚幻4制作动画在很大程度上依赖于之前多年的虚幻引擎使用经验,算是平滑过渡的,但是这对于团队而言是很难的,多数人还是会在原有技术平台下保持一个惯性思维,接受新工具总是会经历阵痛期。&/p&&p&下面我会逐个展开说明虚幻流程做动画的细节问题。&/p&&p&&b&流程之初是在最前面的DCC环节&/b&。这里模型资产的创建选择maya,max,Houdini,blender都没有什么对错,主要看团队更擅长哪个,选择一个作为通用生产工具即可(本人就喜欢softimage这样一款已经停止开发的软件)。&/p&&p&纹理生产的选择使用SD 加SP,Quixel或mari都可以,还是看团队对哪款工具更擅长(本人喜欢mari,因为其强大的映射和遮罩绘制)。&/p&&p&Rigging环节目前我只接触过maya,师从Judd Simantov(神秘海域系列首席角色技术总监)。&/p&&p&Judd Simantov的流派是使用joint绑定加blend shape,这样做也是因为在此之前alembic没有被大多数引擎支持,不过这点随着虚幻4和多数引擎开始支持alembic格式,纯粹的joint绑定被扔进历史垃圾桶已成定论,动画级绑定从此不再受限,alembic可以完美还原动画级绑定的丰富变化。&/p&&p&动画资产的生产在动画片制作里绝对是一个耗费相当大人力和时间的环节。目前来说,采用全身动补来说是可以比较好的加速动画资产创建技术手段。动补有两个方案,一个是不太成熟但是廉价的惯性动补(以诺亦腾为代表),一个是成熟昂贵的光学动补(以vicon为代表),这点上选用哪套设备还是要看财力。我最期望的动补还是能实现全身加表情加手关节动补,这样,即便是初期动补质量不高,也可以非常快速的实现perviz,加速制作进度。这里的选择其实每家公司都不太一样,制作了神秘海域4的顽皮狗就是选择身体动补,面部和手指动画由动画师添加这样的老旧方案,制作出来的效果绝对是世界级的,这点还是看团队的技术储备,老方案只要成熟稳定,一样可以全火力输出。&/p&&p&自然类资产也是多种多样。山体可以选择world machine,blender,houdini,都能做出不错的资产。自然植被有speedtree可选择,功能也十分强大。&/p&&p&近景自然资产我最近接触到了摄影测量技术,这是一个十分强大的技术,可以快速的产出大量的石头,山体,植被。&/p&&p&&b&下一个环节是把资产导入到虚幻引擎里&/b&。最近虚幻开发了一款批量导入的工具可以解决大量物体导入的繁重劳动。导入到虚幻引擎后第一道工序一般是制作材质,而材质上确实是虚幻引擎的一大强项和难点,这点逐渐展开说。材质编辑器在虚幻3时代是个用起来入门门槛很高,通用性很差的环节,易用性上只有material&br&instance这样灵活性不高的功能。但是随着虚幻4引入了material&br&function这项功能,从此把材质使用带入了一个新的时代。material function简单来说就是把之前每个材质球都要做一遍的功能包装城了单独功能的节点,使用的时候直接调用,不要再每个材质都做一遍,这点上确实解放了很大的生产力。伴随material function而生的还有一个东西叫material layer,目前来看material layer还是作为material function的一种存在的,material layer是把一种效果包装成一个material function,再把各种影响这个效果的节点参数暴露出来。了解这个东西的团队把他作为一个内部规范约定,成为一个基础的材质库,使用上高效,调用方便灵活,修改简单。我在unreal roadmap上看到了后面官方会把material layer作为一项功能单独推出来,这点上我是很兴奋的,因为这个功能推出会把材质工艺更加标准化,材质效果这一块会成为一个模块化的存在,大大推动生产力(以后卖material layer应该也是门生意)。估计今后material layer这个概念被大家接受后material layer库的建立也会成为项目中非常重要的一环。&/p&&p&如何使用material layer在最近的Unreal Dev Day Montreal 2017 视频里有提到,感兴趣的可以看看。&/p&&br&&br&&br&&br&&br&&br&&a class=&video-box& href=&/?target=https%3A///video/av/index_3.html%3Ft%3D2866& target=&_blank& data-video-id=&594112& data-video-playable=&true& data-name=&Master Class(3)_演讲o公开课_科技_bilibili_哔哩哔哩& data-poster=&/80/v2-d5ffeafc564ae36_b.jpg& data-lens-id=&&&
&img class=&thumbnail& src=&/80/v2-d5ffeafc564ae36_b.jpg&&&span class=&content&&
&span class=&title&&Master Class(3)_演讲o公开课_科技_bilibili_哔哩哔哩&span class=&z-ico-extern-gray&&&/span&&span class=&z-ico-extern-blue&&&/span&&/span&
&span class=&url&&&span class=&z-ico-video&&&/span&/video/av/index_3.html?t=2866&/span&
&br&&br&&br&&br&&br&&br&&p&资产再完成了材质后下一步会放到场景里搭建,这一步再游戏开发里是由专门的level artist完成的,在动画制作上的有一定区别,主要体现在动画受镜头限制,镜头之外的没有必要制作,场景只需要保证镜头之内的效果。这里有一点制作细节上的东西,就是场景搭建要使用sublevel来制作,把不同的部分放到独立的sublevel里是一个非常重要的制作规范,这在后面制作上会规避掉很多危险操作。&/p&&p&特效上目前虚幻4的特效还真的是不怎么好,特效编辑器还是很老旧的精灵动画作为原始资产,通过修改属性制作各种效果,这点上非常考验特效师的能力。我比较期待的是目前尚在开发中的Niagara粒子系统,Niagara在roadmap上的介绍是一款基于节点式的特效编辑器,这一点很像houdini,估计这个系统出来之后现行的特效制作理念会被颠覆,特效师们会迎来一个选择和挑战。&/p&&p&模拟上虚幻4本身的布料模拟也形成了自己的工具链,不依赖外部工具,完全在引擎内部实现,制作中可以随时调节。当然,使用alembic导入外部更好效果的模拟也是一种方法,但是这真的不是虚幻4本身的长处,它的长处还是在于编辑一次,省去逐帧解算的成本。&/p&&p&灯光环节需要说明一点的是虚幻4的灯光效果很大程度上依赖烘焙的效果,这在资产制作上要求场景模型必须要有2UV用以生成lightmap,烘焙之后,光线追踪的信息会存储到lightmap上,保证引擎运行时候的效率和效果的平衡。灯光环节也要注意灯光应该存储到独立的sublevel里,这样切换不同光照方案的时候会很方便。灯光上有非常多的细节要注意,例如要塑造体积,前景和后景的对比,冷暖对比,色彩饱和度等等,所以一名出色的灯光师最好是学摄影出身的,这样学校里教的理论知识在工作上都能用得上。灯光的使用上有很多虚幻4区别于maya的技巧,毕竟实时渲染和离线渲染在实现方式是非常不同。一个好的灯光师会把之前的素材升华,提升几个档次,所以一个优秀的灯光师在动画团队里非常重要,。&/p&&p&虚幻4推出的sequence是款野心十足的工具,它基本就是AE和premiere的结合体,在sequence里可以完成非常多的事情,例如镜头、剪辑、音效等,用的好的话抛弃premiere都是有可能的。Sequence就相当整个生产线的枢纽,在sequence环节把之前环节的素材全部装载进来进行编辑,在这个环节可以回看之前所有环节的东西,所有问题也会在这个环节暴露出来,这个环节会反复的修改和调整,相比传统流程sequence提供的自由度简直是太高了。镜头上既可以选择在sequence里创建也可以导入DCC里的cmaera,这点上也是看团队的选择。在官方放出的Sequence工程里,可以看到完整的sequence使用方法,例如master sequence,这点在团队工作上非常重要,这可以让多人编辑变的很安全,这也是非线性的体现,多人同时编辑后同步到perforce或svn,结果即会同步(虽然maya也可以使用钩子文件的方式实现,但是真是觉得master sequence更好用)。&/p&&p&到sequence差不多是倒数第二或第三的环节了,后面两个环节有可能是使用sequence输出后再调整和调色,这里可以使用nuke或DaVinci。如果使用nuke的话还要对输出做二次开发,对模型进行ID分配和逐通道输出(这里我是通过修改材质实现了ID和Z通道输出)。&/p&&p&虚幻4目前的难点之一就是要做一定程度上的二次开发,以完成功能上的欠缺,这点估计虚幻官方会在未来几年逐渐完善工具链。下面我再补充几个我熟悉的技术点。&/p&&p&毛发。现在有两种方案可选,一是使用面片加材质实现,这一点在官方的公开工程photorealisticCharacter里有完整的方案。如果有兴趣的话还可以查看由Yibing&br&Jiang公开的文档&/p&&p&The Process of Creating Volumetric-based&br&Materials in Uncharted 4,&/p&&p&下载链接 :&a href=&/?target=http%3A///s/1jHUGCvk& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/s/1jHUGCv&/span&&span class=&invisible&&k&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&这份文档里不仅讲了毛发,还讲了角色材质方面的内容,是一篇非常好的讲解材质制作思路的文档。&/p&&img src=&/50/v2-dc22adf4dca35a3dd0ed464_b.jpg& data-caption=&& data-rawwidth=&554& data-rawheight=&234& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/50/v2-dc22adf4dca35a3dd0ed464_r.jpg&&&p&毛发还有另外一个方案可选,就是nvidia&br&hair works,这个方案就像是实时渲染里的shave,不过缺点就是很吃性能,另外就是开发完成度不高,使用上不那么好用(我就在使用的过程中不断和开发者通过邮件提需求要求工程师改进功能和易用性)&/p&&img src=&/50/v2-33e1b7c7f477c17f93b04ccbabe24c4f_b.jpg& data-caption=&& data-rawwidth=&554& data-rawheight=&231& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/50/v2-33e1b7c7f477c17f93b04ccbabe24c4f_r.jpg&&&p&后处理是非常方便的处理画面风格的方法,虚幻4现在这块儿很强大。&/p&&p&蓝图这块儿目前接触的主要是在AI角色,集群动画上。因为虚幻没有现成的集群动画插件,这块儿就考验团队的二次开发能力了。&/p&&p&赛璐珞渲染在虚幻里也能实现,目前来说很少有项目应用到,这个风格上的选择会很大影响制作细节上的调整,几乎是贯穿整个流水线的,如果采用的话对在项目前期一定要做好充分的技术准备。我在这方面做过一个简单的测试,感觉技术上难点克服后就是流水线的问题了(动画制作总是逃不开流水线的问题)&/p&&img src=&/50/v2-afbed5235b5_b.jpg& data-caption=&& data-rawwidth=&554& data-rawheight=&287& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&/50/v2-afbed5235b5_r.jpg&&&p&模块化资产这点其实是游戏资产里一个很基础的东西,这一点贯穿了资产创建环节,是一门非常细节,讲究设计的实现方法。其目的是为了实现资产的最大化复用,减少额外的成本浪费。这一点上纹理有纹理的做法,角色有角色的做法,建筑有建筑的做法,是一门在细节上相当耗神的事情,哪一块儿展开都能讲半天。&/p&&p&鬼影是由于taa造成的,这个问题在一位图形程序员朋友的帮助下才解决的。实现方法是修改控制台里taa和前后帧的影响权重,是得鬼影现象弱化,但是这样做会让画面变的锐利感很强。&/p&&p&工程结构是一项对项目管理非常重要的知识点,可以说是项目的根本,如果初期没有做好规划的话,后面的项目会如一团乱麻。我在实际项目中总结了一个结构,在我的认识里还算是比较有条理的。这个结构基本是瀑布式结构,DCC资产和unreal结构分离,DCC在前,虚幻制作资源在中和后。前面的DCC资产不以集数分类,以种类分类,不同的资产只要是在一个种类里就在一个目录下,例如:&/p&&p&工程目录&/p&&img src=&/50/v2-6a3cc2fcb0c5c9c2dcb0_b.jpg& data-caption=&& data-rawwidth=&332& data-rawheight=&135& class=&content_image& width=&332&&&p&资产目录&/p&&img src=&/50/v2-7d1f904cc24792bef079bde45d24a706_b.jpg& data-caption=&& data-rawwidth=&248& data-rawheight=&383& class=&content_image& width=&248&&&p&动画目录&/p&&img src=&/50/v2-13fbf32ee4b713ed72bf0_b.jpg& data-caption=&& data-rawwidth=&398& data-rawheight=&197& class=&content_image& width=&398&&&p&命名规范因为需求和功能不同需要制定针对不同类型文件的规范。&/p&&p&例如资产:&/p&&p&ch_mc_WangXiaoMing_set_01.fbx&/p&&p&mech_boss_DaJiQi_01_nor.tga&/p&&p&例如动画:&/p&&p&S01_ep01_seq01_shot01_WangDaLi_171201.mb&/p&&p&几个问题点说完了,说点别的。动画制作有这么一个说法。50%-60%是技术因素,前面说的一堆技术因素之工具,它决定了什么可用和怎么用。20%-30%是团队和人才,团队里有管理层、中层、执行,这块儿决定了合作和能力。还有10%是叙事和故事,这块儿之于灵魂,一个好的叙事者即便是前面的工具和技术落后,团队有短板,也能用他的叙事能力补齐。最后这10%才是最难的。(这块儿是别人的智慧总结,我只是拿来做说明)&/p&&p&说一说团队,一个以虚幻4为平台的动画团队在建立之初最好是个小而精的团队,有4-5个关键岗位的人完成关键技术点的验证,把整个流程跑通。然后再根据项目扩大团队,从新项目,小项目做起,最好不要有历史包袱。执行人最好要有相应的技术背景,并且担任过团队管理角色,以便能从整个流程的角度建立流水线,把风险降低到最低,理想情况下是这样。在难以找到执行人有丰富的技术背景下(这个情况应该比较多),这个时候就要保证用科学的方法和态度来一步步推进了。首先实现技术上的积累,走通流程,客服技术点或绕开,然后逐步推广增加人员操作熟练度。人是一步步成长的,得花时间,从小处入手,慢慢成长才是这个事情的自然发展规律。&/p&&p&前面我提到的每一项环节也许就寥寥几百字就介绍完了,但是真相是每一个环节都有很多坑,都需要专业的人员在一个方向上付出长久的努力和学习才会有所建树,这不仅仅是个人的努力,也有赖于外部的培养,能够提供空间给专业人士成长的空间。流程和技术点我依据我所知的知识都列举出来了,不过有样东西我还是想唠叨一下,单纯的看完此文其实对虚幻引擎制作也只能说是了解,要想可以实现会不会就一定要做一遍,会不会取决于做没做,好不好取决于做过多少遍。&/p&&p&另外附上官方在制作堡垒之夜项目中的动画流程讲解视频,此篇讲解的相当有分量。&/p&&br&&a class=&video-box& href=&/?target=https%3A///video/av/index_4.html%3Ft%3D2620& target=&_blank& data-video-id=&505664& data-video-playable=&true& data-name=&Master Class(4)_演讲o公开课_科技_bilibili_哔哩哔哩& data-poster=&/80/v2-d5ffeafc564ae36_b.jpg& data-lens-id=&&&
&img class=&thumbnail& src=&/80/v2-d5ffeafc564ae36_b.jpg&&&span class=&content&&
&span class=&title&&Master Class(4)_演讲o公开课_科技_bilibili_哔哩哔哩&span class=&z-ico-extern-gray&&&/span&&span class=&z-ico-extern-blue&&&/span&&/span&
&span class=&url&&&span class=&z-ico-video&&&/span&/video/av/index_4.html?t=2620&/span&
&br&&br&&br&&br&&br&&br&&p&&br&&/p&&p&游戏和动画的差别。游戏开发,尤其是中国的游戏开发,在流程上欠缺的东西太多了,一旦做高水准的项目,立马就会把之前的不足暴露出来。动画则是发展的太晚,大环境没有给人才成长的空间。动画圈也不大,相比游戏制作的门槛高不少,对团队和技术的要求都高于游戏,即便是发展很好的动画公司,也不过几百人,而几百人的公司规模在游戏行业还是挺多的。总体看下来就是觉得动画更难做,更不好做,这就是我对游戏和动画的一点看法,多的就不展开了。&/p&&p&最后一点是关于技术爆发。虚幻4目前来说制作动画片是完全可行的,优势也挺明显,但是还没有到技术上完全碾压离线渲染的地步,我估计这也要有一个长达几年或十年的发展期,给技术发展的时间和人才成长的时间。我的看法是在可预见的未来,实时渲染制作动画一定会成为业界主流。&/p&&p&此篇文章有我的经验,也有别人的分享,不全是我的个人成果,尤其是在观点上的东西,有不少业内人士多年的总结经验,在此感谢与我无私分享的业内大咖。&/p&&p&上次项目虽不成熟,但是放出来也是曾经努力过的证明。&/p&&br&&a class=&video-box& href=&/?target=https%3A///video/av/& target=&_blank& data-video-id=&357056& data-video-playable=&true& data-name=&《黎明边际》PV02_1080p_短片·手书·配音_动画_bilibili_哔哩哔哩& data-poster=&/80/v2-bfaea1a90d516cf9e8b0000_b.jpg& data-lens-id=&&&
&img class=&thumbnail& src=&/80/v2-bfaea1a90d516cf9e8b0000_b.jpg&&&span class=&content&&
&span class=&title&&《黎明边际》PV02_1080p_短片·手书·配音_动画_bilibili_哔哩哔哩&span class=&z-ico-extern-gray&&&/span&&span class=&z-ico-extern-blue&&&/span&&/span&
&span class=&url&&&span class=&z-ico-video&&&/span&/video/av/&/span&
&br&&br&&br&&br&&br&&br&&p&&br&&/p&&p&如果有对虚幻4制作动画有想法的同好,欢迎微信交流,个人微信:dantesai&/p&&p&本文首发于《游戏美术笔记》专栏,作者薛红杰,转载请注明出处和作者&/p&&p&&a href=&/?target=https%3A///EKgX6fbs-rN1nHThSe3HKEA& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/EKgX6fbs-r&/span&&span class=&invisible&&N1nHThSe3HKEA&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a& (二维码自动识别)&/p&
使用虚幻4制作动画片这是我在2007年第一次接触虚幻3的时候完全没有想到的,而在现在的2017年已经有使用虚幻4制作完成的商业动画播出已经说明这件事在可行性上没有问题的了。做一下简单介绍,薛红杰,04年进入游戏行业,至今有12年的游戏制作经验,1年动画制…
之前看Simon演讲的时候觉得非常眼熟,想起来是之前看gdc的时候讲过。在gdc 2017上,tequila works的主程mario也讲过类似的话题,而且是在TA bootcamp里的。程序员来讲风格就完全不同了。他们在gamasutra上也发表过文章,内容基本上就是演讲内容,&a href=&/?target=https%3A///blogs/TequilaWorks/347/How_to_take_advantage_of_textures_in_the_vertex_shader.php& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&/blogs/Teq&/span&&span class=&invisible&&uilaWorks/347/How_to_take_advantage_of_textures_in_the_vertex_shader.php&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&。因为和之前Simon的演讲有很多重复,就重点记录下不同的地方。&br&&br&&img data-rawheight=&1152& src=&/v2-ee8c7fbe9c61b89067dac8_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-ee8c7fbe9c61b89067dac8_r.png&&&br&# 回顾&br&&br&从DX11开始,我们可以在vertex shader里访问纹理了,但通常的用法是为了做displacement(这里不准确,其实DX9 shader model 3.0就支持了):&br&&img data-rawheight=&1152& src=&/v2-ac8a17f3e0fd1ee525f2b_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-ac8a17f3e0fd1ee525f2b_r.png&&&br&Mario举了一些简单的例子,都是之前Simon详细讲过的,这里就只放图了(Simon的博客里又有更新,讲了fix to ground的实现):&br&&img data-rawheight=&1152& src=&/v2-b9aa28ec949fdf_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-b9aa28ec949fdf_r.png&&&br&&img data-rawheight=&1152& src=&/v2-cbe34ed1a80cb97701bab0_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-cbe34ed1a80cb97701bab0_r.png&&&br&在提问环节有人问这种使用高度图的制作流程,因为场景一变化就得重新生成,问作者有没有什么经验分享。额,作者说他们的经验就是少改变场景……高度图一般是在后期场景基本不会大动的时候生成。&br&&img data-rawheight=&1152& src=&/v2-72bd33ac4d4e0f1bf05661_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-72bd33ac4d4e0f1bf05661_r.png&&&br&&br&#优缺点&br&&br&我这里打乱下他的演讲顺序,先讲这种方法优点在哪里。作者提到有三个明显的优点,一是减少了CPU到GPU的数据传递,GPU可以直接通过纹理采样就得到大量信息。二是可共享,用这种方法可以方便地在不同开发软件里共享,通常只需要设置下纹理格式就好,而不需要考虑其他的格式转换。三是可以非常容易就获取顺序信息,因为纹理的上下左右就对应了时间和空间顺序信息:&br&&img data-rawheight=&1152& src=&/v2-d7c87fe632df8a9bab4cc77_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-d7c87fe632df8a9bab4cc77_r.png&&&br&缺点也很明显,首先就是精度问题了,虽然可以对数据进行normalize,但纹理精度必然会导致信息丢失(HDR纹理虽然精度高但内存占用也大)。二是调试困难,这就不用说了吧。三是纹理的创建需要自己写工具,但作者说,人类的朋友Houdini已经实现了类似工具(作者说下面做morph targets的类似功能houdini就支持,应该和Simon提到的那个max triangles是指的同一个吧):&br&&img data-rawheight=&1152& src=&/v2-7df64f7f05da909eadd2c5e_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-7df64f7f05da909eadd2c5e_r.png&&&br&后面就是应用举例和分类了。&br&&br&# Particle Animation&br&&br&这里Simon基本也讲过,但Mario给了更详细的数据。我们可以把粒子动画烘焙到纹理里,纹理每一行对应一帧的位置,每一列对应每个例子随时间变化的位置信息:&br&&img data-rawheight=&1152& src=&/v2-a3c024aafd4c4_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-a3c024aafd4c4_r.png&&&br&&img data-rawheight=&1152& src=&/v2-0da731a387fe457ec886_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-0da731a387fe457ec886_r.png&&&br&这样我们还可以利用纹理的线性插值来得到动画插值的效果,还可以把奇偶行单独拿出来来压缩纹理,优化动画:&br&&img data-rawheight=&1152& src=&/v2-fa37c9e55815cbcffa5e8bf6fae42cb1_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-fa37c9e55815cbcffa5e8bf6fae42cb1_r.png&&&br&&br&# Rigid Object Animation&br&&br&也可以用来做刚体动画。粒子动画只需要记录位置信息,可以扩宽一个纬度加上旋转信息的话,就可以用来做刚体动画了:&br&&img data-rawheight=&1152& src=&/v2-9aebd6eb75f763b4644710_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-9aebd6eb75f763b4644710_r.png&&&br&我们不需要记录每个顶点的位置信息,而可以让顶点共享某些中心点来做分组,然后只需要存储中心点的刚体变化就可以了。这样一来就可以节省大量存储空间:&br&&img data-rawheight=&1152& src=&/v2-685b22bf293b7d5f486cb68c90a3f821_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-685b22bf293b7d5f486cb68c90a3f821_r.png&&&br&这是之前的刚体动画的处理结果。需要两张32×128大小的纹理,分别用于存储32个中心点(即分为32个组,如图里每个破碎的砖块)的位置和旋转信息。旋转是按四元数来存储的,同样可以利用删除奇偶数行来优化:&br&&img data-rawheight=&1152& src=&/v2-827c7df6e640a2ad271c0b5_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-827c7df6e640a2ad271c0b5_r.png&&&br&在提问环节,有人问每个组的中心点信息是存在哪里的,作者说应该是存到了顶点色里,如果精度不够的话就存到纹理坐标里。&br&&br&# Morph Targets&br&&br&形态会发生较大变化的morph anination(变形动画)也可以用这种方法,这个应用Simon讲过,这里就快速过一下:&br&&img data-rawheight=&1152& src=&/v2-aff19b2c53a45619bef7cf_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-aff19b2c53a45619bef7cf_r.png&&&br&&img data-rawheight=&1152& src=&/v2-a474fb2cf4de_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-a474fb2cf4de_r.png&&&br&由于每个targets可能有不同的顶点数,我们可以设置一个最大值,那些用不了这么多顶点的就用0补上数据,这样也就看不见这些顶点了:&br&&img data-rawheight=&1152& src=&/v2-7c1fed3d6a09f032d731_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-7c1fed3d6a09f032d731_r.png&&&br&跟前面有所不同的是,这个纹理不可以开启线性插值,因为morph是直接从一个状态切到另一个状态,不需要对位置进行插值:&br&&img data-rawheight=&1152& src=&/v2-ad4e10e4d6d6a1f59b741e_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-ad4e10e4d6d6a1f59b741e_r.png&&&br&作者还提到要利用triangle pairing来优化顶点数:&br&&img data-rawheight=&1152& src=&/v2-ebb75c1cbf586d857cc0268aff540559_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-ebb75c1cbf586d857cc0268aff540559_r.png&&&br&在提问环节有人提出PCA(principal component analysis)常用于压缩这种动画。&br&&br&# Cloth Simulation&br&&br&后来作者就想把脑洞来得更大些,想着能不能靠这种方法来做布料模拟。布料模拟通常需要的顶点数目很大,作者在休息一下的时候想到一个点子,就是不记录顶点、而记录骨骼变换信息:&br&&img data-rawheight=&1152& src=&/v2-2c8d9e2ab3c13635d8baf99e78cd76c8_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-2c8d9e2ab3c13635d8baf99e78cd76c8_r.png&&&br&后面演讲的一半时间都在讲这个实现。为了实现记录骨骼动画的目的,我们需要五种信息:骨骼的平移和旋转(可以存到两张纹理里),每个顶点需要的骨骼索引(存到顶点色里,后面会讲)和对应的混合权重(存到顶点纹理坐标里),以及每个骨骼的初始位置(当然也可以存到纹理里,作者是把初始偏移存到了位置纹理的第一行):&br&&img data-rawheight=&1152& src=&/v2-93bcfb5dd42b9d8d6a927_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-93bcfb5dd42b9d8d6a927_r.png&&&br&接下来具体讲,需要注意的是现在讲的这种实现会限制影响每个顶点的骨骼数目,最多四个,因为可以存到顶点色的RGBA四个通道里,因此所有骨骼数也限制在了256个以内,因为颜色精度只能这么多。第一步,首先从顶点色里读取影响该顶点的四个骨骼索引值:&br&&img data-rawheight=&1152& src=&/v2-3e0bebc5fc2a07eec0fd944aee23c1c5_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-3e0bebc5fc2a07eec0fd944aee23c1c5_r.png&&&br&然后根据索引值从骨骼位置纹理和旋转纹理里读取偏移和旋转信息,使用从纹理坐标通道获取到的混合权重(从第二和第三纹理坐标获取)来计算经骨骼骨骼变换后的顶点位置,再加上由物体位置和同样从纹理的获取到的初始偏移位置,我们就可以求得每个顶点应用骨骼动画后的最终位置:&br&&img data-rawheight=&1152& src=&/v2-ef13efbbf6c_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-ef13efbbf6c_r.png&&&br&下图是一个包含了面部动画和身体动画的例子,就是用这种方法做的:&br&&img data-rawheight=&1152& src=&/v2-25e7d5a174e_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-25e7d5a174e_r.png&&&br&上面动画用到的两张纹理如下:&img data-rawheight=&1152& src=&/v2-c5ceec6eed41a40b397b123_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-c5ceec6eed41a40b397b123_r.png&&&br&那么,靠这种方法能存储多大数据量的动画呢?作者给了一些数据作为参考。对于包含56根骨骼的动画,大概可以存储166分钟、每秒30帧的动画数据到两张4096的纹理里(4096×&a href=&tel:/60&&4096像素/56骨骼/30帧/60&/a&秒=166):&br&&img data-rawheight=&1152& src=&/v2-adb37d3494e7_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-adb37d3494e7_r.png&&&br&如果我们要做一个非常复杂的表情动画,比如说需要450根骨骼,那么就只能存储21分钟了(这里插一句,450根骨骼明显已经超过了我们之前说的256的限制,此时可以再存储到纹理坐标通道,就可以有32位的精度了):&br&&img data-rawheight=&1152& src=&/v2-ede2ea1cbcc_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-ede2ea1cbcc_r.png&&&br&&br&# 未来工作&br&&br&作者提了几点改进方向。一是提高法线的计算,最简单的方法就是使用旋转顶点的四元数也去旋转法线,但这明显是不精确的:&br&&img data-rawheight=&1152& src=&/v2-8570cfc3f39f3ea11f1e99e_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-8570cfc3f39f3ea11f1e99e_r.png&&&br&其次是实现动画混合,上面的方法很适合过场动画,如果需要播放混合动画的话,可以靠做一些改进,比如把多组动画信息都存到纹理里,然后采样对应行来混合动画:&br&&img data-rawheight=&1152& src=&/v2-85ebb3b439ebad7_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-85ebb3b439ebad7_r.png&&&br&三是支持骨骼伸缩,这个可以靠再增加一张纹理来解决:&br&&img data-rawheight=&1152& src=&/v2-54ec403da2896eecc8a75d29bdd938ba_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-54ec403da2896eecc8a75d29bdd938ba_r.png&&&br&四是进一步压缩,目前的做法是直接存储30帧完整数据,但可以靠更加智能的(而非简单的线性纹理插值)插值系统来进一步压缩:&br&&img data-rawheight=&1152& src=&/v2-f9e9947e3b_b.png& data-rawwidth=&2048& class=&origin_image zh-lightbox-thumb& width=&2048& data-original=&/v2-f9e9947e3b_r.png&&&br&&br&# 提问环节&br&&br&放两个感觉比较有价值的。&br&&br&Q1:有对比过数据什么时候使用CPU动画或这种纹理动画的方法?&br&A1:没有,作者说他们一开始是想做液体动画来着,不能使用骨骼,所以才用了这种方法。我感觉后来骨骼动画的应用更多的是他们想要看这种方法能做到什么程度,不过之前知乎上也有文章介绍可以用这种方法来减少draw call。&br&Q2:这个人实际上是提了点建议,他指出principal component analysis,&a href=&/?target=https%3A//en.m.wikipedia.org/wiki/Principal_component_analysis& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&en.m.wikipedia.org/wiki&/span&&span class=&invisible&&/Principal_component_analysis&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&,是一种非常常见的用来做顶点动画的压缩的数学方法,特别适合这种morph animation,可以学习下。
之前看Simon演讲的时候觉得非常眼熟,想起来是之前看gdc的时候讲过。在gdc 2017上,tequila works的主程mario也讲过类似的话题,而且是在TA bootcamp里的。程序员来讲风格就完全不同了。他们在gamasutra上也发表过文章,内容基本上就是演讲内容,…
&h2&0. 牢骚&/h2&&p&我发现,每个月的20+号是我有精力写博客的时间……&br&这次项目算是经历的第一次严格意义上的渠道测试,更换了正式名称,见了更多玩家,开发组也经历的更多通宵……评价和数据如何暂时还未揭晓,趁着没那么忙,来还欠自己的“文章债务”。。。&/p&&p&这篇博客主题是移动平台的天气系统,做这个系统的主要原因是美术需求——大世界沙盘的动态效果太少了,需要一些动态变化的东西来增加效果。之前也看过一篇博客&a href=&/?target=http%3A//blog.csdn.net/qq/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Unity3D手游开发日记(7) - 适合移动平台的天气效果》&i class=&icon-external&&&/i&&/a&,作者对于每种天气效果大致聊了原理,我也挺感兴趣,就在今天五一假期(是的,没看错,就是半年前的五一……)蹲在家里花了一天多时间照着文章的思路撸了一个简单版本,在加上之前积累的多云的效果,算是我们项目中天气系统的雏形。过了假期放出来给同事体验了下,感觉还不错,然后稍微修改了一些bug就被其他事情搁置下来了,所以7月份测试也没有放出来。&/p&&p&7月技术测试之后,美术效果的增强也就被逐渐放到更高的优先级,天气系统也就成为了我的工作重点之一。经历整体结构的重构和一些效果实现方式的改变,才有了目前测试在用的这个版本。本篇文章就以当前已经实现的几种天气效果为例来聊一下在移动平台上实现一套简单的天气系统的思路和方法。&/p&&h2&1. 综述&/h2&&p&整体来说,移动平台的性能还不足以支撑端游上完整的一套天气系统,Unity的Asset Store上有一些不错的天气效果实现,也只能看着流流口水,并不敢用,比如这个&a href=&/?target=https%3A//www./cn/%23%21/content/60955& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Weather Maker - Sky, Weather, Fog, Volumetric Light and Dynamic Environment&i class=&icon-external&&&/i&&/a&,还有&a href=&/?target=https%3A//www./cn/%23%21/content/2714& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&UniStorm&i class=&icon-external&&&/i&&/a&。(UniStorm有一个Mobile版本,效果也还不错,有兴趣的同学可以去搜索看下。)&/p&&p&那么,在移动端,天气系统效果简答来说也就成了美术做做特效,程序按照需求写写挂特效的脚本罢了。的确,在制作各个天气的效果的时候,并没有用到什么特别的技术点,但整个实现天气系统的过程中,我没有依赖于美术,而是自己寻找所有需要的资源,编写逻辑进行整合简化,过程还比较有趣,体会到非常直接的成就感,一些小的细节也自己去处理,非常开心。目前实现的天气效果包括晴天、多云、阴天、雨天和雪天这几种比较常见的效果,逐一来进行说明。&/p&&h2&2. 晴天效果&/h2&&p&我们项目中美术制作的所有场景都是按照晴天的效果来制作的,所以对于程序来说,晴天效果就是没效果,实现最简单,性能最优,哈哈~(就是注意把其他效果清空不要残留……)&/p&&h2&3. 多云效果&/h2&&p&先看一下最终实现的多云效果截图,动态图比较容易看出效果,静态图感觉比较怪,可以注意主城的模型有一半是被云遮住了。为了凸显效果云阴影的浓度被我故意调整得比较高。&/p&&p&&br&&/p&&img src=&/v2-bf6ad2b948bb4b2a7802_b.jpg& data-rawwidth=&1071& data-rawheight=&687& class=&origin_image zh-lightbox-thumb& width=&1071& data-original=&/v2-bf6ad2b948bb4b2a7802_r.jpg&&&p&多云效果截图&/p&&p&这个效果是之前美术想要的一个内容。如果使用真正移动一个半透的云模型在空中移动并且产生投影,移动设备上所能支持的shadowmap尺寸无法提供足够的阴影精度,而直接进行投影的方法又比较难做到在高低不平的山、建筑等物体表面计算投影效果。经过调研之后,使用了一个购买的插件&a href=&/?target=https%3A//www./cn/%23%21/content/57352& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Screen Space Cloud Shadow&i class=&icon-external&&&/i&&/a&。插件页面有动态效果视频,想看动态效果的可以去看下。当时同样调研了另外一个插件&a href=&/?target=https%3A//www./cn/%23%21/content/63050& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Cloud Shadows&i class=&icon-external&&&/i&&/a&,都试玩了下。后者是基于light的cookie的,在当时的unity版本中有些小问题没有解决掉,而且我自己试验的cookie在移动设备上有点小问题,所以就没有选用。Screen Space Cloud Shadow这个插件使用起来比较方便,只需要把prefab丢场景里就好,开关也很简单,代价就是需要深度图,场景内所有物件都要绘制两遍,draw call和面数都会翻倍。这也是整个天气系统中消耗最大的一块,因此多云天气在最终版本里也只有高配下才会开启。&/p&&p&由于是购买的插件,因此贴代码不太合适,简单说一下实现的原理:shader使用Transparent渲染队列,在OnWillRenderObject中将一个平面放到相机的远平面,并且把尺寸缩放成和相机的远平面一样,这样就保证它的绘制过程是在最后,用FrameDebugger抓帧看绘制顺序和参数如下图:&/p&&img src=&/v2-c479bac021c4b2efb9df7dc8e2c625d4_b.jpg& data-rawwidth=&1175& data-rawheight=&419& class=&origin_image zh-lightbox-thumb& width=&1175& data-original=&/v2-c479bac021c4b2efb9df7dc8e2c625d4_r.jpg&&&p&云阴影的绘制过场截图&/p&&p&在Shader的frag过程中,根据深度图和世界空间的摄像机方向射线来计算出阴影应该绘制的浓度。这里包含了一些magic value,我也有些细节没有看得特别懂……再加上本身并不是我自己设计的算法,因此不在这里详述了,有兴趣想了解的朋友可以自己去购买一份插件,source code include。&/p&&p&这里只说明三个遇到并解决的小坑:&/p&&ol&&li&由于云的阴影是飘动的,因此涉及到&b&uv的流动&/b&,这个是根据时间来计算的,最初的时候这个时间直接取了Time.time的值,当游戏运行一段时间之后,这个值就会变得很大,在移动设备上会导致云的移动出现顿卡的感觉。这也是在很多使用uv流动的过程中很容易出现的一个问题,通过取余的方式可以保证精度,但是可能会在取余的那一帧出现采样不连续的问题。由于我们不会非常长时间开启这个效果,因此这个问题可以通过在开关的时候把时间参数重置来规避。&/li&&li&&b&fixed类型在移动设备上精度问题导致马赛克&/b&。原来Shader中使用了fixed的值,在PC上并没有问题,安卓设备上发现了&b&马赛克的现象&/b&,修改几个关键值为float类型可以解决马赛克问题。&/li&&li&由于使用了&b&深度图&/b&,因此深度图的精度对于云的效果影响比较大。我们最初相机的远平摄设置得非常远,近平面又非常近,0.1-1000这样的值域范围。在PC上没有问题,手机上就有非常明显的马赛克,将近平面和远平面都调整一下,变为1-300,效果好了很多。(顺便再推荐一下在UWA群里推荐过的调试插件,&a href=&/?target=https%3A//www./cn/%23%21/content/61863& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Hdg Remote Debug - Live Update Tool&i class=&icon-external&&&/i&&/a&,可以在电脑上连接移动设备进行实时调试,用于排查和调试这种问题比频繁打包要方便很多,节省太多时间,已经被我默认打包进了dev版本的工程里。)&/li&&/ol&&h2&4. 阴天&/h2&&p&阴天的效果其实就是天色变暗的感觉,如果是实时光照的话可以通过调暗方向光的亮度或者颜色来处理,但是由于手游上目前大都还是烘焙的,因此比较方便的方案就是通过后处理来实现。&/p&&p&考虑过Color Grading方案,但是感觉稍微有点耗,而且和昼夜系统实现会有些小冲突,最后实现的时候选择了直接在颜色上乘以一个Tint Color的方案来做,由于我们整合了整个后处理效果栈,因此在开启别的后处理的情况下,这个tint color的过程消耗非常小,每个像素多一个乘法而已。&/p&&blockquote&这里也再推荐一下钱康来一直推荐的将所有的后处理Pass进行整合的方案,也就是参考Unity官方的Github实现:&a href=&/?target=https%3A///Unity-Technologies/PostProcessing& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Post Processing Stack&i class=&icon-external&&&/i&&/a&,Asset Store上也有&a href=&/?target=https%3A//www./cn/%23%21/content/83912& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Post Processing Stack&i class=&icon-external&&&/i&&/a&。&/blockquote&&h2&5. 雨天&/h2&&p&雨天的效果实现了两个版本,最初的版本是基于前文提到的博客里的思路来实现的,就是挂一个uv流动的面片在镜头前,闪电的效果就是把这个面片调整为白色再调整回来。实现非常简单,这里只贴一下Shader代码好了,因为没有真正在项目中使用,所以只算私货。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Shader &Shader/Scene/Rain& {
Properties{
_RainTex(&Main Texture:&, 2D) = &white& {}
_RainIntensity(&Intensity of Rain:&,Float) = 0.0
_FallSpeed(&Fall Speed of Rain:&,Float) = 1
_ThunderLighting(&Thunder Lighting&, Color) = (0, 0, 0, 0.5)
SubShader{
Tags{ &Queue& = &Transparent& &IgnoreProjector& = &True& &RenderType& = &Transparent& }
Blend SrcAlpha One
ZWRITE Off
Lighting Off
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include &UnityCG.cginc&
sampler2D _RainT
float4 _RainTex_ST;
fixed _FallS
fixed _RainI
float4 _ThunderL
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
struct v2f {
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
v2f vert(appdata_t v)
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _RainTex);
fixed4 frag(v2f i) : SV_Target
fixed2 UV = i.
float Time = _Time.y;
fixed vValue = _FallSpeed * T
UV = fixed2(UV.x, UV.y + _FallSpeed * Time);
fixed4 col = tex2D(_RainTex, UV);
col.rgb = col.rgb * col.a * _RainIntensity + _ThunderLighting.
col.a = 1.0f;
&/code&&/pre&&/div&&p&实现的效果截图如下图所示。镜头前的面片使用的贴图也是我从网上找的雨滴噪声贴图自己修改的,因此有不连续的问题,截图中可以看出来,这个也是自己P图的基本功不够的原因……&/p&&img src=&/v2-d654bd920fc38529cbcf9caf_b.jpg& data-rawwidth=&1024& data-rawheight=&566& class=&origin_image zh-lightbox-thumb& width=&1024& data-original=&/v2-d654bd920fc38529cbcf9caf_r.jpg&&&p&初版的下雨效果截图&/p&&p&这里学习到一个小的技巧是可以使用Unity的AnimationCurve来做一些曲线供游戏逻辑使用,从而做出来一些变化的效果,这里就用曲线控制了雨的浓度和与雷声配合的闪电效果,C#代码也贴一下。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&using UnityE
namespace ThorFramework.Weather
[DisallowMultipleComponent]
public class RainController : MonoBehaviour
public AnimationCurve rainC
public AnimationCurve thunderC
private Color lightingColor = Color.
private Material weatherM
private float startTime = 0.0f;
private AudioSource thunderA
// Use this for initialization
void Start()
MeshRenderer r = gameObject.GetComponent&MeshRenderer&();
if (r != null)
weatherMaterial = r.
startTime = Time.
thunderAudio = gameObject.GetComponent&AudioSource&();
void OnEnable()
startTime = Time.
// Update is called once per frame
void Update()
float curveTime = Time.time - startT
if (weatherMaterial == null)
if (rainCurve != null)
float val = rainCurve.Evaluate(curveTime);
weatherMaterial.SetFloat(&_RainIntensity&, val);
thunderAudio.volume = 2.0f *
if (thunderCurve != null)
float val = thunderCurve.Evaluate(curveTime);
weatherMaterial.SetColor(&_ThunderLighting&, lightingColor*val);
&/code&&/pre&&/div&&p&这种实现OverDraw会直接翻倍,但是没有其他的太多额外消耗,因此性能上还比较节省,大致测试了下对于性能几乎感受不出来影响,特别是被降低了分辨率的情况下。但是最终我们还是没有采用这种方案,主要原因是这种效果很难做出深度感,就是雨滴真的在空间中有分布的感觉。最终还是用了粒子特效,一个一直挂在相机前的特效,在区域范围内一直产生垂直坠落的雨滴。&/p&&p&在这之前我没怎么玩过粒子系统,这里从头学习制作一个粒子特效,还是挺有趣的。粒子系统可以用比较简单的方法制作出非常酷炫的效果。最终实现效果的截图如下:&/p&&img src=&/v2-e9dbc814f_b.jpg& data-rawwidth=&1017& data-rawheight=&573& class=&origin_image zh-lightbox-thumb& width=&1017& data-original=&/v2-e9dbc814f_r.jpg&&&p&雨天效果截图&/p&&p&这里雨的效果包括三个部分:&/p&&ol&&li&跟随相机移动的一个产生雨滴的特效,截图中雨滴不是很密集,但是动起来的效果还是不错的。这里为了追求效果粒子数量上限给到了500左右,但是仍然不是非常密集,做不到暴风雨的感觉,还需要添加一些面片来做更加密集的雨滴效果。&/li&&li&跟随角色移动的地面涟漪。在通常的做法中,雨滴涟漪的制作是用粒子系统的碰撞来做的。当粒子产生了碰撞之后就会产生一个新的粒子效果,这样可以做到很精准的感觉,包括落在树叶上、建筑房顶上等,但是消耗也比较大。我们采用的是比较讨巧的方法,角色脚底挂一个不断随机产生涟漪的粒子特效,在斜坡、桥上等地方会有穿帮的小问题,但是也基本满足的策划的需求。&/li&&li&与阴天一样,下雨的时候会阴暗一些,所以同样挂了一个tint color调色的后处理。&/li&&/ol&&blockquote&总结:雨的效果花费了挺多精力来制作,最终的效果基本满意。使用特效的方案整体的overdraw没有那么高,但是为了出效果粒子数量用得还算比较多,因此在粒子系统上的性能消耗还挺大的。对比之前面片的方案各有优劣,只是出了追求高品质效果的考虑选择了效果上限较高的粒子系统来实现。&/blockquote&&h2&6. 雪天&/h2&&p&在实现雨天的效果之后,雪天的效果制作就非常简单了,雾效果加上一个和雨滴相似的粒子特效挂在镜头前就可以啦。由于雪花生命周期比较长,飘落速度比较慢,粒子数最多在300左右就可以达到不错的效果。实现的效果截图如下(这里有一些序列帧动画之类的小技巧可以优化雪片的效果,不过不属于程序的技术了,特效同学应该都会的):&/p&&img src=&/v2-c47fc0fd32ac140b3af0b9_b.jpg& data-rawwidth=&1019& data-rawheight=&576& class=&origin_image zh-lightbox-thumb& width=&1019& data-original=&/v2-c47fc0fd32ac140b3af0b9_r.jpg&&&p&雪天效果截图&/p&&blockquote&也同样研究了一下《镇魔曲》中雪花效果的实现,发现比较讨巧的是他们没有让一个雪花是一个粒子,而是用一张图来表现几片雪花的效果,然后大约只需要同时存在十几个粒子就可以做到比较密集的下雪效果。当然代价是仔细观察的话会发现一些重复感,overdraw也会稍微有些提高,但是粒子数量降低得会比较多,值得借鉴。(我们美术同学尝试了一个版本之后告诉我不太满意,当然在看了完全随机的效果之后,对于略有重复的效果自然能感觉出来瑕疵,没有对比才没有伤害……)&/blockquote&&h2&7. 风&/h2&&p&风不属于任何一个天气,而是用于辅助表现其他天气效果的元素,在我们游戏中主要能做的表现是树木的摇摆和一些相应的音效。摇摆的效果采用顶点动画来实现,已有的实现方案可以参考&a href=&/?target=http%3A//blog.csdn.net/qq/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unity3D手游开发日记(5) - 适合移动平台的植被随风摆动&i class=&icon-external&&&/i&&/a&这篇文章,网上也有很多实现细节的讨论,但比较好的方案追本溯源还是《GPU Gems》中的一篇文章:&a href=&/?target=https%3A///gpugems/GPUGems3/gpugems3_ch16.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Chapter 16. Vegetation Procedural Animation and Shading in Crysis》&i class=&icon-external&&&/i&&/a&。它主要描述了在CryEngine中的实现原理,考虑到树干和树叶的不同,使用顶点色来对振幅进行控制,估计很多人都读过,实现细节可以去参考原文。&/p&&p&这里只说几个我们移植时的几个修改:&/p&&ol&&li&&b&使用Shader的全局变量&/b&。Shader.SetGlobalXXX一系列的接口就是为这种全局参数来设计的,简单易用。&/li&&li&临近测试我们美术比较忙,表示没时间对每棵树的模型去刷顶点色,于是摇摆的幅度控制采用了一个简化的方案——由顶点高度和一个美术设定的模型高度的比值来决定,目前只采用的线性差值,效果一般,勉强够用。&/li&&li&GPU Gem中的实现比较复杂,考虑了横向的和纵向的抖动,有不少计算在里面,这块可以根据自己项目的游戏类型和需求来修改和简化。&/li&&/ol&&h2&8. 整合&/h2&&p&把实现的各个天气效果整合成天气系统,由一个管理器来控制,可以模拟游戏中各个国家的气候风格,这是最后整合进游戏进行实际应用的步骤。由于我们大世界和战斗场景是两种完全不同的镜头方式,因此最终特效挂接的部分实现了两套不同的控制逻辑。除此之外,根据不同国家的特性,也将雨天和雪天统一为了特殊天气,比如在燕国这样靠北的国家,就只会下雪,而其他国家则是下雨。这其中有很多繁杂的与游戏业务相关的逻辑就不谈了,只聊几个实现过程中比较有感触的点:&/p&&ol&&li&&b&渐变需求&/b&。天气效果中所有的控制效果都有不同的渐变细节需要处理,比如下雪天气停止不能突然没有,而是要有渐渐消失的感觉;天气由晴天变阴天,也不应该突然黑下来,而是要有一个亮度渐变的过程。这些需要各个天气系统针对自己的效果做好差值的处理,这个过程使用了&b&DoTween&/b&来做,代码实现非常简单高效。&/li&&li&&b&对于需要跨天气控制的效果进行统一的管理&/b&。在最初的版本里,用于表现变暗效果的Tint Color由每一个天气进行各自的管理和差值,这里就有一些非常恶心的特殊代码要做处理,比如阴天效果的停止函数中,当进入晴天的时候需要把亮度逐渐}

我要回帖

更多关于 我不知道我是谁 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信