原文章发布在自己博客:http://aircloud.10000h.top/45
写在前面
想写这篇文章想好久了,这半年在学校的课程中,自己最认真学习的就是图形学这门课程的内容,当然主要是因为自己想要研究一下WebGl,最终课程部分也取得了5.0的满绩, 也算是对自己一个小小的认可。
WebGl和OpenGL
关于WebGl的学习,虽然有Web也有Gl,但是说实话,首先要懂OpenGl(3.0+),会OpenGl再学WebGl,就会感觉比较顺利,而会Web打算学习WebGl,感觉还是需要学很多东西。
最后我们做的是一个基于WebSocket的多人协作在线密室逃脱,关于游戏的玩家视频,可以看这个链接。还是有很多不完善的地方,比较适合新手入门。
学习WebGl的前期过程是比较苦恼的,这里大家可以直接学习WebGl,也可以先学习OpenGL,后者比较适合c++用的熟练的同学,否则直接上手WebGL也没什么问题。
关于c++的OpenGL,现在学校大多数还是教1.0+,用glut,实际上1.0+的很多概念已经是非常落后了,glut也已经是十多年没有更新的一个库(虽然倒是没有bug),并且1.0如果想画点东西的话相对十分简单,我建议如果不是必须要用1.0,大家都应该拥抱3.0+,关于学习资料,我建议大家可以看看这个页面,是我收藏的一些OpenGL的比较好的教程,慢慢按照教程走一遍,你就大概能摸清点门路了。另外本博客也写过一些配置方面的文章,大家也可以翻翻看。
WebGL入门
学习WebGl的话,我认为如果想要做出像样的小东西,应该有一定的htmlcssjs功底,至少bootstrap是要会用的, css3应该也是知道一些的。
WebGl在js中的写法几乎和c++中的思想无差,主要就是1.创建/解析模型文件并且导入,2.编写可编程管线。前期的主要难题在于WebGl的函数实在是太多,根本不能理解,后期的难点主要在于算法,光照算法绘制算法等。
这里我推荐大家《WebGl编程指南》这本书,这本书讲的是原生WebGl,而其他有的书以three.js为主,我认为,如果是本着学习目的,不建议直接上手three.js等第三方库,还是先了解清楚WebGl本身再说。
这里还有一点值得说的是,我认为WebGL有3个大的作用域(可以这么理解…)一个是JS的作用域,一个是顶点着色器的作用域,一个是片元着色器的作用域,JS作用域就是我们写JS代码的地方,可以定义全局变量全局可见,然而,这个全局变量另外两个着色器的作用域是不可见的。一个顶点着色器的代码大概像这样(这里我建议大家写在特定标签中然后再写个函数读取里面的内容,编程指南那本书把所有的顶点着色器代码和片元着色器代码都写在一个字符串里了,这实在是一个难以维护的方式):
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
precision highp int;
#else
precision mediump float;
precision mediump int;
#endif
uniform samplerCube s_texture;
varying vec3 v_texCoord;
uniform int uiShadowMode;
uniform float u_If_Fog;
varying float If_Fog;
void main(void)
{
If_Fog = u_If_Fog;
if(uiShadowMode == 0) gl_FragColor = textureCube(s_texture, v_texCoord);
else if(uiShadowMode == 1) gl_FragColor = vec4(0.7, 0.7, 0.7, 1.0);
else if(uiShadowMode == 2) gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
else if(uiShadowMode == 3) gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
</script>
1234567891011121314151617181920212223
当然,上面给出的是一个比较简单的,片元着色器也是写在带有特殊type的script标签中类似这样:
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
varying vec3 v_TextCord;
varying vec4 v_Color;
varying float v_Clicked;
varying vec4 v_tempColor;
uniform sampler2D u_Sampler;
uniform vec3 u_FogColor;
uniform vec2 u_FogDist;
varying float If_Fog;
varying float v_Dist;
void main() {
vec4 temp_gl_FragColor;
if(v_TextCord.z==1.0){
temp_gl_FragColor = texture2D(u_Sampler, v_TextCord.xy) * 0.7 + v_Color * 0.3;
}
else{
temp_gl_FragColor = v_Color;
}
if(If_Fog==1.0){
float fogFactor = clamp((u_FogDist.y - v_Dist) / (u_FogDist.y - u_FogDist.x), 0.0, 1.0);
vec3 color = mix(u_FogColor, vec3(temp_gl_FragColor), fogFactor);
//temp_gl_FragColor = vec4(u_FogColor, v_Color.a);
temp_gl_FragColor=vec4(vec3(temp_gl_FragColor*fogFactor),1);
}
if(v_Clicked == 1.0){
temp_gl_FragColor = v_tempColor;
}
gl_FragColor=temp_gl_FragColor;
}
</script>
123456789101112131415161718192021222324252627282930313233
我们如果想共享变量,只能通过绑定的方式比如uniform:
//获取顶点着色器中的uniform:
program.u_If_Fog = gl.getUniformLocation(program, 'u_If_Fog');
//js中的变量:
var If_Fog = 1.0;
//绑定uniform:
gl.uniform1f(gl.program.u_If_Fog,If_Fog);
123456
这一点在临近期末的时候我们小组有的同学还不知道,所以我在这里单独先讲讲。
至于其他的,前期一定要花时间了解WebGl中提供的常用的函数,并且搞懂整个流程,想要画个三角形估计加起来都要100多行代码,这里面的流程大概是这样的:
初始化一个WebGl上下文->编译顶点着色器,片元着色器->赋值三角形要用的顶点数组->绑定顶点数组到着色器变量->绘制函数。
以上的各个步骤,都有相应的函数可供调用,如果把这个过程了解了,也是不难的。
另外,我们在后期一般是通过导入obj的方式来绘制场景,编程指南这本书里面只是简单带过这一部分并且给了一个并不是很好用的demo,但我认为这一部分是重点,所以在原书的启发下,我写了一个obj加载器:这个obj加载器因为经过很多次测试,自认为健壮性是很高的,但是因为目前没有人关注,我的文档也没有太多动力可以写,如果你认为这个对你可能会有帮助,请直接在GitHub上提issue或者给我邮件,从而让我有动力把相关内容补充全面。
这篇文章大概总结了自己学WebGl的心得和资料,当然,后期我们又在算法上有所尝试,相关内容在我们的报告中也有提到,欢迎和我交流相关内容。