Raindrop effect in CesiumJS

Hellow, I just realized raineffect in cesiumJS, with control of direction raindrop and a water surface polygon which have effects of rainripple mixed of reflection&refraction. I’d like to share the result and process with everyone here, hope to be helpful to you, and happy to see any advice about the effects. screenshots are like below:

change imageprovider will not influence the effect:

it is realized by CesiumJS postprocessstage, before this, only I can do is fullview render post,the problem is no matter what direction of camera, the rain drop is in uniform direction, but I want the raindrop drop much more like real raindrop which direction is changed with the camera change, so this time eventually realize it with sdf raymarch in postprocessstage. Now with camera changed, the rain drop direction changed too:

Another effect is water surface,including rainripple, reflection, refration, about rainripple, mainly shamely refer to shadertoy Rainier mood (shadertoy.com), what leave me to do is coordinate transform, otherwise it will be the same ripple no matter how camera changed. As to refration and reflection, what I do is offscreen render and texture coordinate compute.

1 Like

Hi @wangqiuyan ,
Can you share code example or shader content it may help us to understand it.

Hi @mehmet_emin_yalcin

Please refer to my github: GitHub - HapDragon/demos: demos, there are all the executable program. For rain effect source code, please refer to shadertoy: Fork Rain SDF wangqiuyan 912

Thank you Wangqiuyan,
The link for Fork Rain Sdf didt work can you check the link.
I am using code in Shader - Shadertoy BETA . I dont know why but my screan always draw white color. Can you give a hand.
here is my cesium code

this._postProcessStage = new Cesium.PostProcessStage({
        fragmentShader: RainSdfShader,
        uniforms: {
          iResolution: () => {
            return new Cesium.Cartesian3(
              this._viewer.canvas.clientWidth,
              this._viewer.canvas.clientHeight,
              1,
            );
          },

          iTime: () =>
            Cesium.JulianDate.secondsDifference(
              this._viewer.clock.currentTime,
              this._viewer.clock.startTime,
            ),
          iMouse: () => {
            return new Cesium.Cartesian4(
              this._mousePositions.mouseX,
              this._mousePositions.mouseY,
              this._mousePositions.clickX,
              this._mousePositions.clickY,
            );
          },
        }
      });
      this._postProcessStage.enabled = true;
      this._viewer.scene.postProcessStages.add(this._postProcessStage);

Hello, mehmet_emin_yalcin

The link: Rain SDF you find is right. Mine is not public, I forget to change it, so you can’t opened it.

As for the post stage, you need to change it a bit. So it can take effect in Cesium:

function setraineffect(show=true,centerlon,centerlat,viewer,hei=0,dxrad=0,dyrad=0) {
    if(show===false)
    {
        if(stage!=null){
            stage.enabled=false;
        }
        return;
    }


    let umatrixinverse = new Cesium.Matrix3();
    effectcenterlat=centerlat;
    effectcenterlon=centerlon;
    effecthei=hei;
    effectdxrad=dxrad;
    effectdyrad=dyrad;


    if(stage!=null) {
        stage.enabled=show;
        return;
    }

    stage=viewer.scene.postProcessStages.add(new Cesium.PostProcessStage({
        "name": "czm_raineffect",
        fragmentShader: `
                    uniform sampler2D colorTexture;
                    varying vec2 v_textureCoordinates;

                    uniform mat4 pars;
                    //0,1,2,3
                    //4,5,6,7,
                    //8,9,10,11,
                    //12,13,14,15

                   

                    mat2 Rot(float a) {
                        float s=sin(a), c=cos(a);
                        return mat2(c, -s, s, c);
                    }
                    float hash(float x){
                        return fract(sin(x*133.3)*13.13);
                    }
                    //  1 out, 1 in...
                    float hash11(float p)
                    {
                        p = fract(p * .1031);
                        p *= p + 33.33;
                        p *= p + p;
                        return fract(p);
                    }
                    //  1 out, 2 in...
                    float hash12(vec2 p)
                    {
                        vec3 p3  = fract(vec3(p.xyx) * .1031);
                        p3 += dot(p3, p3.yzx + 33.33);
                        return fract((p3.x + p3.y) * p3.z);
                    }
                    float sdSphere( vec3 p, float s )
                    {
                      return length(p)-s;
                    }
                    vec3 repeat( in vec3 p, in vec3 c )
                    {
                        vec3 q = mod(p,c)-0.5*c;
                        return q;
                    }

                    //had to make this to map positions to the infinite repetition sdf function
                    vec3 modularsnap(in vec3 p,float size)//p = position, size can be vec3
                    {
                        return floor(p*size);
                        // return floor((p+size*.5)/size) * size;
                    }

                    float rainsdf(vec3 p)
                    {
                        float rainsize=0.125;
                        float snapsize=0.75;// rain droplets separation
                        float iTime=czm_frameNumber*.01;
                        p.xy*=Rot(pars[3][2]);
                        p.zy*=Rot(pars[3][3]);
                        vec3 m=modularsnap(p,snapsize);

                        float x=1.0-2.0*hash12((m.xz+.5)*1.31457453); //multiplying by a random offset
                        float z=1.0-2.0*hash12((m.xz+.2)*1.41569562);
                        float yrandom=hash12(m.xz*1.4234123); //random y offset
                        float rainspeed=2.5+.3*hash11(yrandom);//random rainfall speed
                        //separating droplets in x,y,z dimension

                        vec3 randomoffset=vec3(x*snapsize*.25,-iTime*(rainspeed)+yrandom,z*snapsize*.25);


                        p=repeat(p-randomoffset,vec3(snapsize));



                        //p.xz*=1.2;
                        p.y*=.015;//elongated sphere effect


                        float r=sdSphere( p,rainsize*0.0025);

                        return r;
                    }

                    vec4 raymarch(vec3 o, vec3 d,out float dis,vec4 origincol)
                    {
                        vec3 color=vec3(0.0);
                        float t=0.5;
                        float maxdist=32000.0;
                        float fade=0.;
                        for(int i=0;i<32;i++)
                        {
                          vec3 p=o+d*t;
                          float plane=p.z-pars[3][1];
                          float r=rainsdf(p.xzy);
                          r=min(r,plane);
                          
                          float dist=r;
                          t+=dist;
                          
                          if(dist<.001||t>maxdist)//
                          {
                              if(dist<.001&&r<plane)
                              {
                                
                                  color+=mix(origincol.rgb,vec3(1),0.4);
                                  
                                  fade=1.;
                              
                               }
                          
                              break;
                          }
                        }
                        
                        return vec4(color,fade);


                    }
                    void main(void){
                        gl_FragColor=texture2D(colorTexture, v_textureCoordinates);
                        //vec4 origincolor=texture2D(colorTexture, v_textureCoordinates);
                        float height=pars[0][0];
                        if(height>11000.) return;

                        vec3 rgt=vec3(pars[0][1],pars[0][2],pars[0][3]);
                        vec3 up=vec3(pars[1][0],pars[1][1],pars[1][2]);
                        vec3 dir=vec3(pars[1][3],pars[2][0],pars[2][1]);
                        vec3 ro=vec3(pars[2][2],pars[2][3],pars[3][0]);
                       

                        vec2 uv = (gl_FragCoord.xy-.5*czm_viewport.zw)/czm_viewport.w;
                        vec3 rd=normalize(dir+uv.x*rgt+uv.y*up);
                        
                        // rd=normalize(dir);
                        float dis=0.;
                        vec4 raincolor=raymarch(ro,rd,dis,gl_FragColor);

                    

                      

                    
                       gl_FragColor=mix(gl_FragColor,raincolor,raincolor.w);
                     

                    }`,
        uniforms: {
         
            'pars':{value:[0,0,0,0,0,0,0,0,0,0,0,0,0,effecthei,effectdxrad,effectdyrad]},
            //'u_transformbase':{value:umatrixinverse}
        },
    }));

    let right,up,f,camerapos;
    let centerpos=Cesium.Cartesian3.fromDegrees(effectcenterlon, effectcenterlat);
    let centermat=new Cesium.Matrix4();
    let umatrix = new Cesium.Matrix3();



    addeventlis=viewer.scene.postRender.addEventListener(function () {
        if(stage.enabled===false) return;
        stage.uniforms.pars[0]=cesiumUtil.CesiumViewer.getInstance().GetCamera().height;

        right=Cesium.Cartesian3.clone(cesiumUtil.CesiumViewer.getInstance().viewer.camera.right);
        up=Cesium.Cartesian3.clone(cesiumUtil.CesiumViewer.getInstance().viewer.camera.up);
        f=Cesium.Cartesian3.clone(cesiumUtil.CesiumViewer.getInstance().viewer.camera.direction);

        // Cesium.Cartesian3.negate(f, f);

        camerapos = Cesium.Cartesian3.clone(cesiumUtil.CesiumViewer.getInstance().viewer.camera.position);
        let localunitx = new coordinate.CoordinateLocal(1, 0, 0);
        let localunity = new coordinate.CoordinateLocal(0, 1, 0);
        let localunitz = new coordinate.CoordinateLocal(0, 0, 1);
        let cartesianx = coordinate.CoordinateLocal.ToCartesian(localunitx, camerapos);
        let cartesiany = coordinate.CoordinateLocal.ToCartesian(localunity, camerapos);
        let cartesianz = coordinate.CoordinateLocal.ToCartesian(localunitz, camerapos);
        let bendiji1 = new Cesium.Cartesian3(cartesianx.x - camerapos.x, cartesianx.y - camerapos.y, cartesianx.z - camerapos.z);
        let bendiji2 = new Cesium.Cartesian3(cartesiany.x - camerapos.x, cartesiany.y - camerapos.y, cartesiany.z - camerapos.z);
        let bendiji3 = new Cesium.Cartesian3(cartesianz.x - camerapos.x, cartesianz.y - camerapos.y, cartesianz.z - camerapos.z);
        umatrix = new Cesium.Matrix3(
            bendiji1.x, bendiji2.x, bendiji3.x,
            bendiji1.y, bendiji2.y, bendiji3.y,
            bendiji1.z, bendiji2.z, bendiji3.z
        )

        Cesium.Matrix3.inverse(umatrix, umatrixinverse);

        Cesium.Matrix3.multiplyByVector(umatrixinverse,right,right);
        Cesium.Matrix3.multiplyByVector(umatrixinverse,up,up);
        Cesium.Matrix3.multiplyByVector(umatrixinverse,f,f);
        Cesium.Cartesian3.normalize(right,right);
        Cesium.Cartesian3.normalize(up,up);
        Cesium.Cartesian3.normalize(f,f);


        stage.uniforms.pars[1]=right.x,stage.uniforms.pars[2]=right.y,stage.uniforms.pars[3]=right.z;
        stage.uniforms.pars[4]=up.x,stage.uniforms.pars[5]=up.y,stage.uniforms.pars[6]=up.z;
        stage.uniforms.pars[7]=f.x,stage.uniforms.pars[8]=f.y,stage.uniforms.pars[9]=f.z;
        //stage.uniforms.pars[10]=camerapos.x,stage.uniforms.pars[11]=camerapos.y,stage.uniforms.pars[12]=camerapos.z;
        // camerapos.x-=centerpos.x;
        // camerapos.y-=centerpos.y;
        // camerapos.z-=centerpos.z;
        const localpos=coordinate.CoordinateLocal.FromCartesian(camerapos,centerpos);
        // Cesium.Matrix3.multiplyByVector(umatrixinverse,camerapos,camerapos);
        // stage.uniforms.pars[10]=camerapos.x,stage.uniforms.pars[11]=camerapos.y,stage.uniforms.pars[12]=camerapos.z;
        stage.uniforms.pars[10]=localpos.x,stage.uniforms.pars[11]=localpos.y,stage.uniforms.pars[12]=localpos.z;



        stage.uniforms.pars[13]=effecthei;

        stage.uniforms.pars[14]=effectdxrad;
        stage.uniforms.pars[15]=effectdyrad;

        // let vmatrix = new Cesium.Matrix3(
        //     right.x, up.x, f.x,
        //     right.y, up.y, f.y,
        //     right.z, up.z, f.z,
        // );
        // Cesium.Matrix3.multiply(umatrixinverse, vmatrix, umatrixinverse);

        //Cesium.Matrix3.clone(umatrixinverse,stage.uniforms.u_transformbase);
        // stage.uniforms.u_transformbase=umatrixinverse;

    });

    stage.enabled=show;
}

centerlon and centerlat in the param of the function above is where you want to drop the rain, and viewer is just the cesiumviewer you initialized. other params just use the default.

Thak you wangqiuyan.
Your code works but direction for rain goes to strange when I change direction of camera . I am working on this .

Hi again. I didnt solve the problem can you help me litle bit
here is the link I am working on
Sandcastle Example

Thanks, @mehmet_emin_yalcin I will review this shortly.

Hi @mehmet_emin_yalcin
I modify your code , please refer to : Hello World - Cesium Sandcastle

this will be OK.

Thank you wangqiuyan. It seems everything is great

Thanks @mehmet_emin_yalcin and @wangqiuyan : That looks pretty cool. I could imagine that others might be interested in that, and it could be useful to flesh this out into a dedicated example. (This could be a small repo, or a Gist, or maybe one of the Sandcastles - some of this depends on details about licensing and copyright…). In any case, it’s good to have a working Sandcastle here, so that it’s possible to point others to this thread when they are looking for a raindrop effect :+1:

Hi Marco13,
Absalutly it has been very prety example of how we can use glsl at 3D scene. And I want to say Wangquiyan deserve most of thanks because he has deep understanding of 3D.

Hi Marco13 , mehmet_emin_yalcin

Great to know this code is useful to the entire community. pleasure to share it.