椰子树

1,028 阅读4分钟

YouTube 又可以用了,我就捡起了好久没搞的shader(数学)绘图 。

看视频还是有不便之处的(代码不太容易看清),幸好站上也有代码。

本文,跟着视频,看着代码,一起来绘制一棵椰子树。

准备好shader编程环境。

一个渐变色的背景没啥好说的。


vec2  st  = v_Uv ;

vec3 color = mix(vec3(1.,.3,.3), vec3(1.,.8,.3) ,st.y) ;

树叶

绘制之前,再啰嗦一下,源代码是没有处理xy分量的差异的,它的绘图尺寸是个矩形,所以绘制出来是这个效果。

这个树叶是由椭圆变化而来的,先画一个(椭)圆。 简单的smoothstep的应用, 圆心在(.33, .7) ,半径为r, 因为后面还会用到以圆心为原点的坐标进行计算, 所以这里声明了 c1

vec2  c1 =  st - vec2 (.33, .7) ;//
float c1s = dot(c1,c1);
float len  =sqrt(c1s);

float r  = .1 ;
color *= smoothstep(r, r+ .01, len) ;
fragColor = vec4(color, 1.);

image.png

叶子

让这个圆长出叶子, 其实就是让这个圆的半径不再是恒定的,而是周期变化的。

r+= .04 * cos(atan(c1.y,c1.x) * 10.) ;

image.png

这里在原本r的基础上又加了一个余弦值, 这个值得范围是【-0.04,0.04】, 结果就是r的范围成了【0.06,0.14】, 周期性变化。 从距离场的角度来理解可能更好一些。 这个图形就是到圆心距离小于r的集合, 而r的值和其以圆心为极点的 极坐标的角度有关。

乘以10是为了增加叶子的数目, 一个周期一个叶子,现在角度的值域范围扩大至原本的10倍,可以表现出10个周期了。 你可以自行调整。

再次强调, 这里之所以是椭圆,是因为没处理。 这里把画布尺寸调整为正方形给大家看看。

image.png

强调这个,是因为要说明,这里的角度本来是(一圈)均匀分布的。 现在要加一点东西,让它不再均匀分布,因为椰子树叶显然不可能这么匀称。

r+= .04 * cos(atan(c1.y,c1.x) * 10. + 20.*st.x ) ;

效果就是这样了, 现在是关于y轴对称, 如果你不希望这样,可以加点别的干扰,或者改变这个三角函数的初相。 下面会修改初相,目的是为了和树干配合。 image.png

树干

先来个简单的矩形,然后这个矩形超出上面圆心的部分要被砍掉。可以看到,一个电线杆似的树干,太直了。 d是树干的宽度 ,后面还要继续处理。

float d = .02 ;
float trunck  = smoothstep( d,d -0.01 ,abs(c1.x ))* step(c1.y,0.) ;
color =  mix( color, vec3(0), trunck);

这个树干太直了,太假了,搞弯一点。 如果是我来写,我肯定就来一个曲线函数,替换d,放到smoothstep里, 这是典型的造型函数绘制曲线的方法。
不过他这里,要巧妙一些。用sin(y)影响了其正直。虽然很巧妙,但是总觉得这东西不能复用。

float trunck  = smoothstep( d,d -0.01 ,abs(c1.x -.2* sin(c1.y*2.)))* step(c1.y,0.) ;

image.png

还有,这个树干表面太光滑了,我想来点凹凸不平。 这里就用上造型函数了。为了让宽度不至于为零这里系数比较小,诸君可以自行调整 , 还有波纹的数量。

d += .005*sin(c1.y *100.) ;

image.png

地形

虽然,这里还有一个地形,但是在代码上这个地形是树干的一部分。 这个地形,就是一个土包,希望就是树底下是这个土包的最高点。

这里使用指数函数, 指数爆炸, 当y = 0的时候,结果是1 ,1就是铺满整个横向了。 当y=某个值,比如0.1的时候,我们希望它趋于0 , 越往上越接近0 ,无穷小, 很明显,只有负指数能做的。 所以函数如下。 后面加了一个常数,可以微调其最高点的位置。

d+= exp(-40.* (st.y -.01)  );

image.png

结束

本文搬运了shaderToy站长的教程 The basics of Painting with Maths源代码

使用了一些内置函数和图形的加减乘运算,最后绘制出一棵椰树的投影。

前面一直在说这里没处理xy的差异,会导致图形随画布而变形。如果想固定形状的,可以先在一定的尺寸下绘制好,记录这个尺寸, 然后在代码里校正xy的差异, 最后再缩放回那个尺寸。 大致思路就是这样了。

至于为什么不直接绘制一个椭圆,然后修改,当然是因为麻烦, 如果用椭圆方程的话,这里就不能简单的使用半径了。

void main (){ 

t = u_Time /1000. ;
float ratio = u_CanvasSize.x/u_CanvasSize.y;
vec2  st  = v_Uv ;
st-= .5 ;
st.x*= ratio ;    
st +=.5;
st*= vec2(1.,2.);
vec2  c1 =  st - vec2 (.33, .7) ;//
float c1s = dot(c1,c1);
float len  =sqrt(c1s);
vec3 color = mix(vec3(1.,.3,.3), vec3(1.,.8,.3) ,st.y) ;
float r  = .1 ;
r+= .04 * cos(atan(c1.y,c1.x) * 10. + 20.*st.x  -2.2) ;


color *= smoothstep(r, r+ .01, len) ;
float d = .02 ;
d += .005*sin(c1.y *100.) ;
d+= exp(-40.* (st.y -.01)  );

float trunck  = smoothstep( d,d -0.01 ,abs(c1.x -.2* sin(c1.y*2.)))* step(c1.y,0.) ;

color =  mix( color, vec3(0), trunck);
    fragColor = vec4(color, 1.);
}

本文的前置知识,在基础专栏里。