早在四年前,索尼想要制做全新风格的“蜘蛛侠”电影这一消息被泄露时,一个显眼的词汇便被反复说起——“rejuvenate”,译为“使其恢复年轻”。当时,除去《蜘蛛侠:返校日》以外,由索尼出品的《蜘蛛侠》真人电影已有五部之多(托比·马圭尔主演《蜘蛛侠》三部曲,以及安德鲁·加菲尔德主演的两部《超凡蜘蛛侠》)。索尼高层但愿以一部动画电影来让这个系列从新恢复活力。html
而为了拍摄这部动画电影,索尼找到了当时因《天降美食》系列以及《乐高大电影》等做品在业界小有名气的导演搭档——菲尔·罗德和克里斯托弗·米勒。有着天马行空的创意以及各类鬼才想法的这两位导演提出的要求则是:但愿这部动画让观众感到“就像走进了漫画里通常”。他们同时也为可以在这部电影中使用到真人电影没法达成的叙事手段而感到兴奋。dom
第一次看到《蜘蛛侠:平行宇宙》这部电影时,就被其中的十足创新的视觉动态效果所惊艳,跟以往 3D 动画追求天然流畅截然相反,《蜘蛛侠:平行宇宙》反而在往漫画观感体验靠拢,让 3D 动画 2D 化,并且不是单纯的 2D 化。ide
正如在一篇文章里看到的评论:混搭上各种平行宇宙间不一样的漫画风格,保留下了对话框、漫画分格、字幕特效,还尤为增强了手绘涂鸦的部分,并刻意打造出这部分的「稚拙感」。当这些再加上神奇到炸裂的分镜,足以让人看得应接不暇。接下来咱们简单的解析下这电影的亮点:工具
在进入正题故事没多久,相信看惯了 3D 动画的你们都会有种稍稍不适应的感受...「这电影是否是有些卡?」动画
没错,它确实就是卡。这是电影第一个让人感受有些「异常」的地方,却也是电影最特别的地方。仔细看你能够发现只有人物角色是有着卡顿感的,而它们的周围环境却很流畅,造成了一种微妙的对比。关于这一点,在知乎中有网友给咱们做出了解答:ui
电影通常采用的是 24 帧每秒的制式,也就是说若是是手绘 2D 动画片,须要每秒连续画 24 张画以使画面中动画流畅,这也就是动画中俗称的1拍1。spa
可是有人发现我一样的动做只画第 1,3,5,7....合计总共只画 12 帧,而后把每帧停留 2 倍时间,观众并不会明显察觉,而动画师却省下一半绘制时间,这就是动画中俗称的1拍2,这个行为的发明只是为了节约成本。3D 动画通常不这么作是由于 3D 角色动画师在设置好关键帧后,其中间帧是自动生成的,因此自己不必去节省中间帧。翻译
1拍2是 2D 动画的传统作法,3D 动画强行这么作可让人产生 2D 动画的质感错觉,但同时 Sony 选择了背景动画、镜头动画和其它位移动画又保持1拍1,从而保持 3D 动画特有的镜头顺滑的优点。设计
这样的处理手法一方面使得画面有种 2D 逐帧动画特有的逐帧质感(如同小时候看的阿凡提动画那样),另外一方面产生一种天然中却又有点违和的感觉,比如人物和场景不在同一个时空中,这使得这个电影的视觉表现与故事主题产生了一种呼应和统一。3d
电影中咱们能够看到不少在美式漫画中经常使用的手法和效果。如爆炸的 BOOM、跳楼时的 AHHHHH、吐丝时的 THWIP 这些手绘拟声词;主角奔跑时、参与战斗前描述内心活动的对话框;速度线、感知危险的信号提示等等。这些都在不断让咱们重温阅读漫画的感觉。下面是这些表现手法在电影中的应用 :
这些表现手法,使得电影画面多了份在平面漫画中才有的张力,这和它们多年历史冲刷下变化而来的属性有关(漫画的历史沿袭不一样文化的不一样路径,且在发展中有了不少的交融,这里主要从美式漫画手法的角度出发)。咱们以漫画中常见的对话框为例。
你们都知道图像成为人类表达媒介的时间要早于文字,然而,在人们学会使用连续的图像来表达一个善始善终的故事以前,遇到须要说明的情节,单靠一幅图像是没法知足的,因而,一直到公元前两千多年前的埃及人,他们想到了一种能够说明的办法。
这是古埃及麦勒卢卡(Mereruka)陵墓中,两个男人正在屠杀鬣狗的情景壁画。让人值得注意的是,在两个角色之间的空白处飘浮着象形文字,经翻译,内容是其中一我的对另外一我的说:“ 捉紧它!”
文字的出现帮助帮助绘者丰富了图像以外的信息——表达主角的思想活动,同时又增长了画面的紧张感。
这些文字虽是总体的一部分,但也独立于图像以外,和图像关联度不大。而到了公元前7世纪左右的中美洲文明中,出现了用抽象的符号来归纳语言的方式,这种符号后来逐渐发展成为一种相似卷轴或旗帜的图形,在欧洲中世纪和文艺复兴的艺术中都曾出现。或许是由于受限于构图,这种对话方式所涉及的语言较为简略,但这个时候文字已可以比较好得融合到画面中。
从18世纪开始流行政治性讽刺漫画,其中出现了大量的对白。这个时期的漫画家们已经很是熟悉如何使用对话框了,有的像简化了的卷轴,有的前粗后细的相似气球的气泡,总之形式便很是接近现代漫画中的对话框。并且这些对话框已经能承载更多的文字,对画面起到了必定的渲染氛围的做用。
发展到了20世纪初,对话框在漫画中的做用已经很关键,同时也衍生了拟声词、情绪符号等等漫画手法,它们不只仅是内容、思想的传达,还具有着情绪表达、推进剧情、氛围渲染等等的做用。
回到咱们的电影。正是这些在平面漫画中常见手法和元素,使得静态的漫画如同能够「发声」通常,且由于这些手法让《蜘蛛侠:平行宇宙》比起通常的 3D 电影,在视觉层面丰富了观众其它感官的感觉。看回前面的动图,哪怕没有声音,咱们都能经过眼睛更具体得「听」到它发生了什么。
既然要还原漫画阅读体验,一些 3D 动画里常常用的手法天然也要抛弃,好比运动模糊。可是去掉运动模糊后,本来在 3D 动画中流畅的运动感和速度感会变得很是不足,这个决定一般会对电影风格产生直接影响。
关于这个问题,电影的特效总监迪米安是这么说的,咱们知道咱们必须解决(没有运动模糊)。咱们只是不想用任何看起来过于传统的东西来解决它。咱们想从2D动画中寻找解决的方案和想法。咱们真的在找一些东西,它看起来像是受到动画的启发,或者是经过涂片、拉伸几何图形来表现运动模糊的方式,但却不是那样作的。最后,采用了一些解决方案。Imageworks 的线条工具容许艺术家绘制一样链接形状的运动线,也有从相机快门启发的技术。例如,迪米安说,快速相机平底镜上的背景,不管是合成仍是借助特效,都会被涂上很大的污点和划线,但却会以一种很是块状的插图方式出现。咱们基本上尽可能避免任何看起来像是平滑的渐变或者由于运动模糊而变得模糊的东西。相反,这是一种很是图形化的处理。
后面咱们能够看到,电影里经过叠影(相机快门启发的方式)、场景的错位、速度线等方式来补充画面的动感,并保证了电影自己的风格语言。
实际上咱们能够从电影画面里看到通常产生于比较老化或者早期的镜头拍摄的照片质感(高光溢色),还有一些镜头色散的效果,这其实也是电影工做室有意为之。
导演 Peter Ramsey 在接受采访时是这么对电影画面解释的:某种意义上说,你根本不用花费精力去注意什么,由于咱们作的就是还原漫画,而咱们在看漫画的时候会发现,有时候漫画里都有错误,有时候印刷很差,颜色都涂到了线条以外,因此看起来就以为模糊不清。并且这和真人电影是相通的,好比有时镜头中一些东西是没有对上焦有些模糊,这样观众才会集中注意力在重要而清晰的画面上。
色散/高光溢色的效果在这里就是起到虚化和模糊的做用。如导演提到的印刷很差的状况,在 20 世纪初中期的印刷行业常常出现,当时的 CMYK 4色印刷会由于有对版的偏差出现致使喷色错版,这样的错版在后面发展中也成为了一种风格化的处理,导演注意到这点,并应用到了电影中,这既把漫画的质感还原得更到位,让观者产生共鸣同时也利用这样的错位生成一种视觉景深感,使得电影的层次更强,解决了向 2D 靠近后在电影显得平的问题。
这部电影在视觉表达上有不少的突破,创做团队很大胆得把多种风格不一的人物混合在一块儿,使得平行宇宙的概念合理化,并经过平面漫画的方式把视觉语言和整个电影的题的结合达到了高度的统一。整个电影透露出一种到处不稳定的矛盾感,创做团队没有把这种矛盾消弱,而是把他们都展示了出来,这样的碰撞感觉让人不得不佩服他们对于总体的把控和自信。
电影从片头就火烧眉毛的展示出了它在表现形式上的创新,但仔细观察你会发现里面用到的效果并不是从0到1的创新,只不过索尼的动画团队把这些视觉元素应用的淋漓尽致。
因为工做中涉及了短视频滤镜和转场的 OpenGL Shader 代码编写,因此在二刷电影的时候特别留意了电影里的特效,并思考有哪些是能够经过 Shader 来实现的。固然了,电影里的效果是动画设计师反复调整和多重加工出来的,绝非一段代码就能完美模拟。这里仅仅是从技术角度去探讨电影里一些效果在 Shader 代码层面的可行性。
电影高度还原了漫画应有的观赏体验,在画面的渲染上使用了 Ben-Day dot (本戴点)让咱们感觉到了阅读纸质漫画书的质感。Ben-Day dot 与 HalfTone 的不一样之处在于 Ben-Day dot 在特定区域中的大小和分布老是相同,而 HalfTone 能根据图像的颜色细节呈现大小和渐变不必定点。咱们能够用 HalfTone 半调滤镜去生成差很少的质感。下面截取几张截图可见一斑:
科普下 Ben-Day dot (本戴点),全名是「本戴点状制版法」,以插图画家和印刷商 Benjamin Henry Day, Jr.(19 世纪出版商 Benjamin Henry Day 的儿子)命名,在 1879 年被发明的印刷制版技术。它根据颜色和视错觉原理,经过小彩色点的间隔疏密、大小或重叠来生成所须要的效果。例如,洋红色圆点间隔比较宽就会造成粉红色。 20 世纪 50 年代和 60 年代的彩色漫画书很受欢迎,可是全彩漫画的成本很高,所以漫画出版商在四色印刷( 青色、品红色、黄色、黑色 )中使用了 Ben-Day dot,经过它打印出阴影和二次色 ,如绿色、紫色、橙色、肉色等,由于本戴点的特性这种方式能够节省大量的油墨,漫画商经过这样廉价的印刷方式印刷在便宜纸张上生产漫画书,从而能够得到较高的收益。
后来这种方式被美国波普艺术大师罗伊·利希滕斯坦(RoyLichtenstein,1923~1997)充分运用在绘画上并使其发扬光大。在 20 世纪中叶,抽象表现绘画盛极一时之际,他和纽约年轻一代画家,提出了新形式的具象绘画——波普艺术,在 60 年代一鸣惊人,成为美国新艺术运动的主推进人,甚至许多曾受美国商业影响的域外文化,亦感觉他做品的震撼力。利希滕斯坦的绘画或雕刻做品,引用自漫画、日用品、广告以及一些美术史上名画家的做品造型,结合本戴点的特色再现了美国社会当时浓厚的商业文化和时代特征。
咱们来看看实现的方式。在 OpenGL 的 Shader 语言中,经过建立一个排列的圆形的 pattern,结合图像的 RGB 通道色值获得图像的半调图案,在跟原图进行「变亮」图像混合模式,能够获得咱们想要的半调质感:
precision highp float;
uniform sampler2D inputImageTexture;
varying vec2 textureCoordinate;
vec2 center = vec2(.5, .5);
float angle = 1.57;
float scale = 1.;
vec2 tSize = vec2(1000., 563.);
float blendLighten(float base, float blend) {
return max(blend,base);
}
vec3 blendLighten(vec3 base, vec3 blend) {
return vec3(blendLighten(base.r,blend.r),blendLighten(base.g,blend.g),blendLighten(base.b,blend.b));
}
vec3 blendLighten(vec3 base, vec3 blend, float opacity) {
return (blendLighten(base, blend) * opacity + base * (1.0 - opacity));
}
float pattern() {
float s = sin( angle ), c = cos( angle );
vec2 tex = textureCoordinate * tSize - center;
vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale;
return ( sin( point.x ) * sin( point.y ) ) * 4.0;
}
void main() {
vec4 color = texture2D(inputImageTexture, textureCoordinate);
float average = ( color.r + color.g + color.b ) / 3.0;
vec4 halftone = vec4( vec3( average * 10.0 - 5.0 + pattern() ), color.a );
gl_FragColor = color + vec4(.02)*halftone;
gl_FragColor = vec4(blendLighten(color.rgb, halftone.rgb, .05), 1.);
}
复制代码
而后使用图层混合模式「变亮」,将两张图作像素融合:
这里作了一个网页 Demo,点击连接后经过右上角的滤镜开关能够看到先后效果:Demo1
Glitch 故障效果在不少都互联网产品中一样能够看到应用,最典型的好比抖音的 LOGO 动画以及短视频中的滤镜效果。在电影中,因为另外几位蜘蛛侠来自不一样的平行世界致使原子不稳定,表现出了差很少的花里胡哨的故障效果。
索尼工做室在制做故障效果的时候使用了手绘图案+多层效果融合的方式来呈现,固然了,代码想要模拟这样的效果并不容易,若是能够找到一张合适的遮罩图也许能够大体模拟出来。不过这里只单纯展现常规的基于图像自己的故障效果(代码经过基于时间来作像素和颜色通道偏移来模拟故障效果):
precision highp float;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;
varying vec2 textureCoordinate;
uniform float time;
float amount = 0.1;
float speed = 0.5;
float random1d(float n){
return fract(sin(n) * 43758.5453);
}
float random2d(vec2 n) {
return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float randomRange (in vec2 seed, in float min, in float max) {
return min + random2d(seed) * (max - min);
}
float insideRange(float v, float bottom, float top) {
return step(bottom, v) - step(top, v);
}
float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
float blendDarken(float base, float blend) {
return min(blend,base);
}
vec3 blendDarken(vec3 base, vec3 blend) {
return vec3(blendDarken(base.r,blend.r),blendDarken(base.g,blend.g),blendDarken(base.b,blend.b));
}
vec3 blendDarken(vec3 base, vec3 blend, float opacity) {
return (blendDarken(base, blend) * opacity + base * (1.0 - opacity));
}
void main() {
vec2 uv = textureCoordinate;
float sTime = floor(sin(time*0.0005) * speed * 6.0 * 24.0);
vec3 inCol = texture2D(inputImageTexture, uv).rgb;
vec3 outCol = inCol;
float maxOffset = amount/4.0;
vec2 uvOff;
for (float i = 0.0; i < 10.0; i += 1.0) {
if (i > 10.0 * amount) break;
float sliceY = random2d(vec2(sTime + amount, 2345.0 + float(i)));
float sliceH = random2d(vec2(sTime + amount, 9035.0 + float(i))) * 0.25;
float hOffset = randomRange(vec2(sTime + amount, 9625.0 + float(i)), -maxOffset, maxOffset);
uvOff = uv;
uvOff.x += hOffset;
vec2 uvOff = fract(uvOff);
if (insideRange(uv.y, sliceY, fract(sliceY+sliceH)) == 1.0 ){
outCol = texture2D(inputImageTexture, uvOff).rgb;
}
}
float maxColOffset = amount/6.0;
vec2 colOffset = vec2(randomRange(vec2(sTime + amount, 3545.0),-maxColOffset,maxColOffset), randomRange(vec2(sTime , 7205.0),-maxColOffset,maxColOffset));
uvOff = fract(uv + colOffset);
float rnd = random2d(vec2(sTime + amount, 9545.0));
if (rnd < 0.33) {
outCol.r = texture2D(inputImageTexture, uvOff).r;
} else if (rnd < 0.66) {
outCol.g = texture2D(inputImageTexture, uvOff).g;
} else {
outCol.b = texture2D(inputImageTexture, uvOff).b;
}
vec3 inCol2 = texture2D(inputImageTexture2, uv).rgb;
vec3 finalColor = blendDarken(outCol, inCol2);
gl_FragColor = vec4(finalColor, 1.0);
}
复制代码
一样的,经过 Demo 右上角的滤镜开关能够看到先后效果: Demo2
细心能够发现,上面第二个 Glitch 效果一样用到了 RGB 颜色分离,在电影中出现了大量的这样的效果,把色彩变化应用到极致,有些魔性有些虚幻。
Shader 实现起来会相比上面两个滤镜更简单一下,经过对图像的 RGB 三个颜色作拆分以及坐标偏移就能够实现:
precision highp float;
uniform sampler2D inputImageTexture;
varying vec2 textureCoordinate;
uniform float time;
float amount = 0.01;
float angle = 0.;
void main() {
vec2 offset = amount * vec2(cos(time*.001), sin(time*.001));
vec4 cr = texture2D(inputImageTexture, textureCoordinate + offset);
vec4 cga = texture2D(inputImageTexture, textureCoordinate);
vec4 cb = texture2D(inputImageTexture, textureCoordinate - offset);
gl_FragColor = vec4(cr.r, cga.g, cb.b, cga.a);
}
复制代码
下面两张图片对比先后效果:
一样的,经过 Demo 右上角的滤镜开关能够看到先后效果:Demo3
最后,这部电影能够说的太多太多,有太多让人以为惊艳的点,没办法一一都提到,咱们只能从视觉方面和实现的可能性简单得聊聊,欢迎补充。
在快写完文章的时候,想起开头 Sony 说到的「rejuvenate(使其恢复年轻)」。咱们能够感受到这个电影从故事和风格无不充斥着如今年轻人会喜欢的元素,能够说这是一部很酷的做品,可是在看到了不少资料后咱们不由有一些感慨,这些很酷炫的效果和元素风格其实在上个世纪就已经存在且有着很深的历史痕迹,从某种意义上,与其说是更年轻的体现,感受倒不如说是这是一种复古了。由此咱们也延伸出了一些问题:
有人说风格是个轮回,这么一看,所谓的「更年轻的风格」或者「过气的风格」都像是个伪命题。也就是说,是否是并不存在什么年轻或过期的风格,只有时间时间久了,看腻了的风格?
又或者说,是否是由于技术的创新才让产品(电影或者其它)变得年轻了,和风格,实际没太大关系?感受挺有意思,能够想一想。
相关连接: