计算机图形学

第二章(2) 图形学编程概述

在浏览器中调用GPU进行绘制

在浏览器页面中运行程序,需要通过WebGL,借助JavaScript向GPU发送绘制命令

课程目标

  • 理解宏观框架: 掌握现代图形编程通用模型
  • 解构程序结构: 识别一个WebGL应用各构成部分
  • 掌握核心语法: 学习GLSL着色器语言基础
  • 理解基本概念: 学习图形绘制基本元素及概念

伪代码挑战

假设你拥有一个神奇的"magic_gpu" 对象,要用它在屏幕上画一个红色三角形。请用伪代码写下你认为需要的步骤。

思考方向:

  • 你需要告诉GPU哪些信息? (形状?位置?颜色?)
  • 按什么顺序告诉它?

第二部分:从OpenGL到WebGL

现代图形编程通用架构

所有现代图形API(OpenGL, WebGL, Vulkan...)都遵循一个相似的模型。

[图形编程管线示意图]

OpenGL体系结构

OpenGL体系结构
OpenGL软件体系

OpenGL程序特点

  • OpenGL是一个状态机,所有的绘制操作都依赖于当前的状态
  • OpenGL提供系列函数库,所有的操作都是通过调用函数来实现的
  • OpenGL通过调用函数,直接写着色器控制GPU

OpenGL事件循环

  • OpenGL示例程序中,主函数是以回调函数或者事件监听的形式对绘制函数进行调用
  • 每个程序都必须定义一个渲染回调函数
  • 对于静态绘制应用,只需要执行一次绘制函数即可,而对于动态更新的应用,绘制函数需要多次调用,也可以递归调用,但是每次对输出的重绘都需要由事件触发

OpenGL面向过程

OpenGL是按面向过程的设计,因此对于一些逻辑函数,往往有多种实现,其区别在于不同的类型,如往着色器中传输数值

  • gl.uniform3f
  • gl.uniform2i
  • gl.uniform3dv

其底层的数据存在模式也同样,因此需要格外关注数据类型及其占用空间大小

WebGL程序执行

[WebGL程序执行流程图]

WebGL vs. 原生OpenGL

特性 WebGL 原生 OpenGL
核心思想 完全一致 (可编程管线, 着色器, 缓冲)
编程语言 JavaScript C / C++
运行平台 浏览器 操作系统原生
环境配置 零配置 复杂 (库, 驱动, 窗口)
基于标准 OpenGL ES (嵌入式版) 完整版 OpenGL

第三部分:WebGL程序基础

WebGL语法

WebGL函数定义

WebGL的常量

  • WebGL中的常量都定义在canvas对象中
  • 在桌面版的OpenGL版本中,这些常量定义在头文件中,如gl.h
  • 示例
    • 桌面版: glEnable( GL_DEPTH_TEST );
    • WebGL: gl.enable( gl.DEPTH_TEST );
    • 桌面版:glClear(GL_COLOR_BUFFER_BIT);
    • WebGL: gl.clear( gl.COLOR_BUFFER_BIT );

第四部分:GLSL基础

WebGL和GLSL

  • WebGL需要写着色器,与面向过程的旧版OpenGL模型相比,不需要过多地依赖OpenGL状态机变量。因此,在OpenGL3.1版本以后,状态机变量、属性及其它相关的函数已不再支持
  • WebGL所有的工作都在着色器中完成,应用程序的主要任务就是往GPU中传递数据

初识GLSL

GLSL,全称为OpenGL Shading Language,是一种与C风格类似的编程语言,提供了预定义的矩阵、向量(2,3,4d)等类型,支持操作符重载,具有C++风格的构造器。与GLSL类似的编程语言还有NVidia的Cg和微软的HLSL。源代码需要送到着色器中编译运行。WebGL中定义的函数其编译、链接运行都在着色器中,可以直接实现对着色器中数据的控制

着色器编程

最早的着色器编程语言类似汇编风格,OpenGL通过扩展添加对顶点着色器和面片着色器的函数支持,主要的着色器编程语言有两种:

  • Cg(C for Graphics),是一种类似C风格的语言,可以同时用于OpenGL和DirectX
  • OpenGL Shading Language(GLSL)
    • 从OpenGL2.0版本以后开始提供支持,是一种具有C语言风格的高级语言,添加了对新的数据类型的支持,如矩阵、向量、采样等
    • 从OpenGL3.1版本以后,程序都必须提供着色器程序

数据类型

  • C类型:int, float, bool
  • 向量:浮点向量 float vec2, vec3, vec4, 或是整型向量ivec以及布尔型向量bvec
  • 矩阵:mat2, mat3, mat4,数组按列存储,访问数组元素的标准形式为m[row][column]
  • C++风格的构造器:vec3 a=vec3(1.0,2.0, 3.0); 或是 vec2 b=vec2(a);

OpenGL ES Reference

关于指针

GLSL中没有定义指针,所有的变量在传递时都按值传递,建立变量的本地副本。并且,因为向量和矩阵都是基本数据类型,可以直接在GLSL函数中作为输入输出使用,如mat3 func(mat3 a)

修饰符

  • GLSL也定义了许多和C/C++类似的修饰符,如const
  • GLSL程序中,变量可以在不同的图元、不同的顶点、不同的面片以及应用程序的不同阶段都会发生变化
  • 在面片着色器中,顶点的属性可以在光栅化阶段通过插值计算得到对应的面片属性值

属性修饰符

  • 每个顶点用属性修饰符修饰的变量最多可以改变一次值,有一些如gl_Position之类的内建变量,但是很多已在新版本中不再支持
  • 属性修饰符也可用于用户定义的变量,如in float temperature; in vec3 velocity;
  • WebGL 1.0用attribute和varying修饰符区分用于输入着色器和从着色器输出的变量

常量修饰符

  • 在图元中不会改变的值即为常量,可以在应用程序中修改并将其值传递给着色器,但在着色器内部不能改变。
  • 一般常量用于往着色器传递时间、图元的包围盒、变换矩阵等信息

变量修饰符

  • 变量会在顶点着色器和面片着色器之间传递,在光栅化阶段会自动通过插值计算
  • 在WebGL 1.0标准中,在两个着色器中都用varying修饰变量,如varying vec4 color;
  • 在新的WebGL版本中,在顶点着色器和面片着色器中,分别用outin表示变量,如
    out vec4 color;//vertex shader
    in vec4 color;//fragment shader

命名原则

  • 一般传递给顶点着色器的变量在应用程序和着色器中命名都以"v"开头,如vPosition, vColor,当然这两者是不同的变量
  • 只存在着色器中的变量命名以"f"开头,如fColor,且必须具有相同的命名
  • 对于常量,不需要前缀,在应用程序和着色器中可以同名

操作符和函数

  • GLSL提供的函数中,有类似C的标准库函数,如三角函数,数学运算函数,一些向量正则化、反射、长度计算等功能函数
  • 并且,根据向量和矩阵的不同数据类型,提供了操作符的重载,如

mat4 a;
vec4 b, c, d;
c=b*a; // a column vector stored as a 1d array
d=a*b; // a row vector stored as a 1d array
                    

数组元素交叉访问和选择

  • 数组中的元素可以用[]或(.)操作符访问,其中数组中的元素可以用以下三种序列形式:
    • x,y,z,w
    • r,g,b,a
    • s,t,p,q
    • 如a[2]和a.b, a.z, a.p都是等效的
  • 交叉访问操作符可以让用户更自由地操作数组中的元素,如

vec4 a, b;
a.yz=vec2(1.0, 2.0, 3.0, 4.0);
b=a.yxzw;
                    

第五部分:着色器编程

顶点着色器和片元着色器

顶点着色器
对每个顶点进行处理,将顶点的位置从三维世界坐标系转换到二维裁剪后的坐标系,用于流水线的下一阶段的处理,比如用于光栅化。同时还能处理顶点的其它属性,包括位置、颜色、纹理坐标等,但顶点着色器不会增加顶点
面片着色器
对光栅化后生成的每个面片计算其在窗口中对应像素的颜色及深度值

顶点着色器应用

形变(Morphing)

波粒运动(Wave motion)

分形(Fractals)

面片着色器应用

面片光照计算

顶点光照计算 vs. 面片光照计算

面片着色器应用(2)

纹理映射

平滑渲染
环境映射
凹凸映射

顶点着色器执行模型

面片着色器执行模型

着色器程序链接

将着色器程序与应用程序进行连接,需要以下步骤:

  • 读着色器程序
  • 编译着色器程序
  • 创建程序对象
  • 将所有内容链接在一起
  • 连接应用程序的变量和着色器中的变量,包括顶点的属性、各种常量等

精度声明

  • GLSL中,我们必须为面片着色器中的数据声明精度,这是因为WebGL的版本继承自OpenGL ES,而ES版本必须运行在一些简单嵌入式设备中,可能并不支持32位浮点运算。同时,所有的实现都必须支持mediump。而在面片着色器中,并未对float类型设定默认精度
  • 精度定义可以用预处理器(#ifdef)检查,如果没有,需要默认设置为mediump

第六部分:多边形

图元


OpenGL图元定义

图元


WebGL图元定义

关于多边形

多边形,可定义以下三种属性:

  • 简单,即所有的边都不相交
  • 凸多边形,连接任意两个多边形顶点的线段上的所有点,均位于多边形内
  • 平面多边形,即所有的点都位于同一个平面上

关于WebGL渲染

  • WebGL只渲染三角形,三角形完全具备以上三种属性,是最简单的多边形
  • 若需要渲染多边形,需将多边形分解成若干三角形(三角化)
  • 在OpenGL中支持GL_QUADS,即能够支持这种分解,但在WebGL中不行

多边形测试

从概念上讲,测试多边形是否是简单多边形,是否是凸多边形虽然简单,实际实现却很耗时,因此在现在的版本实现中只保留了渲染三角形,而将多边形划分成三角形的工作交由用户完成

三角化

待三角化四边形

"坏"三角化

"好"三角化

三角化原则

  • 避免扁长三角形,即内角为钝角的三角形
  • 划分可采用最大化最小角原则
  • 对于非结构化点集,可采用Delaunay三角划分
  • 所有三角形中,等边三角形的绘制速度最快

凸多边形三角化

凸多边形三角化,从abc开始,拿掉b,加上acd,拿掉c,加上ade......

非凸多边形三角化

非凸多边形

非凸多边形分割,从最左边的点开始

属性

属性决定了物体的外观,包括如下几点:

  • 颜色,点、线、多边形的颜色
  • 大小和宽度,点的大小,线的宽度
  • 线的点画模式,线段和多边形的边界用的实线还是虚线
  • 多边形模式,如填充模式,是纯色填充还是阴影填充?是否绘制边框?是否绘制顶点?等等

以上属性在OpenGL中能够得到完整支持,但是在WebGL中只有少部分支持,如gl_PointSize

第七部分:颜色

RGB颜色

每种颜色分量在帧缓存中都是独立存储,每种颜色各占8位,颜色值用0.0到1.0之间的浮点数表示,对应于非负整型的0到255

颜色索引

颜色通过索引表查找得到,一般用8位索引,适用于内存空间较少的情况,能够表示的颜色较少,无法满足阴影绘制的需要,现在已较少采用

颜色平滑

颜色平滑处理默认采用平滑着色(Smooth shading),在光栅化阶段,在可见的多边形上对顶点颜色进行插值

另一种为平面着色(Flat shading),多边形的颜色由第一个顶点的颜色决定,并在着色器中处理

设置颜色

颜色是在面片着色器中设置,但其值可以由应用程序或是任一着色器设定

在应用程序中设定颜色,需将颜色值作为常量传递给顶点着色器或作为顶点属性进行传递

在顶点着色器中设定,需将其值作为变量传递给面片着色器

在面片着色器中设定,可通过代码修改

课程总结

  • 理解了现代图形编程的通用模型,以及WebGL与原生OpenGL的异同
  • 掌握了GLSL着色器语言的基础语法和核心概念
  • 理解了 图形绘制的基本图元(特别是三角形)和颜色表示方法