WebGL入门(三十二)-着色器和着色器程序对象,初始化着色器说明

1.initShaders() 函数创建步骤

从第一个demo开始一直使用了一个着色器初始化函数initShaders(),但是一直没有详细说明,接下来我们了解一下它是怎么把字符串形式的 GLSL ES 代码编译成可在GPU上运行的着色器程序,该函数大致可分为以下七个步骤:

  1. 创建着色器对象–gl.createShader()
  2. 向着色器对象中填充着色器程序源代码–gl.createSource()
  3. 编译着色器–gl.compileShader()
  4. 创建程序对象–gl.createProgram()
  5. 为程序对象分配着色器–gl.attachShader()
  6. 连接程序对象–gl.linkProgram()
  7. 使用程序对象–gl.useProgram()

在以上七个步骤中使用到了两种对象:着色器对象程序对象
它们之间的关系是:程序对象着色器对象 的容器,而着色器对象又管理者两个着色器,一个是顶点着色器,一个是片元着色器

2.相关API介绍

2.1创建着色器对象 gl.createShader()

在WebGL中所有的着色器对象必须通过调用 gl.createShader() 来创建


调用示例:gl.createShader(type)
--------------------------------------------------------------------------
函数功能:创建由type指定的着色器对象
--------------------------------------------------------------------------			
参数		
			type					指定创建着色器的类型,可以是以下中的一个
			gl.VERTEX_SHADER		表示顶点着色器
			gl.FRAGMENT_SHADER		表示片元着色器
--------------------------------------------------------------------------		
返回值		非null					新创建的着色器
			null					创建失败
--------------------------------------------------------------------------
错误		INVALID_ENUM			type不是上述值之一

gl.createShader()根据传入的type创建顶点着色器或者片元着色器,如果不在需要这个着色器了,可以 像这样 gl.deleteShader(shader) 删除它,
注:删除着色器的时候,如果着色器对象还在使用(使用gl.attachShader()函数使这个着色器附件在了程序对象上),那么调用删除函数后,不会立即删除着色器,要等到不在使用该着色器时才会删除掉

2.2指定着色器代码 gl.shaderSource()

在javaScript程序中,源代码是以字符串的形式存储的,可以通过gl.shaderSouce()函数向着色器指定源码


调用示例:gl.shaderSource(shader,source)
--------------------------------------------------------------------------
函数功能:将source指定的字符串形式的代码传入shader指定的着色器,如果已经向shader
中传入过代码了,旧代码会被新代码替换掉
--------------------------------------------------------------------------			
参数		
			shader		指定需要传入代码的着色器对象
			source		指定字符串形式的代码
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无

2.3编译着色器 gl.compileShader()

GLSL ES 语言与C或C++相似,需要编译成二进制的可执行文件才能使用,对于我们创建的着色器,在向着色器对象传入源代码之后,同样需要进行编译才能使用,WebGL 提供了gl.compileShader()函数,用来编译着色器
注:如果调用gl.shaderSource() 函数时,用新代码替换了旧代码,已编译好的可执行文件不会被自动替换掉,需要手动重新编译,即再次调用gl.compileShader()函数


调用示例:gl.compileShader(shader)
--------------------------------------------------------------------------
函数功能:编译shader指定的着色器中的源代码
--------------------------------------------------------------------------			
参数		shader		指定要编译的着色器
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无

2.4获取着色器信息 gl.getShaderParameter()


调用示例:gl.getShaderParameter(shader, pname)
--------------------------------------------------------------------------
函数功能:  获取shader指定的着色器中,pname指定的参数信息
--------------------------------------------------------------------------			
参数		
			shader				指定待获取参数的着色器
			pname				指定待获取参数的类型,可以是
								gl.SHADER_TYPE、gl.DELETE_STATUS或者
								gl.COMPILE_STATUS
--------------------------------------------------------------------------		
返回值		根据pname不同返回不同的值
			gl.SHADER_TYPE		返回是顶点着色器(gl.VERTEX_SHADER)
								还是片元着色器(gl.FRAMENT_SHADER)
			gl.DELETE_STATUS	返回着色器是否被删除成功(truefalse)
			gl.COMPILE_STATUS	返回着色器是否被编译成功(truefalse--------------------------------------------------------------------------
错误		INVALID_ENUM		pname值无效

2.5获取写入着色器日志 gl.getShaderInfoLog()


调用示例:gl.getShaderInfoLog(shader)
--------------------------------------------------------------------------
函数功能:  获取shader指定的着色器的信息日志
--------------------------------------------------------------------------			
参数		shader				指定待获取参数的着色器
--------------------------------------------------------------------------		
返回值		non-null			包含日志信息的字符串
			null				没有编译错误			
--------------------------------------------------------------------------
错误		无

通常 gl.getShaderParameter() 函数和 gl.getShaderInfoLog() 函数是配合使用的,一般是将gl.getShaderParameter()函数的参数pname指定为 gl.COMPILE_STATUS 就可以检查着色器是否编译成功,如果编译失败通过 gl.getShaderInfoLog() 函数获取错误信息
示例如下:

...
//编译顶点、片元着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);

//检查顶点着色器是否编译成功
var vertexShaderStatus = gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS);
if (!vertexShaderStatus) {
  var error = gl.getShaderInfoLog(vertexShader);
  console.log('编译顶点着色器失败: ' + error);
  gl.deleteShader(vertexShader);
  return null;
}
//检查片元着色器是否编译成功
var fragmentShaderStatus = gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS);
if (!fragmentShaderStatus) {
  var error = gl.getShaderInfoLog(fragmentShader);
  console.log('编译片元着色器失败: ' + error);
  gl.deleteShader(fragmentShader);
  return null;
}
...

2.6创建程序对象 gl.createProgram()


调用示例:gl.createProgram()
--------------------------------------------------------------------------
函数功能:创建程序对象
--------------------------------------------------------------------------			
参数		无
--------------------------------------------------------------------------		
返回值		non-null	新创建的程序对象
			null		创建失败
--------------------------------------------------------------------------
错误		无

2.7删除程序对象 gl.deleteProgram()


调用示例:gl.deleteProgram(program)
--------------------------------------------------------------------------
函数功能:创建程序对象
--------------------------------------------------------------------------			
参数		program		要删除的程序对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无

2.8为程序对象分配着色器对象 gl.attachShader()

WebGL程序要运行起来,必须要有两个着色器,一个顶点着色器,一个片元着色器,就需要通过gl.attachShader() 函数为程序对象分配这两个着色器


调用示例:gl.attachShader(program, shader)
--------------------------------------------------------------------------
函数功能:将shader指定的着色器对象分配给program指定的程序对象
--------------------------------------------------------------------------			
参数		program				指定程序对象
			shader				指定着色器对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		INVALID_OPERATION	shader已经被分配给program

2.9取消程序对象分配的着色器对象 gl.detachShader()


调用示例:gl.detachShader(program, shader)
--------------------------------------------------------------------------
函数功能:取消shader指定的着色器对象对program指定的程序对象的分配
--------------------------------------------------------------------------			
参数		program				指定程序对象
			shader				指定着色器对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		INVALID_OPERATION	shader没有被分配给program

2.10连接程序对象 gl.linkProgram()

为程序对象分配了两个着色器对象后,还需要将它们与程序对象连接起来,使用 gl.linkProgram() 函数完成这一操作


调用示例:gl.linkProgram(program)
--------------------------------------------------------------------------
函数功能:连接program指定的程序对象中的着色器
--------------------------------------------------------------------------			
参数		program				指定程序对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无

程序对象进行着色器连接操作,目的是检查以下四项:

  1. 顶点着色器和片元着色器中的varying变量同名同类型,且一一对应
  2. 顶点着色器中的每个varying变量都已经赋值
  3. 顶点着色器和片元着色器中的同名uniform变量也是同类型,无需一一对应
  4. 着色器中的attribute变量、uniform变量、varying变量个数有没有超过着色器的上限

2.11检查着色器是否连接成功 gl.getProgramParameter()


调用示例:gl.getProgramParameter(program, pname)
--------------------------------------------------------------------------
函数功能:  获取program指定的程序对象中,pname指定的参数信息
--------------------------------------------------------------------------			
参数		
			program				指定待获取参数的程序对象
			pname				指定待获取参数的类型,可以是
								gl.DELETE_STATUS、gl.LINK_STATUS、
								gl.VALIDATE_STATUS、gl.ATTACHED_SHADERS、
								gl.ACTIVE_ATTRIBUTES、gl.ACTIVE_UNIFORM
--------------------------------------------------------------------------		
返回值		根据pname不同返回不同的值
			gl.DELETE_STATUS	程序是否被删除(truefalse)
			gl.LINK_STATUS		程序是否已经成功连接(truefalse)
			gl.VALIDATE_STATUS	程序是否已经通过验证(truefalse)	
			gl.ATTACHED_SHADERS	已被分配给程序的着色器数量
			gl.ACTIVE_ATTRIBUTES 	顶点着色器中attribute变量的数量
			gl.ACTIVE_UNIFORM	程序中uniform变量的数量	
			
--------------------------------------------------------------------------
错误		INVALID_ENUM		pname值无效

如果程序已经连接成功,我们就得到一个二进制的可执行模块供WebGL调用,如果连接失败可以通过 gl.getProgramInfoLog() 函数获取连接出错的信息

2.12获取程序对象的日志 gl.getProgramInfoLog()


调用示例:gl.getProgramInfoLog(program)
--------------------------------------------------------------------------
函数功能:  获取program指定的着色器的信息日志
--------------------------------------------------------------------------			
参数		program		指定待获取参数的程序对象
--------------------------------------------------------------------------		
返回值		包含日志信息的字符串		
--------------------------------------------------------------------------
错误		无
...
//链接program
gl.linkProgram(program);

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

2.13使用程序对象 gl.useProgram()

最后需要调用 gl.useProgram() 函数告知WebGL系统绘制时使用哪个程序对象,有了这个函数,我们就可以在绘制前准备多个程序对象,在绘制的时候根据需要切换程序对象


调用示例:gl.useProgram(program)
--------------------------------------------------------------------------
函数功能:  告知WebGL系统绘制时使用program指定的程序对象
--------------------------------------------------------------------------			
参数		program		指定待使用的程序对象
--------------------------------------------------------------------------		
返回值		无
--------------------------------------------------------------------------
错误		无

3.initShaders代码

//初始化着色器函数
function initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE) {
  //创建顶点着色器对象
  var vertexShader = loadShader(gl, gl.VERTEX_SHADER, VSHADER_SOURCE);
  //创建片元着色器对象
  var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, FSHADER_SOURCE);

  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('Failed to link program: ' + error);
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  //使用program
  gl.useProgram(program);
  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;
}

4 后续

使用修改后的initShaders函数,可以在浏览器中调试着色器代码,如果出错,会在控制台上输出出错代码的行数,如下图,它会告诉你错误出现在第4行,在uniform11附件,这时你差不多能知道是你将uniform写成uniform11了
在这里插入图片描述

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

昵称

取消
昵称表情代码图片

    暂无评论内容