webgl——使用着色器给图形给点颜色看看(二)

文章目录

    • 前言
    • 绘制一个点
      • 初始化着色器
      • 存储限定符
      • 向 webgl 系统传递变量

前言

接前面文章,我们实现了一个最简单的 webgl 程序,但是只是程序运行起来只是漆黑一片,我们想要通过 webgl 绘制一些图形。这篇文章让我们来使用 webgl 绘制一个点,并使用着色器来给他点颜色看看。

绘制一个点

还是先上代码:

import React, { useRef, useEffect } from 'react'
import { VSHADERR_SOURCE, FSHADER_SOURCE } from './glsl'

const HelloPoint = () => {
  const canvasDom = useRef<HTMLCanvasElement | null>(null)

  // 创建程序
  const createProgram = (gl: WebGLRenderingContext, vshader: string, fshader: string) => {
    const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader)
    const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader)

    if (!vertexShader || !fragmentShader) {
      return null
    }
    // 创建程序对象
    const program = gl.createProgram()
    if (!program) {
      return null
    }

    // 为程序对象分配着色器
    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)

    // 连接程序对象
    gl.linkProgram(program)

    // Check the result of linking
    const linked = gl.getProgramParameter(program, gl.LINK_STATUS)
    if (!linked) {
      const error = gl.getProgramInfoLog(program)
      console.log(`Failed to link program: ${error}`)
      gl.deleteProgram(program)
      gl.deleteShader(fragmentShader)
      gl.deleteShader(vertexShader)
      return null
    }
    return program
  }

  const loadShader = (gl: WebGLRenderingContext, type: number, source: string) => {
    // 创建 shader 对象
    const shader = gl.createShader(type)
    if (shader === null) {
      console.log('unable to create shader')
      return null
    }
    // 向着色器对象中填充着色器程序的源代码
    gl.shaderSource(shader, source)
    // 编译着色器
    gl.compileShader(shader)
    // Check the result of compilation
    
    const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)

    if(!compiled) {
      const error = gl.getShaderInfoLog(shader)
      console.log(`Failed to compile shader: ${error}`)
      gl.deleteShader(shader)
      return null
    }
    return shader
  }

  // 初始化着色器
  const initShader = (gl: WebGLRenderingContext, vshader: string, fshader: string) => {
    const program = createProgram(gl, vshader, fshader)
    if (!program) {
      console.log('Failed to create program')
      return false
    }
    // 使用程序对象
    gl.useProgram(program);
    (gl as any).program = program
    return true
  }

  const main = () => {
    if (!canvasDom.current) return
    const gl = canvasDom.current.getContext('webgl')

    if (!gl) {
      console.log('Failed to get the rendering context for WebGL')
      return
    }

    // 初始化着色器
    if (!initShader(gl, VSHADERR_SOURCE, FSHADER_SOURCE)) {
      console.log('Failed to intialize shaders.')
      return
    }

    // 获取 attribute 变量的存储位置
    const a_Position = gl.getAttribLocation((gl as any).program, 'a_Position')
    // 获取存储位置其实是从 0 开始根据定义的位置依次计数的结果
    if (a_Position < 0) {
      console.log('Failed to get the storage location of a_Position')
      return
    }

    // 将顶点位置传输给 attribute 变量
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0)

    // 指定清空 canavs 的颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0)

    // 清空 canvas
    gl.clear(gl.COLOR_BUFFER_BIT)

    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1)
  }

  useEffect(() => {
    main()
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <canvas ref={canvasDom} style={{
      width: '500px',
      height: '500px'
    }}></canvas>
  )
}

export default HelloPoint

代码有点多,我们慢慢来看,首先看初始化着色器部分;

初始化着色器

在三维场景中,仅仅用线条和颜色将图层绘制出来是远远不够的,比如我们需要模拟光照打在物体上,高光部门以及阴影部分的展示等等;着色器可以高度灵活地完成对于图形的这些各种渲染效果。

创建和初始化着色器需要以下几个步骤:

  1. 创建着色器对象:gl.createShader();
  2. 向着色器对象中填充着色器程序的源代码:gl.shaderSource();
  3. 编译着色器:gl.compileShader();
  4. 创建程序对象:gl.createProgram();
  5. 为程序对象分配着色器: gl.attachShader();
  6. 连接程序对象:gl.linkProgram();
  7. 使用程序对象:gl.useProgram();

步骤也很简单也就是创建着色器,然后通过 program 将顶点着色器和片元着色器连接起来;program 对象进行着色器连接操作,目的是保证:

  1. 顶点着色器和片元着色器的 varying 变量同名同类型,且一一对应;
  2. 顶点着色器对每个 varying 变量赋了值;
  3. 顶点着色器和片元着色器中的同名 uniform 变量也是同类型的,无需一一对应,即某些 uniform 变量可以出现在一个着色器中而不出现在另一个中。
  4. 着色器中的 attribute 变量、uniform 变量和 varying 变量的个数没有超过着色器的上限。

如果程序已经成功连接,我们得到了一个二进制的可执行模块供 webgl 系统使用。如果连接失败了,也可以通过调用 gl.getProgramInfiLog(program) 从信息日志中获取连接出错信息。

最后我们通过 gl.useProgram(program) 告知 webgl 系统绘制时使用哪个程序对象,该函数接收一个指定待使用的程序对象做为参数。

我们可以在绘制前初始化多个程序对象,然后在绘制的时候根据需要切换程序对象。

存储限定符

我们发现;在 JavaScript 中我们通过 gl.getAttribLocation() 函数获取 webgl 程序变量的存储地址。到现在我们也没看到着色器代码;如下:

export const VSHADERR_SOURCE = `
  attribute vec4 a_Position;
  void main() {
    // 设置坐标
    gl_Position = a_Position;
    // 设置尺寸
    gl_PointSize = 10.0;
  }
`

export const FSHADER_SOURCE = `
  void main() {
    // 设置点的颜色
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }
`

这是 GLSL 着色器语言,很简单相信大家可以看懂。主要需要注意的是,attribute 数据类型。对于着色器语言,具有三种数据类型:

  • attribute:只能在顶点着色器中使用的变量,一般用于传递顶点数据(顶点、索引、颜色、法向量等);
  • uniform:常量,不能被 shader 修改,uniform 变量在顶点着色器和片元着色器两者之间声明方式完全一致,则他可以在顶点着色器和片元着色器共享使用,相当于一个被顶点着色器和片元着色器共享的全局变量。通常是一个全局的变量(如颜色、光照参数等),或者全局的矩阵(如世界矩阵、观察矩阵等等)
  • varying:varying 变量是顶点着色器和片元着色器之间做数据传递用的。

我们上面代码通过 attribute 类型的变量传递我们绘制点的坐标。

向 webgl 系统传递变量

接下来就是需要向 webgl 系统中传递绘制点的坐标;传递值我们首先得获取该值的位置,如下代码:

    // 获取 attribute 变量的存储位置
    const a_Position = gl.getAttribLocation((gl as any).program, 'a_Position')
    // 获取存储位置其实是从 0 开始根据定义的位置依次计数的结果
    if (a_Position < 0) {
      console.log('Failed to get the storage location of a_Position')
      return
    }

对于 attribute 定义的变量通过 getAttribLocation() 方法进行获取,当然通过 uniform 定义的变量通过 getUniformLocation() 方法进行获取。获取存储位置其实是从 0 开始根据定义的位置依次计数的结果;

然后获取到位置,我们需要将值传输给变量:

    // 将顶点位置传输给 attribute 变量
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0)

需要注意的是,webgl 使用的是颜色缓冲区,webgl 系统中的绘制操作实际上是在颜色缓冲区中进行绘制的,绘制结束后系统将缓冲区中的内容显示在屏幕上,然后颜色缓冲区就会被重置,其中的内容会丢失;如果我们需要实时绘制点,比如鼠标点击之后绘制点,需要将鼠标点击的点进行记录,每次点击完成之后通过循环,渲染之前的点。

至此,我们的着色器使用已经完成。

你可能感兴趣的:(webgl,webgl,着色器,三维可视化)