【Threejs进阶教程-着色器篇】9.顶点着色器入门
【Threejs进阶教程-着色器篇】9.顶点着色器入门
- 本系列教程第一篇地址,建议按顺序学习
- 认识顶点着色器
- varying介绍
- 顶点着色器与片元着色器分别的作用
- Threejs在Shader中的内置变量
- 各种矩阵
- gl_Position
- 尝试使用顶点着色器
- 增加分段数增强效果
- 制作平面鼓包效果
- 鼓包效果分析
- 路障效果
- 让路障效果变得圆滑
- 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的值是从顶点着色器中传递给片元着色器的
顶点着色器与片元着色器分别的作用
现阶段我这里不打算讲渲染原理和管线那些,太繁琐,这里先简单的总结一句
后续在讲到后处理的时候,这部分内容会再细讲
顶点着色器决定外型
片元着色器决定色彩
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>相关文章:
【Threejs进阶教程-着色器篇】9.顶点着色器入门
【Threejs进阶教程-着色器篇】9.顶点着色器入门 本系列教程第一篇地址,建议按顺序学习认识顶点着色器varying介绍顶点着色器与片元着色器分别的作用Threejs在Shader中的内置变量各种矩阵gl_Position 尝试使用顶点着色器增加分段数增强效果 制作平面鼓包效果鼓包效果…...
质量留住用户:如何通过测试自动化提供更高质量的用户体验
在当今竞争异常激烈的市场中,用户手头有无数种选择,但有一条真理至关重要: 质量留住用户。 产品的质量,尤其是用户体验 (UX),直接决定了客户是留在您的品牌还是转而选择竞争对手。随着业务的发展,出色的用户…...
【CSP CCF记录】201803-1第13次认证 跳一跳
题目 样例输入 1 1 2 2 2 1 1 2 2 0 样例输出 22 思路 没有技术含量的一道题,解题的关键是理解游戏规则。用state标记跳跃状态,以下是对游戏规则的分析: 1. state1,跳到方块上但没跳到中心,得1分 2. state2…...
详解Qt 中使用虚拟键盘(软键盘qtvirtualkeyboard)
文章目录 详解 Qt 中使用虚拟键盘(软键盘:QtVirtualKeyboard)1. 虚拟键盘简介1.1 虚拟键盘的应用场景 2. 安装和配置2.1 安装 QtVirtualKeyboard2.2 配置环境变量 3. 使用虚拟键盘3.1 示例代码main.cppwidget.hwidget.cpp 4. 总结 详解 Qt 中…...
cocoscreater3.8.4生成图集并使用
1.安装texturepacker,去官网下载https://www.codeandweb.com/texturepacker 2.将图片拖动进来,即可自动生成精灵表,这里输出选用cocos2d-x,打包用免费版的“基本”就行,高级模式是收费的,然后点击“发布精…...
IDEA如何快速地重写方法,如equals、toString等
前言 大家好,我是小徐啊。我们在使用IDEA的时候,有时候是需要重写equals和toString等方法的。这在IDEA中已经很方便的给我们准备好了快速的操作了。今天就来讲解一下。 如何重写 首先,打开要重写方法的文件,让鼠标定位到这个文…...
网络安全——SpringBoot配置文件明文加密
一、前言 在日常开发中,项目中会有很多配置文件。比如SpringBoot项目核心的数据库配置、Redis账号密码配置都在properties、yml配置文件 中。 如果这些信息以明文的方式存储,你的电脑被拿去修理,就会容易泄露,一旦被其他人获取到…...
LightRAG开源了…结合本地ollama实现股票数据接口Akshare智能问答
LightRAG是由香港大学研究团队推出的一种检索增强生成(Retrieval-Augmented Generation, RAG)系统。该系统通过整合图结构索引和双层检索机制,显著提升了大型语言模型在信息检索中的准确性和效率。LightRAG 不仅能够捕捉实体间的复杂依赖关系…...
【PCB设计】AD16教程:分配位号
1、前提条件 确保已经基本画完原理图 2、点击【Tools-Annotate Schematics】 3、依次点击【Reset All】、【Update Changes Lise】、【Close】 最后位号就被自动分配好了...
ElasticSearch7.x入门教程之索引概念和基础操作(三)
文章目录 前言一、索引基本概念二、索引基本使用elasticsearch-head插件Kibana使用 总结 前言 要想熟悉使用ES的索引,则必须理解索引相关的概念,尤其是在工作当中。 在此记录,方便开展工作。 一、索引基本概念 尽量以通俗的话语。 1、集群…...
Python后端flask框架接收zip压缩包方法
一、用base64编码发送,以及接收 import base64 import io import zipfile from flask import request, jsonifydef unzip_and_find_png(zip_data):# 使用 BytesIO 在内存中处理 zip 数据with zipfile.ZipFile(io.BytesIO(zip_data), r) as zip_ref:extracted_paths…...
机器学习中数据集Upsampling和Downsampling是什么意思?中英文介绍
对GPT4o大模型的Prompt如下: Datasets marked with ↓ are downsampled from their original datasets, datasets marked with ↑ are upsampled.这里的上采样和下采样是什么意思 内容援引自:paper (https://allenai.org/papers/tulu-3-repor…...
浏览器控制台中使用ajax下载文件(没有postman等情况下)
有时候,可能电脑里面没有postman(比如内网),然后又需要导出一些文件,前端又没有提供相应的功能(比如循环调用导出等),这时候我们就可以通过在控制台写代码的方式来实现了。这个还是在…...
完全二叉树的基本操作(顺序存储)
#include<iostream> #include<math.h> using namespace std;#define MaxSize 100 struct TreeNode {int value;bool isEmpty;//判断该节点是否为空 }t[MaxSize];/** *定义一个长度位MaxSize的数组,按照从上到下, *从左到右的方式依次存储完全…...
【HTTP】http与https
http与https的关系 应用层协议: http(HyperText Transfer Protocol)超文本传输协议; https(Hypertext Transfer Protocol Secure)超文本传输安全协议; 传输层协议:TCP(Tr…...
【Git多人开发与协作之团队的环境搭建】
Git多人开发与协作之团队的环境搭建 新的改变1. Git 的用途2. 分支的概念与类型3. HEAD 和分支指针如何查看 HEAD 指向的位置: 4. 常见的 Git 操作5. 常见问题与解决方法总结GitHub 项目获取实操在新电脑上运行 Git1. 安装 Git2. 配置用户名和邮箱3.配置 Git 和 SSH…...
java基础概念36:正则表达式1
一、正则表达式的作用 作用一:校验字符串是否满足规则;作用二:在一段文本中查找满足要求的内容。——爬虫 二、正则表达式 2-1、字符类 示例: public static void main(String[] args) {System.out.println("a".matc…...
java实现小程序接口返回Base64图片
文章目录 引言I java 接口返回Base64图片接口设计获取验证码图片-base64字符串获取验证码图片-二进制流arraybufferII 小程序端代码过期代码: 显示文件流图片(arraybuffer)知识扩展:微信小程序下载后端返回的文件流引言 场景: 图形验证码 背景: 接口返回arraybuffer的格式…...
网络编程并发服务器的应用
作业2:完成局域网CS模型,局域网内一个服务器,多个客户端连接一个服务器,完成局域网聊天(select函数,poll函数,完成TCP并发服务器)。 poll函数应用: 服务器部分代码&…...
数据结构——停车场管理问题
目录 1、问题描述2、逐步分析1)涉及操作2)代码实现 3、代码整合 1、问题描述 1、题目 设停车场内只有一个可停放n辆汽车的狭长通道,且只有一个大门可供汽车进出。汽车在停车场内按车辆到达时间的先后顺序,依次由北向南排列&#x…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
