<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          three.js 實現(xiàn)露珠滴落動畫

          共 10375字,需瀏覽 21分鐘

           ·

          2021-04-06 19:18

          前言

          本文我們將用three.js來實現(xiàn)一種很酷的光學效果——露珠滴落。我們知道,在露珠從一個物體表面滴落的時候,會產(chǎn)生一種粘著的效果。2D平面中,這種粘著效果其實用css濾鏡就可以輕松實現(xiàn)。但是到了3D世界,就沒那么簡單了,這時我們就得依靠光照來實現(xiàn),其中涉及到了一個關鍵算法——光線步進(Ray Marching)。以下是最終實現(xiàn)的效果圖

          ray-marching.gif

          撒,哈吉馬路由!

          準備工作

          筆者的three.js模板:點擊右下角的fork即可復制一份 https://codepen.io/alphardex/pen/yLaQdOq

          正片

          全屏相機

          首先將相機換成正交相機,再將平面的長度調整為2,使其填滿屏幕

          class RayMarching extends Base {
            constructor(sel: string, debug: boolean) {
              super(sel, debug);
              this.clock = new THREE.Clock();
              this.cameraPosition = new THREE.Vector3(000);
              this.orthographicCameraParams = {
                left: -1,
                right: 1,
                top: 1,
                bottom: -1,
                near: 0,
                far: 1,
                zoom: 1
              };
            }
            // 初始化
            init() {
              this.createScene();
              this.createOrthographicCamera();
              this.createRenderer();
              this.createRayMarchingMaterial();
              this.createPlane();
              this.createLight();
              this.trackMousePos();
              this.addListeners();
              this.setLoop();
            }
            // 創(chuàng)建平面
            createPlane() {
              const geometry = new THREE.PlaneBufferGeometry(22100100);
              const material = this.rayMarchingMaterial;
              this.createMesh({
                geometry,
                material
              });
            }
          }
          1

          創(chuàng)建材質

          創(chuàng)建好著色器材質,里面定義好所有要傳遞給著色器的參數(shù)

          const matcapTextureUrl = "https://i.loli.net/2021/02/27/7zhBySIYxEqUFW3.png";

          class RayMarching extends Base {
            // 創(chuàng)建光線追蹤材質
            createRayMarchingMaterial() {
              const loader = new THREE.TextureLoader();
              const texture = loader.load(matcapTextureUrl);
              const rayMarchingMaterial = new THREE.ShaderMaterial({
                vertexShader: rayMarchingVertexShader,
                fragmentShader: rayMarchingFragmentShader,
                side: THREE.DoubleSide,
                uniforms: {
                  uTime: {
                    value: 0
                  },
                  uMouse: {
                    value: new THREE.Vector2(00)
                  },
                  uResolution: {
                    value: new THREE.Vector2(window.innerWidth, window.innerHeight)
                  },
                  uTexture: {
                    value: texture
                  },
                  uProgress: {
                    value: 1
                  },
                  uVelocityBox: {
                    value: 0.25
                  },
                  uVelocitySphere: {
                    value: 0.5
                  },
                  uAngle: {
                    value: 1.5
                  },
                  uDistance: {
                    value: 1.2
                  }
                }
              });
              this.rayMarchingMaterial = rayMarchingMaterial;
            }
          }

          頂點著色器rayMarchingVertexShader,這個只要用模板現(xiàn)成的就可以了

          重點是片元著色器rayMarchingFragmentShader

          片元著色器

          背景

          作為熱身運動,先創(chuàng)建一個輻射狀的背景吧

          varying vec2 vUv;

          vec3 background(vec2 uv){
          float dist=length(uv-vec2(.5));
          vec3 bg=mix(vec3(.3),vec3(.0),dist);
          return bg;
          }

          void main(){
          vec3 bg=background(vUv);
          vec3 color=bg;
          gl_FragColor=vec4(color,1.);
          }

          sdf

          如何在光照模型中創(chuàng)建物體呢?我們需要sdf。

          sdf的意思是符號距離函數(shù):若傳遞給函數(shù)空間中的某個坐標,則返回那個點與某些平面之間的最短距離,返回值的符號表示點在平面的內(nèi)部還是外部,故稱符號距離函數(shù)。

          如果我們要創(chuàng)建一個球,就得用球的sdf來創(chuàng)建。球體方程可以用如下的glsl代碼來表示

          float sdSphere(vec3 p,float r)
          {
          return length(p)-r;
          }

          方塊的代碼如下

          float sdBox(vec3 p,vec3 b)
          {
          vec3 q=abs(p)-b;
          return length(max(q,0.))+min(max(q.x,max(q.y,q.z)),0.);
          }

          看不懂怎么辦?沒關系,國外已經(jīng)有大牛把常用的sdf公式都整理出來了

          公式:https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm

          在sdf里先創(chuàng)建一個方塊

          float sdf(vec3 p){
          float box=sdBox(p,vec3(.3));
          return box;
          }

          畫面上仍舊一片空白,因為我們的嘉賓——光線還尚未入場。

          光線步進

          接下來就是本文的頭號人物——光線步進了。在介紹她之前,我們先來看看她的好姬友光線追蹤吧。

          首先,我們需要知道光線追蹤是如何進行的:給相機一個位置eye,在前面放一個網(wǎng)格,從相機的位置發(fā)射一束射線ray,穿過網(wǎng)格打在物體上,所成的像的每一個像素對應著網(wǎng)格上的每一個點。

          而在光線步進中,整個場景會由一系列的sdf的角度定義。為了找到場景和視線之間的邊界,我們會從相機的位置開始,沿著射線,一點一點地移動每個點,每一步都會判斷這個點在不在場景的某個表面內(nèi)部,如果在則完成,表示光線擊中了某東西,如果不在則光線繼續(xù)步進。

          上圖中,p0是相機位置,藍色的線代表射線。可以看出光線的第一步p0p1就邁的非常大,它也恰好是此時光線到表面的最短距離。表面上的點盡管是最短距離,但并沒有沿著視線的方向,因此要繼續(xù)檢測到p4這個點

          shadertoy上有一個可交互的例子:

          https://www.shadertoy.com/view/4dKyRz

          以下是光線步進的glsl代碼實現(xiàn)

          const float EPSILON=.0001;

          float rayMarch(vec3 eye,vec3 ray,float end,int maxIter){
          float depth=0.;
          for(int i=0;i<maxIter;i++){
          vec3 pos=eye+depth*ray;
          float dist=sdf(pos);
          depth+=dist;
          if(dist<EPSILON||dist>=end){
          break;
          }
          }
          return depth;
          }

          在主函數(shù)中創(chuàng)建一條射線,將其投喂給光線步進算法,即可獲得光線到表面的最短距離

          void main(){
          ...
          vec3 eye=vec3(0.,0.,2.5);
          vec3 ray=normalize(vec3(vUv,-eye.z));
          float end=5.;
          int maxIter=256;
          float depth=rayMarch(eye,ray,end,maxIter);
          if(depth<end){
          vec3 pos=eye+depth*ray;
          color=pos;
          }
          ...
          }

          在光線步進的引誘下,野生的方塊出現(xiàn)了!

          居中材質

          目前的方塊有2個問題:1. 沒有居中 2. x軸方向上被拉伸

          居中+拉伸素質2連走起

          vec2 centerUv(vec2 uv){
          uv=2.*uv-1.;
          float aspect=uResolution.x/uResolution.y;
          uv.x*=aspect;
          return uv;
          }

          void main(){
          ...
          vec2 cUv=centerUv(vUv);
          vec3 ray=normalize(vec3(cUv,-eye.z));
          ...
          }

          方塊瞬間飄到了畫面的正中央,但此時的她還沒有顏色

          計算表面法線

          在光照模型中,我們需要計算出表面法線

          https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm

          才能給材質賦予顏色

          vec3 calcNormal(in vec3 p)
          {
          const float eps=.0001;
          const vec2 h=vec2(eps,0);
          return normalize(vec3(sdf(p+h.xyy)-sdf(p-h.xyy),
          sdf(p+h.yxy)-sdf(p-h.yxy),
          sdf(p+h.yyx)-sdf(p-h.yyx)));
          }

          void main(){
          ...
          if(depth<end){
          vec3 pos=eye+depth*ray;
          vec3 normal=calcNormal(pos);
          color=normal;
          }
          ...
          }

          此時方塊被賦予了藍色,但我們還看不出她是個立體圖形

          動起來

          讓方塊360°旋轉起來吧,3D旋轉函數(shù)直接在gist上搜一下就有了

          https://gist.github.com/yiwenl/3f804e80d0930e34a0b33359259b556c

          uniform float uVelocityBox;

          mat4 rotationMatrix(vec3 axis,float angle){
          axis=normalize(axis);
          float s=sin(angle);
          float c=cos(angle);
          float oc=1.-c;

          return mat4(oc*axis.x*axis.x+c,oc*axis.x*axis.y-axis.z*s,oc*axis.z*axis.x+axis.y*s,0.,
          oc*axis.x*axis.y+axis.z*s,oc*axis.y*axis.y+c,oc*axis.y*axis.z-axis.x*s,0.,
          oc*axis.z*axis.x-axis.y*s,oc*axis.y*axis.z+axis.x*s,oc*axis.z*axis.z+c,0.,
          0.,0.,0.,1.);
          }

          vec3 rotate(vec3 v,vec3 axis,float angle){
          mat4 m=rotationMatrix(axis,angle);
          return(m*vec4(v,1.)).xyz;
          }

          float sdf(vec3 p){
          vec3 p1=rotate(p,vec3(1.),uTime*uVelocityBox);
          float box=sdBox(p1,vec3(.3));
          return box;
          }

          融合效果

          單單一個方塊太孤單了,創(chuàng)建一個球來陪陪她吧

          如何讓球和方塊貼在一起呢,你需要smin這個函數(shù)

          https://www.iquilezles.org/www/articles/smin/smin.htm

          uniform float uProgress;

          float smin(float a,float b,float k)
          {
          float h=clamp(.5+.5*(b-a)/k,0.,1.);
          return mix(b,a,h)-k*h*(1.-h);
          }

          float sdf(vec3 p){
          vec3 p1=rotate(p,vec3(1.),uTime*uVelocityBox);
          float box=sdBox(p1,vec3(.3));
          float sphere=sdSphere(p,.3);
          float sBox=smin(box,sphere,.3);
          float mixedBox=mix(sBox,box,uProgress);
          return mixedBox;
          }

          uProgress的值設為0,她們成功地貼在了一起

          uProgress的值調回1,她們又分開了

          動態(tài)融合

          接下來就是露珠滴落的動畫實現(xiàn)了,其實就是對融合圖形應用了一個位移變換

          uniform float uAngle;
          uniform float uDistance;
          uniform float uVelocitySphere;

          const float PI=3.14159265359;

          float movingSphere(vec3 p,float shape){
          float rad=uAngle*PI;
          vec3 pos=vec3(cos(rad),sin(rad),0.)*uDistance;
          vec3 displacement=pos*fract(uTime*uVelocitySphere);
          float gotoCenter=sdSphere(p-displacement,.1);
          return smin(shape,gotoCenter,.3);
          }

          float sdf(vec3 p){
          vec3 p1=rotate(p,vec3(1.),uTime*uVelocityBox);
          float box=sdBox(p1,vec3(.3));
          float sphere=sdSphere(p,.3);
          float sBox=smin(box,sphere,.3);
          float mixedBox=mix(sBox,box,uProgress);
          mixedBox=movingSphere(p,mixedBox);
          return mixedBox;
          }

          matcap貼圖

          默認的材質太土了?我們有帥氣的matcap貼圖來助陣

          uniform sampler2D uTexture;

          vec2 matcap(vec3 eye,vec3 normal){
          vec3 reflected=reflect(eye,normal);
          float m=2.8284271247461903*sqrt(reflected.z+1.);
          return reflected.xy/m+.5;
          }

          float fresnel(float bias,float scale,float power,vec3 I,vec3 N)
          {
          return bias+scale*pow(1.+dot(I,N),power);
          }

          void main(){
          ...
          if(depth<end){
          vec3 pos=eye+depth*ray;
          vec3 normal=calcNormal(pos);
          vec2 matcapUv=matcap(ray,normal);
          color=texture2D(uTexture,matcapUv).rgb;
          float F=fresnel(0.,.4,3.2,ray,normal);
          color=mix(color,bg,F);
          }
          ...
          }

          安排上了matcap和菲涅爾公式后,瞬間cool了有沒有?!

          項目地址

          Ray Marching Gooey Effect:

          https://codepen.io/alphardex/pen/BaQYXvy


          轉自:掘金 - alphardex

          https://juejin.cn/post/6934461126977519629


          ??愛心三連擊

          1.看到這里了就點個在看支持下吧,你的點贊,在看是我創(chuàng)作的動力。

          2.關注公眾號程序員成長指北,回復「1」加入高級前端交流群!「在這里有好多 前端 開發(fā)者,會討論 前端 Node 知識,互相學習」!

          3.也可添加微信【ikoala520】,一起成長。

          “在看轉發(fā)”是最大的支持

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  三区七区九区一区在线 | 啊啊啊操在线观看 | 超碰精品97 | 日韩人成 | 国产1区2区3区 |