在这篇文章中,我们会讲解如何给物体添加颜色,你将了解如下信息:
- 如何在JavaScript中给shader传递颜色
- shader的varying
- shader的浮点数精度问题
- 光栅化概念
添加颜色
还是先给出完整的代码,在这里。
下面我们看看有哪些地方发生了变化。首先来看shader:
vertex shader:
attribute vec3 aPos; attribute vec4 aColor; varying vec4 vColor; void main(void){ gl_Position = vec4(aPos, 1); vColor = aColor; }
fragment shader:
precision highp float; varying vec4 vColor; void main(void) { gl_FragColor = vColor; }
在vertex shader中,出现了第二个attribute:aColor,类型是vec4,另外还有一个varying(varying的字面意思是:可变的,目前没有找到一个合适的词来翻译,你把它理解成一种变量即可,varying的作用后面会说)类型也是vec4。在main函数中我们看到除了给内置变量gl_Position赋值以外还把aColor赋值给了vColor。
在fragment shader中,首先出现了这一行:
precision highp float;
它的作用是设置fragment shader的浮点数精度,因为现在出现了变量,vertex shader因为有默认的精度所以可以不用显式设置。
接下来的一行同样出现了varying,定义和在vertex shader中一样。在main函数中我们把vColor赋值给gl_FragColor。
从上面的代码我们可以看出varying的作用是将数据从vertex shader传递给fragment shader,它是vertex shader的输出、fragment shader的输入。使用时需要在两个shader中对varying进行定义。
最后再说说shader的命名规范。shader中我们通常用第一个字母来标志数据的类型,即匈牙利命名法。attribute用a开头(如aColor)、varying用v开头(比如vColor)等等。
在JavaScript代码里,setupBuffer函数中增加了给aColor赋值的代码,这和给aPos赋值过程几乎一致:
var color = [ 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1 ]; var colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW); var aColorPosition = gl.getAttribLocation(glProgram, 'aColor'); gl.vertexAttribPointer(aColorPosition, 4, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(aColorPosition);
在color数组中,我们为每一个点添加颜色信息,每个点的颜色是个四元组,即RGBA四项内容,取值范围0-1。后面的代码你已经见过了了,无非是创建缓冲区、获取attribute索引并让它指向颜色数据。
在浏览器中你会看到如下结果:
你可能会有疑问,我只给三角形的三个顶点赋值了,那么之间区域的颜色是怎么确定的?这还是与渲染管线有关。
再看渲染管线
在前面的文章中,我们给出了一个简单的渲染管线图,现在我们在这张图上补充一些新内容:
- 在shader中增加varying
- 增加光栅化过程
varying
前面讲到,varying可以用来从vertex shader向fragment shader传递数据,它是前者的输出、后者的输入。
光栅化
光栅化(Rasterization)过程发生在fragment shader执行之前,vertex shader处理的是顶点信息、fragment shader处理的是像素,那么光栅化的作用的就是将顶点信息转换为像素。如果你熟悉Photoshop、Illustrator一类的绘图软件,这个过程就如同将矢量图形转换为位图。在转换过程中,每一个像素的颜色将会通过插值计算的方式计算的得出,这就是为什么我们会在三角形填充区域看到的那些颜色。
现在我们在管线图中增加以上内容。
一些修改
为了验证Rasterization会计算插值颜色,我们可以修改fragment shader代码:
precision highp float; varying vec4 vColor; int findMax(float r, float g, float b) { if (r > g && r > b) { return 0; } if (g > r && g > b) { return 1; } return 2; } void main(void) { float red = vColor.r; float green = vColor.g; float blue = vColor.b; int max = findMax(red, green, blue); vec4 finalColor = vColor; if (max == 0) { finalColor = vec4(1.0, 0.0, 0.0, 1.0); } else if (max == 1) { finalColor = vec4(0.0, 1.0, 0.0, 1.0); } else if (max == 2) { finalColor = vec4(0.0, 0.0, 1.0, 1.0); } gl_FragColor = finalColor; }
vColor是插值好的颜色,我们这里不希望进行插值计算,在不同颜色相交界的地方采用硬处理。做法是读取每个像素的颜色值,看r、g、b三个分量哪个最大,然后将这个像素的颜色赋值为最大的那个颜色,比如r分量最大,则该点最终修改为纯红色,即1, 0, 0, 1。修改之后会得到如下效果:
果然,插值出来的颜色被我们改掉了,同时也验证了在fragment shader执行之前vColor已经是插值好的颜色。完整的代码请看这里。
留言