WebGPU技术前导:ts实现一个3d渲染器入门图形学

2023年 10月 6日 46.9k 0

以下文字引用的文章都会在末尾注明。

前言

WebGPU技术,应该是前端的一次新的机遇,它会比WebGl更入侵我们的开发,以前可能只会有一个物体的渲染或一个项目的开发应用最多的应该是作为一个附属依赖于大屏项目中。但WebGPU改变了游戏规则,它将使前端技术达到一个巅峰。它使游览器有了直接调用图形硬件的能力,让前端项目具有与原生应用一样3D能力的潜力。
但同时对我们的图形学的理解也提出一个较大的挑战。不懂原理就不懂将一个模型渲染到场景中里面每一步的实现过程,不知道一些对我们理解很奇怪api的实现逻辑。所以笔者带大家将一个手动用c++实现渲染器的github中的英文教程,用ts实现并对里面算法加以详细说明。作为我们学习WebGPU技术前的开胃菜

WebGPU是什么?

现在的前端3D能力是WebGL, WebGL是一种3D绘图标准,它也有部分调用硬件加速计算的能力,但运算速度与内存使用等方面都受到限制。WebGPU 是 WebGL 的继任者,为现代 GPU 提供更好的兼容、支持更通用的 GPU 计算、更快的操作以及能够访问到更高级的 GPU 特性。功能定位类似于 Direct3D 12、Metal 和 Vulkan等等现代化的图形Api,坊间流传其受苹果公司影响太深所以Api有Metal的影子。它也有自己独特的着色器语言WGSL。获取由于WebGPU今年正式发布,传统的Three.js以及微软的Babylon.js虽然已经做了很多工作,但现在我观察到真正使用的并不多。

WGPU是啥?

wgpu 是基于 WebGPU API 规范的、跨平台的、安全的、纯 Rust 图形 API。它是 Firefox、Servo 和 Deno 中 WebGPU 整合的核心。wgpu 不仅可以在 Web 环境运行,还可以在 macOS / iOS、Android、Window 和 Linux 等系统上原生运行。那为啥不直接使用WebGPU开发项目?
从 wgpu 及 dawn 这两个主要的 WebGPU 标准的实现库的开发动向可以看出,大量的扩展特性目前只有在 Native 端(Windows、macOS、Linux、iOS、Android)原生运行才能支持。wgpu 更是将 Native 端运行做为首要目标,WebGPU 是做为最低支持的特性集而存在。
在我来看WebGPU专注于游览器中使用,而WGPU目的是将游览器这个平台做为最后的目的,先试图以原生的图形库来渲染以获得最大性能。多嘴说一句感觉前端是和rust跑不开。

回归正题

我学习图形学的出发点,就是WebGPU的发布,和unity的事故以及Gotdot引擎的爆火等等。但苦于自身没有图形学基础,但无意中一个项目的发现是点醒了我。通过学习编写一个微型的光栅化渲染器让我们理解图形Api的工作原理,以便我们后面学习更多的知识打好稳固的基础。
原文是以c++实现的。我试图用ts来按这位老师的逻辑来实现这个过程。
github.com/ssloy/tinyr…
image.png
image.png
image.png

首先搭建一个支持WebGPU的项目提供给以后进阶使用

这里可以看我搭建项目的文章

里面有一点要注意,我们项目是ts项目但目前WebGPU的ts支持还没完善,我们可以安装插件来使用ts的类型提示功能。

image.png
image.pngimage.png
ts中配置

image.png

第 1 课:Bresenham 画线算法

原文第一章对应的是从一个像素点开始使用Bresenham算法绘制直线,然后读取obj模型文件中的顶点信息绘制线框图

image.png
我对第一课安排是绘制线段。
我们开始回忆canvas的api操作,会发现并没有提供给我们任何操作像素点的方法,我们可以用绘制矩形或者线段的方式来,但这样会损失一点性能。现在网上对操作像素有两个方法

  • 使用绘制矩形的方式
  • const drawPixel = (ctx: CanvasRenderingContext2D, x: number, y: number, color: string) => {
      ctx.fillRect(x, y, 1, 1)
      ctx.fillStyle = color
    }
    
  • 使用ImageData 对象绘制
  • const drawPixel = (ctx: CanvasRenderingContext2D, canvasData: ImageData, x: number, y: number, color: Color) => {
      const index = (x + y * ctx.canvas.width) * 4
      canvasData.data[index] = color.r
      canvasData.data[index + 1] = color.g
      canvasData.data[index + 2] = color.b
      canvasData.data[index + 3] = color.a
    }
    
  • 对ImageData采用一种双缓冲机制优化 这个交给你们尝试
  • 我们采用第二种方式来开发,性能会比第一种好点,特别是再大量像素情况下。 但第三种是最好的,你们可以研究下
    我们首先获取canvas对象 以及创建ImageData上下文

    const canvas = document.querySelector('canvas') as HTMLCanvasElement
    const ctx = canvas.getContext('2d')!
    const w = canvas.width
    const h = canvas.height
    
    const canvasData = ctx.createImageData(w, h)
    type Color = {
      r: number
      g: number
      b: number
      a: number
    }
    const white: Color = {
      r: 255,
      g: 255,
      b: 255,
      a: 255
    }
    const updateCanvas = (ctx: CanvasRenderingContext2D, canvasData: ImageData) => {
      ctx.putImageData(canvasData, 0, 0)
    }
    

    调用drawPixel 就可以尝试在canvas绘制像素点了,
    重点最后调用updateCanvas 方法将数据输入到canvas中
    image.png
    Bresenham 画线算法
    对于计算机屏幕来说,是由一个个像素点组成的,而相当于在网格内画出一条尽量像素完美的线
    (学过像素画的应该懂)
    image.png

    Bresenham 画线算法 就是基于斜率与偏移量来实现像素完美的算法
    推导过程
    放一个链接应该都能看得懂。可以手动写一遍。很好理解
    [Bresenham算法画直线-CSDN博客]
    实现代码

    const drawLine = (con: conTend, x1: number, y1: number, x2: number, y2: number, color: Color) => {
      let x = x1
      let y = y1
      let dx = x2 - x1
      let dy = y2 - y1
      let ux = dx > 0 ? 1 : -1
      let uy = dy > 0 ? 1 : -1
      dx = Math.abs(dx)
      dy = Math.abs(dy)
      if (dx > dy) {
        let p = 2 * dy - dx
        for (let i = 0; i = 0) {
            y += uy
            p += 2 * (dy - dx)
          } else {
            p += 2 * dy
          }
        }
      } else {
        let p = 2 * dx - dy
        for (let i = 0; i = 0) {
            x += ux
            p += 2 * (dx - dy)
          } else {
            p += 2 * dx
          }
        }
      }
    }
    

    这里我用原生绘制直线算法演示下差别

    drawLine({ ctx: ctx, cad: canvasData }, 0, 0, w, h, white)
    updateCanvas(ctx, canvasData)
    ctx.beginPath()
    ctx.moveTo(0, h)
    ctx.lineTo(w, 0)
    ctx.closePath()
    ctx.strokeStyle = 'white'
    ctx.stroke()
    

    看下图
    从左上到右下,是我们绘制的线段。反之是原生绘制的线段,可以看出来原生自带一个抗锯齿清晰化的方法
    image.png
    最后放上源码
    github.com/miemieooop/…

    下一课预告

    会根据ThreeJs的obj导入方法,实现我们自己的模型读取方法。读取模型顶点信息渲染出模型线框图。并对项目进行工程化重构。
    原课程对思路的循循善诱值得深入学习,原文值得深入阅读。

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论