在 webGL 中绘制参数化形状(没有 three.js)
Drawing parametric shapes in webGL (without three.js)
我编写了一个程序,仅使用 HTML5 和常规 Javascript 绘制一些参数化形状(球体、环面和圆柱体)。我正在尝试转换此代码,以便它使用 WebGL,实现带有三角形带的形状,就像 this tutorial 中所做的那样。我的困惑点是三角形条带是如何被用来创建球体的。我之前做的方法只是基于 for 循环中的纬度线计算绘制每个水平切片或圆的位置,并嵌套在该循环内是计算该圆上的每个点。在生成所有这些点之后,我将它们传递给一个函数,该函数将所有顶点添加到一个数组中,该数组返回并传递给使用 moveTo()
和 lineTo()
绘制曲线的函数每个点之间的线。问题是当使用三角形时,我不知道 webGL 中 moveTo()
和 lineTo()
的等价物是什么。如何将我的实现转换为 WebGL?
这是我最初实现的一些代码:
//Calculates point on sphere
function spherePoint(uv) {
var u = uv[0];
var v = uv[1];
var phi = -Math.PI/2 + Math.PI * v;
var theta = 2 * Math.PI * u;
return [ Math.cos(phi) * Math.cos(theta),
Math.cos(phi) * Math.sin(theta),
Math.sin(phi)];
}
// Takes the parametric function as an argument and constructs 3D shape
function makeShape(num_u, num_v, eq, possRad) {
var shell = [];
for (var j = 0 ; j <= num_v ; j++) {
var v = j / num_v;
shell.push([]);
for (var i = 0 ; i <= num_u ; i++) {
var u = i / num_u;
var p = eq([u, v], possRad);
shell[j].push(p);
}
}
return shell;
}
// Used to create shapes to render parametric surface
function renderShape(shape) {
var num_j = shape.length;
var num_i = shape[0].length;
for (var j = 0 ; j < num_j - 1 ; j++)
for (var i = 0 ; i < num_i - 1 ; i++) {
plotCurve([shape[j][i],
shape[j + 1][i],
shape[j + 1][i + 1],
shape[j][i + 1]]);
}
}
//plot curve on canvas
function plotCurve(C) {
g.beginPath();
for (var i = 0 ; i < C.length ; i++)
if (i == 0)
moveTo(C[i]);
else
lineTo(C[i]);
g.stroke();
}
function moveTo(p) {
var q = m.transform(p); // APPLY 3D MATRIX TRANFORMATION
var xy = viewport(q); // APPLY VIEWPORT TRANSFORM
g.moveTo(xy[0], xy[1]);
}
function lineTo(p) {
var q = m.transform(p); // APPLY 3D MATRIX TRANFORMATION
var xy = viewport(q); // APPLY VIEWPORT TRANSFORM
g.lineTo(xy[0], xy[1]);
}
我认为 webGL 版本应该是这样的:
普通 Javascript 版本如下所示:
有 gl.LINES 好吧,差不多画出了连接线。所以 lineTo(x,y,z) 只会向用于存储线数据的 VBO 添加一个顶点。 moveTo(x,y,z) 只是在两行之间创建一个 "break" 。这可以通过新的 drawArrays 调用来完成。
这是一个非常基本的 WebGL 问题。 Some more tutorials on webgl might be helpful。
WebGL 只绘制数据块。它实际上没有 lineTo
或 moveTo
。相反,你给它数据缓冲区,告诉它如何从这些缓冲区中提取数据,然后你编写一个函数(顶点着色器)来使用该数据告诉 WebGL 如何将其转换为剪辑 space 坐标以及是否用结果绘制点、线或三角形。您还提供一个函数(片段着色器)来告诉它点、线或三角形使用什么颜色。
基本上,要绘制您想要绘制的东西,您需要为该球体上的每个矩形生成 2 个三角形。换句话说,您需要为每个矩形生成 6 个顶点。原因是为了用不同的颜色绘制每个三角形,您不能共享任何顶点,因为颜色与顶点相关联。
因此对于一个矩形,您需要生成这些点
0--1 4
| / /|
|/ / |
2 3--5
其中0、1、2是粉色点,3、4、5是绿色点。 1 和 4 具有相同的位置,但因为它们的颜色不同,所以它们必须是不同的点。第 2 点和第 3 点也一样。
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
这是上面渲染的球体,但没有任何光照、透视或任何东西。
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: positions,
color: colors,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var uniforms = {
resolution: [gl.canvas.width, gl.canvas.height],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<canvas id="c"></canvas>
<script id="vs" type="not-js">
attribute vec4 position;
attribute vec4 color;
uniform vec2 resolution;
varying vec4 v_color;
void main() {
gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
v_color = color;
}
</script>
<script id="fs" type="not-js">
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
如果您稍后想要点亮它,您还需要 法线(可以用来判断物体朝向哪个方向的值)。我们可以通过添加
来添加它们
var normals = [];
和内部 addPoint
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
normals.push(nx, ny, nz);
}
这是一个带有黑光的示例
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var normals = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
normals.push(nx, ny, nz);
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
var gl = document.getElementById("c").getContext("webgl");
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: positions,
normal: normals,
color: colors,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var uniforms = {
resolution: [gl.canvas.width, gl.canvas.height],
lightDirection: [0.5, 0.5, -1],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<canvas id="c"></canvas>
<script id="vs" type="not-js">
attribute vec4 position;
attribute vec4 color;
attribute vec3 normal;
uniform vec2 resolution;
varying vec4 v_color;
varying vec3 v_normal;
void main() {
gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
v_color = color;
v_normal = normal;
}
</script>
<script id="fs" type="not-js">
precision mediump float;
varying vec4 v_color;
varying vec3 v_normal;
uniform vec3 lightDirection;
void main() {
float light = pow(abs(dot(v_normal, normalize(lightDirection))), 2.0);
gl_FragColor = vec4(v_color.xyz * light, v_color.a);
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
PS:您发布的 picture 实际上是在每个矩形上绘制更多的三角形。绿色和粉红色之间的区别不是直线的。
我编写了一个程序,仅使用 HTML5 和常规 Javascript 绘制一些参数化形状(球体、环面和圆柱体)。我正在尝试转换此代码,以便它使用 WebGL,实现带有三角形带的形状,就像 this tutorial 中所做的那样。我的困惑点是三角形条带是如何被用来创建球体的。我之前做的方法只是基于 for 循环中的纬度线计算绘制每个水平切片或圆的位置,并嵌套在该循环内是计算该圆上的每个点。在生成所有这些点之后,我将它们传递给一个函数,该函数将所有顶点添加到一个数组中,该数组返回并传递给使用 moveTo()
和 lineTo()
绘制曲线的函数每个点之间的线。问题是当使用三角形时,我不知道 webGL 中 moveTo()
和 lineTo()
的等价物是什么。如何将我的实现转换为 WebGL?
这是我最初实现的一些代码:
//Calculates point on sphere
function spherePoint(uv) {
var u = uv[0];
var v = uv[1];
var phi = -Math.PI/2 + Math.PI * v;
var theta = 2 * Math.PI * u;
return [ Math.cos(phi) * Math.cos(theta),
Math.cos(phi) * Math.sin(theta),
Math.sin(phi)];
}
// Takes the parametric function as an argument and constructs 3D shape
function makeShape(num_u, num_v, eq, possRad) {
var shell = [];
for (var j = 0 ; j <= num_v ; j++) {
var v = j / num_v;
shell.push([]);
for (var i = 0 ; i <= num_u ; i++) {
var u = i / num_u;
var p = eq([u, v], possRad);
shell[j].push(p);
}
}
return shell;
}
// Used to create shapes to render parametric surface
function renderShape(shape) {
var num_j = shape.length;
var num_i = shape[0].length;
for (var j = 0 ; j < num_j - 1 ; j++)
for (var i = 0 ; i < num_i - 1 ; i++) {
plotCurve([shape[j][i],
shape[j + 1][i],
shape[j + 1][i + 1],
shape[j][i + 1]]);
}
}
//plot curve on canvas
function plotCurve(C) {
g.beginPath();
for (var i = 0 ; i < C.length ; i++)
if (i == 0)
moveTo(C[i]);
else
lineTo(C[i]);
g.stroke();
}
function moveTo(p) {
var q = m.transform(p); // APPLY 3D MATRIX TRANFORMATION
var xy = viewport(q); // APPLY VIEWPORT TRANSFORM
g.moveTo(xy[0], xy[1]);
}
function lineTo(p) {
var q = m.transform(p); // APPLY 3D MATRIX TRANFORMATION
var xy = viewport(q); // APPLY VIEWPORT TRANSFORM
g.lineTo(xy[0], xy[1]);
}
我认为 webGL 版本应该是这样的:
普通 Javascript 版本如下所示:
有 gl.LINES 好吧,差不多画出了连接线。所以 lineTo(x,y,z) 只会向用于存储线数据的 VBO 添加一个顶点。 moveTo(x,y,z) 只是在两行之间创建一个 "break" 。这可以通过新的 drawArrays 调用来完成。
这是一个非常基本的 WebGL 问题。 Some more tutorials on webgl might be helpful。
WebGL 只绘制数据块。它实际上没有 lineTo
或 moveTo
。相反,你给它数据缓冲区,告诉它如何从这些缓冲区中提取数据,然后你编写一个函数(顶点着色器)来使用该数据告诉 WebGL 如何将其转换为剪辑 space 坐标以及是否用结果绘制点、线或三角形。您还提供一个函数(片段着色器)来告诉它点、线或三角形使用什么颜色。
基本上,要绘制您想要绘制的东西,您需要为该球体上的每个矩形生成 2 个三角形。换句话说,您需要为每个矩形生成 6 个顶点。原因是为了用不同的颜色绘制每个三角形,您不能共享任何顶点,因为颜色与顶点相关联。
因此对于一个矩形,您需要生成这些点
0--1 4
| / /|
|/ / |
2 3--5
其中0、1、2是粉色点,3、4、5是绿色点。 1 和 4 具有相同的位置,但因为它们的颜色不同,所以它们必须是不同的点。第 2 点和第 3 点也一样。
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
这是上面渲染的球体,但没有任何光照、透视或任何东西。
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: positions,
color: colors,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var uniforms = {
resolution: [gl.canvas.width, gl.canvas.height],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<canvas id="c"></canvas>
<script id="vs" type="not-js">
attribute vec4 position;
attribute vec4 color;
uniform vec2 resolution;
varying vec4 v_color;
void main() {
gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
v_color = color;
}
</script>
<script id="fs" type="not-js">
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
如果您稍后想要点亮它,您还需要 法线(可以用来判断物体朝向哪个方向的值)。我们可以通过添加
来添加它们var normals = [];
和内部 addPoint
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
normals.push(nx, ny, nz);
}
这是一个带有黑光的示例
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var normals = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
normals.push(nx, ny, nz);
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
var gl = document.getElementById("c").getContext("webgl");
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: positions,
normal: normals,
color: colors,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var uniforms = {
resolution: [gl.canvas.width, gl.canvas.height],
lightDirection: [0.5, 0.5, -1],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<canvas id="c"></canvas>
<script id="vs" type="not-js">
attribute vec4 position;
attribute vec4 color;
attribute vec3 normal;
uniform vec2 resolution;
varying vec4 v_color;
varying vec3 v_normal;
void main() {
gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
v_color = color;
v_normal = normal;
}
</script>
<script id="fs" type="not-js">
precision mediump float;
varying vec4 v_color;
varying vec3 v_normal;
uniform vec3 lightDirection;
void main() {
float light = pow(abs(dot(v_normal, normalize(lightDirection))), 2.0);
gl_FragColor = vec4(v_color.xyz * light, v_color.a);
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
PS:您发布的 picture 实际上是在每个矩形上绘制更多的三角形。绿色和粉红色之间的区别不是直线的。