Jump to content

Multimaterial Blended Texture Transitions


Rolento
 Share

Recommended Posts

http://www.babylonjs-playground.com/#1IAR36#12

That series, and that playground... is SO cool!  And I love the questions and answers, too.  @Rolento and other readers... NasimiAsl's ShaderBuilder extension uses a programming style called Fluent... and that's why it looks so unusual. You can learn more about Fluent interfaces here, and at other web places.  Funny-looking stuff, huh?  :)  (Just in case Rolento was thinking "What the hell language is THIS thing programmed-in?")  :D

Hey Naz... what is best way to "display" entire shader program(s) instead of compiling?  Is there a way to "view"?  Do we view "result"?  And, can we paste that "result" into Babylon.ShadersStore, and HTML <SCRIPT> elements, and into .fx files?  Curious.  I like to see code that ShaderBuilder "assembles" (if possible).  (thx).  Again, great thread.

Link to comment
Share on other sites

41 minutes ago, Wingnut said:

http://www.babylonjs-playground.com/#1IAR36#12

That series, and that playground... is SO cool!  And I love the questions and answers, too.  @Rolento and other readers... NasimiAsl's ShaderBuilder extension uses a programming style called Fluent... and that's why it looks so unusual. You can learn more about Fluent interfaces here, and at other web places.  Funny-looking stuff, huh?  :)  (Just in case Rolento was thinking "What the hell language is THIS thing programmed-in?")  :D

Hey Naz... what is best way to "display" entire shader program(s) instead of compiling?  Is there a way to "view"?  Do we view "result"?  And, can we paste that "result" into Babylon.ShadersStore, and HTML <SCRIPT> elements, and into .fx files?  Curious.  I like to see code that ShaderBuilder "assembles" (if possible).  (thx).  Again, great thread.

precision highp float;
#extension GL_OES_standard_derivatives : enable


 
                       varying vec2 vuv;
                       varying vec3 pos;
                       varying vec3 nrm;
                       uniform  sampler2D  txtRef_0;
                       uniform  sampler2D  txtRef_1;
                       uniform  sampler2D  txtRef_2;
                       uniform  vec3 center;
                       uniform  vec3 camera;
                       uniform  float time;
                       vec3 random3(vec3 c) {   float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0)));   vec3 r;   r.z = fract(512.0*j); j *= .125;  r.x = fract(512.0*j); j *= .125; r.y = fract(512.0*j);  return r-0.5;  } 
                       float rand(vec2 co){   return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } 
                       const float F3 =  0.3333333;const float G3 =  0.1666667;
                       float simplex3d(vec3 p) {   vec3 s = floor(p + dot(p, vec3(F3)));   vec3 x = p - s + dot(s, vec3(G3));  vec3 e = step(vec3(0.0), x - x.yzx);  vec3 i1 = e*(1.0 - e.zxy);  vec3 i2 = 1.0 - e.zxy*(1.0 - e);   vec3 x1 = x - i1 + G3;   vec3 x2 = x - i2 + 2.0*G3;   vec3 x3 = x - 1.0 + 3.0*G3;   vec4 w, d;    w.x = dot(x, x);   w.y = dot(x1, x1);  w.z = dot(x2, x2);  w.w = dot(x3, x3);   w = max(0.6 - w, 0.0);   d.x = dot(random3(s), x);   d.y = dot(random3(s + i1), x1);   d.z = dot(random3(s + i2), x2);  d.w = dot(random3(s + 1.0), x3);  w *= w;   w *= w;  d *= w;   return dot(d, vec4(52.0));     }  
                       float noise(vec3 m) {  return   0.5333333*simplex3d(m)   +0.2666667*simplex3d(2.0*m) +0.1333333*simplex3d(4.0*m) +0.0666667*simplex3d(8.0*m);   } 
                       float dim(vec3 p1 , vec3 p2){   return sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y)+(p2.z-p1.z)*(p2.z-p1.z)); }
                       vec2  rotate_xy(vec2 pr1,vec2  pr2,float alpha) {vec2 pp2 = vec2( pr2.x - pr1.x,   pr2.y - pr1.y );return  vec2( pr1.x + pp2.x * cos(alpha*3.14159265/180.) - pp2.y * sin(alpha*3.14159265/180.),pr1.y + pp2.x * sin(alpha*3.14159265/180.) + pp2.y * cos(alpha*3.14159265/180.));} 
 vec3  r_y(vec3 n, float a,vec3 c) {vec3 c1 = vec3( c.x,  c.y,   c.z );c1.x = c1.x;c1.y = c1.z;vec2 p = rotate_xy(vec2(c1.x,c1.y), vec2( n.x,  n.z ), a);n.x = p.x;n.z = p.y;return n; } 
 vec3  r_x(vec3 n, float a,vec3 c) {vec3 c1 = vec3( c.x,  c.y,   c.z );c1.x = c1.y;c1.y = c1.z;vec2 p = rotate_xy(vec2(c1.x,c1.y), vec2( n.y,  n.z ), a);n.y = p.x;n.z = p.y;return n; } 
 vec3  r_z(vec3 n, float a,vec3 c) {  vec3 c1 = vec3( c.x,  c.y,   c.z );vec2 p = rotate_xy(vec2(c1.x,c1.y), vec2( n.x,  n.y ), a);n.x = p.x;n.y = p.y;return n; }
                       vec3 normalMap() { vec4 result = vec4(0.); result = vec4(0.5);; 
                  result = vec4( normalize( nrm -(normalize(result.xyz)*2.0-vec3(1.))*(max(-0.5,min(0.5,0.5)) )),1.0); return result.xyz;}
                       float specularMap() { vec4 result = vec4(0.);float float_result = 0.; float_result = 1.0;; return float_result ;}
                       
                        
void main(void) { 
     int discardState = 0;
     vec4 result = vec4(0.);
     vec3 centeri_2_ = center;
vec3 ppo_2_ = r_z( vec3(vuv.x,vuv.y,0.),0.,centeri_2_);  
 ppo_2_ = r_y( ppo_2_,0.,centeri_2_);  
 ppo_2_ = r_x( ppo_2_,0.,centeri_2_); 
vec3 nrm_2_ = r_z( normalMap(),0.,centeri_2_);  
 nrm_2_ = r_y( nrm_2_,0.,centeri_2_);  
 nrm_2_ = r_x( nrm_2_,0.,centeri_2_);  
                        vec4 color_2_ = texture2D(txtRef_0 ,ppo_2_.xy*vec2(1.,1.)+vec2(0.,0.));
                        if(nrm_2_.z < 1.){ 
                       result = vec4(color_2_.rgb , 1.); 
                       }vec4 resHelp_2_ = result; vec4 result_1 = vec4(0.);
                  result_1 = result;result = resHelp_2_ ;vec3 centeri_3_ = center;
vec3 ppo_3_ = r_z( vec3(vec2(vuv*7.).x,vec2(vuv*7.).y,0.),0.,centeri_3_);  
 ppo_3_ = r_y( ppo_3_,0.,centeri_3_);  
 ppo_3_ = r_x( ppo_3_,0.,centeri_3_); 
vec3 nrm_3_ = r_z( normalMap(),0.,centeri_3_);  
 nrm_3_ = r_y( nrm_3_,0.,centeri_3_);  
 nrm_3_ = r_x( nrm_3_,0.,centeri_3_);  
                        vec4 color_3_ = texture2D(txtRef_1 ,ppo_3_.xy*vec2(1.,1.)+vec2(0.,0.));
                        if(nrm_3_.z < 1.){ 
                       result = vec4(color_3_.rgb , 1.); 
                       }vec4 resHelp_3_ = result; vec4 result_2 = vec4(0.);
                  result_2 = result;result = resHelp_3_ ;vec3 centeri_4_ = center;
vec3 ppo_4_ = r_z( vec3(vec2(vuv*20.).x,vec2(vuv*20.).y,0.),0.,centeri_4_);  
 ppo_4_ = r_y( ppo_4_,0.,centeri_4_);  
 ppo_4_ = r_x( ppo_4_,0.,centeri_4_); 
vec3 nrm_4_ = r_z( normalMap(),0.,centeri_4_);  
 nrm_4_ = r_y( nrm_4_,0.,centeri_4_);  
 nrm_4_ = r_x( nrm_4_,0.,centeri_4_);  
                        vec4 color_4_ = texture2D(txtRef_0 ,ppo_4_.xy*vec2(1.,1.)+vec2(0.,0.));
                        if(nrm_4_.z < 1.){ 
                       result = vec4(color_4_.rgb , 1.); 
                       }vec4 res_5_ = vec4(0.);
res_5_.x =  result.x;
res_5_.y =  result.y;
res_5_.z =  result.z;
res_5_.w =  result.w;
res_5_  =  vec4(result.x*noise(pos*0.08+vec3(0.,time*0.01,0.)),result.x*noise(pos*0.08+vec3(0.,time*0.01,0.)),result.x*noise(pos*0.08+vec3(0.,time*0.01,0.)),result.x*noise(pos*0.08+vec3(0.,time*0.01,0.)));
result = res_5_ ;vec4 resHelp_5_ = result; vec4 result_3 = vec4(0.);
                  result_3 = result;result = resHelp_5_ ;vec3 centeri_6_ = center;
vec3 ppo_6_ = r_z( vec3(vec2(vuv*24.).x,vec2(vuv*24.).y,0.),0.,centeri_6_);  
 ppo_6_ = r_y( ppo_6_,0.,centeri_6_);  
 ppo_6_ = r_x( ppo_6_,0.,centeri_6_); 
vec3 nrm_6_ = r_z( normalMap(),0.,centeri_6_);  
 nrm_6_ = r_y( nrm_6_,0.,centeri_6_);  
 nrm_6_ = r_x( nrm_6_,0.,centeri_6_);  
                        vec4 color_6_ = texture2D(txtRef_1 ,ppo_6_.xy*vec2(1.,1.)+vec2(0.,0.));
                        if(nrm_6_.z < 1.){ 
                       result = vec4(color_6_.rgb , 1.); 
                       }vec4 resHelp_6_ = result; vec4 result_4 = vec4(0.);
                  result_4 = result;result = resHelp_6_ ;vec3 centeri_7_ = center;
vec3 ppo_7_ = r_z( vec3(vec2(vuv*14.).x,vec2(vuv*14.).y,0.),0.,centeri_7_);  
 ppo_7_ = r_y( ppo_7_,0.,centeri_7_);  
 ppo_7_ = r_x( ppo_7_,0.,centeri_7_); 
vec3 nrm_7_ = r_z( normalMap(),0.,centeri_7_);  
 nrm_7_ = r_y( nrm_7_,0.,centeri_7_);  
 nrm_7_ = r_x( nrm_7_,0.,centeri_7_);  
                        vec4 color_7_ = texture2D(txtRef_2 ,ppo_7_.xy*vec2(1.,1.)+vec2(0.,0.));
                        if(nrm_7_.z < 1.){ 
                       result = vec4(color_7_.rgb , 1.); 
                       }vec4 resHelp_7_ = result; vec4 result_5 = vec4(0.);
                  result_5 = result;result = resHelp_7_ ;float c =  abs(noise(pos*0.001));result = vec4( min(1.,c*5.-4.) );vec4 resHelp_8_ = result; vec4 result_6 = vec4(0.);
                  result_6 = result;result = resHelp_8_ ;result = vec4( min(1.,c*5.-3.) );vec4 resHelp_9_ = result; vec4 result_7 = vec4(0.);
                  result_7 = result;result = resHelp_9_ ;result = vec4( min(1.,c*5.-2.) );vec4 resHelp_10_ = result; vec4 result_8 = vec4(0.);
                  result_8 = result;result = resHelp_10_ ;result = vec4( min(1.,c*5.-1.) );vec4 resHelp_11_ = result; vec4 result_9 = vec4(0.);
                  result_9 = result;result = resHelp_11_ ;result = vec4( min(1.,c*5.-0.) );vec4 resHelp_12_ = result; vec4 result_10 = vec4(0.);
                  result_10 = result;result = resHelp_12_ ;vec3 centeri_13_ = center;
vec3 ppo_13_ = r_z( vec3(vuv.x,vuv.y,0.),0.,centeri_13_);  
 ppo_13_ = r_y( ppo_13_,0.,centeri_13_);  
 ppo_13_ = r_x( ppo_13_,0.,centeri_13_); 
vec3 nrm_13_ = r_z( normalMap(),0.,centeri_13_);  
 nrm_13_ = r_y( nrm_13_,0.,centeri_13_);  
 nrm_13_ = r_x( nrm_13_,0.,centeri_13_);  
                        vec4 color_13_ = texture2D(txtRef_0 ,ppo_13_.xy*vec2(1.,1.)+vec2(0.,0.));
                        if(nrm_13_.z < 1.){ 
                       result = vec4(color_13_.rgb , 1.); 
                       } if( ((result_6.x*1.-0.)>1.0 ? 0. : max(0.,(result_6.x*1.-0.))) < 0.5 - -0.48  && ((result_6.y*1.-0.)>1.0 ? 0. : max(0.,(result_6.y*1.-0.))) < 0.5 - -0.48  && ((result_6.z*1.-0.)>1.0 ? 0. : max(0.,(result_6.z*1.-0.))) < 0.5 - -0.48  ) { vec4 oldrs_14_ = vec4(result);float al_14_ = max(0.0,min(1.0,1.0-(((result_6.x*1.-0.)>1.0 ? 0. : max(0.,(result_6.x*1.-0.))) + ((result_6.y*1.-0.)>1.0 ? 0. : max(0.,(result_6.y*1.-0.))) + ((result_6.z*1.-0.)>1.0 ? 0. : max(0.,(result_6.z*1.-0.))))/3.0+(0.))); float  l_14_ =  1.0-al_14_;  result = result_1; result = result*al_14_ +  oldrs_14_ * l_14_;    } if( ((result_7.x*1.-0.)>1.0 ? 0. : max(0.,(result_7.x*1.-0.))) < 0.5 - -0.48  && ((result_7.y*1.-0.)>1.0 ? 0. : max(0.,(result_7.y*1.-0.))) < 0.5 - -0.48  && ((result_7.z*1.-0.)>1.0 ? 0. : max(0.,(result_7.z*1.-0.))) < 0.5 - -0.48  ) { vec4 oldrs_15_ = vec4(result);float al_15_ = max(0.0,min(1.0,1.0-(((result_7.x*1.-0.)>1.0 ? 0. : max(0.,(result_7.x*1.-0.))) + ((result_7.y*1.-0.)>1.0 ? 0. : max(0.,(result_7.y*1.-0.))) + ((result_7.z*1.-0.)>1.0 ? 0. : max(0.,(result_7.z*1.-0.))))/3.0+(0.))); float  l_15_ =  1.0-al_15_;  result = result_2; result = result*al_15_ +  oldrs_15_ * l_15_;    } if( ((result_8.x*1.-0.)>1.0 ? 0. : max(0.,(result_8.x*1.-0.))) < 0.5 - -0.48  && ((result_8.y*1.-0.)>1.0 ? 0. : max(0.,(result_8.y*1.-0.))) < 0.5 - -0.48  && ((result_8.z*1.-0.)>1.0 ? 0. : max(0.,(result_8.z*1.-0.))) < 0.5 - -0.48  ) { vec4 oldrs_16_ = vec4(result);float al_16_ = max(0.0,min(1.0,1.0-(((result_8.x*1.-0.)>1.0 ? 0. : max(0.,(result_8.x*1.-0.))) + ((result_8.y*1.-0.)>1.0 ? 0. : max(0.,(result_8.y*1.-0.))) + ((result_8.z*1.-0.)>1.0 ? 0. : max(0.,(result_8.z*1.-0.))))/3.0+(0.))); float  l_16_ =  1.0-al_16_;  result = result_3; result = result*al_16_ +  oldrs_16_ * l_16_;    } if( ((result_9.x*1.-0.)>1.0 ? 0. : max(0.,(result_9.x*1.-0.))) < 0.5 - -0.48  && ((result_9.y*1.-0.)>1.0 ? 0. : max(0.,(result_9.y*1.-0.))) < 0.5 - -0.48  && ((result_9.z*1.-0.)>1.0 ? 0. : max(0.,(result_9.z*1.-0.))) < 0.5 - -0.48  ) { vec4 oldrs_17_ = vec4(result);float al_17_ = max(0.0,min(1.0,1.0-(((result_9.x*1.-0.)>1.0 ? 0. : max(0.,(result_9.x*1.-0.))) + ((result_9.y*1.-0.)>1.0 ? 0. : max(0.,(result_9.y*1.-0.))) + ((result_9.z*1.-0.)>1.0 ? 0. : max(0.,(result_9.z*1.-0.))))/3.0+(0.))); float  l_17_ =  1.0-al_17_;  result = result_4; result = result*al_17_ +  oldrs_17_ * l_17_;    } if( ((result_10.x*1.-0.)>1.0 ? 0. : max(0.,(result_10.x*1.-0.))) < 0.5 - -0.48  && ((result_10.y*1.-0.)>1.0 ? 0. : max(0.,(result_10.y*1.-0.))) < 0.5 - -0.48  && ((result_10.z*1.-0.)>1.0 ? 0. : max(0.,(result_10.z*1.-0.))) < 0.5 - -0.48  ) { vec4 oldrs_18_ = vec4(result);float al_18_ = max(0.0,min(1.0,1.0-(((result_10.x*1.-0.)>1.0 ? 0. : max(0.,(result_10.x*1.-0.))) + ((result_10.y*1.-0.)>1.0 ? 0. : max(0.,(result_10.y*1.-0.))) + ((result_10.z*1.-0.)>1.0 ? 0. : max(0.,(result_10.z*1.-0.))))/3.0+(0.))); float  l_18_ =  1.0-al_18_;  result = result_5; result = result*al_18_ +  oldrs_18_ * l_18_;    } 
     if(discardState == 0)gl_FragColor = result; 
}

vertex

precision highp float;
                       attribute vec3 position;
                       attribute vec3 normal;
                       attribute vec2 uv;
                       varying vec2 vuv;
                       varying vec3 pos;
                       varying vec3 nrm;
                       uniform   mat4 worldViewProjection;
                       uniform  vec3 center;
                       uniform  vec3 camera;
                       uniform  float time;
                       vec3 random3(vec3 c) {   float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0)));   vec3 r;   r.z = fract(512.0*j); j *= .125;  r.x = fract(512.0*j); j *= .125; r.y = fract(512.0*j);  return r-0.5;  } 
                       float rand(vec2 co){   return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } 
                       const float F3 =  0.3333333;const float G3 =  0.1666667;
                       float simplex3d(vec3 p) {   vec3 s = floor(p + dot(p, vec3(F3)));   vec3 x = p - s + dot(s, vec3(G3));  vec3 e = step(vec3(0.0), x - x.yzx);  vec3 i1 = e*(1.0 - e.zxy);  vec3 i2 = 1.0 - e.zxy*(1.0 - e);   vec3 x1 = x - i1 + G3;   vec3 x2 = x - i2 + 2.0*G3;   vec3 x3 = x - 1.0 + 3.0*G3;   vec4 w, d;    w.x = dot(x, x);   w.y = dot(x1, x1);  w.z = dot(x2, x2);  w.w = dot(x3, x3);   w = max(0.6 - w, 0.0);   d.x = dot(random3(s), x);   d.y = dot(random3(s + i1), x1);   d.z = dot(random3(s + i2), x2);  d.w = dot(random3(s + 1.0), x3);  w *= w;   w *= w;  d *= w;   return dot(d, vec4(52.0));     }  
                       float noise(vec3 m) {  return   0.5333333*simplex3d(m)   +0.2666667*simplex3d(2.0*m) +0.1333333*simplex3d(4.0*m) +0.0666667*simplex3d(8.0*m);   } 
                       float dim(vec3 p1 , vec3 p2){   return sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y)+(p2.z-p1.z)*(p2.z-p1.z)); }
                       vec2  rotate_xy(vec2 pr1,vec2  pr2,float alpha) {vec2 pp2 = vec2( pr2.x - pr1.x,   pr2.y - pr1.y );return  vec2( pr1.x + pp2.x * cos(alpha*3.14159265/180.) - pp2.y * sin(alpha*3.14159265/180.),pr1.y + pp2.x * sin(alpha*3.14159265/180.) + pp2.y * cos(alpha*3.14159265/180.));} 
 vec3  r_y(vec3 n, float a,vec3 c) {vec3 c1 = vec3( c.x,  c.y,   c.z );c1.x = c1.x;c1.y = c1.z;vec2 p = rotate_xy(vec2(c1.x,c1.y), vec2( n.x,  n.z ), a);n.x = p.x;n.z = p.y;return n; } 
 vec3  r_x(vec3 n, float a,vec3 c) {vec3 c1 = vec3( c.x,  c.y,   c.z );c1.x = c1.y;c1.y = c1.z;vec2 p = rotate_xy(vec2(c1.x,c1.y), vec2( n.y,  n.z ), a);n.y = p.x;n.z = p.y;return n; } 
 vec3  r_z(vec3 n, float a,vec3 c) {  vec3 c1 = vec3( c.x,  c.y,   c.z );vec2 p = rotate_xy(vec2(c1.x,c1.y), vec2( n.x,  n.y ), a);n.x = p.x;n.y = p.y;return n; }
                       void main(void) { 
    pos = position; 
    nrm = normal; 
    vec4 result = vec4(pos,1.);  
      vuv = uv;
     
    gl_Position = worldViewProjection * result;
     
 }

:)

BABYLON.Effect.ShadersStore["ShaderBuilder_1PixelShader"]

 

Link to comment
Share on other sites

Hi @NasimiAsl

Awesome, your guidance has been A++++.  I need a few days to go over the content you provided and get my head into the domain of shader code, but this looks pretty amazing. 

A question I was going to ask was how do I make the texture transition regions (joins) appear non straight (i.e. random / more natural).  However, looking at some of the posts above I can see similar effects to what I am wanting have been illustrated.  So once again I just need some time to digest what has been said.

Thank you for your help on this topic, you have been a great help!

Link to comment
Share on other sites

My "dream" multi-material shader would be:

- 4 or 16 full PBR Materials (with all settings and parameters, lights and shadows, parallax occlusion and ambient lighting, fog etc. )
- Blended by a 16 color SplatMap, weighted by displacement (for example pebbles come out of the grass first, sand coming out of the water etc.)
- PBR could use albedo + RAD maps (Roughness, Ambient, Displacement compressed into) for less memory, with optional metallic 8 bit texture.
 

Now that is just a dream. i know it's wishful thinking.

I already found 3 different approaches
- @NasimiAsl 's ShaderBuilder, it is so universal, but it would be very hard to replicate a PBR material with it I think. If only, a ShaderBuilder "plugged into" a PBR or Standard material, into vertex and fragment shaders, to generate and mix and distort textures or vertexes)
- @MackeyK24 's SplatMap from Unity exporter, I don't know if it would be usable outside of Unity, I think not. But, it is closer than terrainMaterial.
- and terrainMaterial, which works well, but only uses diffuse and normal maps, but nothing else, and does not support ambientColor. So not even on StandardMaterial's level.

I bump into difficulties like
"How do I tweak the StandardMateral with a vertex shader, that distorts vegetation vith a noise? (wind on leaves)?"
"How do I make a terrain material with all that PBR can give? And also a StandardMaterial version for low quality version?"
"How do I make parallax corrected cubemap reflection on the terrain, or on watery surface?"
"How could I define different texture input / material input connections, like normal map in RG channels and displacement in B (no need for a "third" normal map component, but .jpg cannot store alpha maps). or with only one albedo and one RAD map, I could make many fully PBR materials, instead of wasting texture memory with using RGBA texture for a 8 bit ambient texture..."

Sorry for being too long. A bit too much. But much of my problems have the same root!
I would like to use PBR and Standard materials, but I need "tweeks." like vertex noise or texture movement, or need some mixing/remapping of input textures.

Maybe the road to take is just rewriting the PBR material for supporting all that I want in my fork of babylon, but it seems others have similar problems too.

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...