【Threejs进阶教程-着色器篇】9.顶点着色器入门

ops/2024/11/28 20:45:36/

Threejs进阶教程-着色器篇】9.顶点着色器入门

  • 本系列教程第一篇地址,建议按顺序学习
  • 认识顶点着色器
  • 尝试使用顶点着色器
    • 增加分段数增强效果
  • 制作平面鼓包效果
    • 鼓包效果分析
    • 路障效果
    • 让路障效果变得圆滑
    • uniform 控制鼓包效果
  • 完整源码

本系列教程第一篇地址,建议按顺序学习

本系列目前已累计第九篇,这里直接省略了2到8篇,可以通过上方专栏来查阅前面的教程
Threejs进阶教程-着色器篇】1. Shader入门(ShadertoyShader和ThreejsShader入门)

本篇使用到的模板代码,从这里自取一个shader模板代码即可
【模板代码】用于编写Threejs Demo的模板代码

认识顶点着色器

首先我们把着色器部分的代码拎出来逐一分析

<script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;void main(){vUv = vec2(uv.x,uv.y);vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );gl_Position = projectionMatrix * mvPosition;}</script>
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;void main(){gl_FragColor = vec4(1.0,0.0,0.0,1.0);}
</script>

varying介绍

有人发现顶点着色器的第一行与片元着色器的第一行,完全一致,那么这个varying用来做什么的呢?

这里我们直接借用webgl编程指南211页的介绍
在这里插入图片描述

varying主要用于 顶点着色器到片元着色器传输数据,是个全局变量

现阶段,我们只需要认识到,varying声明的变量,要在顶点着色器和片元着色器都要存在,且初始值需要在顶点着色器中设置即可

了解了varying之后,我们发现,其实uv的值是从顶点着色器中传递给片元着色器

顶点着色器与片元着色器分别的作用

现阶段我这里不打算讲渲染原理和管线那些,太繁琐,这里先简单的总结一句
后续在讲到后处理的时候,这部分内容会再细讲

顶点着色器决定外型
片元着色器决定色彩

ThreejsShader_49">Threejs在Shader中的内置变量

顶点着色器中使用到了几个threejs的内置变量,在下面的官方文档中有说明
Threejs内置变量-WebGLProgram
一般来说我们最需要关注的几个:
position,模型的顶点信息会传递到这里,常用于计算模型的外观和最终渲染的外型
uv,模型的uv信息会传递到这里,常用于传递给顶点着色器用于计算颜色
normal,模型的法线信息会传递到这里,常用于计算光照等高级计算

如果看完了前面的BufferGeometry教程,有没有发现这里很熟悉?
【ThreeJS基础教程-高级几何体篇】2.6 BufferGeometry与BufferAttribute

你们想的没错,这些就是threejs向shader系统传递的数据,如果这里不懂BufferGeometry的,要继续下去学习Shader,就需要去前面补一下BufferGeometry的相关知识了

各种矩阵

在顶点着色器模板代码中的第四行第五行,分别出现了modelViewMatrix和projectionMatrix这两个矩阵,现阶段先不用管,只需要记住顶点着色器最终计算是这样即可

现阶段顶点着色器的代码,在模板代码的最后两行,除了**vec4(position,1.0);**会稍作改变,其他时间不会发生大的变动

gl_Position

一般来说,顶点着色器也需要有个固定输出,gl_Position就是顶点着色器的最终输出结果,最终结果也是一个vec4类型的对象

这样,顶点着色器的代码我们就介绍完毕了,接下来我们要尝试修改一下顶点着色器,感受一下顶点着色器带来的效果

尝试使用顶点着色器

我们用个最简单的方式来操作顶点

<script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;void main(){vUv = vec2(uv.x,uv.y);vec3 aPosition = position;//这里我们直接操作顶点的z轴,偏移的激进一点aPosition.z = sin(aPosition.x * aPosition.y) * 10.0;vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );gl_Position = projectionMatrix * mvPosition;}</script>

在这里插入图片描述
然后,我们运行起来之后,发现,我们的平面,扭曲了,这是因为我们的z轴发生了改变

增加分段数增强效果

这里我们修改一下addMesh()

    function addMesh() {//增加到100分段let geometry = new THREE.PlaneGeometry(10,10,3,3);let material = new THREE.ShaderMaterial({uniforms,vertexShader:document.getElementById('vertexShader').textContent,fragmentShader:document.getElementById('fragmentShader').textContent,transparent:true})let mesh = new THREE.Mesh(geometry,material);scene.add(mesh);}

在这里插入图片描述
我们可以发现,增加了分段和顶点之后,我们的这个平面变化巨大,已经不再是最初的PlaneGeometry了

这样,我们就完成了一次顶点着色器的尝试

制作平面鼓包效果

首先我们把平面横过来,然后需要做一个数据变换,分段数增加到100,然后设定材质的线框模式开,不然我们等一下不好看到效果
然后修改回最初的顶点着色器代码

addMesh

    function addMesh() {//注意这里必须旋转几何体,旋转了几何体,我们的数据才是正确的//mesh.rotation是在矩阵层面修改了旋转方向,最终会传递到modelViewMatrix中//顶点着色器的所有教程,除非特殊说明,否则全部使用旋转几何体let geometry = new THREE.PlaneGeometry(10,10,100,100).rotateX(-Math.PI/2);let material = new THREE.ShaderMaterial({uniforms,vertexShader:document.getElementById('vertexShader').textContent,fragmentShader:document.getElementById('fragmentShader').textContent,transparent:true,wireframe:true})let mesh = new THREE.Mesh(geometry,material);scene.add(mesh);}

顶点着色器

<script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;void main(){vUv = vec2(uv.x,uv.y);vec3 aPosition = position;vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );gl_Position = projectionMatrix * mvPosition;}
</script>

在这里插入图片描述

鼓包效果分析

既然要制作鼓包效果,那么,我们需要一个鼓包顶点,然后鼓包顶点处的高度最高,然后依次递减,所以我们这里直接从顶点着色器来定义这个鼓包点

<script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;void main(){vUv = vec2(uv.x,uv.y);vec3 aPosition = position;//0 为int类型,0.0为float类型, 如果写0,threejs会报错// 可以写成 .0 来替代0.0以及任何 0.X 的数字, 但是个人不是很喜欢这种写法,看着太混乱vec3 swelling = vec3(0.0);//计算鼓包点到顶点的距离float dis = distance(swelling,aPosition);dis = clamp(dis,0.0,5.0);aPosition.y = 5.0 - dis;vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );gl_Position = projectionMatrix * mvPosition;}
</script>

路障效果

这里,我们先声明了鼓包点在中心点,然后,我们计算鼓包点到四周的距离,但是要做一下限制,如果大于5.0的值,则直接赋值为5.0,紧接着直接把这个计算出来的dis值丢给aPosition.y,我们得到了一个漏斗型
在这里插入图片描述
既然我们计算的dis的最大值为5,那么,我们把大小做一下交换即可,用5.0 - dis,即可把漏斗形改成路障型
在这里插入图片描述

让路障效果变得圆滑

这里我们使用指数函数来优化

我们现在知道了最高点是5,最低点为0,那么,我们就可以计算它的高度比例,然后把线性的比例换成指数型比例
在这里插入图片描述
保持最高点和最低点不变,然后我们直接带入图像上面的数学公式,即可得到我们的鼓包效果

<script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;void main(){vUv = vec2(uv.x,uv.y);vec3 aPosition = position;//0 为int类型,0.0为float类型, 如果写0,threejs会报错// 可以写成 .0 来替代0.0以及任何 0.X 的数字, 但是个人不是很喜欢这种写法,看着太混乱vec3 swelling = vec3(0.0);//计算鼓包点到顶点的距离float dis = distance(swelling,aPosition);dis = clamp(dis,0.0,5.0);dis = pow( dis / 5.0, 2.0 ) * 5.0;aPosition.y = 5.0 - dis;vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );gl_Position = projectionMatrix * mvPosition;}
</script>

在这里插入图片描述

uniform 控制鼓包效果

uniform亦可用于顶点着色器

我们在代码中多次使用到5.0,这个实际上是鼓包的最大高度,这里我们抽出来这个常数作为鼓包最大高度,指数函数用的2次幂,这个参数可以抽出一个参数为鼓包圆滑率,我们写到uniform和lil.gui来调试
当然,我们的鼓包中心点,也可以单独拎出来放到uniform中

顶点着色器中编写uniform与片元着色器基本一致

修改后的顶点着色器

<script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;uniform float maxSwelling;uniform vec3 swellingCenter;uniform float swellingPower;void main(){vUv = vec2(uv.x,uv.y);vec3 aPosition = position;//计算鼓包点到顶点的距离float dis = distance(swellingCenter,aPosition);dis = clamp(dis,0.0,maxSwelling);dis = pow( dis / maxSwelling, swellingPower ) * maxSwelling;aPosition.y = maxSwelling - dis;vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );gl_Position = projectionMatrix * mvPosition;}
</script>

修改后的addMesh()和uniforms

let uniforms = {maxSwelling:{value:5.0},swellingCenter:{value:new THREE.Vector3()},swellingPower:{value:2.0}}function addMesh() {//注意这里必须旋转几何体,旋转了几何体,我们的数据才是正确的//mesh.rotation是在矩阵层面修改了旋转方向,最终会传递到modelViewMatrix中//顶点着色器的所有教程,除非特殊说明,否则全部使用旋转几何体let geometry = new THREE.PlaneGeometry(10,10,100,100).rotateX(-Math.PI/2);let material = new THREE.ShaderMaterial({uniforms,vertexShader:document.getElementById('vertexShader').textContent,fragmentShader:document.getElementById('fragmentShader').textContent,transparent:true,wireframe:true})let mesh = new THREE.Mesh(geometry,material);scene.add(mesh);//注意自己引入lil.guilet gui = new GUI();gui.add(uniforms.maxSwelling,'value',0,10).step(0.01).name('最大鼓包高度');gui.add(uniforms.swellingPower,'value',0,10).step(0.01).name('鼓包曲线');let folder = gui.addFolder('鼓包中心');folder.add(uniforms.swellingCenter.value,'x',-5,5);folder.add(uniforms.swellingCenter.value,'y',-5,5);folder.add(uniforms.swellingCenter.value,'z',-5,5);}

在这里插入图片描述

完整源码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>body{width:100vw;height: 100vh;overflow: hidden;margin: 0;padding: 0;border: 0;}</style>
</head>
<body><script type="importmap">{"imports": {"three": "../three/build/three.module.js","three/addons/": "../three/examples/jsm/"}}</script><script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;uniform float maxSwelling;uniform vec3 swellingCenter;uniform float swellingPower;void main(){vUv = vec2(uv.x,uv.y);vec3 aPosition = position;//计算鼓包点到顶点的距离float dis = distance(swellingCenter,aPosition);dis = clamp(dis,0.0,maxSwelling);dis = pow( dis / maxSwelling, swellingPower ) * maxSwelling;aPosition.y = maxSwelling - dis;vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );gl_Position = projectionMatrix * mvPosition;}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;void main(){gl_FragColor = vec4(1.0,0.0,0.0,1.0);}
</script><script type="module">import * as THREE from "../three/build/three.module.js";import {OrbitControls} from "../three/examples/jsm/controls/OrbitControls.js";import {GUI} from "../three/examples/jsm/libs/lil-gui.module.min.js";window.addEventListener('load',e=>{init();addMesh();render();})let scene,renderer,camera;let orbit;function init(){scene = new THREE.Scene();renderer = new THREE.WebGLRenderer({alpha:true,antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(renderer.domElement);camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);camera.add(new THREE.PointLight());camera.position.set(15,15,15);scene.add(camera);orbit = new OrbitControls(camera,renderer.domElement);orbit.enableDamping = true;scene.add(new THREE.GridHelper(10,10));}let uniforms = {maxSwelling:{value:5.0},swellingCenter:{value:new THREE.Vector3()},swellingPower:{value:2.0}}function addMesh() {//注意这里必须旋转几何体,旋转了几何体,我们的数据才是正确的//mesh.rotation是在矩阵层面修改了旋转方向,最终会传递到modelViewMatrix中//顶点着色器的所有教程,除非特殊说明,否则全部使用旋转几何体let geometry = new THREE.PlaneGeometry(10,10,100,100).rotateX(-Math.PI/2);let material = new THREE.ShaderMaterial({uniforms,vertexShader:document.getElementById('vertexShader').textContent,fragmentShader:document.getElementById('fragmentShader').textContent,transparent:true,wireframe:true})let mesh = new THREE.Mesh(geometry,material);scene.add(mesh);let gui = new GUI();gui.add(uniforms.maxSwelling,'value',0,10).step(0.01).name('最大鼓包高度');gui.add(uniforms.swellingPower,'value',0,10).step(0.01).name('鼓包曲线');let folder = gui.addFolder('鼓包中心');folder.add(uniforms.swellingCenter.value,'x',-5,5);folder.add(uniforms.swellingCenter.value,'y',-5,5);folder.add(uniforms.swellingCenter.value,'z',-5,5);}function render() {renderer.render(scene,camera);orbit.update();requestAnimationFrame(render);}</script>
</body>
</html>

http://www.ppmy.cn/ops/137464.html

相关文章

笔记本外接4k显示器只有30Hz刷新率

方法 注意显示器设置里有一个调节帧率的选项是可以选60帧的&#xff0c;如果不能修改 通过按钮找到显示-USBC优先级&#xff0c;选择高分辨率&#xff0c;之后在显示器设置中应该出现60Hz的选项&#xff0c;更改选项则切换至60Hz 原因是USBC线缆存在高分辨率和高数据传输两种模…

Vue-TreeSelect组件最下级隐藏No sub-options

问题&#xff1a;最下级没有数据的话&#xff0c;去除No sub-options信息 为什么没下级&#xff0c;会展示这个&#xff1f; 整个树形结构数据都是由后端构造好返回给前端的。默认子类没数据的话&#xff0c;children是一个空数组。也就是因为这最下级的空数组&#xff0c;导致…

scala模式匹配

object test47 {def main(args: Array[String]): Unit {val id"445646546548858548648"//取出id前两位val provinceid.substring(0,2) // println(province) // if (province"42"){ // println("湖北") // }else if(province&quo…

结构方程模型(SEM)入门到精通:lavaan VS piecewiseSEM、全局估计/局域估计;潜变量分析、复合变量分析、贝叶斯SEM在生态学领域应用

目录 第一章 夯实基础 R/Rstudio简介及入门 第二章 结构方程模型&#xff08;SEM&#xff09;介绍 第三章 R语言SEM分析入门&#xff1a;lavaan VS piecewiseSEM 第四章 SEM全局估计&#xff08;lavaan&#xff09;在生态学领域高阶应用 第五章 SEM潜变量分析在生态学领域…

python简单算法

冒泡 def boll(lis):i 0while i<len(lis)-1:j 0while j<len(lis)-1-i:if lis[j] > lis[j1]:lis[j],lis[j 1] lis[j1],lis[j]j1i1选择排序 def selct1(lit):i 0while i<len(lit)-1:j i1min1 iwhile j < len(lit):if lit[j] < lit[min1]:min1 jj 1li…

HTML、CSS

HTML、CSS 什么是HTML、CSS? HTML(HyperText Markup Language):超文本标记语言 超文本&#xff1a;超越了文本的限制&#xff0c;比普通文本更加强大。除了文字信息&#xff0c;还可以定义图片、音频、视频等内容标记语言&#xff1a;由标签构成的语言HTML标签都是预定义好的…

Linux介绍与安装指南:从入门到精通

1. Linux简介 1.1 什么是Linux&#xff1f; Linux是一种基于Unix的操作系统&#xff0c;由Linus Torvalds于1991年首次发布。Linux的核心&#xff08;Kernel&#xff09;是开源的&#xff0c;允许任何人自由使用、修改和分发。Linux操作系统通常包括Linux内核、GNU工具集、图…

Python 中的 Lxml 库与 XPath 用法

Python 中的 Lxml 库与 XPath 用法 Python 中的 Lxml 库与 XPath 用法Lxml安装 Lxml基础用法加载文档解析与查询创建新的 XML/HTML 高级特性1. 复杂的 XPath 查询2. DTD 和 Schema 验证3. XSLT 变换4. 自定义命名空间5. 异常处理6. 大文件流式处理7. 并发和线程安全性8. 性能优…