当前位置: 首页 > news >正文

Shader基础技巧整理

自从接触了shader之后我便深深得爱上了它,因为它独特的编程思考方式冲击着我这十几年的惯性认知。
在向各位大佬学习的过程中,每学到一个新的技巧,我都不禁感叹:“实在是妙!”

本文将整理一些个人常用的shader技巧/方法,只包含片元着色器相关内容。
由于本人尚属初学,所以内容会比较基础。

简单几何图形

区间(带通)


两个阶梯函数叠加构成的带通函数,用数字信号处理的角度去思考貌似是个不错的选择

float Band(float v, float start, float end) {
    float up = step(start, v);
    float down = 1.0 - step(end, v);
    return up * down;
}

矩形


x, y两个方向的带通函数叠加

float Rect(vec2 uv, float l, float b, float r, float t) {
    float x = Band(uv.x, l, r);
    float y = Band(uv.y, b, t);
    return x * y;
}

圆形


圆形比较容易出现锯齿,所以用 smoothstep 做平滑处理。
由于 distance() 依赖 sqrt() 开根号,在一些老硬件上会比较耗时,实际使用时可以考虑转换为和r平方进行比较的公式。

float Circle(vec2 uv, vec2 o, float r, float blur) {
    return smoothstep(r, r-blur, distance(uv, o));
}

混合叠加

上述几何图形函数的输出值是0或1的float(经过 smoothstep() 可能会出现中间值,不过此处可以不考虑)。
0或1的float值可以利用加、减、乘来模拟位运算。比如上述区间、矩形几何图形都是通过乘法叠加。

加减法例子:ET脸


画一个大圆当脸,减去两个小圆当眼睛

float ETFace(vec2 uv, vec2 o) {
    float c = Circle(uv, vec2(.0, .0), 0.5, 0.01);
    c -= Circle(uv, vec2(-.2, -.2), 0.2, 0.01);
    c -= Circle(uv, vec2(.2, -.2), 0.2, 0.01);
    return c;
}

坐标空间处理

Cocos Creator以 左上角 为坐标原点,范围(0, 1)。
shadertoy, GlslEditor中均以 左下角 为坐标原点,范围(0, 1),接下来所有代码将使用这个坐标系。

在绘制某些对称图形时可能需要将原点调整到屏幕中心,即将(0, 1)区间映射到(-0.5, 0.5)。
根据不同场景也可以将(0, 1)区间映射到(-1, 1),哪个处理起来方便用哪个。

// (0, 1)区间映射到(-1, 1)
uv = uv * 2.0 - 1.0;

也可以用下面的方法从任意区间映射到任意区间

float Remap01(float a, float b, float t) {
    return (t-a) / (b-a);
}


float Remap(float a, float b, float c, float d, float t) {
    return Remap01(a, b, t) * (d-c) + c;
}

长宽适配

不知道这个功能的简称是什么,暂且这么称呼吧。
其作用是在分辨率长宽不等的情况下将坐标空间映射为等边,映射后原先较长的一边其自变量区间会被放大

void main() {
    vec2 uv = gl_FragCoord.xy/u_resolution.xy;
    uv = uv * 2.0 - 1.0;            // 位移到以中间为原点
    uv.x *= u_resolution.x/u_resolution.y;    // 将x的自变量区间拉长
    
    float mask = Rect(uv, -0.5, -0.5, 0.5, 0.5);
    vec3 color = vec3(mask);
    gl_FragColor = vec4(color,1.0);
}


计算角度


atan() 计算角度,图中将(-PI, PI)区间映射到(0, 1)区间,并且将值对应的灰度输出。
atan() 计算比较耗时,实际项目中慎用。

#define PI 3.141592653589793


void main() {
    vec2 uv = gl_FragCoord.xy/u_resolution.xy;
    uv = uv * 2.0 - 1.0;
    uv.x *= u_resolution.x/u_resolution.y;
    
    float angle = atan(uv.y, uv.x);
    angle = Remap(-PI, PI, 0., 1.0, angle);
    vec3 color = vec3(angle);
    gl_FragColor = vec4(color,1.0);
}

旋转


uv乘以旋转矩阵

#define PI 3.141592653589793


mat2 Rotate2d(float angle){
    return mat2(cos(angle), -sin(angle),
           sin(angle), cos(angle));
}


void main() {
    vec2 uv = gl_FragCoord.xy/u_resolution.xy;
    uv = uv * 2.0 - 1.0;
    uv.x *= u_resolution.x/u_resolution.y;
    
    uv = Rotate2d(PI / 6.) * uv;
    float mask = Rect(uv, -0.5, -0.5, 0.5, 0.5);
    vec3 color = vec3(mask);
    gl_FragColor = vec4(color,1.0);
}

网格化


将屏幕分割成5x5个网格,每个格子里画一个圆。
原理是将uv拉伸5倍后取小数部分,这样处理后uv会变成每个网格内的局部坐标,这个技巧被广泛使用。

void main() {
    // ...
    uv = fract(uv * 5.);
    float mask = Circle(uv, vec2(0.5), 0.5, 0.01);
    // ...
}

噪音(随机化hash)


获取噪音的方法很多很灵活,输入一般是和uv相关的变量,输出(0, 1)范围的1维或2维值。
只要让人肉眼难分辨出模式,就是一个好用的噪音函数。
噪音的用途非常广泛,可以利用噪音降低图片的人工痕迹,后面会单独整理一篇文章。

float Noise1(vec2 p) {
    return fract(sin(
        dot(p, vec2(12.9898,78.233))
    ) * 43758.5453123);
}


float Noise2(vec2 p) {
    p = frac(p * vec2(123.34, 345.45));
    p += dot(p, p + 34.345);
    return frac(p.x * p.y);
}

常用链接

  • shadertoy
    可以找到很多大神级shader作品
    http://shadertoy.com/

  • The Art Of Code频道
    边做边讲解,带你领略01之间的艺术
    https://www.youtube.com/c/TheArtofCodeIsCool/videos

  • 《The Book Of Shaders》
    很棒的入门书籍,可惜还没更新完
    https://thebookofshaders.com/

  • 我的博客
    将《The Book Of Shaders》里的GlslEditor嵌入博客用于平时练习
    https://caogtaa.github.io/shader/2020/07/16/shader-playground/

相关文章:

  • 一起用Gradle Transform API + ASM完成代码织入呀~
  • 用shader做一个柿子颜色的过场动画
  • 只需跟着Google学android:ViewModel篇
  • 我用 OpenGL 实现了那些年流行的相机滤镜
  • 有必要去研究Handler和Binder么?
  • 音视频交流群又来啦~~~
  • Android 手机如何拍摄RAW图
  • 华为手机刷微博体验更好?技术角度的分析和思考
  • 播放视频时如何调整音频的音量
  • 视频码控:CBR、VBR和ABR
  • OpenGL ES 相机 LUT 滤镜
  • Android 11 正式发布 | 开发者们的舞台已就绪
  • 刚刚,鸿蒙 OS 2.0 发布!HarmonyOS 将正式开源!
  • 如何给 FFmpeg 添加自定义 Codec 编码器
  • FFmpeg从入门到精通——进阶篇,SEI那些事儿
  • CEF与代理
  • Fastjson的基本使用方法大全
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • Linux各目录及每个目录的详细介绍
  • MySQL主从复制读写分离及奇怪的问题
  • oschina
  • spring学习第二天
  • 初识MongoDB分片
  • 高程读书笔记 第六章 面向对象程序设计
  • 每天一个设计模式之命令模式
  • 日剧·日综资源集合(建议收藏)
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 实现菜单下拉伸展折叠效果demo
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 学习笔记:对象,原型和继承(1)
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ​虚拟化系列介绍(十)
  • $.ajax中的eval及dataType
  • $.proxy和$.extend
  • (33)STM32——485实验笔记
  • (39)STM32——FLASH闪存
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (第27天)Oracle 数据泵转换分区表
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (转)为C# Windows服务添加安装程序
  • . Flume面试题
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET/C# 使用反射注册事件
  • .net操作Excel出错解决
  • .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
  • @GlobalLock注解作用与原理解析
  • [ vulhub漏洞复现篇 ] Grafana任意文件读取漏洞CVE-2021-43798
  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)
  • [20161101]rman备份与数据文件变化7.txt
  • [20190401]关于semtimedop函数调用.txt
  • [APIO2015]巴厘岛的雕塑
  • [Codeforces1137D]Cooperative Game
  • [ffmpeg] 定制滤波器
  • [HackMyVM]靶场 Wild