手写Shader1:UV基础、坐标变换

96 阅读4分钟

1-0 参数说明&性能常识

uniform vec3      iResolution;   	 // 窗口分辨率,单位像素
uniform float     iTime;        	 // 程序运行的时间,单位秒
uniform float     iTimeDelta;   	 // 渲染时间,单位秒
uniform int       iFrame;       	 // 帧率
uniform float     iChannelTime[4];       // 信道播放时间(秒)
uniform vec3      iChannelResolution[4]; // 通道分辨率(像素)
uniform vec4      iMouse;        	 // 鼠标位置
uniform samplerXX iChannel0..3;          // 输入通道  XX = 2D/Cube
uniform vec4      iDate;          // 日期(年,月,日,时)
uniform float     iSampleRate;    // 声音采样率 (i.e., 44100)
void mainImage(){}				  //main函数

周期性红色闪烁

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // 将像素位置映射到0-1
    vec2 uv = fragCoord.xy/iResolution.xy;// [0, 1]
    
    // 获取通道0纹理在uv出的像素颜色
    fragColor = texture(iChannel0, uv);// 黑色
    
    // 让红色分量的值随时间改变。
    fragColor.r = abs(sin(iTime));
}

image.png

当编写着色器代码时,可以考虑以下性能优化的小技巧,以最大程度地提高着色器的执行效率:

  1. 使用向量化操作: 在处理多个像素或顶点时,使用向量和矩阵运算可以显著提高性能。例如,使用矩阵乘法代替逐个元素的计算,使用向量的分量操作代替多次独立的计算。
  2. 避免频繁的纹理采样: 纹理采样是昂贵的操作,尽量减少不必要的纹理采样。可以将多个纹理采样合并为一个,使用缓存或者预计算一些值来减少采样次数。
  3. 减少分支和条件判断: 使用 stepmixsmoothstep 等函数来代替复杂的 if 语句。这些函数在GPU上更容易进行优化,避免了分支预测的问题。
  4. 优化循环: 循环次数应该是常量或者与像素数无关的,避免动态循环次数。在可能的情况下,使用循环展开和向量化操作。
  5. 使用位运算: 对于一些位操作,可以使用位运算符来代替算术运算符,例如使用 &|<<>> 等。
  6. 避免重复计算: 将可能的重复计算结果存储在变量中,避免多次计算相同的值。
  7. 使用常量: 在需要的地方使用常量,避免动态计算常量值。
  8. 尽量使用浮点数运算: GPU上浮点数运算的性能通常较高,尽量使用浮点数运算而不是整数运算。
  9. 使用纹理压缩: 如果可以,使用纹理压缩来减少带宽和内存使用。
  10. 合并操作: 尽量合并多个操作,减少内存读写次数和计算次数。
  11. 使用合适的数据类型: 使用适当的数据类型,避免不必要的精度和开销。
  12. 避免递归: GPU不擅长处理递归,避免在着色器中使用递归。
  13. 利用预处理: 使用预处理指令(如#define)来避免重复代码,提高代码的可维护性。

1-1 把 UV 坐标打印出来

// 建立裁剪坐标系
vec2 ClipCoord(in vec2 fragCoord) {
    return  (fragCoord / iResolution.xy ); // [0, 1]
}
// 主渲染函数,输出渲染结果
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 coord = ClipCoord(fragCoord);
    fragColor = vec4(coord, 0, 1);
}
// UV 坐标,左下角为(0,0) 右上角为(1,1)
// 

image.png

1-2 将坐标原点从左下角移动到右上角

// 建立裁剪坐标系
vec2 ClipCoord(in vec2 fragCoord) {
    return 2. * (fragCoord / iResolution.xy - 0.5); // [-1, 1]
}
// 主渲染函数,输出渲染结果
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 coord = ClipCoord(fragCoord);
    fragColor = vec4(coord, 1, 1);
}

image.png

1-3 绘制圆形

// 建立裁剪坐标系
vec2 ClipCoord(in vec2 fragCoord) {
    return 2. * (fragCoord / iResolution.xy - 0.5); // [-1, 1]
}
// 主渲染函数,输出渲染结果
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 coord = ClipCoord(fragCoord);// 坐标变换
    float coordlen = length(coord);// 
    fragColor = vec4(vec3(coordlen), 1);
}
// 为什么是椭圆?

image.png

1-4 绘制实心圆形

// 建立裁剪坐标系
vec2 ClipCoord(in vec2 fragCoord) {
    return 2. * (fragCoord / iResolution.xy - 0.5); // [-1, 1]
}
// 主渲染函数,输出渲染结果
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 coord = ClipCoord(fragCoord);
    float coordlen = length(coord);
    vec3 rgb = vec3(step(0.1, coordlen));
    fragColor = vec4(rgb, 1);
}

image.png

1-5 绘制真正的圆形

// 建立投影坐标系
vec2 ProjectionCoord(in vec2 fragCoord) {
    return 2. * (fragCoord.xy - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);// 标准化纵向的最大距离是1
}
// 主渲染函数,输出渲染结果
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 coord = ProjectionCoord(fragCoord);//  坐标变换,使得物体被投影到1*1的中心区域
    float coordlen = length(coord);//  求长度
    vec3 rgb = vec3(step(1.0, coordlen) * vec3(1, 0, 1));//  根据长度来染色
    fragColor = vec4(rgb, 1);
}

image.png