本文正在参加「金石计划」
简述
众所周知, 从顶点到片元经历了图元装配 、光栅化这两个主要流程。
图元装配又分图片组装和图元处理。
组装就是,按绘制模式,将顶点结合成完整的图元。 点模式,不用组装,一个点一个图元。 线模式,两个顶点组成一条线段图元。面模式, 三个顶点组成一个三角形图元。
图元处理的主要工作就裁剪,这个裁剪是指消除不在裁剪空间里的片元。 理点图元只需要简单的决定是否剔除这个点即可。 对于线面来说, 裁剪之后可能需要额外增加顶点
光栅化,就是把这些个图元离散化成一个个的栅格。 图形看上去是连续的,但是实际上我们的显示设备是离散的,所以必须处理成一个个的片元(对应一个像素)。
言归正传,本文要说的是顶点着色器到片元着色器, 写代码相关的。
顶点着色器
#version 300 es
#define attribute in
#define varying out
// layout(location = 0)in vec4 a_Position ;
attribute vec4 a_Position ;
// layout(location = 1)in vec3 a_Color ;
attribute vec3 a_Color ;
attribute vec2 a_Uv;
varying highp vec2 v_Uv;
varying vec3 v_Color ;
// 3.0 没有 attribute varying 但是可以通过预编译指令兼容
void main(){
gl_Position = a_Position ;
gl_PointSize = 2.0;
v_Uv = a_Uv;
v_Color= a_Color;
}
我们使用JS往着色器传输数据(也可以说是从CPU到GPU),有两种, attribute
和uniform
。
之所以这么叫法,是GLSL1.0的规范,就是这两个关键字。 而3.0版本后attribute
改成了in
。 语义上更明确了,attribute,就是输入顶点着色器的数据。
顶点着色器的代码,在一次流水线中, 每一个顶点都会执行一次。 什么叫每个顶点都会执行一次,就是如果把整个的顶点着色器代码,看做是一个函数, 这些个用attribute
或者in
修饰的变量,其实就是函数的参数 ,遍历每一个顶点把对应的参数传进去,执行整个着色器代码。
上面的片元着色器中,输入了a_Position, a_Uv, v_Color
这三个attribute
变量,每次执行顶点着色器代码的时候,这个三个变量就会是对应的值。
varying
在GLSL1.0中表示 顶点着色器输出到片元着色器的数据,3.0改为out
关键字。 结合起来看就是一个输入in
,一个输出out
。
gl_Position
是内置的输出变量,所以不需要用关键字。 它决定了顶点的位置,也就是后面在哪一块绘图。
小结一下就是,顶点着色器输入attribute
(in)变量,输入varying
(out)变量 和内置gl_Position
,uniform
只有输入。
片元着色器
#version 300 es
precision mediump float;
#define varying in
out highp vec4 Ocolor ;
#define gl_FragColor Ocolor
varying highp vec2 v_Uv;
in vec3 v_Color ;
uniform vec2 u_CanvasSize ;
uniform vec2 u_Mouse ;
uniform float u_Time ;
// 3.0 没有 attribute varying 但是可以通过预编译指令兼容
void main(){
t = u_Time/1000.;
vec2 uv =gl_FragColor.xy/ u_CanvasSize;
gl_FragColor = vec4(v_Color,1.);
}
这里,单讲面模式。 面模式下的片元,就是开头所说的光栅化得到的。 片元着色器的代码,每个片元都会执行一次。所以,片元着色器的执行次数可想而知,一般都建议把能放在顶点着色器的计算,就不要拿到片元来了。
对于片元着色器来说 , gl_FragCoord
等于是内置的输入变量,四维向量,其xy对应的是物理坐标,如果是webgl,那就是对应画布元素上点坐标,以画布的左下角为原点,Y轴向上,X轴向右。z是深度,值域[0,1],w对应齐次坐标的w。 w的用途一般就是去齐次。
可以看做是,gl_FragCoord
接收了顶点着色器输出的gl_Position
, 当然,是插值后的。
线性插值
上面的代码里,输入了v_Uv v_Color
这两个变量 , 这两个变量哪里来的? 就是前面的顶点着色器输出的, 并且经过插值了,才输入到片元这里。 插值的方式当然是线性插值。
关于插值的理解,请看绘制的结果。 我们只是分别给了四个点四种颜色, 结果可以看到,在四个角上,确实是那四种颜色,但是中间区域却是他们的混合。
假如两个顶点AB的颜色分别是a b, 那么 a ,b 连线上任一点的颜色为 color = x * a + (1-x)* b
。 其中 x 代表的是在线段AB上的百分比。
正常情况下是插值的,我们再来看一下,不插值的效果。 flat 关键字可以实现这一点,不插值的情况下,一般都是取这个图元的最后一个顶点对应的数据。我这里用的是三角扇模式, 第二个三角形的最后一个顶点就是右下角那个,而第一个三角形的最后一个顶点是左下角那个。
flat out vec3 v_Color ;
..........
flat in vec3 v_Color ;
现在,想必对于片元的插值有了更感性的认知了吧。
有了输入,那么输出什么呢? 输出颜色,每个片元会对应一个颜色输出,片元着色器结束之后,对应片元的颜色就确定了。 GLSL1.0 用的是内置变量 gl_FragColor
来接收要输出的颜色, 到了3.0之后,没有这个内置变量了, 需要自己声明一个, 高精度的四维向量,用作颜色输出。
简单理解,就是顶点着色器定形,片元着色器上色。
shaderToy的代码,形已经完全定了,就是一个矩形,剩余的全靠片元着色器发挥。
小结一下就是, 片元着色器能接收从顶点着色器输出的varying(out)变量,并最终输出颜色变量。
补充和结束
补充一下uniform
的例子, 这里就是给所有的顶点加一个相同的偏移量,每个顶点都在x上偏移了.2,结果就是整个图形往右移动了一点,所以也可以称之为整体控制的变量,uniform 影响的是整个图片。
//js
gl.uniform1f(offset,
.2);
// 顶点着色器
uniform float offset ;
。。。
gl_Position.x+=offset ;
最后用一份伪代码结束。
原文链接:https://juejin.cn/post/7214110277120507962 作者:莫石