从零开始的 WebGL GLSL Shader

GLSL Shader

简介

WebGL 的底层是使用 OpenGL ES,OpenGL 是一个用于处理 2D 和 3D 图形的快速库

OpenGL ES 是 OpenGL 的一个版本,面向可能具有更多受限硬件的设备,因此可以很好的运行在移动端设备

要使用 GLSL 需要一个 OpenGL 平台

WebGL 使得可以在浏览器中使用 OpenGL

本章以 WebGL 平台为执行环境的 threeJs 库进行学习

编译部分不多关注,主要交给 Three.js 库负责,主要专注于 GLSL 代码本身

章节简介

  1. 介绍基本概念
  2. 编写第一个着色器
  3. shaping functions 形状函数
  4. noise 噪波函数
  5. shader 中使用图像或纹理
  6. vertex shader & fragment shader
  7. lighting 灯光
  8. 总结回顾

基本概念

OpenGL 可以处理三角形,一个三角形是由三个点组成,称该点为 顶点 ,每个顶点在三个方向上都有一个位置,这三个方向称为 x | y | z ,通过这三个方向值可以将顶点 定位 到世界空间之内

当想在手机上显示这些信息时,需要将这些顶点投影到这三个顶点范围内的 2D 屏幕上,一旦知道了屏幕上的位置,就需要给三角形上色,可以通过用一种简单的颜色或其它来填充三角形顶点范围内的所有像素来实现

不仅是纯色,还可以进行计算,比如通过光量等参数计算灯光照射后的结果

绘制三角形

因此,绘制三角形的过程分为两个阶段

  1. Position the vertices 定位顶点
  2. Color the pixels 绘制内部像素

GLSL Shader

GLSL 着色器分为两个部分

  1. vertex shader 顶点着色器:获取模型顶点坐标,并将其定位在屏幕上,顶点着色器被用于模型中的每个顶点
  2. fragment shader 片元着色器: 片元着色器则被用于每个像素,其输出是该像素的颜色,以 R.G.B.A 格式表示,值为 0 到 1,当 Alpha 值不为 1 时,会显示一个半透明的红色像素。在一个复杂的 3D 屏幕上,一个物体在另一个物体前面时,会覆盖由另一个着色器计算出的像素颜色

Z 缓冲区用来计算物体覆盖,Z 缓冲区表示由渲染器的上一个着色器计算的与相机的距离,渲染器会处理这个问题,我们不需要关心这些,我们需要关注的是我们当前的着色器应该做什么以及应该如何给其像素上色

Parallel Processing 并行处理

CPU or GPU

  • CPU 可以处理具有巨大内存需求的大型程序 / GPU 只能处理内存需求极小的程序

GPU 优势:可以同时运行成百上千个这样的程序,比做相同任务的 CPU 快 50 到 100 倍

之所以这样设计,是因为在绘制复杂图形时需要进行大量的计算,以及用于计算机辅助设计、游戏等

当创建 GLSL shader 时,使用的是并行处理,意味着如果我们绘制这个像素,它对于同时绘制的像素选择了什么颜色一无所知,通常在绘图时,无论是向左、向右、向上还是向下,因为它们是在同一时间被计算的

使用 GLSL shader,每个顶点和像素都是彼此独立的

基本数据类型/语法/内置变量/函数

特殊限定词(Qualifiers)类型

在 GLSL(OpenGL Shading Language)中,uniform 和 attribute(在较新的版本中,对于顶点着色器的输入数据,attribute 被 in 关键字替代)是特殊的限定词(Qualifiers)。它们用于指定变量的存储类别和访问方式,从而定义变量的作用域和生命周期,以及它们如何与着色器程序的其他部分交互。

  • uniform:

    • 作用: uniform 变量用于从 CPU 端(你的 WebGL 应用程序)向 GPU 端(顶点着色器和片元着色器)传递数据,这些数据在渲染一批顶点或片元时保持不变。
    • 特点: uniform 变量在着色器的所有执行实例中都是相同的,常用于传递如变换矩阵、光照参数、材质属性等。
  • attribute(在较新的 GLSL 版本中使用 in 替代):

    • 作用: attribute 变量用于从顶点缓冲区传递数据到顶点着色器,每个顶点都有自己的一组 attribute 数据,如顶点坐标、法线、纹理坐标等。
    • 特点: attribute 变量只能在顶点着色器中使用,用于表示每个顶点的属性数据。
  • varying( GLSL ES 1.00 ) / in & out ( GLSL ES ^3.00 ):

    • 作用: varying(旧版本)/ in 和 out(新版本)变量用于在顶点着色器和片元着色器之间传递数据。在顶点着色器中使用 out 关键字声明变量,然后在片元着色器中使用 in 接收该变量。
    • 特点: 这些变量用于传递如顶点颜色、纹理坐标等数据,允许片元着色器接收顶点着色器处理后的数据。在顶点着色器和片元着色器之间,数据会经过插值处理。

因此,uniform、attribute/in、和 varying/out-in 是用于声明着色器程序中变量的特殊限定词,它们定义了变量的作用域、生命周期和如何被着色器程序访问。

总结

  • uniform:用于传递对于一次绘制调用中所有顶点或片元相同的数据。
  • attribute:用于传递每个顶点特有的数据到顶点着色器。
  • varying/in & out:用于在顶点着色器和片元着色器之间传递数据,数据会被插值。

了解这些变量类型及其用途对于编写有效的着色器代码至关重要。

变量数据类型

标量类型

  • int: 整型
  • float: 浮点型
  • bool: 布尔值
1
2
3
4
int num1 = 1;
float num2 = 22.2;
int res = int(num2) * 3;
bool ifTrue = true

向量类型

  • vec2: 二维向量浮点
  • vec3: 三维向量浮点
  • vec4: 四维向量浮点
  • ivec2: 二维向量整型
  • ivec3: 三维向量整型
  • ivec4: 四维向量整型
  • bvec2: 二维向量布尔
  • bvec3: 三维向量布尔
  • bvec4: 四维向量布尔
1
2
3
4
5
6
vec2 worldCoord = vec2(.5);
// worldCoord.x = 0.5 and worldCoord.y = 0.5
w = worldCoord & 2.0;
// w.x = 1.0 and w.y = 1.0
ivec2 wd = ivec2(1);
bvec2 b1 = bvec2(true);

矩阵类型

  • mat2, mat3, mat4:分别为 2x2、3x3、4x4 的浮点数矩阵
  • mat2x2, mat2x3, mat2x4:2 行的矩阵,列数分别为 2、3、4
  • mat3x2, mat3x3, mat3x4:3 行的矩阵,列数分别为 2、3、4
  • mat4x2, mat4x3, mat4x4:4 行的矩阵,列数分别为 2、3、4
1
2
3
4
5
6
mat2 myMat2 = mat2(1.0, 0.0,
0.0, 1.0); // 单位矩阵
// 使用三个vec3向量初始化
mat3 myMat3 = mat3(myVec3_1, myVec3_2, myVec3_3);
mat4 myMat4;
mat3x4 myMat3x4;

矩阵在 GLSL 中主要用于顶点变换。例如,使用矩阵变换一个顶点位置:

1
vec4 transformedPosition = myMat4 * originalPosition;

采样器类型

用于纹理数据的采样:

  • sampler1D:一维纹理采样器
  • sampler2D:二维纹理采样器
  • sampler3D:三维纹理采样器
  • samplerCube:立方体纹理采样器
  • sampler2DShadow:进行深度比较的二维纹理采样器
  • samplerCubeShadow:进行深度比较的立方体纹理采样器
  • sampler2DArray:二维纹理数组采样器
  • sampler2DArrayShadow:进行深度比较的二维纹理数组采样器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 片元着色器

precision mediump float;

uniform sampler2D u_texture; // 二维纹理采样器
varying vec2 v_texCoord; // 从顶点着色器传递过来的纹理坐标

void main() {
// 使用 texture2D 函数从纹理采样器中采样颜色
vec4 texColor = texture2D(u_texture, v_texCoord);

// 将采样得到的颜色输出为片元的最终颜色
gl_FragColor = texColor;
}

其他类型

  • void: 无类型,通常用于函数没有返回值的情况

结构体和数组

GLSL 也支持结构体(struct)和数组,允许用户定义复杂的数据类型和数据集合。

1
2
3
4
5
6
7
struct Light {
vec3 position;
vec3 color;
float intensity;
};

Light lights[4]; // 定义一个包含4个Light结构体的数组

这些类型为在着色器中处理图形和图像数据提供了强大的灵活性和控制能力。

常量声明

在 GLSL (OpenGL Shading Language) 中,const 关键字用于声明常量,即在编译时已知且在程序执行期间不会改变的值:

1
const float pi = 3.141592653589793;

插值变量类型

用于在顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)之间传递数据。当数据从顶点着色器传递到片元着色器时,这些变量会被自动插值。这意味着,对于片元着色器中的每个像素点,GPU 会根据顶点着色器输出的顶点属性,通过插值计算出这些像素点的属性值。

  • 在顶点着色器中,使用 out 关键字定义的变量用于输出数据到片元着色器。例如:

    1
    2
    // 顶点着色器
    out vec3 vColor; // 定义一个输出变量,用于传递颜色到片元着色器
  • 在片元着色器中,使用 in 关键字接收顶点着色器传递的数据。例如:

    1
    2
    // 片元着色器
    in vec3 vColor; // 接收从顶点着色器传递过来的颜色
  • 常见类型:

    • 颜色(Color):颜色是最常见的插值变量之一。顶点着色器可以为每个顶点指定一个颜色,然后这些颜色会被插值到片元着色器中的每个片元。
    • 纹理坐标(Texture Coordinates 又名 UV 坐标):UV 纹理坐标用于纹理映射。顶点着色器输出每个顶点的纹理坐标,然后这些坐标被插值到片元着色器,用于确定纹理中的对应位置。
    • 法线(Normals):在进行光照计算时,顶点的法线向量也可以被插值传递到片元着色器中。这允许在片元级别进行更精细的光照计算。
    • 位置(Position):虽然顶点的位置通常用于顶点着色器中的变换计算,但有时也会将变换后的位置插值到片元着色器中,用于计算如屏幕空间反射等效果。
  • 插值限定符:

    • flat:不进行插值。片元着色器中的变量将直接使用某个顶点着色器输出的值(通常是与该片元最近的顶点)。
    • smooth(默认):进行平滑插值,适用于大多数情况。
    • noperspective:进行非透视插值,与视角无关,适用于某些特殊效果。

通过使用这些插值变量和限定符,可以在顶点着色器和片元着色器之间高效且灵活地传递数据,实现各种图形效果。

插值的工作原理

假设你有一个三角形,它的三个顶点分别被赋予了不同的颜色(或者其他任何属性,如纹理坐标)。当这个三角形被光栅化(转换为一系列的片元或像素)以在屏幕上显示时,位于三角形内部的每个片元的颜色将根据三个顶点的颜色自动计算得出。这个计算过程就是插值。

  • 本质:
    • 在图形渲染过程中,顶点着色器处理完顶点后,对于图元内部的每个片元,GPU 会对顶点着色器输出的变量进行插值。这意味着,无论是 position 还是 uv 等,它们的值都会根据片元在图元内的相对位置被插值计算。这种插值是线性的,基于图元顶点的值和片元在图元内的位置。
    • 插值的结果差异反映了 position 和 uv 等在它们各自定义域中的不同。position 通常用于定义形状和位置,而 uv 用于定义表面如何被纹理化。

例如:

  • position 的插值:
    • position 变量代表顶点在三维空间中的位置。在顶点着色器中,这些位置经过模型、视图、投影矩阵的变换,用于计算顶点在屏幕上的二维位置以及深度信息。
    • 当 position 用于颜色计算时(如 vec3(v_position.x,v_position.y,0.0)),实际上是在将这些三维坐标直接映射到颜色上。由于 position 是在模型空间中定义的,其值的范围可能远远超出 [0, 1],这是纹理坐标通常所在的范围。因此,使用 position 的插值作为颜色,可能会导致颜色值超出正常的 [0, 1] 范围,从而产生意想不到的颜色效果。
  • uv 的插值
    • uv 变量代表纹理坐标,是定义在 [0, 1] 范围内的二维坐标,指定了模型表面上的每个点如何映射到纹理图像上。
    • 当使用 uv 坐标计算颜色时(如 vec3(v_uv.x,v_uv.y,0.0)),由于 uv 坐标本身就是在 [0, 1] 范围内,这种映射自然会产生一个从黑色过渡到红色和绿色的渐变效果,这个效果通常更符合预期,因为它直接反映了纹理坐标在模型表面的分布。

为什么需要插值

插值允许我们在顶点着色器中为顶点定义属性(如颜色、纹理坐标等),然后自动计算三角形内部片元的这些属性值。这是实现渐变、平滑纹理映射和其他图形效果的基础。

“数据会被插值”意味着顶点着色器向片元着色器传递的属性值(如颜色、纹理坐标等)会根据顶点属性自动计算出内部片元的属性值,从而实现图形的平滑渲染。

WebGL 内置变量

  • vertex shader:
    • gl_Position:这是顶点着色器中最重要的输出变量,用于设置顶点的最终位置。它是裁剪空间中的坐标,之后会被转换为屏幕空间坐标。
    • gl_PointSize:用于设置点的大小(以像素为单位)。这在使用 GL_POINTS 绘制点时非常有用。
  • fragment shader:
    • gl_FragCoord:表示当前片元(像素)在屏幕空间中的坐标。gl_FragCoord.x 和 gl_FragCoord.y 表示片元的屏幕空间坐标,gl_FragCoord.z 表示深度值。
    • gl_FrontFacing:一个布尔值,指示当前片元属于正面还是背面。这对于实现一些特殊效果,如背面着色,非常有用。
    • gl_FragColor:在 WebGL 1 中,这是片元着色器中的输出变量,用于设置当前片元的颜色。在 WebGL 2 中,应使用输出布局限定符来定义自己的片元颜色输出变量。
    • gl_PointCoord:仅在绘制点时有效,表示当前片元在点内的相对坐标。这可以用于实现基于点的纹理映射等效果。
  • 通用内置变量:
    • gl_FragDepth:允许在片元着色器中修改片元的深度值。这对于实现一些高级效果,如深度偏移,非常有用。
  • 特殊用途的内置变量(WebGL 2/GLSL ES 3.0)
    • 输入/输出接口块:如 in 和 out 关键字,允许更灵活地在着色器之间传递数据。
    • gl_InstanceID 和 gl_VertexID:分别用于访问当前实例的 ID 和当前顶点的 ID,这对于实例化渲染和处理顶点数组非常有用。

ThreeJS 内置变量

Three.js 是一个基于 WebGL 的高级 3D 图形库,它简化了 3D 图形的开发过程。在使用 Three.js 编写自定义着色器(Shader)时,你可以利用 Three.js 提供的一些内置变量。这些变量是 Three.js 为了方便着色器开发而在其着色器语言(GLSL)中预定义的。它们包括但不限于以下几类:

  • vertex shader
    • modelMatrix:模型矩阵,用于将顶点从模型空间转换到世界空间。
    • viewMatrix:视图矩阵,用于将顶点从世界空间转换到视图空间。
    • projectionMatrix:投影矩阵,用于将顶点从视图空间转换到裁剪空间。
    • modelViewMatrix:模型视图矩阵,是模型矩阵和视图矩阵的乘积,直接将顶点从模型空间转换到视图空间。
    • normalMatrix:法线矩阵,用于变换法线向量,以便进行正确的光照计算,通常是模型视图矩阵的逆转置矩阵。
    • position:顶点位置,通常作为输入属性(attribute)。
    • normal:顶点法线,用于光照计算,通常作为输入属性(attribute)。
    • uv:顶点的纹理坐标,用于纹理映射,通常作为输入属性(attribute)。
  • fragment shader
    • cameraPosition:相机在世界空间中的位置,虽然这个变量可以在顶点着色器中计算并传递给片元着色器,但在实际应用中,它通常直接在片元着色器中作为 uniform 变量使用,用于实现视角依赖的效果,如镜面反射。
  • 通用: 有些变量既可以在顶点着色器中使用,也可以在片元着色器中使用,但它们的传递方式通常是:在顶点着色器中计算后,通过 varying 变量传递给片元着色器。例如:
    • 纹理坐标(uv):在顶点着色器中作为输入属性,计算后通过 varying 变量传递给片元着色器进行纹理采样。
    • 变换后的法线(使用 normalMatrix 变换的 normal):在顶点着色器中计算并传递给片元着色器,用于光照计算。

Three.js 的版本更新可能会引入新的内置变量或更改现有变量的名称和用法

UV 坐标

UV 坐标是二维纹理坐标的一种表示方式,用于将纹理(图像)映射到三维模型的表面。在三维图形中,每个顶点除了有其在三维空间中的位置坐标(通常是 X、Y、Z 坐标)之外,还可以有一组 UV 坐标(通常是 U、V 坐标)。

  • 定义:
    • U 坐标:对应于纹理的水平方向,可以理解为纹理图像的 X 轴。
    • V 坐标:对应于纹理的垂直方向,可以理解为纹理图像的 Y 轴。
  • 作用:
    • 纹理映射:UV 坐标的主要作用是将二维纹理映射到三维模型的表面。通过为模型的每个顶点指定 UV 坐标,可以控制纹理图像如何贴合到模型上。这使得模型表面可以展示复杂的细节和纹理,而不需要增加模型的多边形数量。
    • 纹理重复和包裹:通过调整 UV 坐标,可以实现纹理的重复、镜像或包裹等效果。例如,将 UV 坐标扩展到 0 到 1 之外的值,可以使纹理在模型表面重复出现。
    • 纹理动画:通过动态修改 UV 坐标,可以在模型表面上实现纹理的滚动、旋转等动画效果,这在游戏和视觉效果中非常有用。
    • 多纹理混合:在高级渲染技术中,可以为同一个模型指定多组 UV 坐标,用于同时映射多个纹理。这可以用于实现多层纹理混合、细节纹理(如法线贴图、置换贴图)等效果。
  • UV 展开:
    • 为了在三维模型上应用纹理,模型的表面需要被展开或展平成二维平面,这个过程称为 UV 展开或 UV 拆解。正确的 UV 展开对于避免纹理扭曲、重叠或其他映射错误至关重要。

总之,UV 坐标在三维图形和游戏开发中扮演着关键角色,它们使得纹理映射成为可能,极大地丰富了三维模型的视觉表现。

If 条件语句

1
2
3
4
5
if (condition) {
// Do something
} else {
// Do something else
}

Loop 循环

1
2
3
4
5
const int count = 10;
for( let i = 0; i < count; i++ ){
// Do something
}

Functions 函数

需要声明参数和返回值的类型
并且支持函数重载,根据调用时的参数类型进行不同的函数调用

1
2
3
4
5
6
// JS
function inRect(pt, rect) {
let result = false;
// 计算
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// GLSL
bool inRect(vec2 pt,vec4 rect){
bool result = false;
// 计算
return result
}
// 函数重载
bool inRect(vec2 pt,float x,float y,float width,float height){
bool result = false;
// 计算
return result
}

内置函数

GLSL(OpenGL Shading Language)提供了一系列内置函数,用于执行常见的数学和图形操作。这些函数对于着色器编程非常有用。下面是一些常用的 GLSL 内置函数:

  • clamp: clamp 函数将一个值限制在两个指定的边界内。如果该值小于下限,它会返回下限值;如果大于上限,它会返回上限值;如果在两者之间,则返回该值本身。
    • params:
      • x:要限制的值。
      • minVal:下限值。
      • maxVal:上限值。
    • return:
      • 限制在[minVal, maxVal]范围内的值。
        1
        2
        3
        float clamp(float x, float minVal, float maxVal);
        vec2 clamp(vec2 x, float minVal, float maxVal);
        vec3 clamp(vec3 x, float minVal, float maxVal);
  • mix: mix 函数根据第三个参数的值在两个值之间进行线性插值。如果第三个参数是 0,它会返回第一个值;如果是 1,它会返回第二个值。
    • params:
      • x:第一个值。
      • y:第二个值。
      • a:插值参数,通常在[0, 1]范围内。
    • return:
      • x 和 y 的线性插值结果。
        1
        2
        3
        4
        float mix(float x, float y, float a);
        vec2 mix(vec2 x, vec2 y, float a);
        vec3 mix(vec3 x, vec3 y, float a);
        vec4 mix(vec4 x, vec4 y, float a);
  • step: 生成阶跃函数的结果。如果第一个参数小于第二个参数,返回 0.0;否则返回 1.0。
    1
    2
    3
    4
    float step(float edge, float x);
    vec2 step(vec2 edge, vec2 x);
    vec3 step(vec3 edge, vec3 x);
    vec4 step(vec4 edge, vec4 x);
  • smoothstep: 在两个边界之间平滑插值,返回值在 0 到 1 之间,使用 Hermite 插值计算,使得返回值在边界处平滑过渡。
    1
    2
    3
    4
    float smoothstep(float edge0, float edge1, float x);
    vec2 smoothstep(vec2 edge0, vec2 edge1, vec2 x);
    vec3 smoothstep(vec3 edge0, vec3 edge1, vec3 x);
    vec4 smoothstep(vec4 edge0, vec4 edge1, vec4 x);
  • length: 计算向量的长度。
    1
    2
    3
    float length(vec2 x);
    float length(vec3 x);
    float length(vec4 x);
  • normalize: 将向量标准化(即向量的长度变为 1)。
    1
    2
    3
    vec2 normalize(vec2 x);
    vec3 normalize(vec3 x);
    vec4 normalize(vec4 x);
  • dot: 计算两个向量的点积。
    1
    2
    3
    float dot(vec2 x, vec2 y);
    float dot(vec3 x, vec3 y);
    float dot(vec4 x, vec4 y);
  • cross: 计算两个三维向量的叉积,返回一个垂直于这两个向量的向量。
    1
    vec3 cross(vec3 x, vec3 y);

这些函数是GLSL编程中的基础,广泛用于实现各种图形效果,如光照、纹理映射、颜色混合等

GLSL 第一个着色器

Vertex Shader

作用

  • 变换顶点位置: 顶点着色器可以将顶点从一个坐标系统转换到另一个坐标系统。例如,它可以将顶点位置从模型空间转换到世界空间,再从世界空间转换到视图空间,最后转换到裁剪空间或屏幕空间
  • 顶点数据处理: 除了位置变换外,顶点着色器还可以用来处理顶点的其他属性,如颜色、纹理坐标、法线等。这些处理可以包括对这些属性的变换或计算,以便于后续的渲染阶段使用
  • 光照计算: 虽然光照效果通常在片元着色器中计算,但顶点着色器也可以用来进行简单的光照计算,尤其是在需要优化性能,减少片元着色器负担时
  • 向下一阶段传递数据: 顶点着色器还负责将处理后的顶点数据传递给下一阶段(如几何着色器或片元着色器)。这包括转换后的顶点位置以及任何自定义的顶点属性,如变换后的法线、处理后的纹理坐标等

特点

  1. 并行处理能力: 在 GPU 上,顶点着色器是并行执行的,每个顶点独立处理。这意味着即使场景中有成千上万个顶点,顶点着色器也能高效地处理。
  2. 高度可编程: 顶点着色器提供了高度的可编程性,允许开发者自定义顶点处理逻辑,实现复杂的变换和视觉效果。
  3. 资源访问限制: 相比片元着色器,顶点着色器在访问纹理等资源时有一定的限制。它主要专注于顶点数据的处理。
  4. 优化性能: 通过在顶点着色器中进行适当的计算,可以减轻片元着色器的负担,从而优化整体渲染性能。例如,通过在顶点着色器中进行光照计算,可以减少需要在片元着色器中处理的光照计算量。

顶点着色器是现代图形渲染管线中不可或缺的一部分,它为顶点的处理提供了极大的灵活性和控制能力,是实现高质量图形渲染的基础之一。

示例

1
2
3
4
5
// GLSL ES 1.00 版本 3.00 以上可以使用 in 代替 attribute
attribute vec3 position
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position*0.5,1.0);
}

这段代码首先将 position 缩小一半(position0.5),然后将其转换为四维向量(vec4(position0.5,1.0)),其中 1.0 是齐次坐标,用于进行矩阵变换。接着,使用 modelViewMatrix 对顶点位置进行模型和视图变换,然后使用 projectionMatrix 进行投影变换,最终计算出顶点在裁剪空间中的位置,并赋值给 gl_Position。这个过程是顶点着色器的典型工作流程,用于确定每个顶点在最终渲染的三维场景中的位置。

  • gl_Position: gl_Position 是一个内置的输出变量,用于指定顶点的最终位置。 顶点着色器的主要任务是计算顶点的位置,并将这个位置赋值给 gl_Position。这个位置是在裁剪空间中的,意味着它是在经过模型变换、视图变换和投影变换之后的位置。WebGL 渲染管线使用 gl_Position 的值来确定顶点在屏幕上的位置以及是否在视图范围内。
    • 类型:
      • gl_Position 的类型是 vec4,这是一个四维向量,包含了 x, y, z, 和 w 分量。这里的 x, y, 和 z 分量代表了顶点的位置,而 w 分量通常用于进行透视除法(Perspective Division),将顶点从裁剪空间转换到归一化设备坐标(Normalized Device Coordinates, NDC)空间。在透视除法之后,x, y, 和 z 分量会被它们自己的 w 分量除,结果是顶点的位置会被适当地缩放,以模拟透视效果。
    • 作用:
      • 顶点位置的确定: gl_Position 确定了顶点在三维空间中的位置,经过顶点着色器的处理,每个顶点的位置可以被变换到新的坐标系统中,比如从模型空间变换到世界空间,再到视图空间,最后到裁剪空间
      • 裁剪和透视除法: 在顶点着色器之后,如果顶点的 gl_Position 落在裁剪空间之外,这个顶点会被裁剪掉不参与后续的渲染过程。对于在裁剪空间内的顶点,它们的 gl_Position 会被用于透视除法,以计算出顶点在屏幕上的二维位置
  • position: position 通常是一个属性(attribute)变量,用于从 WebGL 应用程序接收顶点的初始位置。
    • 类型:
      • position 类型是 vec3 ,三维向量包含了 x , y , z,默认存储着顶点缓冲区的局部坐标
    • 作用:
      • 顶点局部位置的确定: position 确定了顶点在局部空间中的位置。经过顶点着色器的处理,每个顶点的位置可以被变换到新的坐标系统中,比如从模型空间变换到世界空间,再到视图空间,最后到裁剪空间,输出给 gl_Position
      • 顶点数据的传递: position 用于将顶点的位置信息从顶点缓冲区(Vertex Buffer)传递到顶点着色器。这是实现顶点变换和其他顶点级操作的基础。
    • 特点:
      • 单个顶点的信息: 在 GPU 上,顶点着色器是并行执行的,意味着每个顶点都会 独立地执行 一次顶点着色器的 main 函数。因此,每次执行时,position 变量包含的是当前正在处理的单个顶点的位置信息。
      • 高度可编程: 开发者可以自定义 position 的处理逻辑,实现复杂的变换和视觉效果。例如,可以通过修改 position 的值来实现顶点的位移、旋转、缩放等变换。
      • 与矩阵变换结合使用: position 常与模型矩阵(Model Matrix)、视图矩阵(View Matrix)、投影矩阵(Projection Matrix)等结合使用,实现顶点从模型空间到裁剪空间的转换。
      • 灵活性和控制能力: 通过对 position 的操作,GLSL 提供了对顶点处理的极大灵活性和控制能力,是实现高质量图形渲染的基础之一。

总之 position 变量通常定义在顶点着色器的顶部,并通过顶点缓冲区(Vertex Buffer)赋值。它表示顶点的原始位置,通常是在模型(局部)空间中的坐标。在顶点着色器中,你会对 position 进行变换,比如模型变换、视图变换和投影变换,最终计算出顶点在裁剪空间中的位置,并赋值给 gl_Position。position 是单个顶点的信息。在 GPU 上,顶点着色器是并行执行的,意味着每个顶点都会 独立地执行 一次顶点着色器的 main 函数。因此,每次执行时,position 变量包含的是当前正在处理的单个顶点的位置信息。这允许顶点着色器对每个顶点进行独立的处理,如变换位置、计算光照等。

在 WebGL 中,尤其是在使用 Three.js 这类库时,modelMatrix、viewMatrix、projectionMatrix、和 modelViewMatrix 这些矩阵对于顶点着色器(Vertex Shader)来说非常重要,它们用于顶点的变换和投影。但需要注意的是,在原生的 WebGL GLSL 代码中,这些并不是内置变量;它们通常是通过 Three.js 或其他 WebGL 框架提供的,或者你需要自己定义并传递这些矩阵到着色器中。

  • modelMatrix: 将顶点从模型(局部)空间转换到世界空间。 每个物体在其自己的局部空间中被定义,modelMatrix 负责将这些顶点转换到一个共同的世界参考框架中,即世界空间。
  • viewMatrix: 将顶点从世界空间转换到视图空间(相机空间)。 viewMatrix 定义了相机在世界空间中的位置和方向,用于将世界空间中的坐标转换到相对于相机的视图空间。
  • projectionMatrix: 将顶点从视图空间投影到裁剪空间。 projectionMatrix 定义了视图空间到裁剪空间的转换,它考虑了视角(透视投影)或正交投影的影响,以及视锥体的定义,决定了如何将 3D 场景投影到 2D 屏幕上。
  • modelViewMatrix: 是 modelMatrix 和 viewMatrix 的组合,将顶点从模型空间直接转换到视图空间。 在某些情况下,直接使用 modelViewMatrix 比分别应用 modelMatrix 和 viewMatrix 更高效,因为它避免了两次矩阵乘法的需要。它直接将局部空间中的顶点转换到视图空间。

在实际的 GLSL 代码中,这些矩阵需要作为 uniform 变量从 JavaScript 代码中传递到着色器。在 Three.js 中,如果使用内置的材质和着色器,这些矩阵会自动被处理和传递,但如果你正在编写自定义着色器,你可能需要手动管理这些矩阵的传递。

Fragment Shader

Fragment Shader(片元着色器)主要负责处理每个像素(或称为“片元”)的颜色和其他属性,以生成最终的像素颜色输出到屏幕。它在图形渲染管线的后期阶段执行,接收顶点着色器处理后并经过光栅化阶段插值生成的数据。片元着色器的作用和特点包括:

作用

  1. 颜色计算: 计算最终像素的颜色值,这可能涉及纹理映射、光照计算、颜色混合等。
  2. 纹理应用: 应用纹理到几何图形上,包括纹理采样、纹理坐标的处理等。
  3. 光照效果: 实现各种光照模型,如冯氏光照模型(Phong Lighting Model),计算像素在不同光照条件下的颜色。
  4. 视觉效果: 实现视觉效果如阴影、光晕、模糊、反射等。
  5. Alpha 混合和透明度处理: 根据像素的 Alpha 值(透明度)计算最终颜色的混合。

特点

  1. 并行处理: 与顶点着色器一样,片元着色器在 GPU 上并行执行,每个片元独立处理,允许高效的图像渲染。
  2. 高度可编程: 提供了丰富的内置函数和数据类型,允许开发者自定义复杂的渲染效果和图像处理算法。
  3. 数据插值: 接收来自顶点着色器的数据(如颜色、纹理坐标等),这些数据会在光栅化阶段进行插值,片元着色器可以使用这些插值后的数据来计算最终颜色。
  4. 输出限制: 主要输出是颜色值,输出到一个或多个颜色缓冲区,但也可以用于计算深度、模板测试等高级效果。
  5. 资源访问: 可以访问纹理和其他资源进行复杂的图像处理,但相比顶点着色器,对资源的访问和处理能力更强大。

片元着色器是实现图形渲染中细节处理和视觉效果的关键部分,它为现代图形应用程序提供了极大的灵活性和控制能力。

示例

1
2
3
4
5
6
// GLSL ES 1.00 版本
precision mediump float; // 设置默认的浮点精度

void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 纯红色,不透明
}

这段代码中,precision mediump float; 指定了默认的浮点数精度,这是 GLSL ES 1.00 版本中必须的声明。main 函数中,gl_FragColor 被设置为 (1.0, 0.0, 0.0, 1.0),这表示红色分量为最大值 1.0,绿色和蓝色分量为 0,透明度(Alpha)也为最大值 1.0,即不透明的纯红色。

  • gl_FragColor: 是 GLSL ES 1.00 版本中 Fragment Shader(片元着色器)的内置输出变量,用于指定当前片元(像素)的颜色。
    • 作用:
      • 它的作用是允许着色器代码定义每个处理的片元的最终颜色输出,这个颜色随后会被送到后续的渲染管线阶段,可能会经过进一步的处理(如混合操作)后最终显示在屏幕上。
    • 类型:
      • gl_FragColor 的类型是 vec4,这是一个包含四个浮点分量的向量类型,分别对应红色(R)、绿色(G)、蓝色(B)和透明度(A)值。每个分量的取值范围是从 0.0 到 1.0,其中 0.0 代表最低强度(或完全透明),1.0 代表最高强度(或完全不透明)。

如果你使用的是 GLSL ES 3.00 或更高版本,设置片元颜色的方式会稍有不同,你需要定义自己的输出变量来代替 gl_FragColor:

1
2
3
4
5
6
7
8
9
// GLSL ES 3.00 版本
#version 300 es
precision mediump float; // 设置默认的浮点精度

out vec4 outColor; // 定义输出变量

void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0); // 纯红色,不透明
}

在这个例子中,out vec4 outColor; 定义了一个输出变量 outColor,用于替代 gl_FragColor。在 main 函数中,我们将 outColor 设置为纯红色,达到与上一个例子相同的效果。


从零开始的 WebGL GLSL Shader
http://blog.climbed.online/2024/04/20/Web -- Knowledge is infinite/GPU/GLSL shader 入门/
作者
Z.K.
发布于
2024年4月20日
许可协议