You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
9.3 KiB
314 lines
9.3 KiB
// created by florian berger (flockaroo) - 2018
|
|
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
|
|
|
|
// oil paint brush drawing
|
|
|
|
// calculating and drawing drawing the brush strokes
|
|
|
|
#define MULTI_STROKE
|
|
|
|
////#NumTriangles 0x10000
|
|
#ifndef __UNITY3D__
|
|
vec2 vec2i(float x) { return vec2(x,x); }
|
|
vec3 vec3i(float x) { return vec3(x,x,x); }
|
|
vec4 vec4i(vec3 v,int x) { return vec4(v,x); }
|
|
#define mul(m,v) (v*m)
|
|
#endif
|
|
|
|
#ifdef __UNITY3D__
|
|
uniform int NumTriangles;
|
|
#else
|
|
#define NumTriangles 0x10000
|
|
#endif
|
|
|
|
#define Res (iResolution.xy)
|
|
#define Res0 vec2(textureSize(iChannel0,0))
|
|
#define Res1 vec2(textureSize(iChannel1,0))
|
|
|
|
#define PI 3.1415927
|
|
|
|
#define N(v) (v.yx*vec2(1,-1))
|
|
|
|
vec4 getRand(vec2 pos)
|
|
{
|
|
return textureLod(iChannel1,pos/Res1,0.);
|
|
}
|
|
|
|
vec4 getRand(int idx)
|
|
{
|
|
ivec2 rres=textureSize(iChannel1,0);
|
|
idx=idx%(rres.x*rres.y);
|
|
return texelFetch(iChannel1,ivec2(idx%rres.x,idx/rres.x),0);
|
|
}
|
|
|
|
uniform float SrcContrast;
|
|
uniform float SrcBright;
|
|
uniform float SrcBlur;
|
|
|
|
vec4 getCol(vec2 pos, float lod)
|
|
{
|
|
// use max(...) for fitting full image or min(...) for fitting only one dir
|
|
vec2 uv = (pos-.5*Res)*min(Res0.y/Res.y,Res0.x/Res.x)/Res0+.5;
|
|
vec2 mask = step(vec2i(-.5),-abs(uv-.5));
|
|
return clamp(((textureLod(iChannel0,uv,lod+SrcBlur*(log2(Res.x)-1.))-.5)*SrcContrast+.5*SrcBright),0.,1.)*mask.x*mask.y;
|
|
}
|
|
|
|
uniform float FlickerStrength;
|
|
|
|
vec3 getValCol(vec2 pos, float lod)
|
|
{
|
|
return getCol(pos,1.5+log2(Res0.x/600.)+lod).xyz*.7+getCol(pos,3.5+log2(Res0.x/600.)+lod).xyz*.3+.003*getRand(pos*.1+iTime*FlickerStrength*10.).xyz;
|
|
//return getCol(pos,.5+lod).xyz*.7+getCol(pos,lod+2.5).xyz*0.3+.003*getRand(pos*.1+iTime*FlickerStrength*10.).xyz;
|
|
}
|
|
|
|
float compsignedmax(vec3 c)
|
|
{
|
|
vec3 s=sign(c);
|
|
vec3 a=abs(c);
|
|
if (a.x>a.y && a.x>a.z) return c.x;
|
|
if (a.y>a.x && a.y>a.z) return c.y;
|
|
return c.z;
|
|
}
|
|
|
|
vec2 getGradMax(vec2 pos, float eps)
|
|
{
|
|
vec2 d=vec2(eps,0);
|
|
float lod = log2(2.*eps*Res0.x/Res.x);
|
|
lod=0.;
|
|
return vec2(
|
|
compsignedmax(getValCol(pos+d.xy,lod)-getValCol(pos-d.xy,lod)),
|
|
compsignedmax(getValCol(pos+d.yx,lod)-getValCol(pos-d.yx,lod))
|
|
)/eps/2.;
|
|
}
|
|
|
|
vec2 quad(vec2 p1, vec2 p2, vec2 p3, vec2 p4, int idx)
|
|
{
|
|
#ifdef __UNITY3D__
|
|
vec2 p[6] = {p1,p2,p3,p2,p4,p3};
|
|
#else
|
|
vec2[6] p = vec2[6](p1,p2,p3,p2,p4,p3);
|
|
#endif
|
|
return p[idx%6];
|
|
}
|
|
|
|
uniform float BrushDetail;
|
|
|
|
uniform float StrokeBend;
|
|
|
|
int bitinv(int x, int bits)
|
|
{
|
|
int ret=0;
|
|
for(int i=0;i<bits;i++) ret |= ((x>>i)&1)<<(bits-1-i);
|
|
return ret;
|
|
}
|
|
|
|
int SCRAMBLE(int idx, int num)
|
|
{
|
|
return idx;
|
|
// sort of a generalized bit conversion - exchange half domains until smallest scale
|
|
int idx0=0;
|
|
for(int i=0;i<15;i++)
|
|
{
|
|
if(idx-idx0>=num-num/2) { idx=idx-(num-num/2); idx0+=0; num=num/2; }
|
|
else { idx=idx+num/2; idx0+=num/2; num=num-num/2; }
|
|
if (num<=0) break;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
uniform float BrushSize;
|
|
//uniform float StrokeThresh;
|
|
uniform float LayerScale;
|
|
uniform float StrokeAng;
|
|
|
|
#define CS(ang) cos(ang-vec2(0,PI/2.))
|
|
mat2 ROT2(float ang) { vec2 b=CS(ang); return mat2(b,b.yx*vec2(-1,1)); }
|
|
|
|
void mainGeom( out vec4 vertCoord, inout vec4 vertAttrib[3], int vertIndex )
|
|
{
|
|
vertCoord=vec4(0,0,0,1);
|
|
int pidx = vertIndex/6;
|
|
float idxFact = float(pidx)/float(NumTriangles/2);
|
|
|
|
vec3 brushPos;
|
|
//int layerScalePercent = int(floor(LayerScale*100.));
|
|
float ls = pow(LayerScale,2.);
|
|
//float pow(ls,
|
|
int NumGrid=int(float(NumTriangles/2)*(1.-ls));
|
|
float aspect=Res.x/Res.y;
|
|
int NumX = int(sqrt(float(NumGrid)*aspect));
|
|
int NumY = int(sqrt(float(NumGrid)/aspect));
|
|
//int pidx2 = NumX*NumY*4/3-pidx;
|
|
int pidx2 = NumTriangles/2-pidx;
|
|
int NumX2=NumX;
|
|
int NumY2=NumY;
|
|
int layer=0;
|
|
//int maxLayer=int(-log2(float(NumY))/log2(layerScale));
|
|
int imax=8;
|
|
layer = imax;
|
|
for(int i=0; i<imax; i++) { if(pidx2<NumX2*NumY2) { layer=i; break;} pidx2-=NumX2*NumY2; NumX2=NumX2*int(LayerScale*100.)/100; NumY2=NumY2*int(LayerScale*100.)/100; }
|
|
//NumX2=NumX*pow(layerScale,)
|
|
//layer=maxLayer-layer;
|
|
pidx2=NumX2*NumY2-pidx2;
|
|
brushPos.xy = (vec2(SCRAMBLE(pidx2%NumX2,NumX2),SCRAMBLE(pidx2/NumX2,NumY2))+.5)/vec2(NumX2,NumY2)*Res;
|
|
//brushPos.xy = vec2(SCRAMBLE(pidx2%NumX2,NumX2),SCRAMBLE(pidx2/NumX2,NumY2))/(vec2(NumX2,NumY2)-1.)*Res;
|
|
float gridW = Res.x/float(NumX2);
|
|
float gridW0 = Res.x/float(NumX);
|
|
// add some noise to grid pos
|
|
brushPos.xy += gridW*(getRand(brushPos.xy+float(iFrame)*FlickerStrength).xy-.5);
|
|
// more trigonal grid by displacing every 2nd line
|
|
brushPos.x += gridW*.5*(float((pidx2/NumX2)%2)-.5);
|
|
|
|
vec2 g;
|
|
g = .5*getGradMax(brushPos.xy,gridW*1.)+.5*getGradMax(brushPos.xy,gridW*.12);
|
|
//g = getGradMax(brushPos.xy,gridW*.1);
|
|
float gl=length(g);
|
|
// add small error to gardient so big plain areas dont get too simple
|
|
g+=.007*(getRand(brushPos.xy*.5).xy-.5);
|
|
vec2 n = normalize(g);
|
|
vec2 t = N(n);
|
|
|
|
brushPos.z = .5;
|
|
|
|
//float wh = (gridW-gridW0+1.*min(Res.x/Res0.x,10.))*(.8+.4*getRand(pidx).z)/**pow(1.-idxFact,4.)*/;
|
|
//float lh = wh*1.5*exp(float(NumX2)/float(NumX)*.7)*(.8+.4*getRand(pidx).y);
|
|
// bigger scales covering at first, smaller scales not covering completely anymore
|
|
float wh = (gridW-.6*gridW0)*1.2;
|
|
float lh = wh;
|
|
float stretch=sqrt(1.5*pow(3.,1./float(layer+1)));
|
|
//stretch=1.5;
|
|
wh*=BrushSize*(.8+.4*getRand(pidx).y)/stretch;
|
|
lh*=BrushSize*(.8+.4*getRand(pidx).z)*stretch;
|
|
|
|
float wh0=wh;
|
|
//wh/=1.-.25*abs(StrokeBend);
|
|
wh*=1.25;
|
|
|
|
//if(imax!=0)
|
|
wh = (lh>max(Res.x,Res.y)*.1 && layer!=imax-1) ? 0. : wh;
|
|
//float StrokeThresh = iMouse.x/iResolution.x;
|
|
//wh = (layer!=int(StrokeThresh*20.)-1 && int(StrokeThresh*20.)>0) ? 0. : wh;
|
|
wh = (gl*BrushDetail<.003/wh0 && wh0<Res.x*.03 && layer!=imax-1) ? 0. : wh;
|
|
wh = (layer>=imax) ? 0. : wh;
|
|
|
|
vec2 qc = quad( vec2(-1,-1), vec2(1,-1), vec2(-1,1), vec2(1,1), vertIndex );
|
|
// calc the vertCoord of actual line segment
|
|
//vertCoord.xy = quad( -wh*n-lh*t, +wh*n-lh*t, -wh*n+lh*t, +wh*n+lh*t, vertIndex);
|
|
vertCoord.xy = mul(qc,mat2(wh*n,lh*t));
|
|
vertCoord.xy = mul(vertCoord.xy,ROT2(StrokeAng));
|
|
vertCoord.xy += brushPos.xy;
|
|
//vertCoord.xy -= wh0*.25*StrokeBend*n;
|
|
vertCoord.xy = vertCoord.xy/Res*2.-1.;
|
|
// bg plane for drawing canvas
|
|
vertCoord.xy = (pidx==0) ? qc : vertCoord.xy;
|
|
|
|
vertCoord.z = brushPos.z*.01;
|
|
vertCoord.w = 1.;
|
|
|
|
vertAttrib[1].xy = qc*.5+.5;
|
|
vertAttrib[0]=getCol(brushPos.xy,1.);
|
|
vertAttrib[0].w=idxFact;
|
|
vertAttrib[1].w=wh0;
|
|
vertAttrib[1].z=float(layer);
|
|
vertAttrib[2].x=float(pidx);
|
|
//if(int(iMouseData.w)/1!=0)
|
|
// vertAttrib[1].w=1.;
|
|
}
|
|
|
|
uniform float Canvas;
|
|
uniform float StrokeSat;
|
|
uniform float StrokeContour;
|
|
uniform float StrokeDir;
|
|
uniform vec3 CanvasTint;
|
|
|
|
float getCanv(vec2 fragCoord)
|
|
{
|
|
float canv=0.;
|
|
canv=max(canv,(getRand(fragCoord.xy*vec2(.7,.03).xy)).x);
|
|
canv=max(canv,(getRand(fragCoord.xy*vec2(.7,.03).yx)).x);
|
|
canv-=.6;
|
|
return canv;
|
|
}
|
|
|
|
#ifdef MULTI_STROKE
|
|
uniform vec2 strokeNumXY;
|
|
#endif
|
|
|
|
float getStroke(vec2 uv, int pidx, vec2 fragCoord, float canv,vec2 d)
|
|
{
|
|
uv.y*=1.-step(0.1,StrokeDir)*2.;
|
|
|
|
vec4 rnd = getRand(pidx);
|
|
#ifdef SHADEROO_FRAGMENT_SHADER
|
|
uv+=dFdx(uv)*d.x;
|
|
uv+=dFdy(uv)*d.y;
|
|
#endif
|
|
uv+=.5;
|
|
#ifdef MULTI_STROKE
|
|
ivec2 xynum=ivec2i(max(vec2(strokeNumXY),vec2i(1)));
|
|
uv += vec2(pidx%xynum.x,(pidx/xynum.x)%xynum.y);
|
|
uv /= vec2i(xynum);
|
|
#endif
|
|
vec4 stroke = texture(iChannel2,uv);
|
|
float s = stroke.x;
|
|
float smask = stroke.y;
|
|
|
|
fragCoord+=d;
|
|
|
|
s+=clamp(1.-smask*5.,0.,1.)*getCanv(fragCoord)*Canvas*3.;
|
|
s+=clamp(1.-smask*5.,0.,1.)*(getRand(fragCoord.xy*.7).z-.5)*Canvas*1.5;
|
|
|
|
return s;
|
|
}
|
|
|
|
uniform float PaintShiny;
|
|
uniform float PaintSpec;
|
|
uniform float PaintDiff;
|
|
uniform float LightAng;
|
|
uniform float LightOffs;
|
|
uniform float halfFOV;
|
|
uniform float CanvasBg;
|
|
|
|
void mainFragment( out vec4 fragColor, vec4 fragCoord, vec4 vertAttrib[3] )
|
|
{
|
|
int pidx = int(vertAttrib[2].x);
|
|
int layer = int(vertAttrib[1].z);
|
|
float wh0 = vertAttrib[1].w;
|
|
float canv=getCanv(fragCoord.xy);
|
|
|
|
//float w=vertAttrib[0].w/iResolution.x;
|
|
// draw a line with smooth falloff
|
|
// triangular falloff
|
|
vec2 uv=vertAttrib[1].xy-.5;
|
|
vec3 n = vec3(0,0,1);
|
|
float s=getStroke(uv,pidx,fragCoord.xy,canv,vec2i(0));
|
|
float s0=s;
|
|
#ifdef SHADEROO_FRAGMENT_SHADER
|
|
float ws=fwidth(s);
|
|
// use this for non 0-clamped tex
|
|
s=smoothstep(-ws,ws,s);
|
|
// use this for 0-clamped tex:
|
|
//s=clamp(s*10.,0.,1.);
|
|
#endif
|
|
vec2 d=vec2(.7,0);
|
|
vec2 g = vec2(
|
|
getStroke(uv,pidx,fragCoord.xy,canv,d.xy)-s0,
|
|
getStroke(uv,pidx,fragCoord.xy,canv,d.yx)-s0
|
|
)/d.x;
|
|
n=normalize(vec3(-g,1.));
|
|
|
|
vec2 uvs=fragCoord.xy/Res;
|
|
vec2 cso=CS(LightOffs);
|
|
vec3 light = vec3i(CS(LightAng)*cso.y,cso.x);
|
|
float diff=clamp(dot(n,light),0.,1.0);
|
|
vec3 eyeDir = normalize(vec3i((-uvs*2.+1.)*tan(halfFOV)*vec2(1.0,Res.y/Res.x),1));
|
|
float spec=clamp(dot(reflect(-light,n),eyeDir),0.0,1.0);
|
|
//spec=pow(spec,10.0);
|
|
spec=pow(spec,20.*PaintShiny+1.01)*(.5+PaintShiny);
|
|
|
|
fragColor.xyz = vertAttrib[0].xyz*mix(1.,diff,PaintDiff)+spec*PaintSpec;
|
|
fragColor.w=s;
|
|
vec4 canvCol=vec4i(vec3i(mix(1.,canv+.5,.14*CanvasBg))*CanvasTint,1);
|
|
fragColor = pidx==0 ? canvCol : fragColor;
|
|
}
|
|
|
|
|