WebGL+Three.js入门与实战——绘制水平移动的点、通过鼠标控制绘制(点击绘制、移动绘制、模拟画笔)

个人简介

👀个人主页: 前端杂货铺
🙋‍♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js🍒Three.js🍖数据结构与算法体系教程

🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧

文章目录

    • 前言
    • 一、绘制一个水平移动的点(attribute)
    • 二、通过鼠标控制绘制
      • 1、鼠标点击绘制点
      • 2、鼠标移动绘制点
      • 3、模拟画笔
    • 总结

前言

大家好,这里是前端杂货铺

上一篇文章,我们学习了如何给画布换颜色、如何绘制一个点并且了解了三维坐标系…

接下来,继续我们 WebGL 内容的学习!

鼠标绘制的三种效果


一、绘制一个水平移动的点(attribute)

我们在着色器源程序中声明 attribute 变量,js 可以给这个变量传值,再绘制出来,从而就可以实现动态修改点的位置。

以下是 声明 attribute 变量,需要注意的是,attribute 只能在顶点着色器中使用,不能在片元着色器中使用。

// attribute: 存储限定符; vec4: 类型; aPosition: 变量名;
attribute vec4 aPosition;

以下是 获取 attribute 变量,需要注意的是 获取 attribute 变量需要在 initShader 函数之后,因为会用到 program 这个程序对象。获取之后返回变量的存储地址

// program: 程序对象; 'aPosition': 指定想要获取存储地址的 attribute 变量的名称
const aPositon = gl.getAttribLocation(program, 'aPosition');

以下是 给 attribute 变量赋值,可以传递 1/2/3 个分量的值,没有传递的值为 0.0。

// location: 指定 attribute 变量的存储位置; v0, v1, v2, v3: 传入的分量值; 
gl.vertexAttrib1f(location, v0);
gl.vertexAttrib2f(location, v0, v1);
gl.vertexAttrib3f(location, v0, v1, v2);
gl.vertexAttrib4f(location, v0, v1, v2, v3);

绘制流程如下:

在这里插入图片描述

基于以上知识点,我们使用 attribute 变量绘制一个水平移动的点

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./index.js"></script>
</head><body><canvas id="canvas" width="400" height="400" style="background: gray;">此浏览器不支持canvas</canvas><script>const ctx = document.getElementById('canvas');const gl = ctx.getContext('webgl');// 着色器// 创建着色器源码// 顶点着色器const VERTEX_SHADER_SOURCE = `// 存储限定符 类型 变量名 分号 (注: attribute 只传递顶点数据)attribute vec4 aPosition;void main() {gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)gl_PointSize = 30.0;}`;// 片元着色器const FRAGMENT_SHADER_SOURCE = `void main() {// r g b agl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}`const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);// 获取 attribute 变量,返回变量的存储地址const aPosition = gl.getAttribLocation(program, 'aPosition');let x = 0;setInterval(() => {x += 0.1;if (x > 1.0) {x = 0;}// 给 attribute 变量赋值gl.vertexAttrib1f(aPosition, x);// 执行绘制gl.drawArrays(gl.POINTS, 0, 1);}, 200)</script>
</body></html>

attribute绘制一个水平移动的点


二、通过鼠标控制绘制

1、鼠标点击绘制点

接下来,我们通过鼠标来控制在画布上绘制点。

  • 屏幕坐标通过 event.clickXevent.clickY 来获取
  • 画布边距通过 event.target.getBoundingClientRect() 来获取,其 .left 和 .right 等同于 ctx.offsetWidth 和 ctx.offsetHeight
  • 画布的坐标通过 屏幕坐标 - 画布边距 获取
  • 转为 ndc 坐标:设画布的长和宽均为 400px,那么原点为 0,最左为 -200px,最右为 200px。想要转为原点为 0,最左为 -1,最右为 1,就可以均除以 200。最上和最下同理。

在这里插入图片描述

示例:我们点击画布的左上角的位置,查看控制台输出的坐标,如下

在这里插入图片描述

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./index.js"></script><style>* {margin: 0;padding: 0;}canvas {margin: 50px auto 0;display: block;background: yellow;}</style>
</head><body><canvas id="canvas" width="400" height="400">此浏览器不支持canvas</canvas><script>const ctx = document.getElementById('canvas');const gl = ctx.getContext('webgl');// 着色器// 创建着色器源码// 顶点着色器const VERTEX_SHADER_SOURCE = `// 存储限定符 类型 变量名 分号 (注: attribute 只传递顶点数据)attribute vec4 aPosition;void main() {gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)gl_PointSize = 10.0; // 点的大小}`;// 片元着色器const FRAGMENT_SHADER_SOURCE = `void main() {// r g b a 绘制颜色gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}`const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);// 获取 attribute 变量,返回变量的存储地址const aPosition = gl.getAttribLocation(program, 'aPosition');ctx.onclick = function (event) {// 坐标const x = event.clientX;const y = event.clientY;console.log("鼠标点击的屏幕坐标:", x, y);// 获取边距 (上边距和左边距) domPosition.left 等同于 ctx.offsetLeft 的值const domPosition = event.target.getBoundingClientRect();console.log("画布边距:", domPosition.left, domPosition.top);// 点击的点位于画布上方 、 左侧的距离 (domPosition.left: 568(基于我显示屏的长度), domPosition.top: 50)const domx = x - domPosition.left;const domy = y - domPosition.top;console.log("画布的坐标:", domx, domy);// 固定值,画布长和宽的一半,均为200const halfWidth = ctx.offsetWidth / 2;const halfHeight = ctx.offsetHeight / 2;console.log("画布长和宽的一半:", halfWidth, halfHeight);// 转为 ndc坐标 (-1, 1)const clickX = (domx - halfWidth) / halfWidth;const clickY = (halfHeight - domy) / halfHeight;console.log("转为ndc的坐标:", clickX, clickY);// 给 attribute 变量赋值gl.vertexAttrib2f(aPosition, clickX, clickY);// 执行绘制gl.drawArrays(gl.POINTS, 0, 1);}</script>
</body>
</html>

通过鼠标点击绘制点


2、鼠标移动绘制点

改为 ctx.onmousemove 实现鼠标移动:

绘制点跟随鼠标


3、模拟画笔

改为 ctx.onmousemove 的基础上,在 ctx.onmousemove 之前定义一个存储点的数组 points

const points = [];

把以下内容替换掉,从而实现画笔效果:

    // 给 attribute 变量赋值// gl.vertexAttrib2f(aPosition, clickX, clickY);// 执行绘制// gl.drawArrays(gl.POINTS, 0, 1);for (let i = 0; i < points.length; i++) {gl.vertexAttrib2f(aPosition, points[i].clickX, points[i].clickY);gl.drawArrays(gl.POINTS, 0, 1);}

画笔


总结

本文,我们首先实现了绘制水平移动的点,之后通过坐标的转移,认识了 ndc 坐标的求解,从而实现了鼠标控制绘制,(包括点击绘制、移动绘制和模拟画布)的效果。

更多 WebGL 和 Three.js 内容正在更新中…

好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!


参考资料:

  1. 百度百科 · WebGL
  2. WebGL + Three.js入门与实战【作者:yancy_慕课网】

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/671886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM 性能调优 - 常用的垃圾回收器(6)

垃圾收集器 在 JVM(Java虚拟机)中,垃圾收集器(Garbage Collector)是负责自动管理内存的组件。它的主要任务是在程序运行过程中,自动回收不再使用的对象所占用的内存空间,以便为新的对象提供足够的内存。 JVM中的垃圾收集器使用不同的算法和策略来实现垃圾收集过程,以…

VisaulStudio2022下用VB.net实现socket与西门子PLC进行通讯案例(优化版)

前言 对于电气工程师来说,不仅要会PLC,还要会上位机。 此前,我写过一个VB.net下雨西门子PLC通讯案例的博文: VisaulStudio2019下用VB.net实现socket与西门子PLC进行通讯案例 但当时很多东西都理解不深,博文也写的比较浅,但我看有不少收藏,也有些朋友在底下询问,所以,…

第4章——深度学习入门(鱼书)

第4章 神经网络的学习 本章的主题是神经网络的学习。这里所说的“学习”是指从训练数据中自动获取最优权重参数的过程。本章中&#xff0c;为了使神经网络能进行学习&#xff0c;将导入损失函数这一指标。而学习的目的就是以该损失函数为基准&#xff0c;找出能使它的值达到最…

一些常见的电源方案

开关电源&#xff1a; RM C 板&#xff1a;&#xff08;24V电压&#xff0c;10A电流&#xff09; SMBJ30CA&#xff1a;静电和浪涌保护(TVS/ESD) 一般使用NMOS管&#xff0c;因为PMOS管导通电阻与NMOS管比较会较大 模电非基础01——从一种常见的防反接&#xff0c;上电缓启…

Mysql大表添加字段失败解决方案

背景 最近遇到一个问题&#xff0c;需要在user用户表千万级别数据中添加两个字段&#xff0c;发现老是加不上去&#xff0c;一直卡死。表数据量不仅大&#xff0c;而且是一个热点表&#xff0c;访问频率特别高&#xff0c;而且该表的访问是在一个大事务中。加字段的时候一直在…

深度解析与推荐:主流Web前端开发框架

一、引言 在信息化社会中,Web前端开发的重要性日益凸显。作为连接用户与后台服务的关键桥梁,前端界面不仅直接影响用户体验,更是企业品牌形象、产品价值传递的重要载体。随着互联网技术的飞速发展,用户对于网站和应用的交互性、响应速度以及视觉效果等方面的要求越来越高,…

【Android】手机端使用NanoHTTPD搭建服务器

序言 NanoHTTPD 是一个轻量级的 Java HTTP 服务器库&#xff0c;可以在应用程序中快速搭建一个简单的 HTTP 服务器。 准备工作 引入依赖 implementation org.nanohttpd:nanohttpd:2.3.1添加网络访问权限 <uses-permission android:name"android.permission.INTERN…

挑战杯 python 爬虫与协同过滤的新闻推荐系统

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python 爬虫与协同过滤的新闻推荐系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&…

【人工智能】神奇的Embedding:文本变向量,大语言模型智慧密码解析(10)

什么是嵌入&#xff1f; OpenAI 的文本嵌入衡量文本字符串的相关性。嵌入通常用于&#xff1a; Search 搜索&#xff08;结果按与查询字符串的相关性排序&#xff09;Clustering 聚类&#xff08;文本字符串按相似性分组&#xff09;Recommendations 推荐&#xff08;推荐具有…

SIMD学习笔记1

参考 http://const.me/articles/simd/simd.pdf https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#catsArithmetic&expand3904,3913,4011,34,4014,4602,4011&techsMMX,AVX_512,AMX,SVML,Other&ig_expand11 示例&#xff1a;对4个数字求平…

AUTOSAR汽车电子嵌入式编程精讲300篇-基于FPGA和CAN协议2.0B的总线控制器研究与设计(续)

目录 3.2 寄存器管理模块 3.2.1寄存器规划及设计 3.2.2 BasicCAN模式 3.2.3 PeliCAN模式

【Scala 】3. 类和对象

3. 类和对象 scala是一种面向对象的函数&#xff0c;其中一部分就是scala语言中支持类的实现。 3.1 类的继承 &#xff08;1&#xff09;基类&#xff08;父类&#xff09; 类中包含实现面积的计算方法 class Shape { def getArea():Float { return 0 } }&#xff0…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之RichText组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之RichText组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、RichText组件 鸿蒙&#xff08;HarmonyOS&#xff09;富文本组件&#xff0c;…

【学习心得】Python好库推荐——tqdm

在很多时候&#xff0c;你都需要一个进度条来告知当前程序的运行状况和进展。tqdm 是一个在Python中广泛使用的命令行进度条工具库&#xff01;下面是这个库的主页&#xff1a;https://tqdm.github.io/ 一、快速入门 &#xff08;1&#xff09;for循环中 代码&#xff1a; …

句子嵌入: 交叉编码和重排序

这个系列目的是揭开嵌入的神秘面纱&#xff0c;并展示如何在你的项目中使用它们。第一篇博客介绍了如何使用和扩展开源嵌入模型&#xff0c;选择现有的模型&#xff0c;当前的评价方法&#xff0c;以及生态系统的发展状态。第二篇博客将会更一步深入嵌入并解释双向编码和交叉编…

C语言的起源

1940年代&#xff0c;最早的开始&#xff0c;编程语言是机器语言&#xff0c;用0/1表示的、计算机能直接识别和执行的一种机器指令的集合。最早的编程方式&#xff0c;就是给纸带打孔或者卡片机打孔。机器语言直接与硬件沟通&#xff0c;极具针对性&#xff0c;但是非常难于理解…

企业微信会话存档:大文件拉取、加密、上传

承接之前的文章企业微信会话存档sdk报错&#xff1a;A fatal error has been detected by the Java Runtime Environment 在之前的那篇文章结尾&#xff0c;我说发现了系统另外一个隐含的bug&#xff1a;企业微信会话存档拉取媒体文件是分片拉取&#xff0c;我的处理方式有问题…

IP地址如何保护网络安全

面对网络攻击时&#xff0c;仅依靠常态化的网络安全防御系统已捉襟见肘&#xff0c;如联合使用IP地址数据可以形成多元化的安全解决方案&#xff0c;全面监控网络活动&#xff0c;发现潜在威胁&#xff0c;制定有针对性的应对措施。 网络攻击追踪 当网站或应用遭受DDoS等网络攻…

Helm 简介:Kubernetes 的包管理器

简介 将应用程序部署到 Kubernetes&#xff08;一个功能强大且流行的容器编排系统&#xff09;可能会很复杂。设置单个应用程序可能涉及创建多个相互依赖的 Kubernetes 资源&#xff0c;例如 pods、services、deployments 和 replicasets&#xff0c;每个资源都需要编写详细的…

基于YOLOv8的暗光低光环境下(ExDark数据集)检测,加入多种优化方式---DCNv4结合SPPF ,助力自动驾驶(一)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文主要内容:详细介绍了暗光低光数据集检测整个过程&#xff0c;从数据集到训练模型到结果可视化分析&#xff0c;以及如何优化提升检测性能。 &#x1f4a1;&#x1f4a1;&#x1f4a1;加入 DCNv4结合SPPF mAP0.5由原始的0.682提升至…