Jump to content

SSLR WIP


Josh
 Share

Recommended Posts

Not nearly working, although the code looks right to me.

 

Vert:

#version 400

 

uniform mat4 projectionmatrix;

uniform mat4 drawmatrix;

uniform vec2 offset;

uniform vec2 position[4];

 

in vec3 vertex_position;

 

void main(void)

{

gl_Position = projectionmatrix * (drawmatrix * vec4(position[gl_VertexID]+offset, 0.0, 1.0));

}

 

Frag:

#version 400

 

uniform sampler2DMS texture0;//depth

uniform sampler2D texture1;//color

uniform sampler2DMS texture2;//normal

uniform bool isbackbuffer;

uniform vec2 buffersize;

uniform vec2 camerarange;

uniform float camerazoom;

uniform mat3 camerainversenormalmatrix;

 

out vec4 fragData0;

 

float depthToPosition(in float depth, in vec2 depthrange)

{

return depthrange.x / (depthrange.y - depth * (depthrange.y - depthrange.x)) * depthrange.y;

}

 

float LinePointDistance(in vec3 v, in vec3 w, in vec3 p)

{

// Return minimum distance between line segment vw and point p

vec3 d = w-v;

float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt

if (l2 == 0.0) return distance(p, v); // v == w case

// Consider the line extending the segment, parameterized as v + t (w - v).

// We find projection of point p onto the line.

// It falls where t = [(p-v) . (w-v)] / |w-v|^2

// We clamp t from [0,1] to handle points outside the segment vw.

float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2));

vec3 projection = v + t * (w - v); // Projection falls on the segment

return distance(p, projection);

}

 

void main(void)

{

//----------------------------------------------------------------------

//Calculate screen texcoord

//----------------------------------------------------------------------

vec2 coord = gl_FragCoord.xy / buffersize;

if (isbackbuffer) coord.y = 1.0 - coord.y;

ivec2 icoord = ivec2(gl_FragCoord.xy);

if (isbackbuffer) icoord.y = int(buffersize.y) - icoord.y;

 

//Get the original color

vec4 c = texture(texture1,coord);

 

//----------------------------------------------------------------------

//Calculate screen position and vector

//----------------------------------------------------------------------

float depth = texelFetch(texture0,icoord,0).x;

vec3 screencoord = vec3(((gl_FragCoord.x/buffersize.x)-0.5) * 2.0 * (buffersize.x/buffersize.y),((-gl_FragCoord.y/buffersize.y)+0.5) * 2.0,depthToPosition(depth,camerarange));

screencoord.x *= screencoord.z / camerazoom;

screencoord.y *= -screencoord.z / camerazoom;

vec3 screennormal = normalize(screencoord);

if (!isbackbuffer) screencoord.y *= -1.0;

 

//Get the normal at this pixel

vec3 pixelnormal = normalize(texelFetch(texture2,icoord,0).xyz * 2.0 - 1.0);

 

//Raytrace settings

const int maxSteps = 1000;

float stepSize = 1.0 / buffersize.x * 1.0;

const float hitThreshold = 0.5;

 

//2D Vector for stepping along texture

vec3 dir = vec3(normalize(pixelnormal.xy),pixelnormal.z);

dir.z *= dir.x / pixelnormal.x;

 

vec4 reflection = vec4(0.0f);

 

for (int i=0; i<maxSteps; ++i)

{

//Calculate new tex coord

coord -= dir.xy * stepSize;

if (coord.x<0.0f || coord.y<0.0f || coord.x>1.0f || coord.y>1.0f) break;

icoord = ivec2(coord);

 

//Get this pixel's screen position

depth = texelFetch(texture0,icoord,0).x;

vec3 screencoord1 = vec3(((coord.x)-0.5) * 2.0 * (buffersize.x/buffersize.y),((-coord.y)+0.5) * 2.0,depthToPosition(depth,camerarange));

screencoord1.x *= screencoord1.z / camerazoom;

screencoord1.y *= -screencoord1.z / camerazoom;

 

//Test the distance between this pixel's position and the ray to see if it hits this point

if (LinePointDistance(screencoord,screencoord+dir,screencoord1)<hitThreshold)

{

//Get this point's normal

vec3 pixelnormal1 = normalize(texelFetch(texture2,icoord,0).xyz * 2.0 - 1.0);

 

//Compare to see if the pixels face each other

if (dot(pixelnormal,pixelnormal1) < 0.0f)

{

//Look up this pixel's color

reflection = texture(texture1,coord);

break;

}

}

}

 

fragData0 = c + reflection;

}

  • Upvote 2

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

The work you guys did is great. I want to eliminate the remaining errors, figure out a better way to blend in the visible area, and try to get it to work together with the GI cubemaps. When the reflection is available, the SSLR reflection should be used, and when it's not the GI should take over, on a per-pixel basis.

  • Upvote 2

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Check it out!

 

I added a test to see if the normal of the picked pixel faces the reflection vector. This ensures that only surfaces that are actually facing the surfaces can be reflected. Notice the bottom of that edge on the machine does not reflect, because there is no reflection information to use.

 

I also made it so the routine checks the distance between the collided point to see if the point actually lies along the raytrace ray. This eliminates all those bad reflections and ensures only actual reflections get displayed.

 

post-1-0-94824000-1480204692_thumb.jpg

 

post-1-0-26270300-1480205111_thumb.jpg

 

/*

//---------------------------------------------------------------------------

-- SSLR (Screen-Space Local Reflections) by Igor Katrich and Shadmar 26/03/2015

-- Email: igorbgz@outlook.com

//---------------------------------------------------------------------------*/

#version 400

 

uniform sampler2DMS texture0;

uniform sampler2D texture1;

uniform sampler2DMS texture2;

uniform sampler2DMS texture3;

 

uniform bool isbackbuffer;

uniform vec2 buffersize;

 

uniform mat4 projectioncameramatrix;

uniform vec3 cameraposition;

uniform mat3 cameranormalmatrix;

uniform mat3 camerainversenormalmatrix;

 

//User variable's

#define reflectionfalloff 10.0f

#define raylength 1.1f

#define maxstep 10

#define edgefadefactor 0.95f

#define hitThreshold 0.1

 

out vec4 fragData0;

 

vec4 getPosition(in vec2 texCoord, out float z)

{

float x = texCoord.s * 2.0f - 1.0f;

float y = texCoord.t * 2.0f - 1.0f;

z = texelFetch(texture0, ivec2(texCoord*buffersize),0).r;

vec4 posProj = vec4(x,y,z,1.0f);

vec4 posView = inverse(projectioncameramatrix) * posProj;

posView /= posView.w;

return posView;

}

 

 

float LinePointDistance(in vec3 v, in vec3 w, in vec3 p)

{

// Return minimum distance between line segment vw and point p

vec3 d = w-v;

float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt

if (l2 == 0.0) return distance(p, v); // v == w case

//float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2));

float t = dot(p - v, w - v) / l2;

vec3 projection = v + t * (w - v); // Projection falls on the segment

return distance(p, projection);

}

 

void main(void)

{

vec2 icoord = vec2(gl_FragCoord.xy/buffersize);

if (isbackbuffer) icoord.y = 1.0f - icoord.y;

 

//Get screen color

vec4 color = texture(texture1,icoord);

 

//Get normal + alpha channel.

vec4 n=texelFetch(texture2, ivec2(icoord*buffersize),0);

vec3 normalView = normalize(n.xyz * 2.0f - 1.0f);

 

//Get roughness from gbuffer (normal.a)

int materialflags = int(n.a*255.0+0.5);

int roughness=1;

if ((32 & materialflags)!=0) roughness += 4;

if ((64 & materialflags)!=0) roughness += 2;

 

roughness=1;

 

//Get specmap from gbuffer

float specularity = texelFetch(texture3, ivec2(icoord*buffersize),0).a;

specularity=1.0;

 

//only compute if we hvae specularity

if (specularity > 0.0f)

{

//Get position and out depth (z)

float z;

vec3 posView = getPosition(icoord,z).xyz;

//if (localposView.z<0.0f) break;

 

//normal distort by rougness

//normalView.z *= roughness * 2.0f;

 

//Reflect vector

vec4 reflectedColor = color;

vec3 reflected = normalize(reflect(normalize(posView-cameraposition), normalView));

 

float rayLength = raylength;

vec4 T = vec4(0.0f);

vec3 newPos;

 

//Raytrace

for (int i = 0; i < maxstep; i++)

{

newPos = posView + reflected * rayLength;

T = projectioncameramatrix * vec4(newPos, 1.0f);

T.xy = vec2(0.5f) + 0.5f * T.xy / T.w;

T.z /= T.w;

 

if (abs(z - T.z) < 1.0f && T.x <= 1.0f && T.x >= 0.0f && T.y <= 1.0f && T.y >= 0.0f)

{

float depth;

newPos = getPosition(T.xy,depth).xyz;

rayLength = length(posView - newPos);

 

//Check distance of this pixel to the reflection ray. If it's close enough we count it as a hit.

if (LinePointDistance(posView,posView+reflected,newPos) < hitThreshold)

{

//Get the pixel at this normal

vec4 n1=texelFetch(texture2, ivec2(T.xy*buffersize),0);

vec3 normalView1 = normalize(n1.xyz * 2.0f - 1.0f);

 

//Make sure the pixel faces the reflection vector

if (dot(reflected,normalView1)<0.0f)

{

float m = max(1.0f-T.y,0.0f);

m = max(1.0f-T.x,m);

m += roughness * 0.1f;

 

vec4 rcol=texture(texture1,T.xy);

reflectedColor = mix(rcol,color,clamp(m,0.0f,1.0f));

 

//We hit the pixel, so we're done, right?

break;

}

}

}

else

{

break;//exit because we're out of the texture

}

}

 

//Fading to screen edges

vec2 fadeToScreenEdge = vec2(1.0f);

fadeToScreenEdge.x = distance(icoord.x , 1.0f);

fadeToScreenEdge.x *= distance(icoord.x, 0.0f) * 4.0f;

fadeToScreenEdge.y = distance(icoord.y, 1.0f);

fadeToScreenEdge.y *= distance(icoord.y, 0.0f) * 4.0f;

 

float fresnel = reflectionfalloff*(1.0f-(pow(dot(normalize(posView-cameraposition), normalize(normalView)), 2.0f)));

color = mix(color, reflectedColor,clamp(fresnel*pow(fadeToScreenEdge.x * fadeToScreenEdge.y,edgefadefactor)*(specularity),0.0f,1.0f));

}

fragData0 = color;

}

  • Upvote 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Now to remove those ugly edges, we also fade the reflection out based on the reflected pixel's coordinate as well. This fixes a lot of ugly lines:

/*

//---------------------------------------------------------------------------

-- SSLR (Screen-Space Local Reflections) by Igor Katrich and Shadmar 26/03/2015

-- Email: igorbgz@outlook.com

//---------------------------------------------------------------------------*/

#version 400

 

uniform sampler2DMS texture0;

uniform sampler2D texture1;

uniform sampler2DMS texture2;

uniform sampler2DMS texture3;

 

uniform bool isbackbuffer;

uniform vec2 buffersize;

 

uniform mat4 projectioncameramatrix;

uniform vec3 cameraposition;

uniform mat3 cameranormalmatrix;

uniform mat3 camerainversenormalmatrix;

 

//User variable's

#define reflectionfalloff 10.0f

#define raylength 1.1f

#define maxstep 10

#define edgefadefactor 0.95f

#define hitThreshold 0.1

 

out vec4 fragData0;

 

vec4 getPosition(in vec2 texCoord, out float z)

{

float x = texCoord.s * 2.0f - 1.0f;

float y = texCoord.t * 2.0f - 1.0f;

z = texelFetch(texture0, ivec2(texCoord*buffersize),0).r;

vec4 posProj = vec4(x,y,z,1.0f);

vec4 posView = inverse(projectioncameramatrix) * posProj;

posView /= posView.w;

return posView;

}

 

 

float LinePointDistance(in vec3 v, in vec3 w, in vec3 p)

{

// Return minimum distance between line segment vw and point p

vec3 d = w-v;

float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt

if (l2 == 0.0) return distance(p, v); // v == w case

//float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2));

float t = dot(p - v, w - v) / l2;

vec3 projection = v + t * (w - v); // Projection falls on the segment

return distance(p, projection);

}

 

void main(void)

{

vec2 icoord = vec2(gl_FragCoord.xy/buffersize);

if (isbackbuffer) icoord.y = 1.0f - icoord.y;

 

//Get screen color

vec4 color = texture(texture1,icoord);

 

//Get normal + alpha channel.

vec4 n=texelFetch(texture2, ivec2(icoord*buffersize),0);

vec3 normalView = normalize(n.xyz * 2.0f - 1.0f);

 

//Get roughness from gbuffer (normal.a)

int materialflags = int(n.a*255.0+0.5);

int roughness=1;

//if ((32 & materialflags)!=0) roughness += 4;

//if ((64 & materialflags)!=0) roughness += 2;

 

//Get specmap from gbuffer

float specularity = texelFetch(texture3, ivec2(icoord*buffersize),0).a;

 

//only compute if we hvae specularity

if (specularity > 0.0f)

{

//Get position and out depth (z)

float z;

vec3 posView = getPosition(icoord,z).xyz;

 

//Reflect vector

vec4 reflectedColor = color;

vec3 reflected = normalize(reflect(normalize(posView-cameraposition), normalView));

 

float rayLength = raylength;

vec4 T = vec4(0.0f);

vec3 newPos;

 

//Raytrace

for (int i = 0; i < maxstep; i++)

{

newPos = posView + reflected * rayLength;

T = projectioncameramatrix * vec4(newPos, 1.0f);

T.xy = vec2(0.5f) + 0.5f * T.xy / T.w;

T.z /= T.w;

 

if (abs(z - T.z) < 1.0f && T.x <= 1.0f && T.x >= 0.0f && T.y <= 1.0f && T.y >= 0.0f)

{

float depth;

newPos = getPosition(T.xy,depth).xyz;

rayLength = length(posView - newPos);

 

//Check distance of this pixel to the reflection ray. If it's close enough we count it as a hit.

if (LinePointDistance(posView,posView+reflected,newPos) < hitThreshold)

{

//Get the pixel at this normal

vec4 n1=texelFetch(texture2, ivec2(T.xy*buffersize),0);

vec3 normalView1 = normalize(n1.xyz * 2.0f - 1.0f);

 

//Make sure the pixel faces the reflection vector

if (dot(reflected,normalView1)<0.0f)

{

float m = max(1.0f-T.y,0.0f);

m = max(1.0f-T.x,m);

m += roughness * 0.1f;

 

vec4 rcol=texture(texture1,T.xy);

reflectedColor = mix(rcol,color,clamp(m,0.0f,1.0f));

 

//Fading to screen edges

vec2 fadeToScreenEdge = vec2(1.0f);

fadeToScreenEdge.x = distance(icoord.x , 1.0f);

fadeToScreenEdge.x *= distance(icoord.x, 0.0f) * 4.0f;

fadeToScreenEdge.y = distance(icoord.y, 1.0f);

fadeToScreenEdge.y *= distance(icoord.y, 0.0f) * 4.0f;

 

//Also fade by the reflected pixel's coordinate to eliminate ugly lines :)

fadeToScreenEdge.x *= distance(T.x , 1.0f);

fadeToScreenEdge.x *= distance(T.x, 0.0f) * 4.0f;

fadeToScreenEdge.y *= distance(T.y, 1.0f);

fadeToScreenEdge.y *= distance(T.y, 0.0f) * 4.0f;

 

float fresnel = reflectionfalloff*(1.0f-(pow(dot(normalize(posView-cameraposition), normalize(normalView)), 2.0f)));

color = mix(color, reflectedColor,clamp(fresnel*pow(fadeToScreenEdge.x * fadeToScreenEdge.y,edgefadefactor)*(specularity),0.0f,1.0f));

 

//We hit the pixel, so we're done, right?

break;

}

}

}

else

{

break;//exit because we're out of the texture

}

}

}

fragData0 = color;

}

  • Upvote 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

There's actually no need to fade out near the screen edge. You just need to fade out when the reflected pixel is near the screen edge:

						//Fading to screen edges

vec2 fadeToScreenEdge = vec2(1.0f);

//fadeToScreenEdge.x = distance(icoord.x , 1.0f);

//fadeToScreenEdge.x *= distance(icoord.x, 0.0f) * 4.0f;

//fadeToScreenEdge.y = distance(icoord.y, 1.0f);

//fadeToScreenEdge.y *= distance(icoord.y, 0.0f) * 4.0f;

 

//Also fade by the reflected pixel's coordinate to eliminate ugly lines :)

fadeToScreenEdge.x *= distance(T.x , 1.0f);

fadeToScreenEdge.x *= distance(T.x, 0.0f) * 4.0f;

fadeToScreenEdge.y *= distance(T.y, 1.0f);

fadeToScreenEdge.y *= distance(T.y, 0.0f) * 4.0f;

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Really cool. Maybe if polished enough, it can be a standard scene option? This way the user doesn't need to worry about having this shader on top of the stack like the old one. A tick-box option for this would be great.

  • Upvote 2

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

This interacts with the GI cubemapping so eventually it should be built into the engine and not treated as a post-effect. I need to make it write into the stencil buffer or something to prevent pixels from receiving static GI lighting.

 

I bet we could do some pretty nice water that doesn't require a second render of everything. That would make scenes with water not really any more demanding than without.

 

I still do not understanding how this code is providing such accurate high-res results with so few samples!

  • Upvote 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Improved the reflection blending and the edge fade:

/*

//---------------------------------------------------------------------------

-- SSLR (Screen-Space Local Reflections) by Igor Katrich and Shadmar 26/03/2015

-- Email: igorbgz@outlook.com

//---------------------------------------------------------------------------*/

#version 400

 

uniform sampler2DMS texture0;

uniform sampler2D texture1;

uniform sampler2DMS texture2;

uniform sampler2DMS texture3;

 

uniform bool isbackbuffer;

uniform vec2 buffersize;

 

uniform mat4 projectioncameramatrix;

uniform vec3 cameraposition;

uniform mat3 cameranormalmatrix;

uniform mat3 camerainversenormalmatrix;

 

//User variables

#define reflectionfalloff 10.0f

#define raylength 1.1f

#define maxstep 10

#define edgefadefactor 0.95f

#define hitThreshold 0.1

 

out vec4 fragData0;

 

vec4 getPosition(in vec2 texCoord, out float z)

{

float x = texCoord.s * 2.0f - 1.0f;

float y = texCoord.t * 2.0f - 1.0f;

z = texelFetch(texture0, ivec2(texCoord*buffersize),0).r;

vec4 posProj = vec4(x,y,z,1.0f);

vec4 posView = inverse(projectioncameramatrix) * posProj;

posView /= posView.w;

return posView;

}

 

float LinePointDistance(in vec3 v, in vec3 w, in vec3 p)

{

// Return minimum distance between line segment vw and point p

vec3 d = w-v;

float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt

if (l2 == 0.0) return distance(p, v); // v == w case

//float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2));

float t = dot(p - v, w - v) / l2;

vec3 projection = v + t * (w - v); // Projection falls on the segment

return distance(p, projection);

}

 

void main(void)

{

vec2 icoord = vec2(gl_FragCoord.xy/buffersize);

if (isbackbuffer) icoord.y = 1.0f - icoord.y;

 

//Get screen color

vec4 color = texture(texture1,icoord);

 

//Get normal + alpha channel.

vec4 n=texelFetch(texture2, ivec2(icoord*buffersize),0);

vec3 normalView = normalize(n.xyz * 2.0f - 1.0f);

 

//Get roughness from gbuffer (normal.a)

int materialflags = int(n.a*255.0+0.5);

int roughness=1;

//if ((32 & materialflags)!=0) roughness += 4;

//if ((64 & materialflags)!=0) roughness += 2;

 

//Get specmap from gbuffer

float specularity = texelFetch(texture3, ivec2(icoord*buffersize),0).a;

 

//only compute if we hvae specularity

if (specularity > 0.0f)

{

//Get position and out depth (z)

float z;

vec3 posView = getPosition(icoord,z).xyz;

 

//Reflect vector

vec4 reflectedColor = color;

vec3 reflected = normalize(reflect(normalize(posView-cameraposition), normalView));

 

float rayLength = raylength;

vec4 T = vec4(0.0f);

vec3 newPos;

 

//Raytrace

for (int i = 0; i < maxstep; i++)

{

newPos = posView + reflected * rayLength;

 

T = projectioncameramatrix * vec4(newPos, 1.0f);

T.xy = vec2(0.5f) + 0.5f * T.xy / T.w;

T.z /= T.w;

 

if (abs(z - T.z) < 1.0f && T.x <= 1.0f && T.x >= 0.0f && T.y <= 1.0f && T.y >= 0.0f)

{

float depth;

newPos = getPosition(T.xy,depth).xyz;

rayLength = length(posView - newPos);

 

//Check distance of this pixel to the reflection ray. If it's close enough we count it as a hit.

if (LinePointDistance(posView,posView+reflected,newPos) < hitThreshold)

{

//Get the pixel at this normal

vec4 n1=texelFetch(texture2, ivec2(T.xy*buffersize),0);

vec3 normalView1 = normalize(n1.xyz * 2.0f - 1.0f);

 

//Make sure the pixel faces the reflection vector

if (dot(reflected,normalView1)<0.0f)

{

/*float m = max(1.0f-T.y,0.0f);

m = max(1.0f-T.x,m);

m += roughness * 0.1f;

*/

float m = 0.5;

vec4 rcol=texture(texture1,T.xy);

//reflectedColor = mix(rcol,color,clamp(m,0.0f,1.0f));

reflectedColor = rcol + color;

 

//Fading to screen edges

vec2 fadeToScreenEdge = vec2(1.0f);

 

float edgedistance[2];

edgedistance[1] = 0.5;

edgedistance[0] = edgedistance[1] * (buffersize.y / buffersize.x);

 

if (T.x<edgedistance[0])

{

fadeToScreenEdge.x = T.x / edgedistance[0];

}

else if (T.x > 1.0 - edgedistance[0])

{

fadeToScreenEdge.x = 1.0 - ((T.x - (1.0 - edgedistance[0])) / edgedistance[0]);

}

if (T.y<edgedistance[1])

{

fadeToScreenEdge.y = T.y / edgedistance[1];

}

else if (T.y>1.0-edgedistance[1])

{

fadeToScreenEdge.y = 1.0 - (T.y - (1.0-edgedistance[1])) / edgedistance[1];

}

 

float fresnel = reflectionfalloff * (1.0f-(pow(dot(normalize(posView-cameraposition), normalize(normalView)), 2.0f)));

fresnel = clamp(fresnel,0.0f,1.0f);

color = mix(color, reflectedColor,clamp(fresnel * fadeToScreenEdge.x * fadeToScreenEdge.y * specularity, 0.0f, 1.0f));

 

//We hit the pixel, so we're done, right?

break;

}

}

}

else

{

break;//exit because we're out of the texture

}

}

}

fragData0 = color;

}

  • Upvote 2

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

  • 3 weeks later...

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.

 Share

×
×
  • Create New...