WebGL入门(四十)-通过切换着色器实现一个页面同时展示多个立方体

1. demo效果

在这里插入图片描述

2. demo实现步骤

这一次的demo实现的核心是切换着色器程序对象,首先要创建所需的着色器和着色器的程序对象,然后在进行绘制的时候选择使用的着色器程序对象,使用 gl.useProgram() 函数实现切换着色器程序对象,整个demo的具体步骤如下

2.1 准备绘制单色立方体的着色器

顶点着色器

//单色立方体顶点着色器
var SOLID_VSHADER_SOURCE = '' +
  'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
  'attribute vec4 a_Color;\\n' + //声明attribute变量a_Color,用来存放顶点颜色信息
  'attribute vec4 a_Normal;\\n' + //声明attribute变量a_Normal,用来存放法向量
  'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
  'uniform mat4 u_NormalMatrix;\\n' + //声明uniform变量u_NormalMatrix,用来存放变换法向量矩阵
  'uniform vec3 u_LightColor;\\n' + //声明uniform变量u_LightColor,用来存放光线颜色
  'uniform vec3 u_LightDirection;\\n' + //声明uniform变量u_LightDirection,用来存放入射光方向,归一化的世界坐标
  'varying vec4 v_Color;\\n' + //声明varying变量v_Color,用来向片元着色器传值顶点颜色信息
  'void main(){\\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\\n' + //将模型视图投影组合矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
  '  vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\\n' + //对计算变换后的法向量并归一化处理
  '  float nDotL = max(dot(u_LightDirection, normal), 0.0);\\n' + //计算光线方向和法向量点积
  '  vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\\n' + //计算漫反射光的颜色
  '  v_Color = vec4(diffuse, a_Color.a);\\n' + //将光线颜色和环境光颜色相加的结果传给片元着色器
  '}\\n'

片元着色器

//单色立方体片元着色器
var SOLID_FSHADER_SOURCE = '' +
  '#ifdef GL_ES\\n' +
  ' precision mediump float;\\n' + // 设置精度
  '#endif\\n' +
  'varying vec4 v_Color;\\n' + //声明varying变量v_Color,用来接收顶点着色器传送的片元颜色信息
  'void main(){\\n' +
  '  gl_FragColor = v_Color;\\n' + //将顶点着色器传送的片元颜色赋值给内置变量gl_FragColor
  '}\\n'

2.2 准备绘制纹理立方体的着色器

顶点着色器

//纹理立方体顶点着色器
var TEXTURE_VSHADER_SOURCE = '' +
  'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
  'attribute vec4 a_Normal;\\n' + //声明attribute变量a_Normal,用来存放法向量
  'attribute vec2 a_TexCoord;\\n' + //声明attribute变量a_TexCoord,用来存放纹理坐标
  'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
  'uniform mat4 u_NormalMatrix;\\n' + //声明uniform变量u_NormalMatrix,用来存放变换法向量矩阵
  'varying float v_NdotL;\\n' + //声明varying变量v_NdotL,用来向片元着色器传值光线方向和法向量点积
  'varying vec2 v_TexCoord;\\n' + //声明varying变量v_TexCoord,用来向片元着色器传值纹理坐标
  'void main(){\\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\\n' + //将模型视图投影组合矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
  '  vec3 lightDirection = vec3(0.0, 0.0, 1.0);\\n' + //声明光线方向
  '  vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\\n' + //对计算变换后的法向量并归一化处理
  '  v_NdotL = max(dot(normal, lightDirection), 0.0);\\n' + //计算光线方向和法向量点积
  '  v_TexCoord = a_TexCoord;\\n' + //将纹理坐标传给片元着色器
  '}\\n'

片元着色器

//纹理立方体片元着色器
var TEXTURE_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,用来接收顶点着色器传送的纹理坐标
  'varying float v_NdotL;\\n' + //声明varying变量v_NdotL,用来接收顶点着色器传送的光线方向和法向量点积
  'void main(){\\n' +
  '  vec4 color = texture2D(u_Sampler, v_TexCoord);\\n' + //抽取纹理单元0纹素颜色
  '  gl_FragColor = vec4(color.rgb * v_NdotL, color.a);\\n' + //计算物体漫反射光照射后的颜色并赋值给内置变量gl_FragColor
  '}\\n'

2.3 创建绘制单色立方体的程序对象

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 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
  }

  //返回程序program对象
  return program
}

//创建绘制单色立方体的程序对象
var solidProgram = createProgram(gl, SOLID_VSHADER_SOURCE, SOLID_FSHADER_SOURCE)

2.4 创建绘制纹理立方体的程序对象

创建绘制纹理立方体的程序对象与创建单色立方体的程序对象使用了相同的函数,这里只展示调用部分

//创建绘制纹理立方体的程序对象
var texProgram = createProgram(gl, TEXTURE_VSHADER_SOURCE, TEXTURE_FSHADER_SOURCE)

2.5 准备绘制单色立方体的数据并写入缓冲区对象

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 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 fontColor = [0.8, 0.5, 1.0, 0.4]
  var backColor = [0.5, 1.0, 1.0, 0.4]
  var leftColor = [1.0, 1.0, 0.5, 0.4]
  var rightColor = [0.2, 1.0, 0.5, 0.8]
  var topColor = [0.2, 0.5, 1.0, 0.8]
  var downColor = [1.0, 1.0, 1.0, 0.8]
  // 顶点的颜色
  var colors = new Float32Array([
    ...fontColor, ...fontColor, ...fontColor, ...fontColor, // v0-v1-v2-v3 前
    ...rightColor, ...rightColor, ...rightColor, ...rightColor, // v0-v3-v4-v5 右
    ...topColor, ...topColor, ...topColor, ...topColor, // v0-v5-v6-v1 上
    ...leftColor, ...leftColor, ...leftColor, ...leftColor, // v1-v6-v7-v2 左
    ...downColor, ...downColor, ...downColor, ...downColor, // v7-v4-v3-v2 下
    ...backColor, ...backColor, ...backColor, ...backColor, // v4-v7-v6-v5 后
  ])

  var font = [0.0, 0.0, 1.0]
  var back = [0.0, 0.0, -1.0]
  var left = [-1.0, 0.0, 0.0]
  var right = [1.0, 0.0, 0.0]
  var top = [0.0, 1.0, 0.0]
  var down = [0.0, -1.0, 0.0]
  // 法向量
  var normals = new Float32Array([
    ...font, ...font, ...font, ...font, // v0-v1-v2-v3 前
    ...right, ...right, ...right, ...right, // v0-v3-v4-v5 右
    ...top, ...top, ...top, ...top, // v0-v5-v6-v1 上
    ...left, ...left, ...left, ...left, // v1-v6-v7-v2 左
    ...down, ...down, ...down, ...down, // v7-v4-v3-v2 下
    ...back, ...back, ...back, ...back, // 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.colorBuffer = initArrayBufferForLaterUse(gl, colors, 4, gl.FLOAT)
  obj.normalBuffer = initArrayBufferForLaterUse(gl, normals, 3, gl.FLOAT)
  obj.texCoordBuffer = initArrayBufferForLaterUse(gl, texCoords, 2, gl.FLOAT)
  obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)

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

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

  return obj
}

2.6 准备绘制纹理立方体的数据并写入缓冲区对象

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
}

2.7 绘制单色立方体

在绘制的过程中有以下三点需要注意

  • 切换着色器
    由于我们绘制物体要使用不同的着色器程序对象,所以每次绘制物体的时候要指定对应的着色器程序对象,调用gl.useProgram()函数,每次切换着色器程序对象后,需要重新绑定缓冲区对象
  • 为变量分配缓存并开启
    绘制物体的变量都是绘制之前写入缓冲区对象,但是没有分配和开启,所以在绘制的时候需要为这些变量分配缓冲区并开启,需要依次调用initAttributeVariable() 函数
  • 重新绑定索引缓冲区
    顶点索引存放在不同的缓冲区目标上,这里需要单独重新绑定对应的缓冲区
//绘制单色立方体  
drawSolidCube(gl, solidProgram, cube, -2.0, viewProjMatrix, currentAngle)

//绘制单色立方体
function drawSolidCube(gl, program, o, x, viewProjMatrix, angle) {

  gl.useProgram(program) //指定程序对象
  
  initAttributeVariable(gl, program.a_Position, o.vertexBuffer) //顶点坐标 
  initAttributeVariable(gl, program.a_Color, o.colorBuffer) //顶点颜色
  initAttributeVariable(gl, program.a_Normal, o.normalBuffer) //法向量
  
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer) //绑定顶点索引
  
  setValueToShaderForColorCube(gl, program) //给顶点着色器中绘制单色立方体所需的变量传值
  
  drawCube(gl, program, o, x, viewProjMatrix, angle)
}

//为指定变量分配缓存并开启
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指定的变量
}
    
function setValueToShaderForColorCube(gl, program) {
  //给顶点着色器uniform变量u_LightColor-光线颜色传值(1.0,1.0,1.0)
  gl.uniform3f(program.u_LightColor, 1.0, 1.0, 1.0)

  var lightDirection = new Vector3([1.5, 3.0, 4.0])
  lightDirection.normalize() //归一化
  //给顶点着色器uniform变量u_LightColor- 光线颜色传值(1.0,1.0,1.0)
  gl.uniform3fv(program.u_LightDirection, lightDirection.elements)
}
    
//模型矩阵、模型视图投影矩阵、法向量矩阵
var g_modelMatrix = new Matrix4()
var g_mvpMatrix = new Matrix4()
var g_normalMatrix = new Matrix4()

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

  //根据模型矩阵计算变换法向量的矩阵并传值
  g_normalMatrix.setInverseOf(g_modelMatrix)
  g_normalMatrix.transpose()
  gl.uniformMatrix4fv(program.u_NormalMatrix, false, g_normalMatrix.elements)

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

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

2.8 绘制纹理立方体

绘制纹理立方体的第一步也是先切换着色器,切换完着色器不但需要为变量分配缓冲区并开启重新绑定索引缓冲区,还需要重新激活纹理单元并绑定到纹理对象

//绘制纹理贴图立方体  
drawTexCube(gl, texProgram, cube, texture, 2.0, viewProjMatrix, currentAngle)

function drawTexCube(gl, program, o, texture, x, viewProjMatrix, angle) {
  //指定程序对象
  gl.useProgram(program)

  //为变量分配缓存并开启
  initAttributeVariable(gl, program.a_Position, o.vertexBuffer) //顶点坐标 
  initAttributeVariable(gl, program.a_Normal, o.normalBuffer) //法向量
  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)

  //绘制纹理立方体
  drawCube(gl, program, o, x, viewProjMatrix, angle)

}

3. 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 SOLID_VSHADER_SOURCE = '' +
      'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
      'attribute vec4 a_Color;\\n' + //声明attribute变量a_Color,用来存放顶点颜色信息
      'attribute vec4 a_Normal;\\n' + //声明attribute变量a_Normal,用来存放法向量
      'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
      'uniform mat4 u_NormalMatrix;\\n' + //声明uniform变量u_NormalMatrix,用来存放变换法向量矩阵
      'uniform vec3 u_LightColor;\\n' + //声明uniform变量u_LightColor,用来存放光线颜色
      'uniform vec3 u_LightDirection;\\n' + //声明uniform变量u_LightDirection,用来存放入射光方向,归一化的世界坐标
      'varying vec4 v_Color;\\n' + //声明varying变量v_Color,用来向片元着色器传值顶点颜色信息
      'void main(){\\n' +
      '  gl_Position = u_MvpMatrix * a_Position;\\n' + //将模型视图投影组合矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
      '  vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\\n' + //对计算变换后的法向量并归一化处理
      '  float nDotL = max(dot(u_LightDirection, normal), 0.0);\\n' + //计算光线方向和法向量点积
      '  vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\\n' + //计算漫反射光的颜色
      '  v_Color = vec4(diffuse, a_Color.a);\\n' + //将光线颜色和环境光颜色相加的结果传给片元着色器
      '}\\n'

    //单色立方体片元着色器
    var SOLID_FSHADER_SOURCE = '' +
      '#ifdef GL_ES\\n' +
      ' precision mediump float;\\n' + // 设置精度
      '#endif\\n' +
      'varying vec4 v_Color;\\n' + //声明varying变量v_Color,用来接收顶点着色器传送的片元颜色信息
      'void main(){\\n' +
      '  gl_FragColor = v_Color;\\n' + //将顶点着色器传送的片元颜色赋值给内置变量gl_FragColor
      '}\\n'

    //纹理立方体顶点着色器
    var TEXTURE_VSHADER_SOURCE = '' +
      'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
      'attribute vec4 a_Normal;\\n' + //声明attribute变量a_Normal,用来存放法向量
      'attribute vec2 a_TexCoord;\\n' + //声明attribute变量a_TexCoord,用来存放纹理坐标
      'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
      'uniform mat4 u_NormalMatrix;\\n' + //声明uniform变量u_NormalMatrix,用来存放变换法向量矩阵
      'varying float v_NdotL;\\n' + //声明varying变量v_NdotL,用来向片元着色器传值光线方向和法向量点积
      'varying vec2 v_TexCoord;\\n' + //声明varying变量v_TexCoord,用来向片元着色器传值纹理坐标
      'void main(){\\n' +
      '  gl_Position = u_MvpMatrix * a_Position;\\n' + //将模型视图投影组合矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
      '  vec3 lightDirection = vec3(0.0, 0.0, 1.0);\\n' + //声明光线方向
      '  vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\\n' + //对计算变换后的法向量并归一化处理
      '  v_NdotL = max(dot(normal, lightDirection), 0.0);\\n' + //计算光线方向和法向量点积
      '  v_TexCoord = a_TexCoord;\\n' + //将纹理坐标传给片元着色器
      '}\\n'

    //纹理立方体片元着色器
    var TEXTURE_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,用来接收顶点着色器传送的纹理坐标
      'varying float v_NdotL;\\n' + //声明varying变量v_NdotL,用来接收顶点着色器传送的光线方向和法向量点积
      'void main(){\\n' +
      '  vec4 color = texture2D(u_Sampler, v_TexCoord);\\n' + //抽取纹理单元0纹素颜色
      '  gl_FragColor = vec4(color.rgb * v_NdotL, color.a);\\n' + //计算物体漫反射光照射后的颜色并赋值给内置变量gl_FragColor
      '}\\n'

    //创建程序对象
    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
      }

      //返回程序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 solidProgram = createProgram(gl, SOLID_VSHADER_SOURCE, SOLID_FSHADER_SOURCE) //创建绘制单色立方体的程序对象
      var texProgram = createProgram(gl, TEXTURE_VSHADER_SOURCE, TEXTURE_FSHADER_SOURCE) //创建绘制纹理立方体的程序对象
      if (!solidProgram || !texProgram) {
        console.log('初始化着色器失败')
        return
      }
      //获取绘制单色立方体相关变量的存储地址
      solidProgram.a_Position = gl.getAttribLocation(solidProgram, 'a_Position') //顶点坐标
      solidProgram.a_Color = gl.getAttribLocation(solidProgram, 'a_Color') //顶点颜色
      solidProgram.a_Normal = gl.getAttribLocation(solidProgram, 'a_Normal') //顶点法向量
      solidProgram.u_MvpMatrix = gl.getUniformLocation(solidProgram, 'u_MvpMatrix') //模型视图投影矩阵
      solidProgram.u_NormalMatrix = gl.getUniformLocation(solidProgram, 'u_NormalMatrix') //变换法向量矩阵
      solidProgram.u_LightColor = gl.getUniformLocation(solidProgram, 'u_LightColor') //光线颜色
      solidProgram.u_LightDirection = gl.getUniformLocation(solidProgram, 'u_LightDirection') //入射光方向

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


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

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

      //初始化纹理贴图立方体顶点信息
      var texture = initTextures(gl, texProgram)
      if (!texture) {
        console.log('初始化纹理贴图立方体顶点信息失败')
        return
      }
      // 设置canvas的背景色
      gl.clearColor(0, 0, 0, 1)
      //开启隐藏面消除
      gl.enable(gl.DEPTH_TEST)


      //创建、设置视图投影矩阵
      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 currentAngle = 0.0
      var tick = function () {
        currentAngle = getCurrentAngle(currentAngle) //获取当前要旋转的角度
        //清空颜色和深度缓冲区
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
        //绘制单色立方体  
        drawSolidCube(gl, solidProgram, cube, -2.0, viewProjMatrix, currentAngle)

        //绘制纹理贴图立方体  
        drawTexCube(gl, texProgram, cube, texture, 2.0, viewProjMatrix, 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
    }

    //绘制单色立方体
    function drawSolidCube(gl, program, o, x, viewProjMatrix, angle) {

      gl.useProgram(program) //指定程序对象

      initAttributeVariable(gl, program.a_Position, o.vertexBuffer) //顶点坐标 
      initAttributeVariable(gl, program.a_Color, o.colorBuffer) //顶点颜色
      initAttributeVariable(gl, program.a_Normal, o.normalBuffer) //法向量

      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer) //绑定顶点索引

      setValueToShaderForColorCube(gl, program) //给顶点着色器中绘制单色立方体所需的变量传值
      
      drawCube(gl, program, o, x, viewProjMatrix, angle)
    }

    function setValueToShaderForColorCube(gl, program) {
      //给顶点着色器uniform变量u_LightColor-光线颜色传值(1.0,1.0,1.0)
      gl.uniform3f(program.u_LightColor, 1.0, 1.0, 1.0)

      var lightDirection = new Vector3([1.5, 3.0, 4.0])
      lightDirection.normalize() //归一化
      //给顶点着色器uniform变量u_LightColor- 光线颜色传值(1.0,1.0,1.0)
      gl.uniform3fv(program.u_LightDirection, lightDirection.elements)
    }

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

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

      //根据模型矩阵计算变换法向量的矩阵并传值
      g_normalMatrix.setInverseOf(g_modelMatrix)
      g_normalMatrix.transpose()
      gl.uniformMatrix4fv(program.u_NormalMatrix, false, g_normalMatrix.elements)

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

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

    function drawTexCube(gl, program, o, texture, x, viewProjMatrix, angle) {
      //指定程序对象
      gl.useProgram(program)

      //为变量分配缓存并开启
      initAttributeVariable(gl, program.a_Position, o.vertexBuffer) //顶点坐标 
      initAttributeVariable(gl, program.a_Normal, o.normalBuffer) //法向量
      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)

      //绘制纹理立方体
      drawCube(gl, program, o, x, viewProjMatrix, angle)

    }

    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 fontColor = [0.8, 0.5, 1.0, 0.4]
      var backColor = [0.5, 1.0, 1.0, 0.4]
      var leftColor = [1.0, 1.0, 0.5, 0.4]
      var rightColor = [0.2, 1.0, 0.5, 0.8]
      var topColor = [0.2, 0.5, 1.0, 0.8]
      var downColor = [1.0, 1.0, 1.0, 0.8]
      // 顶点的颜色
      var colors = new Float32Array([
        ...fontColor, ...fontColor, ...fontColor, ...fontColor, // v0-v1-v2-v3 前
        ...rightColor, ...rightColor, ...rightColor, ...rightColor, // v0-v3-v4-v5 右
        ...topColor, ...topColor, ...topColor, ...topColor, // v0-v5-v6-v1 上
        ...leftColor, ...leftColor, ...leftColor, ...leftColor, // v1-v6-v7-v2 左
        ...downColor, ...downColor, ...downColor, ...downColor, // v7-v4-v3-v2 下
        ...backColor, ...backColor, ...backColor, ...backColor, // v4-v7-v6-v5 后
      ])

      var font = [0.0, 0.0, 1.0]
      var back = [0.0, 0.0, -1.0]
      var left = [-1.0, 0.0, 0.0]
      var right = [1.0, 0.0, 0.0]
      var top = [0.0, 1.0, 0.0]
      var down = [0.0, -1.0, 0.0]
      // 法向量
      var normals = new Float32Array([
        ...font, ...font, ...font, ...font, // v0-v1-v2-v3 前
        ...right, ...right, ...right, ...right, // v0-v3-v4-v5 右
        ...top, ...top, ...top, ...top, // v0-v5-v6-v1 上
        ...left, ...left, ...left, ...left, // v1-v6-v7-v2 左
        ...down, ...down, ...down, ...down, // v7-v4-v3-v2 下
        ...back, ...back, ...back, ...back, // 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.colorBuffer = initArrayBufferForLaterUse(gl, colors, 4, gl.FLOAT)
      obj.normalBuffer = initArrayBufferForLaterUse(gl, normals, 3, gl.FLOAT)
      obj.texCoordBuffer = initArrayBufferForLaterUse(gl, texCoords, 2, gl.FLOAT)
      obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)

      //设置顶点索引数量
      obj.indicesNum = 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>

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

昵称

取消
昵称表情代码图片

    暂无评论内容