<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 實(shí)現(xiàn)露珠滴落動(dòng)畫

          共 10221字,需瀏覽 21分鐘

           ·

          2021-03-18 18:21


          前言

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

          ray-marching.gif

          撒,哈吉馬路由!

          準(zhǔn)備工作

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

          正片

          全屏相機(jī)

          首先將相機(jī)換成正交相機(jī),再將平面的長(zhǎng)度調(diào)整為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)建材質(zhì)

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

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

          class RayMarching extends Base {
            // 創(chuàng)建光線追蹤材質(zhì)
            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;
            }
          }

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

          重點(diǎn)是片元著色器rayMarchingFragmentShader

          片元著色器

          背景

          作為熱身運(yùn)動(dòng),先創(chuàng)建一個(gè)輻射狀的背景吧

          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的意思是符號(hào)距離函數(shù):若傳遞給函數(shù)空間中的某個(gè)坐標(biāo),則返回那個(gè)點(diǎn)與某些平面之間的最短距離,返回值的符號(hào)表示點(diǎn)在平面的內(nèi)部還是外部,故稱符號(hào)距離函數(shù)。

          如果我們要?jiǎng)?chuàng)建一個(gè)球,就得用球的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.);
          }

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

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

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

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

          畫面上仍舊一片空白,因?yàn)槲覀兊募钨e——光線還尚未入場(chǎng)。

          光線步進(jìn)

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

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

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

          上圖中,p0是相機(jī)位置,藍(lán)色的線代表射線??梢钥闯龉饩€的第一步p0p1就邁的非常大,它也恰好是此時(shí)光線到表面的最短距離。表面上的點(diǎn)盡管是最短距離,但并沒有沿著視線的方向,因此要繼續(xù)檢測(cè)到p4這個(gè)點(diǎn)

          shadertoy上有一個(gè)可交互的例子:

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

          以下是光線步進(jìn)的glsl代碼實(shí)現(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)建一條射線,將其投喂給光線步進(jìn)算法,即可獲得光線到表面的最短距離

          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;
          }
          ...
          }

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

          居中材質(zhì)

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

          居中+拉伸素質(zhì)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));
          ...
          }

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

          計(jì)算表面法線

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

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

          才能給材質(zhì)賦予顏色

          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;
          }
          ...
          }

          此時(shí)方塊被賦予了藍(lán)色,但我們還看不出她是個(gè)立體圖形

          動(dòng)起來

          讓方塊360°旋轉(zhuǎn)起來吧,3D旋轉(zhuǎn)函數(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;
          }

          融合效果

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

          如何讓球和方塊貼在一起呢,你需要smin這個(gè)函數(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的值設(shè)為0,她們成功地貼在了一起

          uProgress的值調(diào)回1,她們又分開了

          動(dòng)態(tài)融合

          接下來就是露珠滴落的動(dòng)畫實(shí)現(xiàn)了,其實(shí)就是對(duì)融合圖形應(yīng)用了一個(gè)位移變換

          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貼圖

          默認(rèn)的材質(zhì)太土了?我們有帥氣的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了有沒有?!

          項(xiàng)目地址

          Ray Marching Gooey Effect:

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


          轉(zhuǎn)自:掘金 - alphardex

          https://juejin.cn/post/6934461126977519629



          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲国产婷婷 | 欧美三级片在线播放 | 精品亚洲黄色视频 | 热久久最新地址 | 黄色一级视频在线观看 |