本文标题:WebGL第七课:搞一搞 vertex shader(1)
在接下来的几次课里,我们将通过几个实例,来熟悉 vertex shader
的各种用法。
- 我们将用 1000 个点,来绘制我们的图形。
- 我们的代码模板给出如下:
<!doctype html>
<html>
<head>
<style>
canvas {
border: 1px solid #000000;
}
</style>
</head>
<body>
<input id="slider" type="range" min="0" max="100" value="50" step="1" οnchange="sliderfunc()" />
<canvas id="point" style="width:300px; height:300px">
</canvas>
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
uniform float u_x_offset;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_Position.y = sin(gl_Position.x * 3.14);
if ( fract(gl_Position.x * 100.0) < 0.5)
{
gl_Position.y = gl_Position.x;
}
gl_Position.x = gl_Position.x + u_x_offset;
gl_PointSize = 3.0;
}
</script>
<script id="fragment_shader" type="myshader">
// Fragment shader
precision mediump int;
precision mediump float;
void main() {
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
</script>
<script type="text/javascript">
var pointCanvas = document.getElementById('point'); // 我们的纸
var gl = pointCanvas.getContext('webgl', { preserveDrawingBuffer: true }); // 我们的笔
var pointCount = 0;
var pointData = [];
for (var idx = -500; idx <= 500; idx++) {
pointCount++;
pointData.push(idx / 500);
pointData.push(0);
}
//
var pointArray = new Float32Array(pointData);
var buffer_id;
buffer_id = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer_id);
gl.bufferData(gl.ARRAY_BUFFER, pointArray, gl.STATIC_DRAW);
//
var vertex_shader_code = document.getElementById('vertex_shader').textContent;
console.log(vertex_shader_code);
var vertex_shader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertex_shader, vertex_shader_code);
gl.compileShader(vertex_shader);
//
var fragment_shader_code = document.getElementById('fragment_shader').textContent;
var fragment_shader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragment_shader, fragment_shader_code);
gl.compileShader(fragment_shader);
//
var program = gl.createProgram();
gl.attachShader(program, vertex_shader);
gl.attachShader(program, fragment_shader);
gl.linkProgram(program);
gl.useProgram(program);
//
var a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex');
gl.vertexAttribPointer(a_PointVertex, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_PointVertex);
//
// gl.drawArrays(gl.POINTS, 0, pointCount);
</script>
<script>
var u_x_offset_loc = gl.getUniformLocation(program, "u_x_offset");
var sliderDom = document.getElementById("slider");
function sliderfunc() {
console.log("___________________", u_x_offset_loc, typeof sliderDom.value, sliderDom.value);
gl.uniform1f(u_x_offset_loc, parseFloat(sliderDom.value) / 100);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, pointCount);
}
sliderDom.addEventListener("change", sliderfunc);
</script>
</script>
</body>
</html>
- 我们的重点在上述代码的 这一部分, 也就是
vertex shader
部分 :
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_PointSize = 3.0;
}
</script>
第一个例子: 绘制 sin 正弦图像
- 我们准备的数据点有1000个,这些点的横坐标,从 -1 到 1
- 纵坐标都是0
那么按到里讲,绘制的东西是一根横的直线,在屏幕中间
我们在浏览器里打开这个页面,结果也确实如此:
根据 正弦函数式子:
y = sin (x)
我们改动我们的 vertex shader
, 如下:
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_Position.y = sin(gl_Position.x);
gl_PointSize = 3.0;
}
</script>
刷新一下页面,结果如下图:
我们发现上图不怎么完美,因为并没有完整显示一个周期之内的正弦
原因就是因为我们的 x 的范围是 -1---1 之间,这不够一个周期的
一个周期应该是 -π --- +π 之间。
那这样来变一下代码:
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_Position.y = sin(gl_Position.x * 3.14);
gl_PointSize = 3.0;
}
</script>
刷新一下页面,结果如下,这样就好看多了:
太棒了,我们可以用webgl
来绘制函数图形了,多好玩啊~
我要画两个周期的 sin 图像:
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_Position.y = sin(gl_Position.x * 3.14 * 2.0);
gl_PointSize = 3.0;
}
</script>
是的,我们成功了:
接下来我们来搞一些奇怪的东西,比如说,我们的代码怎么改动,能绘制下面的图:
我们发现,在图的左半边,好像图形往中间压缩了一半。
线索:
- 图的左半边:(
x<0
) - 压缩一半 :(
y = y * 0.5
)
我们将上面的线索,更换成相应的代码如下:
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_Position.y = sin(gl_Position.x * 3.14 * 2.0);
if (gl_Position.x < 0.0)
{
gl_Position.y = gl_Position.y * 0.5;
}
gl_PointSize = 3.0;
}
</script>
还是两个周期的图像,不过,代码里写了,如果 x<0
, y
就变成一半,很容易理解!
第二个例子: 同时绘制 一条直线
和 sin
图像
就像下图这样:
有读者脑子里就在想了,简单:
我在外面绘制两次,就是用 gl.drawArrays
,绘制两次图像,事先准备好两个 vertex shader
。
结果是:行,你这么搞是可以的,我不会说任何反对的话,但是我再加一个限制,
-
只准有一个
vertex shader
, -
只准用一次
gl.drawArrays
。 -
而且不准改外面的生成数据的代码,我们只能改
vertex shader
代码!!!
思路:
我们有1000
个点,我们交叉绘制上面两个图形不就行了吗!!!
比如说,奇数下标的绘制sin
图像,偶数下标的绘制直线
。
问题来了,我们能在vertex shader 里获取下标吗?????
显然不能。
我们只能在x
本身上下手,我们列一下最左边的几个x
:
-1.000, -0.998, -0.996, -0.994, -0.992, -0.990 ……
我们将上面的数全部乘以 100就会得到下面的数:
-100.0, -99.8, -99.6, -99.4, -99.2, -99
我们再把小数部分保留,去掉整数部分:
0, 0.8, 0.6, 0.4, 0.2 , 0
我们发现,除了0之外,两个小于0.5
的,两个大于0.5
的
那我们就按照这个来交叉,代码如下:
<script id="vertex_shader" type="myshader">
// Vertex Shader
precision mediump int;
precision mediump float;
attribute vec2 a_PointVertex;
void main() {
gl_Position = vec4(a_PointVertex, 0.0, 1.0);
gl_Position.y = sin(gl_Position.x * 3.14);
if ( fract(gl_Position.x * 100.0) < 0.5)
{
gl_Position.y = gl_Position.x;
}
gl_PointSize = 3.0;
}
</script>
刷新页面,图像出来了!!成功!!
正文结束,下面是答疑