OpenGL ES 着色器语言 开篇 通过一个例子体验如何画一个三维物体

令狐冲 LV9
2023-05-31 · 96 阅读
OpenGL ES 着色器语言   
开篇 通过一个例子体验如何画一个三维物体

WebGL 是很底层的 API,它只能用来画点、线和三角形,那么我们如何来画正方形呢?
其实大家看到的那些精美的3D 模型,其实都是一个个非常小的三角形组成的。

比如这个冰箱就是由3 万多个三角形组成。为什么选择三角形呢?这是因为任何多边形都可以最终分解为多个三角形,也就是说三角形是多边形的基本单位,并且三角形一定在一个平面上。
可以使用两个三角形组合来表示一个正方形,立方体有6 个面,也就需要 12 个三角形,每个三角形需要 3 个顶点,那么最终我们就需要 36 个顶点!
但是立方体比较特殊,它其实只有8 个顶点,一个顶点被三个面共用。那么有什么方法让我们只用定义 8 个顶点呢?OpenGL 还可以通过我们定义的顶点索引来渲染三角形,比如我们发送 8 个顶点和一个顶点索引数组到 GPU,然后 OpenGL 就可以使用索引数组的顺序来渲染三角形了。
比如索引数组 [1,2,3,3,2,0] 并且我们是画三角形的话,这就表示使用顶点数组下标为 1、2 和 3 的顶点来渲染一个三角形,然后用 3、2 和 0 下标渲染另一个三角形。

const canvas = document.createElement('canvas')

canvas.width = canvas.height = 300document.body.appendChild(canvas)const gl = canvas.getContext('webgl')

gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)

const program =createProgramFromSource(gl, `

attribute vec4 aPos;

attribute vec4 aColor;

varying vec4 vColor;



void main() {

   gl_Position = aPos;

   vColor = aColor;

}

`, `

precision mediump float;

varying vec4 vColor;



void main() {

   gl_FragColor = vColor;

}

`)

const points =newFloat32Array([

  -0.5,0.5,-0.5, 0.5,0.5,-0.5, 0.5,-0.5,-0.5, -0.5,-0.5,-0.5,

  0.5,0.5,0.5, -0.5,0.5,0.5, -0.5,-0.5,0.5, 0.5,-0.5,0.5])const colors =newFloat32Array([

  1,0,0, 0,1,0, 0,0,1, 1,0,1,

  0,0,0, 0,0,0, 0,0,0, 0,0,0])const indices =newUint8Array([

  0, 1, 2, 0, 2, 3, // 前

  1, 4, 2, 4, 7, 2, // 右

  4, 5, 6, 4, 6, 7, // 后

  5, 3, 6, 5, 0, 3, // 左

  0, 5, 4, 0, 4, 1, // 上

  7, 6, 3, 7, 3, 2   // 下])

const [posLoc, posBuffer] =createAttrBuffer(gl, program, 'aPos', points)const [colorLoc, colorBuffer] =createAttrBuffer(gl, program, 'aColor', colors)const indexBuffer = gl.createBuffer()

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)

gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)



gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer)

gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 0, 0)

gl.enableVertexAttribArray(posLoc)



gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)

gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, 0, 0)

gl.enableVertexAttribArray(colorLoc)



gl.enable(gl.DEPTH_TEST)

gl.clearColor(0, 1, 1, 1)

gl.clear(gl.COLOR_BUFFER_BIT| gl.DEPTH_BUFFER_BIT)



gl.drawElements(

  gl.TRIANGLES, // 要渲染的图元类型

  indices.length, // 要渲染的元素数量

  gl.UNSIGNED_BYTE, // 元素数组缓冲区中的值的类型

  0// 元素数组缓冲区中的偏移量, 字节单位)

functioncreateShader(gl, type, source) {

  const shader =gl.createShader(type)

  gl.shaderSource(shader, source)

  gl.compileShader(shader)

  return shader;}

functioncreateProgramFromSource(gl, vertex, fragment) {

  const vertexShader =createShader(gl, gl.VERTEX_SHADER,vertex)

  const fragmentShader =createShader(gl, gl.FRAGMENT_SHADER, fragment)

  const program =gl.createProgram()

  gl.attachShader(program, vertexShader)

  gl.attachShader(program, fragmentShader)

  gl.linkProgram(program)

  gl.useProgram(program)

  return program}

functioncreateAttrBuffer(gl, program, attr, data) {

  const location =gl.getAttribLocation(program, attr)

  const buffer =gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

  return [location, buffer]}


上面代码画了一个边长是 1 的立方体,立方体的正中心就在坐标轴原点。
我们除了定义每个顶点的坐标,还定义了每个顶点的颜色,靠近屏幕外的4 个顶点设置成彩色,后 4 个顶点设置成黑色。
然后使用 Uint8Array 定义了顶点索引(如果又索引值大于 256 就应该使用 Uint16Array)。
颜色数据和坐标一样,创建一个缓存然后,告诉WebGL 如何获取获取。但是顶点索引数据有一点点不同,它的绑定点不是 gl.ARRAY_BUFFER 而是 gl.ELEMENT_ARRAY_BUFFER 它是用于元素索引的 Buffer。
这里还开启了深度测试,这样后画的三角形就不会覆盖先画的,而是根据它们的Z 值判断。另外清理的时候不用调用两次 clear 函数,而是使用 | 运算符,gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT。
最后一步将 drawArrays 换成 drawElements,表示我们用索引来渲染图形。

存储限定字varying
存储限定字其实一共有三个 attribute、uniform 和 varying。上面已经介绍了前两个,它们都是从外部JS 获取数据。
varying 是顶点着色器向片元着色器传送数据。上面例子中我们将 aColor 赋值给 vColor,然后在片元着色器中就可以使用 vColor 了。
叫 varying 也是有原因的,我们可以先来看看上面代码最终渲染成什么样子。

我们设置前面4 个顶点颜色分别是红、绿、蓝和粉色,怎么渲染出来的是一种渐变色?
前面将过,片段着色器执行的次数一般比顶点着色器执行次数多得多。这是因为在片元着色器之前会执行光栅化,会将图元离散化,变成一个个像素,然后每个像素都会执行片元着色器,来确定这个像素的颜色。
varying 变量从顶点着色器向片元着色器传递时会被 OpenGL 插值,也就是我们定义了三角形 3 个顶点的颜色,三角形内部的像素都是根据这 3 个顶点颜色插值出来的。比如一个线段一个端点是红色,另一个是绿色,那么这个线段中间就是 50% 的红色和 50% 的绿色。

旋转和透视
我们渲染的是一个立方体,为什么显示出来确实一个正方形?
因为这个立方体的正面正对着我们,我们就只能看见它的正面,如果我们将这个立方体稍微旋转一下,就可以看出来这个是立方体了。
现实生活中,我们看物体会有近大远小的效果,也就是有透视效果。在3D 图形中也应该也有类似的效果,现在我们渲染的这个立方体是没有透视效果的,也就是前面那个面会和后面那个面一样大。


版块:
web3.0前端学院
分类:
1. 本站所有资源来源于用户上传和网络,仅作为演示数据,如有侵权请邮件联系站长!
2. 盗版,破解有损他人权益和违法作为,请各位站长支持正版!
回复

举报

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则