WebGL画粗线

news2024/11/19 1:27:21

目录

前言 

基本思路

求左右端点

实现

组织数据

顶点着色器计算端点坐标 

效果


前言 

WebGL绘制模式有点、线、面三种;通过点的绘制可以实现粒子系统等,通过线可以绘制一些连线关系;面就强大了,通过面,我们可以绘制我们想绘制的所有的三维对象。然而,WebGL在绘制线条的时候,存在一个缺陷,那就是在一些机器的一些浏览器上面(大多数情况下)线宽只能设置为1,而不能设置成其他的值。

http://alteredqualia.com/tmp/webgl-linewidth-test/,可以测试自己的电脑是否可以绘制粗线

可以看出,我的Chrome有这个 问题

 

基本思路

既然画线实现不了加粗,那就使用面来模拟粗线。

如下,一条线 p0 p1 构成,可算出两个点的左右两个端点0 1 、 2 3,这四个端点组成两个三角形

求左右端点

  1. 二维向量(x, y)的法向量为(-y, x),为方便后续平移操作,将法向量标准化为单位向量;
  2. 通过平移变换将p0、p1沿上述得到法向量正向平移width/2得到点0、1,沿法向量反向平移width/2得到点2、3。
     

但是,这样简单粗暴的求法向量会有问题,如下图,多个线段间会有冗余分割线条

为了消除节点处的可见分割,有必要将相邻节段的相邻点组合起来,以保持相邻节段两个节段的厚度。要做到这一点,需要更进一步的求法向量

如下图,用last、current、next表示前、当前、后三个点,算出当前now点的切线方向(黄色),切线方向乘以复数i,逆时针旋转90就为法线方向,乘以复数-i,顺时针旋转90度为负法线方向,则有了Normal和-Normal(紫色)。Normal和-Normal分别乘以宽度的一半,即可求出每个点的左右端点

将端点坐标写入WebGL顶点坐标缓冲区,另外通过索引[0, 1, 3, 1, 2, 3]来表示两个三角形的顶点序号写入WebGL索引缓冲区,调用gl.drawElements绘制出带线宽的直线。 

实现

事实上,计算左右端点的操作应该发生在顶点着色器中的,而且也只能在着色器中计算,因为最终显示到屏幕上的顶点与镜头相关,上述只是简单的用了2维的情况模拟,如果在js端计算,将极大消耗性能。 

组织数据

组织要传给着色器的数据:position、prevPositions、nextPositions、side

比如,三个点 p0 p1 p2,要组织数据的形式:

position = [p0, p0, p1, p1, p2, p2]

prevPositions = [p0, p0, p0, p0, p1, p1]

nextPositions= [p1, p1, p2, p2, p2, p2]

side = [1, -1, 1, -1, 1, -1]

    let geometry = new THREE.BufferGeometry()
    let vertices = []
    let count = vertices.length
    let prevPositions = new Float32Array(count * 3 * 2)
    let nextPositions = new Float32Array(count * 3 * 2)
    let side = new Float32Array(count * 2)
    let position = new Float32Array(count * 3 * 2)
    let indexes = new Uint16Array(6 * (count - 1))
    for (let j = 0; j < count * 2; j += 2) {

      // side
      side[j] = 1
      side[j + 1] = -1

      // index
      let current = vertices[j / 2]
      let prev = vertices[(j === 0 ? j : j - 2) / 2]
      let next = vertices[(j === (count - 1) * 2 ? j : j + 2) / 2]
      // position
      position[j * 3] = current.x
      position[j * 3 + 1] = current.y
      position[j * 3 + 2] = current.z
      position[j * 3 + 3] = current.x
      position[j * 3 + 4] = current.y
      position[j * 3 + 5] = current.z

      // prev
      prevPositions[j * 3] = prev.x
      prevPositions[j * 3 + 1] = prev.y
      prevPositions[j * 3 + 2] = prev.z
      prevPositions[j * 3 + 3] = prev.x
      prevPositions[j * 3 + 4] = prev.y
      prevPositions[j * 3 + 5] = prev.z

      // next
      nextPositions[j * 3] = next.x
      nextPositions[j * 3 + 1] = next.y
      nextPositions[j * 3 + 2] = next.z
      nextPositions[j * 3 + 3] = next.x
      nextPositions[j * 3 + 4] = next.y
      nextPositions[j * 3 + 5] = next.z
    }
    for (let i = 0; i < count * 2 - 2; i += 2) {
      indexes[6 * (i - i / 2)] = i
      indexes[6 * (i - i / 2) + 1] = i + 1
      indexes[6 * (i - i / 2) + 2] = i + 2
      indexes[6 * (i - i / 2) + 3] = i + 2
      indexes[6 * (i - i / 2) + 4] = i + 1
      indexes[6 * (i - i / 2) + 5] = i + 3
    }

    geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
    geometry.setAttribute('prevPositions', new THREE.BufferAttribute(prevPositions, 3))
    geometry.setAttribute('nextPositions', new THREE.BufferAttribute(nextPositions, 3))
    geometry.setAttribute('side', new THREE.BufferAttribute(side, 1))
    geometry.index = new THREE.BufferAttribute(indexes, 1)

 

顶点着色器计算端点坐标 

正如上述 计算左右端点方法  两个点为单位轮回;算出法向量乘以side,得到最终的正负法线方向

需要注意的是,为了能够计算顶点在屏幕上的最终位置,需要把canvans的尺寸大小传递给着色器

    <script id="vertexShader" type="x-shader/x-vertex">
      attribute float side;
      attribute vec3 prevPositions;
      attribute vec3 nextPositions;
      uniform float width;
      uniform vec2 resolution;
  
      void main(){
        float aspect = resolution.x / resolution.y;
  
        mat4 pvm = projectionMatrix * modelViewMatrix;
        vec4 currentV4 = pvm * vec4(position, 1.0);
        vec4 prevV4 = pvm * vec4(prevPositions, 1.0);
        vec4 nextV4 = pvm * vec4(nextPositions, 1.0);
  
        vec2 currentV2 = currentV4.xy / currentV4.w;
        vec2 prevV2 = prevV4.xy / prevV4.w;
        vec2 nextV2 = nextV4.xy / nextV4.w;
  
        vec2 dir1 = normalize(nextV2 - currentV2);
        vec2 dir2 = normalize(currentV2 - prevV2);
        vec2 dir = normalize(dir1 + dir2);

        vec2 normal = vec2( -dir.y, dir.x );
        
        normal.x /= aspect;
  
        normal *= width;
        normal /= resolution.y;
         
        currentV4.xy += (normal * side) * currentV4.w;
        
        gl_Position = currentV4;
  
      }
  
    </script>

效果

export default [  
    {x:  0.2980022405076852, y:  0.0007317477689525731, z:  1},
    {x:  1.1869354597466781, y:  0.0009180096778322877, z:  0},
    {x:  2.0758235116605874, y:  0.0012041573064607292, z:  1},
    {x:  2.9647108701853995, y:  0.0016485209298480186, z:  1},
    {x:  3.8535979009456014, y:  0.002361701064614863, z:  0},
    {x:  4.7424853104305384, y:  0.00351269568443513, z:  0},
    {x:  5.631373663413683, y:  0.005294665597602943, z:  1},
    {x:  6.520261987572894, y:  0.007922778765419025, z:  0},
    {x:  7.409147752210174, y:  0.01167311915872915, z:  2},
    {x:  8.298025451981857, y:  0.016846610763536773, z:  0},
    {x:  9.186886934688118, y:  0.023772568206652522, z:  -1},
    {x:  10.07571907490967, y:  0.0328001040410868, z:  0},
    {x:  10.964496398756296, y:  0.044288140005107834, z:  5},
    {x:  11.85319602294021, y:  0.05862768318564804, z:  10},
    {x:  12.741786244815671, y:  0.07613330348408454, z:  0},
    {x:  13.630226377381064, y:  0.09712025624207854, z:  20},
    {x:  14.518501144938796, y:  0.12186988355398398, z:  1},
    {x:  15.406618008779674, y:  0.15055661742513848, z:  0},
    {x:  16.294597333615457, y:  0.18321126049687564, z:  1},
    {x:  17.182483414117996, y:  0.21982671919579388, z:  0},
    {x:  18.07027996070667, y:  0.26025710620086784, z:  0},
    {x:  18.957985046609565, y:  0.3042638844103749, z:  2},
    {x:  19.845588805245143, y:  0.35157055005709026, z:  20},
    {x:  20.733061996025413, y:  0.40187981369570025, z:  12},
    {x:  21.620398578083382, y:  0.4548763144693453, z:  0},
    {x:  22.50759421058183, y:  0.510277754478011, z:  0},
    {x:  23.394649626125783, y:  0.5678463420269395, z: 10},
    {x:  24.28156938481652, y:  0.6273966818090457, z:  10},
    {x:  25.16835632444645, y:  0.6887999401383809, z:  0},
    {x:  26.055014124105355, y:  0.7520044141307949, z:  2},
    {x:  26.94154278405199, y:  0.8170242843056599, z:  0},
    {x:  27.827935344684192, y:  0.8839563938809647, z:  2},
    {x:  28.71418144341419, y:  0.9530044273309386, z:  4},
    {x:  29.60025448188958, y:  1.0244047577025412, z:  0},
    {x:  30.486116503444237, y:  1.0984849046006389, z: 50},
    {x:  31.371726404611536, y:  1.175603041504246, z:  6},
    {x:  32.25701896209273, y:  1.2562969882843618, z:  4},
    {x:  33.14193521146046, y:  1.3409977805800963, z:  10},
    {x:  34.0263999228323, y:  1.4302624318554535, z:  0},
    {x:  34.91033330314474, y:  1.5245805770430252, z:  0},
    {x:  35.79361266847275, y:  1.6247205249796934, z:  0},
    {x:  36.676130186271166, y:  1.7312884009689355, z:  0},
    {x:  37.557760528031736, y:  1.844910698533738, z:  2},
    {x:  38.43832338116283, y:  1.9663458748508447, z:  0},
    {x:  39.31764605054343, y:  2.0958906489368587, z:  0},
    {x:  40.19564230123137, y:  2.234175946683024, z:  0},
    {x:  41.07190059501124, y:  2.3830193460015607, z:  10},
    {x:  41.946324364051975, y:  2.5416768732814035, z:  -20},
    {x:  42.81851559265749, y:  2.712179223072269, z:  0},
    {x:  43.688235247276, y:  2.8943826198482725, z:  -10},
    {x:  44.55509274214762, y:  3.089389858893753, z:  -10},
    {x:  45.41866612330193, y:  3.298229478551491, z:  -20}
    ]

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

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

相关文章

材质技术在AI去衣中的作用

随着人工智能技术的飞速发展&#xff0c;越来越多的应用场景开始涌现。其中&#xff0c;AI去衣技术作为一种新兴的图像处理技术&#xff0c;已经在很多领域得到了广泛的应用。而在AI去衣技术中&#xff0c;材质技术起到了至关重要的作用。本文将详细介绍材质技术在AI去衣中的作…

【前缀和 记忆化搜索】LeetCode1444. 切披萨的方案数

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 动态规划 记忆化搜索 LeetCode1444. 切披萨的方案数 给你一个 rows x cols 大小的矩形披萨和一个整数 k &#xff0c;矩形包含两种字符&#xff1a; ‘A’ &#xff…

生成式AI,在云端的绽放与盛开

编辑&#xff1a;阿冒 设计&#xff1a;沐由 毫无疑问&#xff0c;生成式AI已然成为当今技术发展和应用创新的重要引擎之一。 过去的一年多时间里&#xff0c;我们每个人都在目睹和见证着生成式AI是如何以移山倒海的力量&#xff0c;为诸多行业带来革命性乃至颠覆性的变革&…

Python3位运算符

前言 本文介绍的是位运算符&#xff0c;位运算可以理解成对二进制数字上的每一个位进行操作的运算&#xff0c;位运算分为 布尔位运算符 和 移位位运算符。 文章目录 前言一、位运算概览1、布尔位运算符1&#xff09;按位与运算符 ( & )2&#xff09;按位或运算符 ( | )3…

输入输出安全防护指南

输入输出安全防护指南 在现代网络应用程序中&#xff0c;输入输出的安全性是至关重要的。未经验证的输入和未编码的输出可能导致严重的安全漏洞&#xff0c;如SQL注入、跨站脚本攻击&#xff08;XSS&#xff09;等。本文将详细讨论如何通过输入验证和输出编码来确保应用程序的…

9. C++通过epoll+fork的方式实现高性能网络服务器

epollfork 实现高性能网络服务器 一般在服务器上&#xff0c;CPU是多核的&#xff0c;上述epoll实现方式只使用了其中的一个核&#xff0c;造成了资源的大量浪费。因此我们可以将epoll和fork结合来实现更高性能的网络服务器。 创建子进程函数–fork( ) 要了解线程我们先来了解…

零配件相关销售业务

测试场景CRM订单类型描述SAP订单类型描述发货开票备注零部件销售&退货服务商零配件订单标准订单&#xff08;服务商零配件&#xff09;参考DN开票YY 服务商零配件退货单退货订单(服务商零配件&#xff09;不开票退返账号金额YN服务商收到的零配件&#xff08;不能使用&…

Charles-ios无法抓包原因之一证书

VPN证书安装完成后依然无法抓包存在无网络问题 VPN安装证书后直接抓包这时候抓包接口返回无网络&#xff0c;原因是IOS通用-关于本机-证书信任设计未开启信任

最佳实践:REST API 的 HTTP 请求参数

HTTP 请求中的请求参数解释 当客户端发起 HTTP 请求 时&#xff0c;它们可以在 URL 末尾添加请求参数&#xff08;也叫查询参数或 URL 参数&#xff09;来传递数据。这些参数以键值对的形式出现在 URL 中&#xff0c;方便浏览和操作。 请求参数示例 以下是一些带有请求参数的…

c基础 - 输入输出

目录 一.scanf() 和 printf() 函数 1.printf 2.scanf 二 . getchar() & putchar() 函数 1.int getchar(void) 2.int putchar(int c) 三. gets() & puts() 函数 一.scanf() 和 printf() 函数 #include <stdio.h> 需要引入头文件,stdio.h 1.printf print…

【Linux】权限的概念

1.Linux权限的概念 Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受权限限制 普通用户&#xff1a;在linux下做有限的事情&#xff0c;受权限设置。 windows下也有超级用户…

ADC数模转换器

一、ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器 1、ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 2、12位逐次逼近型ADC&#xff0c;1us转换时间 3、输入电压范围&#xff1a;0~3.3V&a…

掌握 JavaScript 基本输出方法

掌握 JavaScript 基本输出方法 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 JavaScript 是一种强大且灵活的编程语言&#xff0c;广泛用于 Web 开发。通过 JavaScript&#xff…

2.2 Docker安装redis

2.2 Docker安装redis 1.安装redis docker run --restartalways -p 6379:6379 --name myredis -d redis:7.0.12 --requirepass Hgh675667%–restartalways 开机自启动 -p 6379:6379 端口映射 –name myredis 容器名称 -d redis:7.0.12 redis版本 –requirepass Hgh675667% 密…

异常处理1--5.31

try--catch--finally格式&#xff1a; try{ }catch(错误类型){ }finally{ } 执行情况&#xff1a; package javatest5;public class test {public static void main(String[] args) {int num115;int num20;try{System.out.println("商等于&#xff1a;"num1/num…

JVM学习-类加载过程(二)

Initialization初始化阶段 为类的静态变量赋予正确的初始值 具体描述 类的初始化是类装载的最后一个阶段&#xff0c;如果前面的步骤没有问题&#xff0c;那么表示类可以顺利装载到系统中&#xff0c;此时&#xff0c;类才会开始执行Java字节码(即&#xff0c;到了初始化阶段…

龙芯3A4000+FPGA云终端解决方案,搭载昆仑国产化固件,支持UOS、银河麒麟等国产操作系统

龙芯云终端基于国产化龙芯高性能四核3A4000处理器平台的国产自主可控解决方案&#xff0c;搭载昆仑国产化固件,支持UOS、银河麒麟等国产操作系统&#xff0c;满足国产化信息安全运算的需求&#xff0c;实现从硬件、操作系统到应用的完全国产、自主、可控&#xff0c;是国产信息…

AI 赋能前端 -- 文本内容概要生成

幸福不在于你获得了什么,而在于你比他人多获得了什么 是比较出来的 大家好,我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder 此篇文章所涉及到的技术有 OpenAILangChainRust/WebAssemblyWeb Workerreact+ts+vite配置环境变量(env)因为,行文字数所限,有些概…

常用电机测试方法的介绍与功能实现(M测试方法)

目录 概述 1 常用电机测速方法简介 1.1 方法概览 1.2 编码器测速方法 2 M法测速 2.1 理论描述 2.2 实现原理 2.3 速度计算方法 3 功能实现 3.1 功能介绍 3.2 代码实现 3.2.1 使用STM32Cube配置参数 3.2.2 脉冲计数功能 3.2.3 测速函数 4 测试 概述 本文主要介绍…

Mybatis-plus 更新或新增时设置某些字段值为空

方式一 在实体中设置某个字段为的注解中 TableField(updateStrategy FieldStrategy.IGNORED)private Date xxxxxxTime;通过这种方式会指定更新时该字段的策略&#xff0c;通常情况下updateById这种会根据字段更新&#xff0c;通常都会判断null 以及空值 指定 updateStrategy …