Zhili's blog Zhili's blog
首页
关于
GitHub (opens new window)

Zhili

转型中的前端老兵
首页
关于
GitHub (opens new window)
  • webpack 区分环境使用CDN以及HtmlWebpackPlugin插件的编写
  • 使用 Mpvue 开发小程序总结
  • 前端知识汇总-持续更新
  • webGL知识汇总-持续更新
  • React思想要点
  • Mapbox 入门初试
  • 构建自己的 GLSL 绘图器 - 2d 版
  • webGL二维有向距离场(SDF)及布尔运算
  • webGL入门:绘制一个三角形
  • 使用 GLSL 绘图尝试:绘制正弦曲线
    • 简单解析虚拟 DOM
    • 自己动手开发一个 markdown 转微信文章工具
    • Vue组件扩展及权限管理的实现技巧
    • 使用 CSS 绘制三角形
    • Nginx 前端配置和使用
    • webpack按需加载配置和babel编译
    • 面试总结39题
    • 使用node反向代理接口改造旧项目
    • 可视化表单搭建系统的开发与思考
    • 前端
    zhili
    2019-08-16

    使用 GLSL 绘图尝试:绘制正弦曲线

    使用片元着色器绘制正弦曲线

    看了一部分 《The Book of Shaders》 (opens new window)之后大受启发,之前画三角形,画矩形以及画其他的图形都是在 JS 计算好各个点的位置,然后通过 buffer 传递到顶点着色器中,然后绘制出图形。这些计算点位的工作主要集中在 CPU 端进行,这种绘制方式大大浪费了 GPU 并行计算的能力,其实我们只在片元着色器也能绘制出这些图形,并且效率更高。

    在片元着色器中绘图,我们将使用一个重要的内置变量vec4 gl_FragCoord,在官方文档中这样解释:

    Available only in the fragment language, gl_FragCoord is an input variable that contains the window relative coordinate (x, y, z, 1/w) values for the fragment. If multi-sampling, this value can be for any location within the pixel, or one of the fragment samples. This value is the result of fixed functionality that interpolates primitives after vertex processing to generate fragments. The z component is the depth value that would be used for the fragment's depth if no shader contained any writes to gl_FragDepth

    gl_FragCoord 仅在片段着色器中可用,是一个输入变量,包含片段的窗口相对坐标(x,y,z,1 / w)值。这句话的意思是它的坐标就是当前片段的窗口相对坐标,回想起 webGL 的绘制方式:确定了定点之后,会调用片段着色器进行逐像素渲染。这时,每一个像素的坐标便是 gl_FragCoord 的 xyz 值。它的值的范围为 0 到渲染区域的宽高。它的坐标原点在左下角,x 向右递增,y 向上递增。

    # 绘制准备工作

    首先,我们要使用 gl_FragCoord,需要先创建一个简单的 webGL 程序,我们绘制 2 个三角形组成一个矩形,在绘制三角形的时候 webGL 会调用片元着色器对三角形进行光栅化,在光栅化的过程中调用 fragment shader 进行对每个像素进行光栅化,在对每个像素进行处理的时候,当前被处理像素的坐标我们通过 gl_FragCoord 就可以得到。

    绘制三角形步骤略。

    webgl 坐标及颜色范围都在[-1,1]中间,而 gl_FragCoord 的范围是[0,width],[0,height],因此在使用中我们通常需要(也可以不做处理)归一化处理。 我们需要在 fragment 中获取 gl.canvas 的高宽,因此我们需要一个变量

    uniform vec2 uResolution;
    
    1

    在 JS 程序中将 canvas 的高宽传给它,

    const uResolution = gl.getUniformLocation(program, 'uResolution')
    gl.uniform2f(uResolution, gl.canvas.width, gl.canvas.height)
    
    1
    2

    然后 vec2 st=gl_FragCoord.xy/uResolution;得到的就是归一化坐标值。

    # 绘制正弦曲线

    现在,我们可以尝试绘制一条正弦曲线 y=sin(x),但是我们已知的只有画布各个点的坐标值,我们可以将在正弦曲线上的点颜色设置为绿色,将正弦曲线外的点颜色设置为黑色,这样就能将曲线绘制出来;并且我们坐标的递增为一个像素值,y 值不可能完全与 sin(x)相等,我们需要设置一个范围,比如正负 0.01,只要 y 值落在这个范围内,我们就认为 y 的值为 sin(x),这样能够生成更为平滑的曲线,避免锯齿。

    首先我们写一个 plot 函数,该函数接收 2 个值,一个真实坐标值,一个目标值,如果真实值在目标值误差范围内则返回 1.0,否则范围 0.0

    float plot(float sy,float y){
      // float p=smoothstep(sy+.01,sy,y)-smoothstep(sy,sy-.01,y);
      // return sign(p);
      if(y-.01<sy && sy<y+.01){
        return 1.;
      }else{
        return 0.;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    由于归一化之后 gl_FragCoord 坐标范围为[0,1],为了绘制正弦曲线,我们将 x 轴范围映射到[0,2π],y 轴范围映射到[-1,1]。main 函数如下所示:

    void main(){
      // gl_FragCoord是着色器当前位置的坐标值,xy范围为canvas的[0,width],[0,height]
      // 下面st将gl_FragCoord转为webgl坐标[0,1]
      vec2 st=gl_FragCoord.xy/uResolution;
      float PI=3.1415926;
    
      // x范围转到[0,2PI]
      float tx=st.x*2.*PI;
      // x范围转到[-1,1]
      float ty=(st.y*2.)-1.;
    
      // y = sin(x) 曲线
      float pct=plot(ty,sin(tx));
      vec3 color=pct*vec3(0.,1.,.0);
    
      gl_FragColor=vec4(color,1.);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    如果点在 sin(tx)上,pct 为 1.0,乘以 vec3(0.,1.,.0)为其本身,否则,跑 pct 为 0.0,color 值为 vec3(0.0,0.0,0.0); 这样点在正弦曲线上颜色将为绿色,而曲线之外的部分颜色为黑色。

    接着,我们可以添加 x 轴与 y 轴辅助线,x 轴方程为 y=0,y 轴我们绘制为 x=PI,接着修改我们的 main 函数:

    // y=0 直线 ,即X轴
      float xPct=plot(ty,0.);
      vec3 xAxisColor=xPct*vec3(1.,0.,0.);
    
      // x=PI 直线,即 Y轴
      float yPct=plot(tx,PI);
      vec3 yAxisColor=yPct*vec3(0.,0.,1.);
    
      // 颜色相加即的得到要绘制的图形
      color+=xAxisColor+yAxisColor;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    最后,显示的颜色最终值为三个绘图函数颜色值的和,如果在 y=sin(x)上,颜色为绿色;如果在 y=0 上,颜色为红色,如果在 x=π 上,颜色为蓝色。 最终我们的成果如下图所示:

    result

    # 总结

    通过这个示例,我们可以绘制出更多的图形,根据各种造型函数,将函数区域与非函数区域绘制为 2 种不同的颜色我们便可以绘制出各种形状;另外,我们可以将多个绘图函数组合起来,绘制出更加多样的效果。而且这些直接在 GPU 中计算,性能将比通过 CPU 传递数据绘制更快。

    学习资源:

    webGL 基础知识 (opens new window)

    着色器之书 (opens new window)

    着色器资源网 (opens new window)

    距离场着色器 (opens new window)

    编辑 (opens new window)
    #GLSL#webGL
    上次更新: 2022/03/14, 06:54:51
    webGL入门:绘制一个三角形
    简单解析虚拟 DOM

    ← webGL入门:绘制一个三角形 简单解析虚拟 DOM→

    最近更新
    01
    可视化表单搭建系统的开发与思考
    03-09
    02
    使用node反向代理接口改造旧项目
    11-17
    03
    面试总结39题
    10-11
    更多文章>
    Theme by Vdoing | Copyright © 2022-2023 zhili | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式