WebGL入门(四十一)-使用帧缓冲区对象(FBO)实现将渲染结果作为纹理绘制到另一个物体上

1. demo效果

在这里插入图片描述

2. 相关知识点

我们已经知道,在默认情况下,WebGL是在 颜色缓冲区 中进行绘制,当开启隐藏面消除功能时,会用到 深度缓冲区,如果你想将渲染结果作为纹理贴到另一个三维物体上时,就需要用到 帧缓冲区对象

2.1 帧缓冲区对象(FBO)

帧缓冲区对象(framebuffer object)可以用来替代颜色缓冲区或深度缓冲区,在帧缓冲区中进行绘制的过程又称为 离屏绘制 (offscreen drawing)
使用帧缓冲区绘制图形并不是直接发生在帧缓冲区中,而是发生在帧缓冲区所关联的对象上,帧缓冲区一共有三个关联对象:

  1. 颜色关联对象(color attachment) 可以用来替代颜色缓冲区
  2. 深度关联对象(depth attachment) 可以用来替代深度缓冲区
  3. 模板关联对象(stencil attachment) 可以用来替代模板缓冲区

每个关联对象都可以关联两种类型的对象,分别是 纹理对象渲染缓冲区对象

如果将纹理对象作为颜色关联对象关联到帧缓冲区对象,然后在帧缓冲区中进行绘制,此时的颜色关联对象(即纹理对象)就替代了颜色缓冲区;

如果要进行隐藏面消除,就需要创建一个渲染缓冲区对象,将这个渲染缓冲区对象作为深度关联对象关联到帧缓冲区对象,完成关联后这个渲染缓冲区对象 就可以替代 深度缓冲区

2.2 渲染到纹理配置步骤

  1. 创建帧缓冲区对象(gl.createFramebuffer())
  2. 创建纹理对象并设置参数(gl.createTexture()、gl.bindTexture()、gl.texImage2D()、gl.Parameteri())
  3. 创建渲染缓冲区对象(gl.createRenderbuffer())
  4. 绑定渲染缓冲区对象并设置尺寸(gl.bindRenderbuffer()、gl.renderbufferStorage())
  5. 将帧缓冲区的颜色关联对象指定为一个纹理对象(gl.framebufferTexture2D())
  6. 将帧缓冲区的深度关联对象指定为一个渲染缓冲区(gl.framebufferRenderbuffer())
  7. 检查帧缓冲区是否配置正确(gl.checkFramebufferStatus())
  8. 在帧缓冲区中进行绘制(gl.bindFramebuffer())

3. 相关API

3.1 创建帧缓冲区对象 gl.createFramebuffer()


调用示例:gl.createFramebuffer()
--------------------------------------------------------------------------
函数功能:创建帧缓冲区对象
--------------------------------------------------------------------------		
参数		无
--------------------------------------------------------------------------		
返回值		non-null	新创建的帧缓冲区对象
			null		创建帧缓冲区对象失败
--------------------------------------------------------------------------
错误		无	

3.2 删除帧缓冲区对象 gl.deleteFramebuffer()


调用示例:gl.deleteFramebuffer(framebuffer)
--------------------------------------------------------------------------
函数功能:删除帧缓冲区对象
--------------------------------------------------------------------------		
参数		framebuffer		指定要删除的帧缓冲区对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无	

3.3 创建渲染缓冲区对象 gl.createRenderbuffer()


调用示例:gl.createRenderbuffer()
--------------------------------------------------------------------------
函数功能:创建渲染缓冲区对象
--------------------------------------------------------------------------		
参数		无
--------------------------------------------------------------------------		
返回值		non-null	新创建的渲染缓冲区对象
			null		创建渲染缓冲区对象失败
--------------------------------------------------------------------------
错误		无	

3.4 删除渲染缓冲区对象 gl.deleteRenderbuffer()


调用示例:gl.deleteRenderbuffer(renderbuffer)
--------------------------------------------------------------------------
函数功能:删除渲染缓冲区对象
--------------------------------------------------------------------------		
参数		renderbuffer		指定要删除的渲染缓冲区对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无	

3.5 绑定渲染缓冲区 gl.bindRenderbuffer()

在使用创建出的渲染缓冲区之前,还需要先将它绑定到目标上


调用示例:gl.bindRenderbuffer(target, renderbuffer)
--------------------------------------------------------------------------
函数功能:将renderbuffer指定的渲染缓冲区对象绑定在target目标上,
如果renderbuffer为null,则将已经绑定在target目标上的渲染缓冲区对象解除绑定
--------------------------------------------------------------------------		
参数		
			target			必须为 gl.RENDERBUFFER
			renderbuffer	指定要绑定的渲染缓冲区对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		INVALID_ENUM	target 不是 gl.RENDERBUFFER	

3.6 设置渲染缓冲区尺寸 gl.renderbufferStorage()

渲染缓冲区完成绑定后,就可设置它的格式、宽度、高度等,这里需要注意
作为深度关联对象的渲染缓冲区,宽度和高度必须与作为颜色关联对象的纹理缓冲区一致


调用示例:gl.renderbufferStorage(target, internalforamt, width, height)
-----------------------------------------------------------------------------------
函数功能:创建并初始化渲染缓冲区的数据区
-----------------------------------------------------------------------------------		
参数		
		target					必须为 gl.RENDERBUFFER
		internalforamt			指定渲染缓冲区中的数据格式,可以是以下几项参数
		  gl.EDPTH_COMPONENT16	表示渲染缓冲区将替代深度缓冲区
		  gl.STENCIL_INDEX8		表示渲染缓冲区将替代模板缓冲区
		  gl.RGBA4				表示渲染缓冲区将替代颜色缓冲区,RGBA四个分量各占4比特
		  gl.RGB5_A1			表示渲染缓冲区将替代颜色缓冲区,RGB各占5比特,A1比特
		  gl.RGB565				表示渲染缓冲区将替代颜色缓冲区,RGB分别占565比特
		width 和 height			表示渲染缓冲区的宽度和高度,以像素为单位
								
-----------------------------------------------------------------------------------	
返回值	无
-----------------------------------------------------------------------------------
错误	INVALID_ENUM			target 不是 gl.RENDERBUFFER	
		INVALID_OPERATION		target上没有绑定渲染缓冲区

3.7 绑定帧缓冲区 gl.bindFramebuffer()


调用示例:gl.bindFramebuffer(target, framebuffer)
--------------------------------------------------------------------------
函数功能:将framebuffer指定的帧缓冲区对象绑定在target目标上,
如果framebuffer为null,则将已经绑定在target目标上的帧缓冲区对象解除绑定
--------------------------------------------------------------------------		
参数		
			target			必须为 gl.FRAMEBUFFER
			framebuffer		指定要绑定的帧缓冲区对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		INVALID_ENUM	target 不是 gl.FRAMEBUFFER	

3.8 纹理对象关联到帧缓冲区对象 gl.framebufferTexture2D()


调用示例:gl.framebufferTexture2D(target, attachment, textarget, texture, level)
-----------------------------------------------------------------------------------------
函数功能:将texture指定的纹理对象关联到绑定在target目标上的帧缓冲区对象
-----------------------------------------------------------------------------------------		
参数		
			target			必须为 gl.FRAMEBUFFER
			attachment		指定关联的类型
			  gl.COLOR_ATTACHMENT0	表示texture是颜色关联对象
			  gl.DEPTH_ATTACHMENT	表示texture是深度关联对象
			textarget		gl.TEXTURE_2D或gl.TEXTURE_BUVE_MAP分别代表二维纹理和立方体纹理
			texture			指定关联的纹理对象
			level			指定为0(在使用MIPMAP纹理时指定纹理的层级)
-----------------------------------------------------------------------------------------		
返回值		无
-----------------------------------------------------------------------------------------
错误		INVALID_ENUM	target 不是 gl.FRAMEBUFFER 或者attachment或textarget的值无效	
			INVALID_VALUE	level的值无效
			INVALID_OPERATION	target上没有绑定帧缓冲区

3.9 将渲染缓冲区对象关联到帧缓冲区对象 gl.framebufferRenderbuffer()


调用示例:gl.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer)
-----------------------------------------------------------------------------------------
函数功能:将renderbuffer指定的渲染缓冲区对象关联到绑定在target目标上的帧缓冲区对象
-----------------------------------------------------------------------------------------		
参数		
			target			必须为 gl.FRAMEBUFFER
			attachment		指定关联的类型
			  gl.COLOR_ATTACHMENT0	表示renderbuffer是颜色关联对象
			  gl.DEPTH_ATTACHMENT	表示renderbuffer是深度关联对象
			  gl.STENCIL_ATTACHMENT	表示renderbuffer是模板关联对象
			renderbuffertarget		必须是 gl.RENDERBUFFER
			renderbuffer			指定要关联的渲染缓冲区对象
-----------------------------------------------------------------------------------------		
返回值		无
-----------------------------------------------------------------------------------------
错误		INVALID_ENUM	target 不是 gl.FRAMEBUFFER 或者renderbuffer不是gl.RENDERBUFFER,
							或者attachment的值无效	

3.10 检查帧缓冲区的配置 gl.checkFramebufferStatus()


调用示例:gl.checkFramebufferStatus(target)
-----------------------------------------------------------------------------------------
函数功能:检查绑定在target上的帧缓冲区对象的配置状态
-----------------------------------------------------------------------------------------		
参数		target			必须为 gl.FRAMEBUFFER
-----------------------------------------------------------------------------------------		
返回值		0				target不是gl.FRAMEBUFFER
			其他
			  gl.FRAMEBUFFER_COMPLETE	 			帧缓冲区对象已正确配置
			  gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT	某一个关联对象为空,或者关联对象不合法
			  gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS	颜色关联对象和深度关联对象尺寸不一致
			  gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT	帧缓冲区未关联任何关联对象	
-----------------------------------------------------------------------------------------
错误		INVALID_ENUM	target 不是 gl.FRAMEBUFFER 

3.11 设置绘图区域 gl.viewport()

在进行离线绘图前还需要做一项工作,设置绘图区域的大小


调用示例:gl.viewport(x, y, width, height)
--------------------------------------------------------------------------
函数功能:设置gl.drawArrays() 和 gl.drawElements() 函数的绘图区域,
在canvas上绘图时,x和y就是canvas中的坐标
--------------------------------------------------------------------------		
参数		
			x,y				指定绘图区域的左上角,以像素为单位
			width,height	指定绘图区域的宽度和高度
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无

4. demo代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title></title>
</head>

<body>
  <!--通过canvas标签创建一个800px*800px大小的画布-->
  <canvas id="webgl" width="800" height="800"></canvas>
  <script type="text/javascript" src="./lib/cuon-matrix.js"></script>
  <script>
    //顶点着色器
    var VSHADER_SOURCE =
      'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
      'attribute vec2 a_TexCoord;\\n' + //声明attribute变量a_TexCoord,用来存放纹理坐标
      'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
      'varying vec2 v_TexCoord;\\n' + //声明varying变量v_TexCoord,用来向片元着色器传值纹理坐标
      'void main() {\\n' +
      '  gl_Position = u_MvpMatrix * a_Position;\\n' + //将模型视图投影组合矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
      '  v_TexCoord = a_TexCoord;\\n' + //将纹理坐标传给片元着色器
      '}\\n'

    //片元着色器
    var FSHADER_SOURCE =
      '#ifdef GL_ES\\n' +
      'precision mediump float;\\n' +
      '#endif\\n' +
      'uniform sampler2D u_Sampler;\\n' + //声明uniform变量u_Sampler,存放第一个纹理单元编号
      'varying vec2 v_TexCoord;\\n' + //声明varying变量v_TexCoord,用来接收顶点着色器传送的纹理坐标
      'void main() {\\n' +
      '  gl_FragColor = texture2D(u_Sampler, v_TexCoord);\\n' + //抽取纹理单元0纹素颜色并赋值给内置变量gl_FragColor
      '}\\n'
    // Size of off screen
    var OFFSCREEN_WIDTH = 256
    var OFFSCREEN_HEIGHT = 256

    //创建程序对象
    function createProgram(gl, vshader, fshader) {
      //创建顶点着色器对象
      var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader)
      //创建片元着色器对象
      var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader)

      if (!vertexShader || !fragmentShader) {
        return null
      }

      //创建程序对象program
      var program = gl.createProgram()
      if (!gl.createProgram()) {
        return null
      }
      //分配顶点着色器和片元着色器到program
      gl.attachShader(program, vertexShader)
      gl.attachShader(program, fragmentShader)
      //链接program
      gl.linkProgram(program)

      //检查程序对象是否连接成功
      var linked = gl.getProgramParameter(program, gl.LINK_STATUS)
      if (!linked) {
        var error = gl.getProgramInfoLog(program)
        console.log('程序对象连接失败: ' + error)
        gl.deleteProgram(program)
        gl.deleteShader(fragmentShader)
        gl.deleteShader(vertexShader)
        return null
      }

      gl.program = program
      //返回程序program对象
      return program
    }

    function loadShader(gl, type, source) {
      // 创建顶点着色器对象
      var shader = gl.createShader(type)
      if (shader == null) {
        console.log('创建着色器失败')
        return null
      }

      // 引入着色器源代码
      gl.shaderSource(shader, source)

      // 编译着色器
      gl.compileShader(shader)

      // 检查顶是否编译成功
      var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
      if (!compiled) {
        var error = gl.getShaderInfoLog(shader)
        console.log('编译着色器失败: ' + error)
        gl.deleteShader(shader)
        return null
      }

      return shader
    }

    function init() {
      //通过getElementById()方法获取canvas画布
      var canvas = document.getElementById('webgl')

      //通过方法getContext()获取WebGL上下文
      var gl = canvas.getContext('webgl')

      //初始化着色器
      var texProgram = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE) //创建绘制纹理立方体的程序对象
      if (!texProgram) {
        console.log('初始化着色器失败')
        return
      }

      //获取绘制纹理贴图立方体相关变量的存储地址
      texProgram.a_Position = gl.getAttribLocation(texProgram, 'a_Position')
      texProgram.a_TexCoord = gl.getAttribLocation(texProgram, 'a_TexCoord')
      texProgram.u_MvpMatrix = gl.getUniformLocation(texProgram, 'u_MvpMatrix')
      texProgram.u_Sampler = gl.getUniformLocation(texProgram, 'u_Sampler')


      if (texProgram.a_Position < 0 || texProgram.a_TexCoord < 0 || !texProgram.u_MvpMatrix || !texProgram.u_Sampler) {
        console.log('获取attribute变量或uniform变量存储地址失败')
        return
      }

      //初始化立方体顶点信息
      var cube = initVertexBuffers(gl)
      //初始化平面顶点信息
      var plane = initVertexBuffersForPlane(gl)
      if (!cube || !plane) {
        console.log('初始化顶点信息失败')
        return
      }

      //初始化纹理贴图立方体顶点信息
      var texture = initTextures(gl, texProgram)
      if (!texture) {
        console.log('初始化纹理贴图立方体顶点信息失败')
        return
      }

      //初始化帧缓冲区对象(FBO)
      var fbo = initFramebufferObject(gl)
      if (!fbo) {
        console.log('初始化帧缓冲区对象失败')
        return
      }

      // 设置canvas的背景色
      gl.clearColor(0, 0, 0, 1)
      //开启隐藏面消除
      gl.enable(gl.DEPTH_TEST)
      //gl.enable(gl.CULL_FACE)


      //创建、设置视图投影矩阵
      var viewProjMatrix = new Matrix4()
      viewProjMatrix.setPerspective(30, 1, 1, 100)
      viewProjMatrix.lookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)

      var viewProjMatrixFBO = new Matrix4() //为帧缓冲区对象创建视图投影矩阵
      viewProjMatrixFBO.setPerspective(30.0, OFFSCREEN_WIDTH / OFFSCREEN_HEIGHT, 1.0, 100.0)
      viewProjMatrixFBO.lookAt(0.0, 2.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)


      var currentAngle = 0.0
      var tick = function () {
        currentAngle = getCurrentAngle(currentAngle) //获取当前要旋转的角度
        //清空颜色和深度缓冲区
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
        //绘制图形
        draw(gl, canvas, fbo, plane, cube, texture, viewProjMatrix, viewProjMatrixFBO, currentAngle)
        requestAnimationFrame(tick)
      }

      tick() // 调用tick

    }

    var g_LastTime = Date.now() // 上次绘制的时间
    var ANGLE_SET = 30.0 // 旋转速度(度/秒)
    function getCurrentAngle(angle) {
      var now = Date.now()
      var elapsed = now - g_LastTime //上次调用与当前时间差
      g_LastTime = now
      var newAngle = angle + (ANGLE_SET * elapsed) / 1000
      return newAngle %= 360
    }

    //初始化帧缓冲区(FBO)
    function initFramebufferObject(gl) {
      var framebuffer, texture, depthBuffer

      //处理错误
      var error = function () {
        if (framebuffer) gl.deleteFramebuffer(framebuffer)
        if (texture) gl.deleteTexture(texture)
        if (depthBuffer) gl.deleteRenderbuffer(depthBuffer)
        return null
      }

      //创建帧缓冲区
      framebuffer = gl.createFramebuffer()
      if (!framebuffer) {
        console.log('创建帧缓冲区对象失败')
        return error()
      }

      //创建纹理对象并设置参数
      texture = gl.createTexture() //创建纹理对象
      if (!texture) {
        console.log('创建纹理对象失败')
        return error()
      }
      gl.bindTexture(gl.TEXTURE_2D, texture) //绑定纹理对象
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE,
        null) //纹理图像分配给纹理对象
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) //配置纹理对象参数
      framebuffer.texture = texture //将纹理对象关联到帧缓冲区的颜色关联对象

      //创建渲染缓冲区对象并设置参数
      depthBuffer = gl.createRenderbuffer() //创建渲染缓冲区对象
      if (!depthBuffer) {
        console.log('创建渲染缓冲区对象失败')
        return error()
      }
      gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer) //绑定渲染缓冲区
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置渲染缓冲区尺寸

      //将纹理对象和渲染缓冲区对象关联到帧缓冲区对象
      gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer) //绑定帧缓冲区对象
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)
      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer)

      //检查帧缓冲区的配置状态
      var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
      if (gl.FRAMEBUFFER_COMPLETE !== e) { //gl.FRAMEBUFFER_COMPLETE 表示帧缓冲区对象已正确配置
        console.log('帧缓冲区配置不正确: ' + e.toString())
        return error()
      }

      //解绑帧缓冲区对象、纹理对象、渲染缓冲区对象
      gl.bindFramebuffer(gl.FRAMEBUFFER, null)
      gl.bindTexture(gl.TEXTURE_2D, null)
      gl.bindRenderbuffer(gl.RENDERBUFFER, null)

      return framebuffer
    }

    function drawTexturedPlane(gl, program, o, texture, viewProjMatrix, angle) {
      //计算模型矩阵
      g_modelMatrix.setTranslate(0, 0, 1)
      g_modelMatrix.rotate(20.0, 1.0, 0.0, 0.0)
      g_modelMatrix.rotate(angle, 0.0, 1.0, 0.0)

      //计算模型视图投影矩阵并传值
      g_mvpMatrix.set(viewProjMatrix)
      g_mvpMatrix.multiply(g_modelMatrix)
      gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements)

      drawTexturedObject(gl, program, o, texture)
    }

    function drawTexturedObject(gl, program, o, texture) {

      //为变量分配缓存并开启
      initAttributeVariable(gl, program.a_Position, o.vertexBuffer) //顶点坐标 
      initAttributeVariable(gl, program.a_TexCoord, o.texCoordBuffer) //纹理贴图
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer) //绑定顶点索引

      //激活0号纹理单元并绑定到纹理对象
      gl.activeTexture(gl.TEXTURE0)
      gl.bindTexture(gl.TEXTURE_2D, texture)

      gl.drawElements(gl.TRIANGLES, o.numIndices, o.indexBuffer.type, 0)
    }


    function draw(gl, canvas, fbo, plane, cube, texture, viewProjMatrix, viewProjMatrixFBO, angle) {
      gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) //绑定帧缓冲区对象,后续绘制在绑定帧缓冲区中进行
      gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置在帧缓冲区绘制时窗口大小

      gl.clearColor(0.2, 0.2, 0.4, 1.0) //设置清空缓冲区的颜色
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空帧缓冲区

      drawTexCube(gl, gl.program, cube, texture, viewProjMatrixFBO, angle) //绘制立方体

      gl.bindFramebuffer(gl.FRAMEBUFFER, null) //帧缓冲区对象解绑,解绑后绘制在默认的颜色缓冲区中进行
      gl.viewport(0, 0, canvas.width, canvas.height) //设置在默认的颜色缓冲区绘制时窗口大小

      gl.clearColor(0.0, 0.0, 0.0, 1.0) //设置清空缓冲区的颜色
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空颜色和深度缓冲区

      drawTexturedPlane(gl, gl.program, plane, fbo.texture, viewProjMatrix, angle) //绘制平面
    }

    //模型矩阵、模型视图投影矩阵
    var g_modelMatrix = new Matrix4()
    var g_mvpMatrix = new Matrix4()

    function drawTexCube(gl, program, o, texture, viewProjMatrix, angle) {

      //计算模型矩阵
      g_modelMatrix.setRotate(20.0, 1.0, 0.0, 0.0)
      g_modelMatrix.rotate(angle, 0.0, 1.0, 0.0)


      //计算模型视图投影矩阵并传值
      g_mvpMatrix.set(viewProjMatrix)
      g_mvpMatrix.multiply(g_modelMatrix)
      gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements)

      //为变量分配缓存并开启
      initAttributeVariable(gl, program.a_Position, o.vertexBuffer) //顶点坐标 
      initAttributeVariable(gl, program.a_TexCoord, o.texCoordBuffer) //纹理贴图
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer) //绑定顶点索引

      //激活0号纹理单元并绑定到纹理对象
      gl.activeTexture(gl.TEXTURE0)
      gl.bindTexture(gl.TEXTURE_2D, texture)

      //绘制图形
      gl.drawElements(gl.TRIANGLES, o.indicesNum, o.indexBuffer.type, 0) //绘图

    }

    function initTextures(gl, program) {

      //创建纹理对象
      var texture = gl.createTexture()
      if (!texture) {
        console.log('创建纹理对象失败')
        return false
      }

      //创建图片对象
      var image = new Image()
      if (!image) {
        console.log('创建图片对象失败')
        return false
      }

      image.src = './resources/sky_roof.jpg' //设置纹理资源路径

      //注册图片加载事件的响应函数
      image.onload = function () {
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) //对纹理图像镜像y轴反转
        gl.activeTexture(gl.TEXTURE0) //激活纹理单元0 
        gl.bindTexture(gl.TEXTURE_2D, texture) //绑定纹理对象
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) //配置纹理对象参数
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image) //纹理图像分配给纹理对象

        gl.useProgram(program) //指定程序对象
        gl.uniform1i(program.u_Sampler, 0) //纹理单元编号0传给片元着色器中uniform变量u_Sampler
        gl.bindTexture(gl.TEXTURE_2D, null) //解绑纹理对象
      }
      return texture
    }

    //初始化绘制立方体的信息
    function initVertexBuffers(gl) {

      var v0 = [1.0, 1.0, 1.0]
      var v1 = [-1.0, 1.0, 1.0]
      var v2 = [-1.0, -1.0, 1.0]
      var v3 = [1.0, -1.0, 1.0]
      var v4 = [1.0, -1.0, -1.0]
      var v5 = [1.0, 1.0, -1.0]
      var v6 = [-1.0, 1.0, -1.0]
      var v7 = [-1.0, -1.0, -1.0]

      //顶点
      var vertices = new Float32Array([
        ...v0, ...v1, ...v2, ...v3, // 前
        ...v0, ...v3, ...v4, ...v5, // 右
        ...v0, ...v5, ...v6, ...v1, // 上
        ...v1, ...v6, ...v7, ...v2, // 左
        ...v7, ...v4, ...v3, ...v2, // 下
        ...v4, ...v7, ...v6, ...v5 // 后
      ])

      // 纹理贴图
      var texCoords = new Float32Array([
        1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v1-v2-v3 前
        0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, // v0-v3-v4-v5 右
        1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // v0-v5-v6-v1 上
        1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v1-v6-v7-v2 左
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // v7-v4-v3-v2 下
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 // v4-v7-v6-v5 后
      ])
      // 绘制的索引
      var indices = new Uint8Array([
        0, 1, 2, 0, 2, 3, // 前
        4, 5, 6, 4, 6, 7, // 右
        8, 9, 10, 8, 10, 11, // 上
        12, 13, 14, 12, 14, 15, // 左
        16, 17, 18, 16, 18, 19, // 下
        20, 21, 22, 20, 22, 23 // 后
      ])

      var obj = {}
      //所需顶点、纹理、索引信息写入缓冲区对象
      obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT)
      obj.texCoordBuffer = initArrayBufferForLaterUse(gl, texCoords, 2, gl.FLOAT)
      obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)
      if (!obj.vertexBuffer || !obj.texCoordBuffer || !obj.indexBuffer) {
        return null
      }

      //设置顶点索引数量
      obj.indicesNum = indices.length

      //缓冲区对象解绑
      gl.bindBuffer(gl.ARRAY_BUFFER, null)
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)

      return obj
    }

    //初始化绘制平面的信息
    function initVertexBuffersForPlane(gl) {
      //平面顶点信息
      var vertices = new Float32Array([
        1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0 // v0-v1-v2-v3
      ])

      //平面纹理坐标
      var texCoords = new Float32Array([1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0])

      //平面索引
      var indices = new Uint8Array([0, 1, 2, 0, 2, 3])

      var obj = new Object()

      //所需顶点、纹理、索引信息写入缓冲区对象
      obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT)
      obj.texCoordBuffer = initArrayBufferForLaterUse(gl, texCoords, 2, gl.FLOAT)
      obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)
      if (!obj.vertexBuffer || !obj.texCoordBuffer || !obj.indexBuffer) {
        return null
      }

      obj.numIndices = indices.length

      //缓冲区对象解绑
      gl.bindBuffer(gl.ARRAY_BUFFER, null)
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)

      return obj
    }

    function initArrayBufferForLaterUse(gl, data, num, type) {
      //创建缓冲区对象
      var buffer = gl.createBuffer()
      if (!buffer) {
        console.log('创建缓冲区对象失败')
        return null
      }

      //将数据写入缓冲区对象
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
      gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

      //设置num和type后续使用
      buffer.num = num
      buffer.type = type
      return buffer
    }

    function initElementArrayBufferForLaterUse(gl, data, type) {
      //创建缓冲区对象
      var buffer = gl.createBuffer()
      if (!buffer) {
        console.log('创建缓冲区对象失败')
        return null
      }

      //将数据写入缓冲区对象
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer)
      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW)

      //设置type后续使用
      buffer.type = type
      return buffer
    }
    //为指定变量分配缓存并开启
    function initAttributeVariable(gl, a_attribute, buffer) {
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer) //将buffer绑定到缓冲区对象
      gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0) //缓冲区对象分配给a_attribute指定的地址
      gl.enableVertexAttribArray(a_attribute) //开启a_attribute指定的变量
    }

    init()
  </script>
</body>

</html>

5. 后续

你会发现demo效果中,矩形的正反两个表面都被贴上了纹理,这时因为WebGL默认绘制图形的正反两个表面,如果你不希望绘制图形的背面,那么可以使用 gl.enable(gl.CULL_FACE) 来开启 消隐功能 ,开启消隐功能后demo效果如下
在这里插入图片描述

© 版权声明
THE END
喜欢就支持一下吧
点赞694 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容