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

【three.js】Shader着色器

原始着色器材质RawShaderMaterial

两种着色器材质的 RawShaderMaterialShaderMaterial 的区别和用法

区别:

ShaderMaterial 会自动将一些初始化着色器的参数添加到代码中(内置 attributesuniforms

RawShaderMaterial 则什么都不会添加。

RawShaderMaterial

特点:允许直接编写原始的 GLSL(OpenGL Shading Language)代码来控制顶点和片元的处理。它提供了最大的灵活性,但也需要开发者对着色器编程有深入的理解。

设置精度:使用 RawShaderMaterial 时,开发者需要在 GLSL 代码中手动设置浮点数的精度(如 precision mediump float;)。

适用场景:适合需要高度定制着色器行为的高级用户,或者当现有的 ShaderMaterial 不能满足特定需求时。

ShaderMaterial

特点:是 Three.js 提供的一个高级封装,它基于 RawShaderMaterial,但增加了一些便利性功能和默认设置。例如,它会自动处理浮点数的精度问题。

设置精度:在 ShaderMaterial 中,开发者通常不需要手动设置精度,因为 Three.js 会根据渲染器的配置自动处理。

适用场景:适合大多数需要自定义着色器的场景,特别是当开发者希望避免手动管理精度设置和其他底层细节时。

塑料质感(Plastic Shader)和动画质感(Toon Shader)

塑料质感

特点:模拟塑料表面的光泽和反射效果。通常通过调整高光强度、光泽度和颜色来实现。

实现:在片元着色器中,可以使用菲涅尔方程来计算反射率,并结合环境光和漫反射来计算最终颜色。

动画质感

特点:模拟卡通或动画风格的渲染效果。通常表现为颜色分块、边缘硬化和简化的光照模型。

实现:在片元着色器中,可以使用阶跃函数或平滑阶跃函数来控制颜色的过渡,从而实现分块效果。同时,可以通过调整光照模型来简化阴影和高光的表现。

着色器的类型

顶点着色器Vertex Shader

  顶点着色器的作用是将几何体的每个顶点放置在 2D 渲染空间上,即顶点着色器将 3D 顶点坐标转换为 2Dcanvas 坐标。

main函数

它将被自动调用,并且不会返回任何内容。

void main() {}
gl_Position

gl_Position 是一个内置变量,我们只需要给它重新赋值就能使用,它将会包含屏幕上的顶点的位置。下面 main 函数中就是用于给它设置合适的值。执行这段指令后,将得到一个 vec4,意味着我们可以直接在 gl_Position 变量上使用其xyzw 属性。

void main() {
  gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
  gl_Position.x += 0.5;
  gl_Position.y += 0.5;
}

        平面向右上角发生了位移,但是需要注意的是,我们并没有像在 Three.js 中一样将平面在三维空间中进行了移动,我们只是在二维空间中移动了平面的投影。就像你在桌子上画了一幅具有透视效果的画,然后把它向桌子右上角移动,但是你的画中的透视效果并没有发生变化。

  gl_Position 的作用是在 2D 空间上定位 📍 顶点,既然是 2D 空间,为什么需要使用一个四维向量表示呢?实际上是这些坐标并不是精确的在 2D 空间,而是位于被称为 Clip Space 需要四个维度的裁切空间。裁切空间是指在 -1+1 范围内所有 xyz3个方向上的空间,第四个值 w 用于表示透视。就像把所有东西都放在 3D 盒子中一样,任何超出范围的内容都将被裁切。gl_Position 这些内容的这些内容都是自动完成的,我们只需明白其大概原理即可。

位置属性Position attributes

       相同的代码将应用于几何体的每一个顶点,属性变量 attribute 是在顶点之间唯一会发生改变的变量。相同的顶点着色器 Vertex Shader 将应用于每一个顶点,position 属性将包含具体顶点的 x, y, z 坐标值。我们可以使用如下代码获取顶点位置:

attribute vec3 position;

        因为 gl_Positionvec4 类型,可以使用以下方法将 vec3 转化成 vec4

gl_Position = /* ... */ vec4(position, 1.0);
矩阵限定变量Matrices uniforms

        每个矩阵将转换 position,直到我们获得最终的裁切空间坐标。下面是 3 个矩阵,因为在几何体所有顶点中它们的值都是相同的,我们可以通过 uniform 来获取它们。

uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;

下面将对每个矩阵做出一些变换:

modelMatrix:将进行网格相关的变换,如缩放、旋转、移动等操作变换都将作用于 position

viewMatrix:将进行相机相关的变换,如我们向左移动相机,顶点应该在右边、如果我们朝着网格方向移动相机,顶点会变大等。

projectionMatrix:会将我们的坐标转化为裁切空间坐标。

        为了使用矩阵,我们需要将其相乘,如果想让一个 mat4 作为变量,则该变量类型必须是 vec4。我们也可以将多个矩阵相乘:

gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);

        实际上还可以使用更短的写法来让 viewMatrixmodelMatrix 组合成一个 projectionMatrix,虽然代码少了,但我们可控制的步骤也少了。

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec3 position;

void main(){
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

        实际中我们会选择更长的写法,以便于更好地理解及对 position 进行更多的控制。

uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;

void main(){
  vec4 modelPosition = modelMatrix * vec4(position, 1.0);
  vec4 viewPosition = viewMatrix * modelPosition;
  vec4 projectedPosition = projectionMatrix * viewPosition;~~~~
    gl_Position = projectedPosition;
}

        上面两种写法都是等价的,使用下面这种时,我们可以更方便地进行控制,比如可以通过调整 modelPosition 的值来对整个模型进行移动,通过以下代码,就能向上移动模型:

void main() {
  vec4 modelPosition = modelMatrix * vec4(position, 1.0);
  modelPosition.y += 1.0;
  // ...
}
void main() {
  vec4 modelPosition = modelMatrix * vec4(position, 1.0);
  modelPosition.z += sin(modelPosition.x * 10.0) * 0.1;
  // ...
}

attribute:使用顶点数组封装每个顶点的数据,一般用于每个顶点都各不相同的变量,如顶点的位置。

uniform:顶点着色器使用的常量数据,不能被修改,一般用于对同一组顶点组成的单个 3D 物体中所有顶点都相同的变量,如当前光源的位置。

片元着色器Fragment Shader

  Fragment ShaderVertex Shader 之后执行,它的作用是为几何体的每个可见像素进行着色。我们可以通过uniforms 将数据发送给它,也可以将 Vertex Shader 中的数据发送给它,我们将这种从 Vertex Shader 发送到 Fragment Shader 的数据称为 varying

  Fragment Shader 中最直接的指令就是可以使用相同的颜色为所有像素进行着色。如果只设置了颜色属性,就相当于得到了与 MeshBasicMaterial 等价的材质。如果我们将光照的位置发送给 Fragment Shader,然后根据像素收到光照影响的多少来给像素上色,此时就能得到与 MeshPhongMaterial 效果等价的材质。

        片元着色器的代码将应用于几何体的每个可见像素,这就是片元着色器在顶点着色器之后运行的原因,它的代码比顶点着色器更易于管理

主函数main

void main() {}

精度Precision

在顶部有一条这样的指令,我们用它来决定浮点数的精度,有以下几种值供选择:

highp:会影响性能,在有些机器上可能无法运行;

mediump:常用的类型;

lowp:可能会由于精度问题产生错误

precision mediump float;

  RawShaderMaterial 原始着色器材质才需要设置精度,在着色器材质 ShaderMaterial中会自动处理。

在顶点着色器中也可以是指精度,但是这是非必须的。

gl_FragColor

  gl_FragColorgl_Position 类似,但它用于颜色。它也一样是已经被内置声明了的,我们只需要在main 函数中重新给它赋值。它是一个 vec4,前三个值是红色、绿色、蓝色通道 (r, g, b),第四个值是透明度 alpha(a)gl_FragColor 的每个值的取值范围是 0.01.0,如果我们设置的值高于它们,也不会产生报错。

gl_FragColor = vec4(0.5, 0.0, 1.0, 1.0);

        为了 alpha 透明度值可以生效,我们需要在材质中将 transparent 属性设置为 true

const material = new THREE.RawShaderMaterial({
  vertexShader: testVertexShader,
  fragmentShader: testFragmentShader,
  transparent: true
})
属性Attributes

  Attributes 是每个顶点之间变化的值,我们之前已经有一个命名为 position 的属性变量,它是每个顶点在坐标轴中的 vec3 值。我们将为每个顶点添加一个随机值,并根据这个值在 z 轴上移动该顶点。在 JavaScript 代码中我们可以像下面这个直接给 BufferGeometry 添加 attribute 属性。然后再创建一个 32位 的浮点类型数组 Float32Array,为了知道几何体中有多少个顶点,现在可以通过 attributes 属性获取。最后在 BufferAttribute 中使用该数组,并将它添加到几何体的属性中。

setAttribute:第一个参数是需要设置的 attribute属性名称,然后在着色器中可以使用该名字,属性名命名时最好加一个 a 前缀方便区分。

BufferAttribute:第一个参数是数据数组;第二个参数表示组成一个属性的值的数量,如我们要发送一个 (x, y, z) 构成位置,则需要使用 3,示例中每个顶点的随机数只有 1个,因此这个参数使用 1

const geometry = new THREE.PlaneBufferGeometry(1, 1, 32, 32)
const count = geometry.attributes.position.count
const randoms = new Float32Array(count)
// 使用随机数填充数组
for(let i = 0; i < count; i++) {
  randoms[i] = Math.random()
}
// 添加到几何体的属性中
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1))

        现在,我们可以在顶点着色器中获取该属性,并使用它移动顶点,可以得到一个如下图所示的一个由随机尖峰构成的平面。

attribute float aRandom;

void main(){
  // ...
  modelPosition.z += aRandom * 0.1;
  // ...
}

限定变量Varyings

varying: 从顶点着色器发送到片元着色器中的插值计算数据

       现在我们若想在片元着色器中想使用 aRandom 属性给片元着色,是无法直接使用 attribute 属性变量的。此时,实现这个功能的方法就是将这个值从顶点着色器发送到片元着色器,称这种变量为 varying。我们需要在两种着色器中都做如下的操作:

        在顶点着色器中,我们需要在 main 函数之前创建 varying,将其命名为以 v 作为前缀的变量名 vRandom,然后在 main 函数中给它赋值:

varying float vRandom;

void main() {
  // ...
  vRandom = aRandom;
}

        在片元着色器中,使用相同的方法声明,然后在 main 函数中使用它,可以得到如下的染色效果:

precision mediump float;
varying float vRandom;

void main() {
  gl_FragColor = vec4(0.5, vRandom, 1.0, 1.0);
}

        varying的一个有趣之处是,顶点之间的值是线性插值的,如GPU在两个顶点之间绘制一个片元,一个顶点的varying是1.0,另一个顶点的varying是0.0,则该片元值将为0.5。这个特性可以实现平滑的渐变效果。

统一变量Uniforms

1、概念

        Uniforms(统一变量)是OpenGL着色器编程中的一种特殊全局变量,用于在图形渲染管线的不同阶段(如顶点着色器、片段着色器等)共享常量数据。

2、特点

        Uniforms的值由CPU端的应用程序设置,并且在整个渲染调用过程中保持不变。

        Uniforms的生命周期跨越整个渲染帧,在这一帧内其值是恒定的。

        一个Uniforms变量在被链接在一起的所有着色器阶段中都是可见的,如果顶点着色器和片段着色器都引用了同一个Uniforms变量,则它们将访问到相同的值。

        Uniforms变量不能在着色器代码中进行赋值,但可以在声明时初始化一个缺省值。

3、定义方法

        在GLSL(OpenGL Shading Language)中,使用关键字uniform进行声明,并指定其数据类型,例如矩阵、向量、标量或者结构体。例如:uniform mat4 modelMatrix;(用于存储模型变换矩阵),uniform vec3 lightPosition;(存储光源位置),uniform float time;(表示当前时间)。

4、赋值与传参

        应用程序需要使用OpenGL API函数来为Uniforms变量分配内存地址并设置其值。这些函数包括glGetUniformLocation(获取Uniforms变量在着色器中的位置标识符),然后使用glUniform*系列函数(如glUniform1fglUniform3fvglUniformMatrix4fv等)来传递实际的数据。

        当Uniforms变量数量较多时,可以利用Uniform Buffer Objects(UBOs)进行优化。UBOs允许将多个Uniforms变量打包到连续的缓冲区中,一次性上传至GPU,从而减少API调用开销。

5、自定义Location

        在着色器代码中,可以使用location布局限定符来指定Uniforms变量的位置。这样,OpenGL在链接着色器时会尝试将指定的位置分配给这些Uniforms变量。

6、活动Uniforms

        在链接后的着色器程序中,并非所有声明的Uniforms都会被认为是“活跃”的。活跃的Uniforms是指在任何片段着色器、顶点着色器或其他类型的着色器阶段中有用到的Uniforms变量。

        可以使用glGetActiveUniform等函数来查询这些在链接后的着色器程序中确实起作用的Uniforms变量的详细信息,包括其名称、类型、大小和数组元素的数量等。

7、限制与注意事项

        OpenGL对Uniforms变量的数量和大小有限制,这取决于硬件支持。超出限制会导致无法创建更多的Uniforms变量或无法设置更大的Uniforms数组。

        如果在顶点着色器和片段着色器中均声明了同一个Uniforms变量,则声明的类型必须相同,且在两个着色器中的值也需相同。

码字不易,各位大佬点点赞呗


http://www.kler.cn/a/468603.html

相关文章:

  • 每日AIGC最新进展(80): 重庆大学提出多角色视频生成方法、Adobe提出大视角变化下的人类视频生成、字节跳动提出快速虚拟头像生成方法
  • R语言基础| 中级绘图
  • 【读书与思考】历史是一个好东西
  • shell脚本总结2
  • 海外云服务器能用来做什么?
  • nginx学习之路-nginx配置https服务器
  • 如何弥补开源大语言模型解决推理任务的不足
  • 深度 SEO 优化
  • 常见的框架漏洞复现
  • HarmonyOS NEXT应用开发实战(一):边学边玩,从零开发一款影视APP
  • 如何使用SparkSql
  • GESP202406 二级【计数】题解(AC)
  • html生成注册与登录代码
  • 使用LINUX的dd命令制作自己的img镜像
  • 【CSS】第一天 基础选择器与文字控制属性
  • 实时数仓:基于数据湖的实时数仓与数据治理架构
  • 【人工智能】基于Python与OpenCV构建简单车道检测算法:自动驾驶技术的入门与实践
  • [读书日志]从零开始学习Chisel 第四篇:Scala面向对象编程——操作符即方法(敏捷硬件开发语言Chisel与数字系统设计)
  • 【开源监控工具】Uptime Kuma:几分钟设置实时监控你的网站性能
  • 计算机网络掩码、最小地址、最大地址计算、IP地址个数
  • Android学习20 -- NDK5--操作camera(TODO)
  • 【能用的方案】springBoot集成netty中如何使用@Value(通过依赖注入(DI)来访问)配置文件中的属性值
  • MaxKB知识库问答系统v1.9版本有哪些具体的改进?
  • 【网络安全 | 漏洞挖掘】通过模拟功能实现提权(Bugcrowd)
  • ESP32学习--SPIFFS文件系统
  • gaussdb中怎么查询一个表有多少GB