1.5 如何编写shader

该文章为翻译而来,原文链接 https://itp-xstory.github.io/p5js-shaders/#/./docs/how-to-write-a-shader

该系列翻译文章将不定期更新,将用于学习记录,若有侵权,请联系我。

1.5 如何编写shader

现在让我们来谈谈要加载的shader文件。要制作一个shader,你必须在你的p5js脚本的文件夹中编写两个代码文件。一个shader.vert文件和一个shader.frag文件。你可以给shader文件起任何你想要的名字。myAwesomeShader.vertmyAwesomeShader.frag是完全有效的名字。只要你有以.vert.frag为后缀的文件,就可以开始了。

我们将从制作单色填充的shader开始,就像p5js中的fill()一样,如下图所示。我们将两个shader文件分别命名为onecolor.vertonecolor.frag

sketch.js代码如下所示。

// 设置一个shader变量
let theShader;

function preload(){
  // 加载shader
  theShader = loadShader('onecolor.vert', 'onecolor.frag');
}

function setup() {
  // 运行shader需要加入WEBGL参数
  createCanvas(windowWidth, windowHeight, WEBGL);
  noStroke();
}

function draw() {
  // shader() 函数用我们自定义的shader设置活动着色器
  shader(theShader);

  // rect() 函数在屏幕中创建一个矩形
  rect(0,0,width,height);

  // 打印帧率
  //  print(frameRate());
}

function windowResized(){
  resizeCanvas(windowWidth, windowHeight);
}

onecolor.frag代码如下所示。

// Adapted by Louise Lessel - 2019
// from
// Author @patriciogv - 2015
// http://patriciogonzalezvivo.com
// https://thebookofshaders.com/02/


/*
示例:
将整个背景涂成蓝色
*/

// 这些是必要的定义,让你的GPU知道如何渲染shader。
#ifdef GL_ES
precision mediump float;
#endif

void main() {
    // 设置一个蓝色变量
    // 在shader中,RGB的数据范围是从0-1,而不是0-255。
    vec3 color = vec3(0.0, 0.0, 1.0);

    // gl_FragColor是一个内置的shader变量,你的.frag文件必须包含它。
    // 我们将vec3向量的颜色设置为一个新的vec4颜色向量,透明度为1(即完全不透明)。
    gl_FragColor = vec4(color, 1.0);
}

onecolor.vert代码如下所示。

/*
vert file and comments from adam ferriss
https://github.com/aferriss/p5jsShaderExamples
with additional comments from Louise Lessel
*/ 


// 这些是必要的定义,让你的GPU知道如何渲染shader。
#ifdef GL_ES
precision mediump float;
#endif


// 这个 "vec3 aPosition "是一个内置的shader功能。你必须保持这个命名不变。
// 它自动获得你画布上每个顶点的位置

attribute vec3 aPosition;

// 我们必须在顶点着色器中做的一件事是:
// 告诉每个像素它在屏幕中的哪个位置

void main() {
  // 将位置数据复制到一个vec4中,使用1.0作为w分量。
  vec4 positionVec4 = vec4(aPosition, 1.0);

  // 确保shader覆盖整个屏幕:
  // 将矩形放大2倍,并将其移动到屏幕中央。
  // 如果我们不这样做,矩形的左下角就会位于草图的中心。
  // 尝试注释下面这行代码看看会发生什么
  positionVec4.xy = positionVec4.xy * 2.0 - 1.0;

  // 将顶点信息发送给片段着色器
  // 这是自动完成的,只要你把它放到内置的着色器函数"gl_Position "中。
  gl_Position = positionVec4;
}

值得注意的是,这些shader文件是用GLSL(OpenGL着色语言)编写的,这是一种比Javascript更低级的语言,意味着它能更直接地与计算机对话,特别是与GPU对话。这些代码一开始会显得很陌生和混乱,但在这个介绍之后,你应该能对其有一个更好的理解。

shader的结构剖析

.vert文件处理所有与顶点有关的运算–也就是你所有的几何图形(形状)和它在画布上的位置。顶点是形状角点的另一个名称,所以如果你代码中有rect()函数,即绘制了一个矩形,其就有四个顶点。更复杂的形状如3D模型有更多的顶点,当所有这些顶点被连接起来时,这就被称为网格(mesh)Sphere()函数是p5js中网格的一个很好例子。

.vert文件处理每个顶点的操作,所以把与网格上的像素位置有关的代码放在.vert文件中是很好的习惯。在我们的第一个例子中,我们将使用一个覆盖整个画布的矩形,所以.vert文件是非常简单的。文件的结尾是将内置变量gl_Position设置为我们的计算结果,这确保shader可以自动在.frag文件中使用这些位置。

.frag文件处理所有与像素的实际着色有关的事情,其需要在最后将内置变量gl_FragColor设置为一种颜色。.frag文件处理 **每个片段(每个像素)**的操作。使用.frag着色器来给像素着色是很好的做法。

shader程序是这样运行的

首先运行.vert文件,并自动将我们对画布上的几何图形(形状)进行的计算传递给.frag文件。然后,.frag文件会根据像素的位置给它们上色。

.vert文件是针对几何体上的每个顶点运行的,而.frag文件是针对每个像素运行的。这两个文件的最终结果将同时应用于所有的像素! 正如我们在上一节教程中看到的视频解释的那样。

在本指南中,你不需要过多地考虑.vert文件,因为我们只涉及.frag着色器。这只是意味着我们要做的所有计算,将放在.frag文件中。我们在.vert文件中要做的唯一一件事,就是把几何体上的每个像素的位置传递给.frag文件。我们稍后将讨论这个问题。

要解释一下其中的区别:如果我们把计算放在.vert文件中,那么如何给一个像素上色的最终结果就是在几何体的顶点之间进行插值(代码实际上是问–如果某个像素在网格(mesh)的某两个顶点之间,应该是什么颜色?)其结果并不像在.frag着色器中进行计算那样精细,在.frag中它是为每个像素(而不仅仅是每个顶点)进行计算的。在.frag文件中进行计算可能会在性能方面付出更多的代价,这取决于你的着色器看起来有多酷炫。就像所有其他编程一样,你增加的代码行数越多(或者每个像素的计算越多),程序就会变得越慢。但这正是shader在GPU上运行的原因,所以我们可以同时进行这些海量的计算。

如果你有兴趣深入了解运行这些文件时会发生什么,这个关于OpenGL的wikibook页面是一个很好的资源。

shader.vert文件的内容

首先我们写一些必要的定义,让你的GPU知道如何渲染shader。在刚开始使用着色器时,你不应该太担心这个问题。但知道这一点有好处,所以我们将简单地解释一下。

GL_ES是一个着色器API,如果你在浏览器或移动平台上显示shader,它将自动被你的GPU使用。#ifdef的意思是如果定义。它只是简单地对我们希望图形渲染的精确程度做了一个全局设置,这取决于我们在哪里查看渲染结果。因此,如果我们在浏览器中,他们将得到我们在这里定义的精度水平。在这种情况下,我们将代码中的所有浮点数字设置为中等精度。这对于制作平滑的颜色渐变非常重要。

浮点类型(float)在着色器中是至关重要的,所以其所对应的精度水平也是极为关键的。**较低的精度意味着更快的渲染,但要以质量为代价。**你可以自己指定每个使用浮点的变量的精度。在第一行(precision mediump float;)中,我们将所有浮点设置为中等精度。但我们可以选择将它们设置为低精度(precision lowp float;)或高精度(precision highp float;)。 - The Book of Shaders

#ifdef GL_ES

precision mediump float;

#endif

然后我们设置一个叫做属性(attributes)的类型,这些包含的信息由p5js的JavaScript脚本自动发送给shader。关于属性的更多信息见此
对于p5中的shader,我们必须确保在.vert文件中完成一件事,即每个像素必须被告知它在画布上的位置! 这个属性被称为vec3 aPosition。你不能改变它的名字,而且这个属性是
只读
的,也就是说你不能覆盖它。属性通常是以a作为前缀来命名的。比如aSomething

该属性包含位置信息,它是一个vec3(三维向量),意味着它包含x、y和z值。

attribute vec3 aPosition;

所有的着色器文件都必须有一个void main()函数,这是程序开始的地方。请记住,这里的所有内容都是为画布上的每个像素而服务的!所以你需要调整你的思路,考虑只为一个像素编码

对于p5js中的shader,有一些奇怪的事情需要在main()函数中解决。我们需要在属性aPosition被传递到.frag文件之前对其进行缩放。这可能是一个bug,在以后的p5js版本中会解决。但现在我们可以通过简单地缩放所有像素并将它们移动到正确的位置来解决这个问题。

想象一下,你有一个画布,你正在把一个图像放在画布内。由于某种原因,画布决定图像的左下角应该放在画布的中心,而且图像覆盖于画布中心到画布的右上角的位置,所以它不会填满整个画布。我们需要用一点数学方法来解决这个问题。

首先,我们把位置数据复制到vec4(四维向量)中,这意味着我们现在将有以下数字在里面(x,y,z,w)。我们不使用z,因为我们只在两个维度上操作:x和y。我们将把1.0作为w参数(当w=1.0时,矢量被当作位置,当w=0.0时,矢量被当作方向,这是标准的矢量数学)。这里有一个关于非常基本的矢量数学的伟大指南

然后,我们将像素的位置放大2倍,使其变成2倍大小–这意味着图像的右上角超过了我们的画布右上角,并通过对所有像素做-1操作,将其向左和向下移动。这些奇怪的小数字(0、1)现在可能没有任何意义,但在你阅读了重要的着色器概念之后,它们会变得更有意义。

positionVec4.xy = positionVec4.xy * 2.0 - 1.0; // .xy意味着我们对x和y位置同时进行数学计算

无论我们是否选择在.frag文件中使用位置数据,这个位置计算总是需要的。

最后,顶点着色器.vert需要有一个名为gl_Positionvec4输出。这将自动把位置信息发送到片段着色器(.frag)文件中。把它放在你的.vert文件的最后一行是个好习惯。所以让我们把它设置为我们所做的缩放计算:gl_Position = positionVec4

void main() {

  vec4 positionVec4 = vec4(aPosition, 1.0); // 将位置数据复制到一个vec4向量中,添加1.0作为w参数。

  positionVec4.xy = positionVec4.xy * 2.0 - 1.0; // 缩放以使图像位于画布中心并充满整个画布。

  gl_Position = positionVec4;

}

shader.frag文件的内容

在我们讨论如何使用.vert文件中的位置信息之前,暂时先不考虑它,而是给所有像素涂上一种颜色。

在我们的.frag文件中,我们有和之前一样的定义(指ifdef那一段),我们也有一个运行程序的main()函数。

我们将建立一个名为color的新vec3向量,并在其中放入一个颜色。这只是一个变量,所以你可以把它赋值为你想要的颜色。让我们填充一个蓝色的颜色。在shader中,RGB颜色是0-1而不是0-255。所以我们编写如下代码。

vec3 color = vec3(0.0, 0.0, 1.0);

最后,片段着色器要求有一个名为gl_FragColorvec4输出。这一行是告诉GPU如何为像素着色的。这必须是你的.frag文件的最后一行,任何在这一行之后的代码都不会有任何效果,你可能会得到一个"代码未达到(code not reached) "的错误。

gl_FragColor期望的格式是vec4(r,g,b,a),所以我们输入1.0作为我们的alpha,意味着没有透明度

gl_FragColor = vec4(color, 1.0);

整个代码看起来像这样。

#ifdef GL_ES

precision mediump float;

#endif


void main() {

    // 填充一个蓝色的颜色。在着色器中,RGB颜色从0 - 1,而不是0 - 255

    vec3 color = vec3(0.0, 0.0, 1.0);

    gl_FragColor = vec4(color, 1.0);

}

写在最后

我创建了一个公众小号【p5js艺术小站】,里面会更新各种作品、教程等精彩内容,欢迎各位大佬关注指导,共同进步!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hzxwonder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值