宝哥软件园

只要1K纯JS脚本送你一朵3D红玫瑰

编辑:宝哥软件园 来源:互联网 时间:2021-09-07

罗曼科尔特斯带来了用JavaScript编写的红玫瑰。用代码做的玫瑰是牛逼程序员送给女朋友最好的情人节礼物!(提示:不同浏览器下观看效果和速度会有很大不同)

图片由代码生成,用户可以刷新页面,反复观看这支玫瑰的呈现过程。

3D rose的实现代码如下:

复制代码如下:其中(m=数学)c=cos,s=sin,p=pow,r=randomc . width=c . height=f=500;h=-250;函数p(a,b,c){if(c60)返回[S(a*7)*(13 5/(.2 P(b*4,4)))-S(b)*50,b*f 50,625 C(a*7)*(13 5/(.2 P(b*4,4))) b*400,a*1-b/2,a];a=a * 2-1;b=b * 2-1;if(A*A B*B1){if(c37){n=(j=c1)?6:4;o=. 5/(a . 01)C(b * 125)* 3-a * 300;w=b * h;返回[o*C(n) w*S(n) j*610-390,o*S(n)-w*C(n) 550-j*350,1180 C(B A)*99-j*300, 4-a*.1 P(1-B*B,-h * 6)* . 15-A * B * . 4 C(A B)/5 P(C((o *(A)1)(B0?w:-w))/25)、30)*.1*(1-B*B)、o/1e 3 . 7-o * w * 3e-6]} if(c32){ c=c * 1.16-. 15;o=a * 45-20;w=b * b * h;z=o * S(C)w * C(C)620;返回[o*C(c)-w*S(c),28 C(B*.5)*99-b*b*b*60-z/2-h,z,(b*b*.3 P((1-(A*A)),7)*.15 .3)*b,B *]} o=A *(2-B)*(80-C * 2);w=99-C(A)* 120-C(b)*(-h-C * 4.9)C(P(1-b,7))* 50 C * 2;z=o * S(C)w * C(C)700;返回[o*C(c)-w*S(c),B*99-C(P(b,7))*50-c/3-z/1.35 450,z,(1-b/1.2)*.9 a*.1,P((1-b),20)/4 . 05]} } setInterval(' for(I=0;i1e4i )if(s=p(R),(R(),iF/. 74)){ z=s[2];x=~ ~(s[0]* f/z-h);y=~ ~(s[1]* f/z-h);if(!m[q=y*f x]|m[q]z)m[q]=z,a . fill style=' RGB(~(s[3]* h)',' ~(s[4]*h)',' ~(s[3]*s[3]*-80)')',a.fillRect(x,y,1,1)} ',0)

当然,感兴趣的人可以了解以下实施过程和相关理论:

这款3D代码玫瑰的渲染效果采用了蒙特卡罗方法,受到了创作者的高度赞扬。他说,蒙特卡罗方法在函数优化和采样方面是一个“非常强大的工具”。可以参考蒙特卡罗方法。

具体操作:

外观采样渲染效果图

我使用了许多不同的形状来形成这个代码玫瑰。总共使用了31种形状:24个花瓣、4个萼片、2片叶子和1个花茎,每一种都用代码描述。

首先,定义一个采样范围:

函数曲面(a,b) { //我使用a和b作为参数,范围从0到1.return {x: a*50,y : b * 50 };//这个表面将是大小为50x50个单位的正方形}然后,写出形状绘制代码:

var canvas=document . body . appendchild(document . createelement(' canvas '),context=canvas.getContext('2d '),a,b,position//现在我将以. 1的间隔对a和b参数进行表面采样: for(a=0;a 1;a=. 1){ for(b=0;B1;b=. 1){位置=曲面(a,b);context.fillRect(position.x,position.y,1,1);} }

现在,尝试更密集的采样间隔:

我们现在可以看到,因为采样间隔越来越密集,点越来越近。在最高密度下,相邻点之间的距离小于一个像素,肉眼看不到间隔(见0.01)。为了不造成太大的视觉差异,采样间隔进一步缩短。此时,绘图区域已被填充(比较结果为0.01和0.001)。

接下来,我用这个公式画一个圆:(x-X0) 2 (y-Y0) 2半径2,其中(X0,Y0)是圆心:

函数曲面(a,b) {var x=a * 100,y=b * 100,半径=50,x0=50,y0=50if ((x - x0) * (x - x0) (y - y0) * (y - y0)半径*半径){//在circlereturn {x: x,y: y}内;circlereturn null之外的else {//个;}}为了防止溢出,应增加一个采样条件:

if (position=surface(a,b)){ context . fill rect(position . x,position.y,1,1);}定义圆有不同的方法,其中一些不需要拒绝采样。我不必用哪一个来定义圆,所以这里有另一种定义圆的方法:

函数曲面(a,b) {//使用极坐标的圆svar角=a *数学.PI * 2,半径=50,x0=50,y0=50返回{x: Math.cos(角度)*半径* b x0,y:数学. sin(角度)*半径* b y0 };}(此方法相比前一个方法需要密集采样以进行填充。)

好了,现在让圆变形,以使它看起来更像是一个花瓣:

函数曲面(a,b) {var x=a * 100,y=b * 100,半径=50,x0=50,y0=50if((x-x0)*(x-x0)(y-y0)*(y-y0)半径*半径){return {x: x,y: y * (1 b)/2 //形变};} else {返回null}}这看起来已经很像一个玫瑰花瓣的形状了。在这里也可以试试通过修改一些函数数值,将会出现很多有趣的形状。

接下来应该给它添加色彩了:

函数曲面(a,b) {var x=a * 100,y=b * 100,半径=50,x0=50,y0=50if((x-x0)*(x-x0)(y-y0)*(y-y0)半径*半径){return {x: x,y: y * (1 b)/2,r: 100 Math.floor((1 - b) * 155),//这将添加gradientg: 50,b : 50 };} else {返回null} }对于(a=0;a 1;a=。01){ for(b=0;B1;b=.001) {if (point=surface(a,b)){ context。fillstyle=' RGB '('点。r '、point.g '、point。b ')';context.fillRect(point.x,point.y,1,1);}}}一片带色的花瓣就出现了。

三维(三维的缩写)曲面和透视投影

定义三维表面很简单,比如,来定义一个管状物体:

函数曲面(a,b) {var angle=a * Math .PI * 2,半径=100,长度=400;返回{x: Math.cos(角度)*半径,y: Math.sin(角度)*半径,z: b * length - length/2,//通过减去长度/2,我已将试管居中于(0,0,0)r : 0 0,g: Math.floor(b * 255),b : 0 { 0 };}接着添加投影透视图,首先需要我们定义一个摄像头:

如上图,将摄像头放置在(0,0,Z)位置,画布在X/Y平面。投影到画布上的采样点为:

var pX,pY,//投影在画布上x和y坐标透视=350,halfHeight=canvas.height/2,halfWidth=canvas.width/2,cameraZ=-700;for(a=0;a 1;a=。001){ for(b=0;B1;b=.01) {if(点=曲面(a,b)){ Px=(点。x *透视)/(点。z-cameraZ)半宽;pY=(点。y *透视)/(点。z-cameraZ)半高;语境。FillStyle=' RGB '(点。r '、point.g '、point。b ')';context.fillRect(pX,pY,1,1);}}}效果为:

z缓冲器

z缓冲器在计算机图形学中是一个相当普遍的技术,在为物件进行着色时,执行"隐藏面消除"工作,使隐藏物件背后的部分就不会被显示出来。

上图是用z缓冲器技术处理后的玫瑰。(可以看到已经具有立体感了)

代码如下:

var zBuffer=[],zBufferIndexfor(a=0;a 1;a=。001){ for(b=0;B1;b=.01) {if(点=曲面(a,b)){ Px=数学。楼层((点。x *透视)/(点。z-cameraZ)半宽);pY=数学。楼层((点。y *透视)/(点。z-cameraZ)半高);zBufferIndex=pY * canvas。宽度Px;if((zBuffer[zBufferIndex]的类型)===' undefined ')| |(点。z zBuffer[zBufferIndex])){ zBuffer[zBufferIndex]=点。z;语境。FillStyle=' RGB '(点。r '、point.g '、point。b ')';context.fillRect(pX,pY,1,1);}}}}旋转

你可以使用任何矢量旋转的方法。在代码玫瑰的创建中,我使用的是欧拉旋转。现在将之前编写的管状物进行旋转,实现绕Y轴旋转:

函数曲面(a,b) {var angle=a * Math .PI * 2,半径=100,长度=400,x=Math.cos(角度)*半径,y=Math.sin(角度)*半径,z=b * length - length/2,yaxirisionangle=-。4,//以弧度为单位!旋转x=x *数学。cos(yaxirisionangle)z *数学。sin(yaxirisionangle),旋转z=x *-数学。sin(yaxirisionangle)z *数学。cos(yaxirisionangle);返回{x: rotatedX,y: y,z: rotatedZ,r: 0,g: Math.floor(b * 255),b : 0 };}效果:

蒙特卡罗方法

至于采样时间,间隔过大或过小都会造成视觉感知不佳,因此需要设置合理的采样间隔,这里使用的是蒙特卡罗方法。

风险值I;window.setInterval(函数(){ for(I=0;我10000;I){ if(point=surface(math . random())(math . random()){ Px=math . floor((point . x * perspective)/(point . z-cameraZ)half width);pY=math . floor((point . y * perspective)/(point . z-cameraZ)half height);zBufferIndex=pY * canvas . width Px;if((type of zBuffer[zBufferIndex]===' undefined ')| |(point . z zBuffer[zBufferIndex])){ zBuffer[zBufferIndex]=point . z;context . FillStyle=' RGB(' point . r ',' point.g ',' point . b ')';context.fillRect(pX,pY,1,1);}}}}, 0);将a和b设置为随机参数,用足够的样本填充表面。我一次画10000点,然后等待屏幕更新。

另外需要注意的是,如果随机数不对,表面填充效果也会不对。在某些浏览器中,Math.random的执行是线性的,这可能会导致表面填充效果出现错误。这时候,像Mersenne Twister(一种随机数算法)这样的东西就不得不用来进行高质量的PRNG采样,以免出错。

完成

为了使rose的每个部分都完整并同时出现,需要添加一个函数来为每个部分设置一个参数,以返回同步的值。玫瑰的每一部分都用一个分段函数来表示。例如,在花瓣中,我使用旋转和变形来创建它们。

虽然表面采样方法是创建三维图形最著名和最古老的方法之一,但在表面采样中添加蒙特卡罗和z缓冲并不常见。对于现实场景的制作来说,这可能不是很有创意,但其简单的代码实现和小巧的体积还是令人满意的。

希望这篇文章能启发计算机图形爱好者尝试不同的渲染方法,玩得开心。(罗曼科尔特斯)

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

更多资讯
游戏推荐
更多+