许多演示场景中使用的技术之一称为 光线追踪(Ray Marching) 。该算法与一种称为 有符号距离函数 的特殊函数结合使用,可以实时创建一些非常酷的东西。这是系列教程,陆续推出,这篇涵盖以下黑体所示内容

  • 符号距离函数
  • Ray-marching算法
  • 曲面法线和光照
  • 相机变换
  • 构造实体形状(CSG)
  • 模型变换
    • 平移和旋转
    • 比例缩放
    • 非均匀缩放
  • 结论
  • 参考
困惑

ShaderToy最让初学者困惑的:看不到它显示的绘制什么图形,它是隐式的,由数学公式定义的
我们知道,raymarching和raytracing都是用于渲染3D对象的算法,无论如何渲染某个3D对象,我们首先需要构造/定义其形状。

显示的方式
一般而言,使用一系列参数化函数定义显式几何。例如,对于中心位于(x0,y0,z0)和半径r的球体:
f(x)=x0+rsin⁡φcos⁡θf(y)=y0+rsin⁡φsin⁡θ(0≤φ≤π,0≤θ<2π)f(z)=z0+rcos⁡φ{\begin{aligned}f(x)&=x_{0}+r\sin \varphi \;\cos \theta \\f(y)&=y_{0}+r\sin \varphi \;\sin \theta \qquad (0\leq \varphi \leq \pi ,\;0\leq \theta <2\pi )\\f(z)&=z_{0}+r\cos \varphi \,\end{aligned}}f(x)f(y)f(z)​=x0​+rsinφcosθ=y0​+rsinφsinθ(0≤φ≤π,0≤θ<2π)=z0​+rcosφ​

在raytracing中,通常使用顶点显式定义几何形状。 这些顶点形成三角形,然后逐边连接以创建最终的几何形状 。如果你使用过ThreeJS, 你会对顶点定义有更好的体会。

隐式的方式 - SDF
另一种方法是用数学方程隐式定义3D几何形状。
例如,满足此等式的任何3D点都位于半径为1个单位且原点为(0,0,0)的球体表面上:
f(x,y,z)=x2+y2+z2−1f(x, y, z) = \sqrt{x^2 + y^2 + z^2} - 1f(x,y,z)=x2+y2+z2​−1

  • f(x,y,z)<0f(x,y,z)<0f(x,y,z)<0,该点在球体内;
  • f(x,y,z)>0f(x,y,z)> 0f(x,y,z)>0,该点在球体外;
  • f(x,y,z)=0f(x,y,z)= 0f(x,y,z)=0,该点位于球面上。

因为结果f(x,y,z)也是点与球体表面之间的距离,并且它的符号告诉该点是否在球体表面的内部/外部/上,因此该函数也称为符号距离功能(SDF)。

本教程使用的SDF方式,初学者这一点务需明白。

符号距离函数

符号距离函数,或简称为SDF,当给出空间中一个点坐标时,返回该点与某些曲面之间的最短距离。 返回值的符号表示该点是在该曲面内部还是外部(因此叫做符号距离函数)

我们来看一个例子,一个以原点为中心的球体,球体内的点与原点之间的距离小于半径,球体上的点则等于半径的距离,球体外部的点将有大于半径的距离。所以我们的第一个SDF函数,对于以半径为1的原点为中心的球体,看起来像这样:

f(x,y,z)=x2+y2+z2−1f(x,y,z)=\sqrt {x^2+y^2+z^2}-1f(x,y,z)=x2+y2+z2​−1

例如,点(1,0,0)和(1,0,0)在表面上,点(0,0,0.5)在表面内,表面上最近的点0.5个单位 ,点(0,3,0)在表面之外,表面上距离最近的点2个单位。

当我们使用GLSL着色器时,这样的公式将以矢量方式进行计算。更多信息参照 Euclidean规范,上面的SDF看起来像这样:

在GLSL中,转换为:

float sphereSDF(vec3 p) {return length(p) - 1.0;
}

其他的SDF,请查看 使用距离函数建模

Ray-marching算法

一旦我们将某些东西建模为SDF函数,我们如何渲染它?这就是光线追踪(ray marching)算法的用武之地!

就像在光线跟踪中(raytracing)一样,我们为相机选择一个位置,在其前面放置一个网格,通过网格中的每个点从相机发送光线,每个网格点对应于输出图像中的一个像素。可以把相机位置认为是眼睛的位置,网格可以认为是输出图像的区域,例如,对于shadertoy而言,就是那个图像区。下图可以帮助你理解:

不同之处在于如何定义场景,这反过来又影响我们查找视线和场景之间交点的方式。

在光线追踪(raytracing)中,场景通常显式的定义为三角形,球体等形状。为了找到视线和场景之间的交点,我们进行了一系列几何形状的相交测试:此光线与此三角形是否相交?如果是球体怎么样?

有关光线跟踪的教程,请查看 scratchchapixel.com。

而在 光线追踪(raymarching) 中,整个场景是用有符号距离函数(SDF)来定义的。为了找到视线和场景之间的交点,我们从相机开始,一点一点地沿着视线移动一个点。在每一步,我们都会问“这个点在场景表面内吗?”,或者可选地说:“此时SDF是否评估为负数?”。如果确实如此,我们就完成了!如果不是,我们会沿着光线继续前进到设定的最大步数为止。

我们可以每次沿着视线的非常小的步长方式前进进行相交判断,但是我们可以使用“球体跟踪”会更好(在速度方面和精度方面)。实际上,我们一般不是采用小步长前进判断,而是采取我们所知道的最大安全步长前进,即使用SDF为我们定义的:目前的点到曲面的最短距离为步长,这个步长在前进过程中是变化的!以下这张图表现了这种思想:

在此图中,P0P_0P0​是相机。蓝线位于从摄像机通过视平面投射的光线方向上。采取的第一步非常大:它以距离表面最短的距离步进。由于表面上最接近的点P0P_0P0​不沿视图线所在,我们不断加强,直到我们最终得到的表面,在 P4P_4P4​

在GLSL中实现,此光线行进算法如下所示:

float depth = start;
for (int i = 0; i < MAX_MARCHING_STEPS; i++) {float dist = sceneSDF(eye + depth * viewRayDirection);if (dist < EPSILON) {// We're inside the scene surface!return depth;}// Move along the view raydepth += dist;if (depth >= end) {// Gone too far; give upreturn end;}
}
return end;

再结合选择适当的视线方向和球体SDF,把相交部分标记为红色,我们最终得到:


注意:不要将法线(normal)和normalize()混淆。Normalize()是让一个向量(任意向量,不一定是法线)除以其长度,从而使新长度为1。法线(normal) 则是某一类向量的名字。
完整代码入下:

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float EPSILON = 0.0001;/*** 中心位于原点半径为1的球体的符号距离函数定义*/
float sphereSDF(vec3 samplePoint) {return length(samplePoint) - 1.0;
}/*** 用SDF描述场景*/
float sceneSDF(vec3 samplePoint) {return sphereSDF(samplePoint);
}/*** 返回最短距离函数* * eye: 射线的起点,可理解为相机* marchingDirection: 射线的标准化方向向量* start: 从相机开始的最短距离* end: 最远距离*/
float shortestDistanceToSurface(vec3 eye, vec3 marchingDirection, float start, float end) {float depth = start;for (int i = 0; i < MAX_MARCHING_STEPS; i++) {float dist = sceneSDF(eye + depth * marchingDirection);if (dist < EPSILON) {return depth;}depth += dist;if (depth >= end) {return end;}}return end;
}/*** 返回相机的标准化方向向量* * fieldOfView: 垂直视野的角度* size: 输出图像的分辨率* fragCoord: 输出图像中的像素坐标*/
vec3 rayDirection(float fieldOfView, vec2 size, vec2 fragCoord) {vec2 xy = fragCoord - size / 2.0;float z = size.y / tan(radians(fieldOfView) / 2.0);return normalize(vec3(xy, -z));
}void mainImage( out vec4 fragColor, in vec2 fragCoord ){vec3 rd = rayDirection(45.0, iResolution.xy, fragCoord);vec3 ro = vec3(0.0, 0.0, 5.0);float dist = shortestDistanceToSurface(ro, rd, MIN_DIST, MAX_DIST);if (dist > MAX_DIST - EPSILON) {// Didn't hit anythingfragColor = vec4(0.0, 0.0, 0.0, 0.0);return;}fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

对于所有示例代码,都可以在 http://shadertoy.com/ 网站进行在线测试。

继续下一篇阅读 ShaderToy入门教程(2) - 光照和相机

ShaderToy入门教程(1) - SDF 和 Raymarching 算法相关推荐

  1. ShaderToy入门教程(2) - 光照和相机

    回顾上一篇 ShaderToy入门教程(1) - SDF 和 Raymarching 算法 继续上篇,这篇涵盖以下黑体所示内容 符号距离函数 Ray-marching算法 曲面法线和光照 相机变换 构 ...

  2. shadertoy入门教程

    shadertoy for beginners shadertoy入门 介绍 画一个圆 首先看一下uv坐标关于屏幕位置的变化规律 画一个椭圆 画一个圆 画多个圆 画一个笑脸:) shadertoy入门 ...

  3. RNN 入门教程 Part 3 – 介绍 BPTT 算法和梯度消失问题

    转载 - Recurrent Neural Networks Tutorial, Part 3 – Backpropagation Through Time and Vanishing Gradien ...

  4. OpenCVSharp入门教程 基础篇⑤——GaussianBlur高斯模糊算法

    文章目录 一.前文 二.GaussianBlur高斯模糊算法流程 三.界面布局 四.功能实现 4.1 打开图片 4.2 GaussianBlur高斯模糊-源码 4.3 GaussianBlur高斯模糊 ...

  5. walking机器人入门教程-视觉转激光建图-cartographer算法建图

    系列文章目录 walking机器人入门教程-目录 walking机器人入门教程-硬件清单 walking机器人入门教程-软件清单 walking机器人入门教程-测试底盘 walking机器人入门教程- ...

  6. walking机器人入门教程-硬件清单

    系列文章目录 walking机器人入门教程-目录 walking机器人入门教程-硬件清单 walking机器人入门教程-软件清单 walking机器人入门教程-测试底盘 walking机器人入门教程- ...

  7. Turbot4机器人入门教程-配置网络

     系列文章目录: Turbot4机器人入门教程-硬件清单 Turbot4机器人入门教程-软件清单 Turbot4机器人入门教程-NoMachine远程控制 Turbot4机器人入门教程-配置网络 Tu ...

  8. Turbot4机器人入门教程-使用统一建图入口

      系列文章目录: Turbot4机器人入门教程-硬件清单 Turbot4机器人入门教程-软件清单 Turbot4机器人入门教程-NoMachine远程控制 Turbot4机器人入门教程-配置网络 T ...

  9. walking机器人入门教程-深度学习-使用yolov5进行物体识别

    系列文章目录 walking机器人入门教程-目录 walking机器人入门教程-硬件清单 walking机器人入门教程-软件清单 walking机器人入门教程-测试底盘 walking机器人入门教程- ...

最新文章

  1. 引用次数在 19000 次+的,都是什么神仙论文?
  2. java象棋无框架版_Java版中国象棋
  3. .net 获取 存储过程的输出参数
  4. Codeforces Round #580 (Div. 2)
  5. Spark中Data skew(数据倾斜)Java+Python+Scala三种接口完整代码
  6. 行业看点 | 英特尔成功开发超导量子计算芯片 推动产业加速发展
  7. 转成数组_JavaScript之数组扁平化
  8. c语言向指定文件写入程序,C语言同时向不同的文件写入不同的数据
  9. RTP协议解析和H264码流提取
  10. python ctypes 回调函数_如何用Python中的ctypes创建回调函数?
  11. Linux 命令(59)—— c++filt 命令
  12. 设计之星 ai_二十万人的AI成长之路 ,百度之星用十五年去点亮
  13. 使用Mapviz、中科图新 进行机器人GPS轨迹卫星地图绘制
  14. linux zip文件无法解压,无法解压zip文件在linux centos
  15. STM32锁死解锁方法
  16. wifi mouse linux,WiFi Mouse Pro
  17. 【狂神说:秦疆】SpringMVC笔记
  18. CF115B Lawnmower(贪心)
  19. 未来的计算机儿童画,儿童画未来的科学幻想绘画优秀作品
  20. CodeForces 1000A Codehorses T-shirts

热门文章

  1. 底层软件测试规范,QA评审底层测试
  2. 那些年啊,那些事——一个程序员的奋斗史 ——46
  3. 分布式系统(2)系统模型
  4. 音乐播放器制作一(Windows Media Player控件)
  5. 《树莓派项目实战》第八节 使用光敏电阻传感器检测环境中是否有光照
  6. linux中重置是啥意思,linux – netstat -s中“由于意外数据而重置连接”是什么意思...
  7. 大公司 or 小公司,你要怎么选?
  8. 在线HTML文本提取URL链接工具
  9. 李弘毅机器学习笔记:第二章
  10. 论文阅读:Saliency-Guided Region Proposal Network for CNN Based Object Detection