这种透明LOWPOLY效果是怎么做出来的?

还看过一张类似的 是一只手的海报 这种效果不仅是因为LOWPOLY好看 跟色块不一样,是点和线构成的 科技感超强 不知道用PS 或者AI能否做出来?还是说这其实只是一个视频截图?
关注者
643
被浏览
21444

7 个回答

多谢邀请。
我再来抛一个砖:
lowpoly风格本身如果用Ai直接绘制就是一件比较耗时耗力的工作,也有不少制作lowpoly风格的小工具,这些在知乎上已经有很多知友提到过了,见:如何简单地把一张相片处理成低多边形 Low Poly 3D 风格效果? - 图像处理如何使用JavaScript生成lowpoly风格图像? - 前端开发等。所以说把这种风格的图像制作方式局限于Ai和Ps是不太容易轻易得到理想的效果的(或者不太容易想到)。再加上题主本身给出的图并不完全是lowpoly,所以我这里采用的方法是:

使用Processing

我也是最近刚接触Processing,目前找到一种能够实现相似效果的方法。
首先根据原图可以看出主要任务仍然是散点+连线,也就是剖分。常用的剖分算法是delaunay三角剖分,经过查询,有一个Processing的第三方库mesh可以实现这一点,具体介绍和下载地址详见:Mesh – A Processing Library by Lee Byron
接下来我说一下我实现题图效果的思路:
  1. 首先是找到一张你要实现上述效果的原图,这里建议使用depth map深度图,或者背景为黑色、主体为浅色的图片(光线从摄影机方向正对主体对象打过去),或者使用背景为浅色或者白色、逆光的图片,然后对图片取一个反色(因为只要取一个反色就和前一种情况一样了),原因后面解释。
  2. 把画布大小设置为和图片尺寸一样。
  3. 在画布上随机打点,点的数量可以根据最后得出的结果调整。
  4. 因为我们最终要把点用圆的形式画出来,所以要确定圆的半径和透明度,然后通过圆的半径和透明度来实现一个较为立体的效果(近大远小,近明远暗)。区分某一个点是近还是远的标准就是看该点在原图中对应的像素的明度,因此第一步对原图的选择有一些要求。这里适当使用random函数可以让原图看起来有前后的区分,更具有立体效果。
  5. 把散点用圆的形式画出来。
  6. 利用delaunay三角剖分将散点连起来,连线两端可以不完全接触散点圆(题主给的图那样)。
  7. 生成pdf文件,可以随后在Ai中手动编辑。
那么下面就是一个示例程序,我找的原图是下面这张,把这张图片放到sketch生成的.pde文件路径下:示例程序如下,保存为.pde:
import megamu.mesh.*;
import processing.pdf.*;
PImage gollum;
int[][] points=new int[3][6000];
float[][] meshpoints = new float[points[0].length][2];
float[][] myEdges;
Delaunay myDelaunay;
void setup() {
  size(1100, 787);
  gollum=loadImage("77aed73e704c09a69af43dd78dc012bb_r.jpg");
  for (int i=0; i<points[0].length; i++) {
    points[0][i]=(int)random(gollum.width);
    points[1][i]=(int)random(gollum.height);
    points[2][i]=0;
    meshpoints[i][0]=points[0][i];
    meshpoints[i][1]=points[1][i];
  }
  myDelaunay=new Delaunay(meshpoints);
  myEdges=myDelaunay.getEdges();
  noLoop();
  beginRecord(PDF, "gollum.pdf");
}
void draw() {
  background(9, 24, 83);
  loadPixels();
  for (int i=0; i<points[0].length; i++) {
    points[2][i]=(int)(brightness(gollum.pixels[points[0][i]+points[1][i]*width])/255.0*10);
    if (points[2][i]!=0) {
      ellipseMode(CENTER);
      noStroke();
      fill(250, 201, 13, random(sqrt(points[2][i]*8000)));
      float r=random(points[2][i]/2, points[2][i]);
      ellipse(points[0][i], points[1][i], r, r);
    }
  }
  for (int j=0; j<myEdges.length; j++) {
    float startX = myEdges[j][0];
    float startY = myEdges[j][1];
    float endX = myEdges[j][2];
    float endY = myEdges[j][3];
    float bold1=brightness(gollum.pixels[(int)startX+(int)startY*width])/255.0*10;
    float bold2=brightness(gollum.pixels[(int)endX+(int)endY*width])/255.0*10;
    if (bold1!=0&&bold2!=0) {
      stroke(250, 201, 13, random(sqrt(bold1+bold2)*40/1.2, sqrt(bold1+bold2)*40));
      strokeWeight(sqrt(bold1+bold2)/4);
      line(startX+(endX-startX)*0.2, startY+(endY-startY)*0.2, endX-(endX-startX)*0.2, endY-(endY-startY)*0.2);
    }
  }
  endRecord();
}
运行完毕后路径下面会多出来一个gollum.pdf的文件,就可以直接编辑了。
效果:
咕噜
使用其他图片做出来的效果:
小丑
飞机
飞机这个边缘不太好看,可以在draw函数里加一段查找边缘的代码:
  for (int a=1; a<width-1; a++) {
    for (int b=1; b<height-1; b++) {
      if (brightness(plane.pixels[a+b*width])>brightness(plane.pixels[(a-1)+(b)*width])*1.2||
        brightness(plane.pixels[a+b*width])>brightness(plane.pixels[(a+1)+(b)*width])*1.2||
        brightness(plane.pixels[a+b*width])>brightness(plane.pixels[(a)+(b-1)*width])*1.2||
        brightness(plane.pixels[a+b*width])>brightness(plane.pixels[(a)+(b+1)*width])*1.2) {
        stroke(250, 201, 13, pow(brightness(plane.pixels[a+b*width]), 1.2)/5);
        point(a, b);
      }
    }
  }
加了边缘的飞机
加了边缘的骷髅
我不是程序员,所以这个程序可以优化的地方还不少,有兴趣的可以进一步修改。
Processing的入门介绍可以参考知乎的这个问题:作为一名交互设计师应该如何学习 Processing? - Processing(编程语言)
本答案仅提供一种思路,欢迎交流。
这种效果我能想到的只有两种办法。第一种是建模然后使用AE里面的Plexus,第二种就是@Secant 所说的Processing。第一种办法不需要编程,但是需要建模,比较局限,如果我想做一个人脸的点线网格效果还要建模,太麻烦了。第二种办法不需要建模,只要写好一个程序,其实就可以反复利用。
为了方便不想打代码的同学,在这里我借鉴@Secant 的思路,使用Mesh的库写了一个可控的小小程序(貌似电脑要装java才能用?),希望能够帮到你。

小程序下载地址:pan.baidu.com/s/1c0DfN7


【文字网格效果】


步骤一:用PS制作一张纯文字的图,背景为黑色,字为白色。我还添加了一点外发光,以让它的边缘不那么锐利。最好宽高要小于等于1280*720。



步骤二:存储为jpg格式,把图片放到小程序的文件夹里面,命名为[img.jpg]。



步骤三:双击exe文件,运行小程序。你可以通过控制一些简单地参数:

[+]键:增加点线数量。

[-]键:减少点线数量。

[p]键:显示/隐藏点。

[l]键:显示/隐藏线。



步骤四:调好参数之后,关闭程序,你会发现文件夹多了一个[img.pdf]的文件,是矢量的哦。这时候你可以把它导入ps、ai软件进一步处理啦!




【人物网格效果】


步骤一:在PS里面讲图片处理成黑白,不处理成黑白也可以,不过黑白方便点。然后我们把不需要形成点线的背景用[笔刷]刷成黑色,只留下人物主体。大小也不要超过1280*720。



步骤二:同文字网格效果一样。导出jpg格式放到小程序文件夹里,命名为[img.jpg],执行程序,并控制点线。

[+]键:增加点线数量。

[-]键:减少点线数量。

[p]键:显示/隐藏点。

[l]键:显示/隐藏线。


默认



隐藏点


隐藏线



代码:
import megamu.mesh.*;
import processing.pdf.*;

int pointNum = 4000;
 // 点的数量
int minPointRadius = 1;  // 最小点的半径
int maxPointRadius = 4;  // 最大点的半径
float minStrokeWidth = 0.3;  // 最小的线宽 
float maxStrokeWidth = 1;  // 最大的线宽
int alphaRandomStength = 15;  // 透明度随机强度
boolean createEdges = true;  // 显示边
boolean createPoints = true;  // 显示点

PImage image;
Delaunay myDelaunay;
void setup() {
  image = loadImage("img.jpg");
  image.loadPixels();

  size(1280,720);
  noLoop();
}
void draw() {
  
  float[][] points = new float[pointNum][2];
  float[][] edges = new float[pointNum][4];
  float[] alpha = new float[pointNum];
  float[] r = new float[pointNum];
  
  for (int i = 0 ; i < pointNum ; i++) {
    int x = (int)random(0, image.width);
    int y = (int)random(0, image.height);
    float b = brightness(image.get(x, y))/255.0;
    if (b != 0) {
      r[i] = (int)(minPointRadius + (maxPointRadius-minPointRadius)*b + random(-1, 1));
      alpha[i] = (int)(b*255+random(-alphaRandomStength, alphaRandomStength));
      if (alpha[i] > 255)
        alpha[i] = 255;
      else if (alpha[i] < 0)
        alpha[i] = 0;
      points[i][0] = x;
      points[i][1] = y;
    } else {
      i--;
    }
  }
  myDelaunay = new Delaunay(points);
  edges = myDelaunay.getEdges();

  // 开始绘画
  beginRecord(PDF, "img.pdf");
  background(0);
  noStroke();
  if (createPoints) {
    for (int i = 0 ; i < pointNum ; i++) {
      ellipseMode(CENTER);
      fill(255, alpha[i]);
      ellipse(points[i][0], points[i][1], r[i],r[i]);
    }
  }
  if (createEdges) {
    for (int i = 0 ; i < edges.length ; i++) {
      int startX = (int)edges[i][0];
      int startY = (int)edges[i][1];
      int endX = (int)edges[i][2];
      int endY = (int)edges[i][3];
      float b1 = brightness(image.get(startX, startY))/255.0;
      float b2 = brightness(image.get(endX, endY))/255.0;
      float edgeAlpha = ((b1 < b2 ? b1 : b2)*255+random(-alphaRandomStength, alphaRandomStength));
       if (edgeAlpha > 255)
         edgeAlpha = 255;
       else if (edgeAlpha < 0)
         edgeAlpha = 0;
      float distance = sqrt(pow(startX,startY) + pow(endX, endY));
      float factor =  distance > 15 ? 1 : distance/15.0;
      float strokeWidth = minStrokeWidth + (maxStrokeWidth - minStrokeWidth)*factor;
      strokeWeight(strokeWidth);
      stroke(255, edgeAlpha);
      line(startX, startY, endX, endY);
    }
  }
  endRecord();
}
// 处理按键事件
void keyPressed() {
  if (key == '+') {
    pointNum = pointNum + 400;  // 增加点
    if (pointNum > 15000)
      pointNum = 15000;
    redraw();
  } else if(key == '-') {
    pointNum = pointNum - 400;  // 减少点
    if (pointNum < 400)
      pointNum = 400;
    redraw();
  } else if(key == 'l') {
    createEdges = !createEdges;
    redraw();
  } else if(key == 'p') {
    createPoints = !createPoints;
    redraw();
  }
}