根据 HTML5 Rocks,WebGL 实际上是一个 2D API,而不是 3D API。他们为什么这么说,这是什么意思?
我们可以在 WebGL 顶点着色器和片段着色器中指定 X、Y、Z 坐标。我无法理解 2D 和 3D 图形 API 之间的区别。你能解释一下为什么他们说这是一个 2D API 吗?
根据 HTML5 Rocks,WebGL 实际上是一个 2D API,而不是 3D API。他们为什么这么说,这是什么意思?
我们可以在 WebGL 顶点着色器和片段着色器中指定 X、Y、Z 坐标。我无法理解 2D 和 3D 图形 API 之间的区别。你能解释一下为什么他们说这是一个 2D API 吗?
WebGL 实际上是一个 2D API,而不是 3D API。这是什么意思?
这意味着您应该停止听任何网站或人告诉您的任何内容。当人们说这样愚蠢的话时,最好忽略它们并继续进行更合理的教程/信息/讨论。
您当然可以在纯 2D 术语中使用 WebGL。您可以将 2D 位置传递给顶点着色器。您可以完全关闭深度测试。等等。但是你的顶点着色器的输出是一个4D 齐次坐标,即使你的 W 是 1 而你的 Z 是 0。所以渲染系统将完成它通常对 3D 场景所做的所有 3D 数学运算。
是的,光栅化基本上是一个 2D 过程,深度测试是一种“黑客”,可以去除隐藏的表面。但这对于所有基于光栅化的渲染都是如此。D3D、OpenGL、GLIDE 和每个软件光栅化器也将是这种逻辑的“2D API”。
如果它们都是 2D API,那么这种说法毫无意义。它将 OpenGL/D3D 置于与SDL 和 Direct2D 等实际“2D API”相同的级别。然而,那些“2D API”根本无法进行 3D 渲染(或者并非没有很大的痛苦)。
因此,该陈述在事实上是不正确的,并且具有令人难以置信的误导性。谁说这不值得你花时间或注意力。
来自评论:
最初写这个“WebGL 是 2D”的人已经屈尊解释他的推理,所以我将在这里解决这些问题。
让我们使用他对 API 维度的定义。他的准确报价是:
你只给了他们 3D 数据,他们只给了你一个 3D 显示。OpenGL ES 2.0 是一个 2D api。您必须自己提供所有 3D 到 2D 的数学转换。
由此,我们可以推断出“3D API”的意思是“‘你’将 3D 值输入以导致 3D 渲染发生的 API”。类似地,“2D API”表示“‘你’将 2D 值输入以导致 2D 渲染发生的 API”。
让我们假设“你”不仅仅意味着从缓冲区对象中获取的值的维度。“你”是指你可以直接控制的每一段代码,包括你的着色器。好的。因此,对于 WebGL,“你”会停在顶点着色器的末尾。因此,WebGL 开始处理顶点着色器输出。
顶点着色器的输出是一个 4D 齐次坐标。我猜这个论点是 4D 齐次坐标在某种程度上与 2D 坐标相同。即使它显然不是,因为它还有 2 个组件,并且您对它们执行的各种数学运算非常不同。
我会让您决定是否要将 4D 齐次坐标视为与 2D 坐标相同。
相反,我将看看WebGL如何处理 4D 输出。它是否将其转换为二维坐标?OpenGL规范说不。
来自OpenGL ES 2.0,第 2.12 节,对开页 44:
顶点着色器执行会产生一个顶点坐标
gl_Position
,该坐标假定在剪辑坐标中。对剪辑坐标进行透视分割以产生归一化的设备坐标,然后进行视口变换以将这些坐标转换为窗口坐标(见图 2.4)。
剪辑坐标是由 x、y、z 和 w 坐标(按此顺序)组成的四维齐次向量。如果一个顶点的剪辑坐标是:
(x c , y c , z c , w c )
那么顶点的归一化设备坐标是
(x d , y d , z d ) = (x c /w c , y c /w c , z c /w c )
标准化设备坐标空间有 3 个分量。因此它不是二维空间。但是后来的转变呢?
好吧,来自同一规范的第 2.12.1 节(对开页 44-45):
视口变换由视口的宽度和高度(分别以像素 p x和 p y为单位)及其中心 (o x , o y )(也以像素为单位)确定。顶点的窗口坐标 (x w , y w , z w ) 由下式给出
x w = (p x /2)x d + o x
y w = (p y /2)y d + o y
z w = ((f - n)/2)z d + (n + f)/2
所以是的,即使是窗口空间也是一个 3D 坐标系。窗口空间是 OpenGL 在其计算中使用的最终空间;窗口空间直接进入光栅化器。这就是渲染的内容。
因此,根据 OpenGL ES 2.0 规范,在整个渲染管道中没有一点可以将任何东西转换为纯2D 空间。
WebGL 是一种 API,您可以将 4D 齐次坐标输入其中。WebGL 绝不会执行任何“3D 到 2D 数学转换”,用户也不会。没有人会在 WebGL 中的任何点将任何东西转换为 2D 坐标。2D 值不通过 2D 管道馈送;4D 值通过 3D 管道馈送。
因此,按照他自己的定义,WebGL 不是 2D API。
QED。
在我看来(作为拥有 15 年以上 3D 图形经验的游戏开发人员),gman 将 WebGL 描述为 2D API 充其量是高度误导,我倾向于认为这完全是错误的。Nicol Bolas 在他的回答中指出了大部分原因,但对我来说,关键是一旦你从 gman 在他的画布 / WebGL 示例中使用的线框图形移动到纹理化,就不可能获得正确渲染的 3D 场景如果 WebGL 没有在顶点着色器的输出中提供 z 和 w 信息,并且它在光栅化期间没有使用它们来获得透视正确插值并使用 z 缓冲区执行隐藏表面移除,则为三角形。
gman 似乎真正试图说明的一点是,WebGL 不像古老的古老 3D 图形 API 那样是固定功能的 3D 图形 API,而是具有可编程管道。然而,所有现代 3D 图形 API(Direct3D 8、9、10、11;OpenGL 2.0 及更高版本;您可以在 PS3、PS4、Wii U 等控制台上找到的专有 API)都是如此。它们都以基本相同的方式工作:顶点着色器输出齐次坐标,光栅器使用 z 和 w 信息正确插入投影到 2D 图像的 3D 三角形,并使用 z 缓冲区执行隐藏表面移除。这与没有 z 和 w 坐标、没有透视正确插值概念和没有用于去除隐藏表面的 z 缓冲区的 2D API 非常不同。
[更新] 在他的一篇文章中, gman 或多或少地交替使用“API”和“库”。我认为这两个术语没有明确且完善的定义,但我认为对这些术语的不同理解可能会导致这里的一些分歧。
Khronos 描述了 WebGL:
WebGL™ 是专为 Web 设计的即时模式 3D 渲染 API。
我认为这是一个准确的描述。“API”的一个常用含义是定义的用于访问底层硬件或操作系统服务的软件接口,是指面向公众的软件接口,而不是任何特定的实现。从这个意义上说,所有旨在访问 3D 图形硬件的主流现代 API 都可以被认为是低级的“即时模式 3D 渲染 API”。我会在这个类别中包括 OpenGL、OpenGL ES、WebGL、Direct3D 和在控制台上找到的专有 API。
在行业中将所有这些都称为“3D API”是正常的,因为它们旨在提供对主要功能是渲染 3D 图形的 GPU 的访问,并且它们公开了支持该功能的低级功能(透视正确插值和 z -光栅化期间基于缓冲区的隐藏表面去除、各向异性纹理过滤、在某些情况下镶嵌硬件等)以及对 3D 管道的可编程部分(顶点、像素、几何着色器、外壳和域着色器等)进行编程的方法。 )。
我倾向于认为“库”与“API”的含义略有不同。像three.js 这样的东西将自己描述为“库”而不是“API”:
Three.js 是一个使 WebGL(浏览器中的 3D)变得非常简单的库。虽然原始 WebGL 中的简单立方体会产生数百行 Javascript 和着色器代码,但 Three.js 等价物只是其中的一小部分。
虽然这两个术语没有硬性和快速的定义,但我倾向于认为库更多地指代功能的特定实现,并暗示可能比直接 API 更高级的辅助功能。
其他更高级别的 3D“库”可能将自己描述为“引擎”或“框架”,例如
OGRE(面向对象的图形渲染引擎)是用 C++ 编写的面向场景的灵活 3D 引擎,旨在使开发人员更轻松、更直观地使用硬件加速 3D 图形生成应用程序。
有一大堆功能不是旧式固定功能“API”的一部分,例如 2.0 之前的 OpenGL 或 DX8 之前的 DirectX,但如果您只想渲染一些 3D 对象而不需要详细了解 3D 图形,那么它非常有用——比如场景图、加载和渲染模型的函数以及附加材质、对灯光和阴影的高级支持等,但这不是 Direct3D 或 WebGL 等低级 3D 'API' 的目标。这不是他们试图解决的问题。我可以看到尝试将其传达给只想在浏览器中渲染一些 3D 对象的新手可能会有用,但我不认为声称 WebGL 是“2D API”是一种有用或准确的方式来实现这一点.
好吧,我不知道其他人——无论 Khronos 在他们的网站上说什么,我都会倾向于去。对我来说似乎很清楚。:耸肩:
WebGL™ 是专为 Web 设计的即时模式 3D 渲染 API。它源自 OpenGL® ES 2.0,并提供类似的渲染功能,但在 HTML 上下文中。WebGL 被设计为 HTML Canvas 元素的呈现上下文。HTML Canvas 为网页中的编程呈现提供了一个目的地,并允许使用不同的呈现 API 执行该呈现。描述为 Canvas 规范一部分的唯一此类接口是 2D 画布渲染上下文 CanvasRenderingContext2D。本文档描述了另一个这样的接口,WebGLRenderingContext,它提供了 WebGL API。
这是一个观点。
在 WebGL 中没有任何要求以 3d 建模世界、创建相机、设置灯光等。最后,渲染器只关心点、线和三角形,它们的 4 个坐标与|w*x|<w, |w*y|<w, |w*z|<w.
默认情况下只能传递给着色器两个坐标,x 和 y,而框架设置 z=0 和 w=1。
也可以使用 opengl 来绘制 2d 精灵,而不用担心设置一些投影矩阵。可以省略 z 缓冲区和 z 坐标的处理,直到需要保存的点:|z*w| <= w 表示要渲染的任何内容。
但也很明显,API 非常适合渲染 3D 模型并非巧合。
WebGL 是光栅化 API 而不是 3D api。您必须为其提供投影坐标。在很多方面它与 Canvas 没有什么不同。它只是更快。让我们比较一下。
这是 Canvas 中的 3D
const cubeVertices = [
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
];
const indices = [
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7,
];
const canvas = document.querySelector("#c");
const ctx = canvas.getContext("2d");
function render(time) {
time *= 0.001;
const scale = 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.scale(canvas.width / scale, -canvas.height / scale);
ctx.lineWidth = scale / canvas.width;
ctx.strokeStyle = "black";
const fieldOfView = Math.PI * 0.25;
const aspect = canvas.width / canvas.height;
const projection = m4.perspective(fieldOfView, aspect, 1, 500);
const radius = 5;
const eye = [
Math.sin(time) * radius,
2,
Math.cos(time) * radius];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const worldViewProjection = m4.multiply(projection, view);
drawLines(cubeVertices, indices, worldViewProjection);
ctx.restore();
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawLines(cubeVertices, indices, worldViewProjection) {
ctx.beginPath();
//
// transform points from 3D to 2D.
//
const points = [];
for (let ii = 0; ii < cubeVertices.length; ii += 3) {
points.push(m4.transformPoint(
worldViewProjection,
cubeVertices.slice(ii, ii + 3)));
}
for (let ii = 0; ii < indices.length; ii += 2) {
var p0 = points[indices[ii + 0]];
var p1 = points[indices[ii + 1]];
ctx.moveTo(p0[0], p0[1]);
ctx.lineTo(p1[0], p1[1]);
}
ctx.stroke();
}
canvas { border: 1px solid red; }
<!-- just a math lib -->
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
<canvas id="c"></canvas>
这是 WebGL 中相同的 3D
const cubeVertices = [
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
];
const indices = [
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7,
];
const canvas = document.querySelector('#c');
const gl = canvas.getContext('webgl');
const vs = `
attribute vec4 a_position;
uniform mat4 u_worldViewProjection;
void main() {
//
// transform points from 3D to 2D.
//
gl_Position = u_worldViewProjection * a_position;
}
`;
const fs = `
void main() {
gl_FragColor = vec4(0,0,0,1);
}
`;
const program = webglUtils.createProgramFromSources(
gl, [vs, fs]);
gl.useProgram(program);
const positionLoc = gl.getAttribLocation(program, "a_position");
const worldViewProjectionLoc =
gl.getUniformLocation(program, "u_worldViewProjection");
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(cubeVertices),
gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 0, 0);
const buffer2 = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer2);
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array(indices),
gl.STATIC_DRAW);
function render(time) {
time *= 0.001;
const scale = 4;
const fieldOfView = Math.PI * 0.25;
const aspect = canvas.width / canvas.height;
const projection = m4.perspective(fieldOfView, aspect, 0.0001, 500);
const radius = 5;
const eye = [
Math.sin(time) * radius,
2,
Math.cos(time) * radius];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const worldViewProjection = m4.multiply(projection, view);
gl.uniformMatrix4fv(
worldViewProjectionLoc, false, worldViewProjection);
gl.drawElements(gl.LINES, indices.length, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid red; }
<!-- just a math lib -->
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<canvas id="c"></canvas>
Canvas 和 WebGL 之间的唯一区别是在 Canvas 中我使用 JavaScript 进行投影,而在 WebGL 中我在着色器中进行投影。在这两种情况下,我编写的代码都进行了投影。
在 Canvas 版本中,该代码为:
m4.transformPoint(
worldViewProjection,
cubeVertices.slice(ii, ii + 3));
在 WebGL 版本中,该代码是:
gl_Position = u_worldViewProjection * a_position
API 本身只光栅化。无论哪种情况,我都必须提供投影代码。WebGL 中没有任何东西可以做 3D。只有一个光栅化 api 和 2 个函数,顶点着色器和片段着色器,它们都是用 GLSL 编写的,我必须提供它们运行速度非常快并包含一个数学库。在这两种情况下,我仍然需要提供对 API 进行 3D 处理的代码。
WebGL **不是* 3D API
我认为指出这一点很重要。有各种版本的 OpenGL。1993 年的原始 OpenGL 是一个 3D api。你给了它 3D 数据,你告诉它用什么颜色来制作东西,你告诉它各种灯光。你给了它一个模型矩阵和一个投影矩阵,它为你绘制了 3D。
OpenGL ES 2.0 和 WebGL 摆脱了所有这些。它们提供光栅化 API 和着色器,让您对硬件进行编程。但是由你来写所有的预测。您必须从 3D 计算投影坐标。你有计算照明方程,颜色和所有其他的。
这使得 WebGL 和 OpenGL ES 2.0 可以说比旧的固定函数 OpenGL 更难,但同时也使它们更加灵活。如果您对所有这些转换和数学操作感到满意,或者如果您不介意学习它,那么就开始做吧。如果您不习惯做所有事情,那么有很多 WebGL 3D 库可以为您做这件事。
对于声称 WebGL 是 3D 库的您,让我们尝试一个思维游戏。
这是一个物理库box2d.js。你给它形状、质量和力,它会为你计算物理。如果它真的只是一个数学库,而你必须自己提供所有物理方程,你还会称它为物理库吗?一个叫做物理库的东西必须提供物理知识,否则它就不是物理库。同样,称为 3D 库的东西必须提供 3D 知识,否则它就不是 3D 库。
OpenGL 1.0 是一个 3D 库。你给它 3D 位置、顶点颜色、灯光,它会为你绘制 3D。您不需要 3D 知识。另一方面,WebGL 不提供任何 3D 知识。你必须知道如何进行 3D 投影,你必须知道如何采样纹理,你必须知道如何进行光照计算。它不再是一个 3D 库,它只是一个光栅化 API。将其称为 3D 库是一个谎言,对那些真正寻找 3D 库(即提供 3D 的库)的人来说是一种伤害。
称它为 2D 库可能有点夸张,但称它为 3D 库是错误的。
哇,你的问题太棒了!你让所有这些大师像上一场战斗一样战斗 Oo
好吧,也许您还想阅读具有 1 年 WebGL 经验且几乎没有 OpenGL 编程技能的人的答案,所以让我们这样做吧!:)
首先,我必须说一些我还没有读过的东西,我觉得它很重要。@gman 说:
API 提供了一个解决方案,因此您个人并不具备该知识。
好吧,我真的不同意这种说法。API 提供了一些解决方案,但这并不意味着您不需要这些知识。
现在回到你的问题,正如你所说:
我们可以在 webGL 顶点着色器和片段着色器中指定 x,y,z 坐标。我无法理解 2D 和 3D 图形 API 之间的区别。
正如其他人所说,您可以在着色器中指定(我认为您甚至必须指定)它是 4D 矢量。gl_Position
以前,只有 javascript 和一些分离的 gpu。然后 WebGL 出现了新的选项。WebGL 本身不为3D 行为gl_Position
提供任何解决方案,但它带来了在着色器中指定的选项。
我计算新选项,而不仅仅是整个解决方案。所以我认为这个问题也是关于 API 真正适合你的。为了我?在这种情况下,它带来了通过着色器制作 3D 应用程序的可能性。所以它是 3D API。
我希望它有帮助...