1.新建代码文件02-绘制坐标轴(归一化,偏导数).glsl,导入如下代码:
//shader toy 是一个在线的着色器编辑器,可以在线编辑着色器代码,实时预览效果。
//下面是一些shader toy的常见内置变量和函数,内置变量和函数可以直接在shader toy中使用,不需要自己定义。
//uniform vec3 意思是从外部传入一个vec3类型的变量
//uniform float 意思是从外部传入一个float类型的变量
//uniform sampler2D 意思是从外部传入一个纹理
//uniform vec2 意思是从外部传入一个vec2类型的变量
//uniform vec3 iResolution; // 屏幕分辨率
//uniform float iTime; // 时间
//uniform float iTimeDelta; // 时间增量
//uniform int iFrame; // 帧数
//uniform float iChannelTime[4]; // 通道时间
//uniform vec3 iChannelResolution[4]; // 通道分辨率
//uniform vec4 iMouse; // 鼠标点击位置
//uniform sampler2D iChannel0; // 通道0纹理
//uniform sampler2D iChannel1; // 通道1纹理
//uniform sampler2D iChannel2; // 通道2纹理
//uniform sampler2D iChannel3; // 通道3纹理
//uniform float iDate; // 年月日时分
//uniform float iSampleRate; // 采样率
//uniform vec4 fragCoord; // 片元坐标
//uniform vec4 fragColor; // 片元颜色
//uniform float fragDepth; // 片元深度
//mainImage是一个片元着色器函数,入参为out vec4 fragColor, in vec2 fragCoord,出参为片元颜色,
//这个函数是shader toy中的一个固定函数,shader toy会自动调用这个函数,所以我们只需要在这个函数中写我们的片元着色器代码即可。
//有没有其他的固定函数呢?有的,比如mainImage之前还有一个固定函数叫做main,这个函数是shader toy中的一个固定函数,shader toy会自动调用这个函数,所以我们只需要在这个函数中写我们的代码即可。
//顶点着色器函数是什么呢?名为vertexShader,入参为out vec4 fragColor, in vec2 fragCoord,出参为片元颜色,这个函数是shader toy中的一个固定函数,shader toy会自动调用这个函数,所以我们只需要在这个函数中写我们的顶点着色器代码即可。
//main函数和顶点着色器函数、片元着色器函数的关系是这样的,先调用main函数,然后调用顶点着色器函数,最后调用片元着色器函数。
//他们是同步进行的吗?是的,他们是同步进行的,也就是说main函数执行完毕之后,才会调用顶点着色器函数,顶点着色器函数执行完毕之后,才会调用片元着色器函数。
//main函数中通常写些什么代码呢?main函数中通常写一些初始化代码,比如设置背景颜色,设置纹理,设置一些uniform变量等。
//uniform变量是什么意思?uniform变量是从外部传入的变量,比如我们可以从外部传入一个纹理,一个颜色,一个时间等。
//还有其他变量吗?有的,比如我们可以定义一个局部变量,一个全局变量,一个常量等。
//如何从外部传入变量,以js为例怎么样传入?下面是具体的代码:
//如何自己定义一个uniform变量
//uniform vec3 bg_color; // 背景颜色
//自己定义的uniform变量,可以在shader toy中设置,也可以在代码中设置,在代码中设置的方法如下:
//uniform vec3 bg_color = vec3(0.0, 0.0, 0.0); // 背景颜色
//注意这里没有变量类型,直接使用等号赋值,这是因为uniform变量只能在shader toy中设置,不能在代码中设置,所以这里只是声明了一个变量,没有赋值。
// 归一化屏幕坐标系
vec2 ProjectionCoord(in vec2 fragCoord, in float scale) {
//本函数实现将屏幕坐标转换为以画布中心为原点的坐标系,并进行归一化
//首先当前画布左上角为原点,右下角为iResolution.xy,我们需要将其转换为以中心点为原点,
vec2 centerCoord = fragCoord - 0.5 * iResolution.xy;
//然后将坐标归一化, 这里需要取屏幕宽高的最小值,因为画布可能不是正方形,我们需要保证最小的一边长度为1,范围为(-0.5,0.5)
vec2 normalizationCoord = centerCoord / min(iResolution.x, iResolution.y);
//此时坐标系的范围是(-0.5,0.5), 为了让坐标系的范围是(-1,1), 我们再乘以2
vec2 dobuleNormalizationCoord = 2. * normalizationCoord;
//最后我们再乘以scale, 这样我们就可以控制坐标系的大小
return scale * dobuleNormalizationCoord;
}
//坐标轴辅助对象,筛选出归属于坐标轴的片元,返回坐标轴颜色
vec4 AxisHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor) {
//设置默认颜色为黑色透明度为0
vec4 color = vec4(0.0);
//这里dx为例,dFdx返回当前点的coord.x与相邻点的coord.x的差值,然后乘以axisWidth,得到一个屏幕上的一个像素在x方向上的长度
// dFdx 的入参 必须是一个引用 而不能是一个具体的数值是吗,因为他要靠这个引用来查找相邻片源的该值
float dx = dFdx(coord.x) * axisWidth;
float dy = dFdy(coord.y) * axisWidth;
if(abs(coord.x) < dx) {
color = xAxisColor;
} else if(abs(coord.y) < dy) {
color = yAxisColor;
}
return color;
}
//网格辅助对象,筛选出归属于网格的片元,返回网格颜色
vec4 GridHelper(in vec2 coord, in float gridWidth, in vec4 gridColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * gridWidth;
float dy = dFdy(coord.y) * gridWidth;
//每单位长度的余数,如果说想要2个单位长度的网格,那么需要把coord.x和coord.y都除以2,然后取余数
vec2 fraction = fract(coord);
if(fraction.x < dx || fraction.y < dy) {
color = gridColor;
}
return color;
}
// 投影坐标系辅助对象
vec4 ProjectionHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor, in float gridWidth, in vec4 gridColor) {
// 坐标轴
vec4 axisHelper = AxisHelper(coord, axisWidth, xAxisColor, yAxisColor);
// 栅格
vec4 gridHelper = GridHelper(coord, gridWidth, gridColor);
// =投影坐标系
return bool(axisHelper.a) ? axisHelper : gridHelper;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
//片元着色器代码,入参为片元坐标,出参为片元颜色
vec2 uv = ProjectionCoord(fragCoord, 3.);
fragColor = ProjectionHelper(uv, 1., vec4(1, 0, 0, 1), vec4(0, 1, 0, 1), 1., vec4(0.57, 0.57, 0.57, 1.0));
}
右键预览,能看到如图所示坐标系:
总结:
分模块绘制坐标轴与网格有利于更清晰的管理代码逻辑,使用归一化来处理坐标有利于在不同屏幕画布尺寸上有相同规则的结果
使用偏导数函数入参为一个值的引用,返回的是相邻片元与当前片元该引用的差值,为什么叫变化率,这是因为shader中的偏导数函数的分母始终是1个片元,实际上这个差值是除以了1的,但是除1结果不变,最终的结果就是两个片元在该值上的差