因为对不同的场景以及不同的材质需要使用不同的着色器,因此需要使用多个着色器,而不是单个,单个着色器是无法绘制出很复杂的场景,因为有的物体没有纹理而有的有纹理以及别的效果
着色器语言就是 GLSL
格式的代码,我们可以先把顶点和片元着色器分别写在.glsl
后缀名的文件中,如果你的编译器支持这种语法检测,那么代码就会有颜色,而不仅仅是字符串样式显示,尽管着色器代码本身就是字符串
下面是将要绘制的单色立方体的着色器文件
single_vertex_shader.glsl
// 顶点着色器
attribute vec4 a_Position;
attribute vec4 a_Normal;
uniform mat4 u_MvpMatrix;
uniform mat4 u_NormalMatrix;
varying vec4 v_Color;
void main(){
vec3 lightDirection = vec3(0.0, 0.0, 1.0); // 灯光的位置 在世界坐标系中
vec4 color = vec4(0.5, 0.5, 0.8, 1.0);// 表面颜色
gl_Position = u_MvpMatrix * a_Position; // 计算后的顶点坐标
vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));//标准化法向量
float nDot = max(dot(normal, lightDirection), 0.0);
v_Color = vec4(color.rgb * nDot, color.a);
}
single_fragment_shader.glsl
precision mediump float;
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color
}
从外部文件加载着色器,其实就是加载.glsl
文件,读取里面的数据,然后通过顶点着色器和片元着色器创建程序对象
关于如何加载外部文件的方法,在上一篇文章中有介绍,你也可以直接去我的gitee
上仓库的lib
目录直接下载加载器
加载完.glsl
文件之后,就可以使用这两个着色器程序了,创建program
对象,下面是两个程序对象,通过四个着色器代码创建得到的
var singleProgram = createProgram(gl, single_vertex_shader, single_fragment_shader);
var textureProgram = createProgram(gl, texture_vertex_shader,texture_fragment_shader);
采用javascript
对象属性的方式,把每个变量的存储地址挂载到程序对象上,便于使用
// 获取 singleProgram 中的 attribute 和 uniform 变量存储位置
singleProgram.a_Position = gl.getAttribLocation(singleProgram, 'a_Position');
singleProgram.a_Normal = gl.getAttribLocation(singleProgram, 'a_Normal');
singleProgram.u_MvpMatrix = gl.getUniformLocation(singleProgram, 'u_MvpMatrix');
singleProgram.u_NormalMatrix = gl.getUniformLocation(singleProgram, 'u_NormalMatrix');
// 获取 textureProgram 中的 attribute 和 uniform 变量存储位置
textureProgram.a_Position = gl.getAttribLocation(textureProgram, 'a_Position');
textureProgram.a_Normal = gl.getAttribLocation(textureProgram, 'a_Normal');
textureProgram.a_TexCoord = gl.getAttribLocation(textureProgram, 'a_TexCoord');
textureProgram.u_MvpMatrix = gl.getUniformLocation(textureProgram, 'u_MvpMatrix');
textureProgram.u_NormalMatrix = gl.getUniformLocation(textureProgram, 'u_NormalMatrix');
textureProgram.u_Sampler = gl.getUniformLocation(textureProgram, 'u_Sampler');
因为创建的是两个立方体,所以它们的顶点数据和顶点索引以及法线数据都是可以共用的
var cube = initVertexBuffers(gl);
if (!cube) {
console.log('failed to set the vertex information');
return false;
}
上面通过initVertexBuffers()
方法来设置顶点信息
设置完公用的顶点信息后,一个立方体是需要加载贴图的,因此需要把贴图的数据给加载到贴图立方体的片元着色器上,此处是通过initTexture(gl, program)
方法来进行设置
var texture = initTextures(gl, textureProgram);
if (!texture) {
console.log('failed to intialize the texture.');
return false;
}
设置完以上数据之后,就可以开始绘制
因为是三维物体,有z
轴深度,需要开启深度
gl.enable(gl.DEPTH_TEST)
// 设置canvas清空后的颜色
gl.clearColor(0.0, 0.5, 0.5, 1.0)
var viewProjMatrix = new Matrix4();
viewProjMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0)
viewProjMatrix.lookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
下面就可以在循环渲染方法中,进行帧刷新渲染
实际上在真正的项目开发过程中,重复这些步骤是很繁琐的,对一些反复的操作封装比较好的引擎例如three.js
它隐藏了webgl
的内部执行流程,同时也内置了很多的着色器,每一种材质以及灯光的添加都对应了一种着色器,这些着色器是可以进行修改的,并且three.js
也支持自己去写着色器以及原生webgl
代码
https://ithanmang.gitee.io/webgl-notes/home/07-高级技术示例/08-使用不同着色器.html