提问人:fields 提问时间:6/27/2023 最后编辑:Rabbid76fields 更新时间:6/27/2023 访问量:72
与保留模式 OpenGL3 相比,WebGL2 在哪些方面是即时的?
In what way is WebGL2 immediate compared to retained mode OpenGL3?
问:
我以前在 learnopengl.com 上的教程中有一些使用 OpenGL3 的经验,最近一直在学习 WebGL2 的比较。我仍然是n00b,所以请原谅任何误解。
我感到困惑的一个原因是 WebGL 将自己宣传为“即时模式 3D 渲染 API”,但我很难理解编写 WebGL2 程序与等效的“保留”OpenGL3 程序的设计模式实际上有什么不同。
据我了解,保留模式 OpenGL3 程序的基本流程如下;
- 通过适当的 API 调用创建 VBO 和 VAO
- 绑定 VAO
- 绑定 VBO
- 使用顶点属性数据填充 VBO
- 在 VAO 中填写有关绑定的 VBO 所描述的属性以及如何切碎缓冲区的信息。
- 根据需要重复上述 3 个步骤
- 绑定 VAO
- 调用 glDrawArrays
在我对术语“保留模式”的理解中,上述设计模式之所以被认为是保留的,是因为所有关于渲染内容的信息都存储在 GPU 上,而不是由客户端在渲染时提供。客户端所要做的就是告诉 GPU 要绑定哪个 VAO 并指示它进行渲染。这是 Khronos 的一个例子,据我所知,它显示了上述设计模式。我包含了下面的代码。
#include <stdlib.h>
#include <stdio.h>
/* Ensure we are using opengl's core profile only */
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#include <SDL.h>
#define PROGRAM_NAME "Tutorial2"
/* A simple function that will read a file into an allocated char pointer buffer */
char* filetobuf(char *file)
{
FILE *fptr;
long length;
char *buf;
fptr = fopen(file, "rb"); /* Open file for reading */
if (!fptr) /* Return NULL on failure */
return NULL;
fseek(fptr, 0, SEEK_END); /* Seek to the end of the file */
length = ftell(fptr); /* Find out how many bytes into the file we are */
buf = (char*)malloc(length+1); /* Allocate a buffer for the entire length of the file and a null terminator */
fseek(fptr, 0, SEEK_SET); /* Go back to the beginning of the file */
fread(buf, length, 1, fptr); /* Read the contents of the file in to the buffer */
fclose(fptr); /* Close the file */
buf[length] = 0; /* Null terminator */
return buf; /* Return the buffer */
}
/* A simple function that prints a message, the error code returned by SDL, and quits the application */
void sdldie(char *msg)
{
printf("%s: %s\n", msg, SDL_GetError());
SDL_Quit();
exit(1);
}
void setupwindow(SDL_WindowID *window, SDL_GLContext *context)
{
if (SDL_Init(SDL_INIT_VIDEO) < 0) /* Initialize SDL's Video subsystem */
sdldie("Unable to initialize SDL"); /* Or die on error */
/* Request an opengl 3.2 context.
* SDL doesn't have the ability to choose which profile at this time of writing,
* but it should default to the core profile */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
/* Turn on double buffering with a 24bit Z buffer.
* You may need to change this to 16 or 32 for your system */
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
/* Create our window centered at 512x512 resolution */
*window = SDL_CreateWindow(PROGRAM_NAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
512, 512, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
if (!*window) /* Die if creation failed */
sdldie("Unable to create window");
/* Create our opengl context and attach it to our window */
*context = SDL_GL_CreateContext(*window);
/* This makes our buffer swap syncronized with the monitor's vertical refresh */
SDL_GL_SetSwapInterval(1);
}
void drawscene(SDL_WindowID window)
{
int i; /* Simple iterator */
GLuint vao, vbo[2]; /* Create handles for our Vertex Array Object and two Vertex Buffer Objects */
int IsCompiled_VS, IsCompiled_FS;
int IsLinked;
int maxLength;
char *vertexInfoLog;
char *fragmentInfoLog;
char *shaderProgramInfoLog;
/* We're going to create a simple diamond made from lines */
const GLfloat diamond[4][2] = {
{ 0.0, 1.0 }, /* Top point */
{ 1.0, 0.0 }, /* Right point */
{ 0.0, -1.0 }, /* Bottom point */
{ -1.0, 0.0 } }; /* Left point */
const GLfloat colors[4][3] = {
{ 1.0, 0.0, 0.0 }, /* Red */
{ 0.0, 1.0, 0.0 }, /* Green */
{ 0.0, 0.0, 1.0 }, /* Blue */
{ 1.0, 1.0, 1.0 } }; /* White */
/* These pointers will receive the contents of our shader source code files */
GLchar *vertexsource, *fragmentsource;
/* These are handles used to reference the shaders */
GLuint vertexshader, fragmentshader;
/* This is a handle to the shader program */
GLuint shaderprogram;
/* Allocate and assign a Vertex Array Object to our handle */
glGenVertexArrays(1, &vao);
/* Bind our Vertex Array Object as the current used object */
glBindVertexArray(vao);
/* Allocate and assign two Vertex Buffer Objects to our handle */
glGenBuffers(2, vbo);
/* Bind our first VBO as being the active buffer and storing vertex attributes (coordinates) */
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
/* Copy the vertex data from diamond to our buffer */
/* 8 * sizeof(GLfloat) is the size of the diamond array, since it contains 8 GLfloat values */
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), diamond, GL_STATIC_DRAW);
/* Specify that our coordinate data is going into attribute index 0, and contains two floats per vertex */
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
/* Enable attribute index 0 as being used */
glEnableVertexAttribArray(0);
/* Bind our second VBO as being the active buffer and storing vertex attributes (colors) */
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
/* Copy the color data from colors to our buffer */
/* 12 * sizeof(GLfloat) is the size of the colors array, since it contains 12 GLfloat values */
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(GLfloat), colors, GL_STATIC_DRAW);
/* Specify that our color data is going into attribute index 1, and contains three floats per vertex */
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
/* Enable attribute index 1 as being used */
glEnableVertexAttribArray(1);
/* Read our shaders into the appropriate buffers */
vertexsource = filetobuf("tutorial2.vert");
fragmentsource = filetobuf("tutorial2.frag");
/* Create an empty vertex shader handle */
vertexshader = glCreateShader(GL_VERTEX_SHADER);
/* Send the vertex shader source code to GL */
/* Note that the source code is NULL character terminated. */
/* GL will automatically detect that therefore the length info can be 0 in this case (the last parameter) */
glShaderSource(vertexshader, 1, (const GLchar**)&vertexsource, 0);
/* Compile the vertex shader */
glCompileShader(vertexshader);
glGetShaderiv(vertexshader, GL_COMPILE_STATUS, &IsCompiled_VS);
if(IsCompiled_VS == FALSE)
{
glGetShaderiv(vertexshader, GL_INFO_LOG_LENGTH, &maxLength);
/* The maxLength includes the NULL character */
vertexInfoLog = (char *)malloc(maxLength);
glGetShaderInfoLog(vertexshader, maxLength, &maxLength, vertexInfoLog);
/* Handle the error in an appropriate way such as displaying a message or writing to a log file. */
/* In this simple program, we'll just leave */
free(vertexInfoLog);
return;
}
/* Create an empty fragment shader handle */
fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
/* Send the fragment shader source code to GL */
/* Note that the source code is NULL character terminated. */
/* GL will automatically detect that therefore the length info can be 0 in this case (the last parameter) */
glShaderSource(fragmentshader, 1, (const GLchar**)&fragmentsource, 0);
/* Compile the fragment shader */
glCompileShader(fragmentshader);
glGetShaderiv(fragmentshader, GL_COMPILE_STATUS, &IsCompiled_FS);
if(IsCompiled_FS == FALSE)
{
glGetShaderiv(fragmentshader, GL_INFO_LOG_LENGTH, &maxLength);
/* The maxLength includes the NULL character */
fragmentInfoLog = (char *)malloc(maxLength);
glGetShaderInfoLog(fragmentshader, maxLength, &maxLength, fragmentInfoLog);
/* Handle the error in an appropriate way such as displaying a message or writing to a log file. */
/* In this simple program, we'll just leave */
free(fragmentInfoLog);
return;
}
/* If we reached this point it means the vertex and fragment shaders compiled and are syntax error free. */
/* We must link them together to make a GL shader program */
/* GL shader programs are monolithic. It is a single piece made of 1 vertex shader and 1 fragment shader. */
/* Assign our program handle a "name" */
shaderprogram = glCreateProgram();
/* Attach our shaders to our program */
glAttachShader(shaderprogram, vertexshader);
glAttachShader(shaderprogram, fragmentshader);
/* Bind attribute index 0 (coordinates) to in_Position and attribute index 1 (color) to in_Color */
/* Attribute locations must be setup before calling glLinkProgram. */
glBindAttribLocation(shaderprogram, 0, "in_Position");
glBindAttribLocation(shaderprogram, 1, "in_Color");
/* Link our program */
/* At this stage, the vertex and fragment programs are inspected, optimized and a binary code is generated for the shader. */
/* The binary code is uploaded to the GPU, if there is no error. */
glLinkProgram(shaderprogram);
/* Again, we must check and make sure that it linked. If it fails, it would mean either there is a mismatch between the vertex */
/* and fragment shaders. It might be that you have surpassed your GPU's abilities. Perhaps too many ALU operations or */
/* too many texel fetch instructions or too many interpolators or dynamic loops. */
glGetProgramiv(shaderprogram, GL_LINK_STATUS, (int *)&IsLinked);
if(IsLinked == FALSE)
{
/* Noticed that glGetProgramiv is used to get the length for a shader program, not glGetShaderiv. */
glGetProgramiv(shaderprogram, GL_INFO_LOG_LENGTH, &maxLength);
/* The maxLength includes the NULL character */
shaderProgramInfoLog = (char *)malloc(maxLength);
/* Notice that glGetProgramInfoLog, not glGetShaderInfoLog. */
glGetProgramInfoLog(shaderprogram, maxLength, &maxLength, shaderProgramInfoLog);
/* Handle the error in an appropriate way such as displaying a message or writing to a log file. */
/* In this simple program, we'll just leave */
free(shaderProgramInfoLog);
return;
}
/* Load the shader into the rendering pipeline */
glUseProgram(shaderprogram);
/* Loop our display increasing the number of shown vertexes each time.
* Start with 2 vertexes (a line) and increase to 3 (a triangle) and 4 (a diamond) */
for (i=2; i <= 4; i++)
{
/* Make our background black */
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
/* Invoke glDrawArrays telling that our data is a line loop and we want to draw 2-4 vertexes */
glDrawArrays(GL_LINE_LOOP, 0, i);
/* Swap our buffers to make our changes visible */
SDL_GL_SwapWindow(window);
/* Sleep for 2 seconds */
SDL_Delay(2000);
}
/* Cleanup all the things we bound and allocated */
glUseProgram(0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDetachShader(shaderprogram, vertexshader);
glDetachShader(shaderprogram, fragmentshader);
glDeleteProgram(shaderprogram);
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
glDeleteBuffers(2, vbo);
glDeleteVertexArrays(1, &vao);
free(vertexsource);
free(fragmentsource);
}
void destroywindow(SDL_WindowID window, SDL_GLContext context)
{
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
SDL_Quit();
}
/* Our program's entry point */
int main(int argc, char *argv[])
{
SDL_WindowID mainwindow; /* Our window handle */
SDL_GLContext maincontext; /* Our opengl context handle */
/* Create our window, opengl context, etc... */
setupwindow(&mainwindow, &maincontext);
/* Call our function that performs opengl operations */
drawscene(mainwindow);
/* Delete our opengl context, destroy our window, and shutdown SDL */
destroywindow(mainwindow, maincontext);
return 0;
}
通过阅读 WebGL2 的设计模式,这似乎也正是我们对这个规范所做的!更具体地说,我一直在看 WebGL2 Fundementals 中的这个例子,我在下面转载了它。
"use strict";
var vs = `#version 300 es
in vec2 a_position;
in vec4 a_color;
uniform mat3 u_matrix;
out vec4 v_color;
void main() {
// Multiply the position by the matrix.
gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
// Copy the color from the attribute to the varying.
v_color = a_color;
}
`;
var fs = `#version 300 es
precision highp float;
in vec4 v_color;
out vec4 outColor;
void main() {
outColor = v_color;
}
`;
function main() {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromSources(gl, [vs, fs]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var colorLocation = gl.getAttribLocation(program, "a_color");
// lookup uniforms
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
// Create set of attributes
var vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// Create a buffer for the positons.
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// Set Geometry.
setGeometry(gl);
// tell the position attribute how to pull data out of the current ARRAY_BUFFER
gl.enableVertexAttribArray(positionLocation);
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
// Create a buffer for the colors.
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// Set the colors.
setColors(gl);
// tell the color attribute how to pull data out of the current ARRAY_BUFFER
gl.enableVertexAttribArray(colorLocation);
var size = 4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
var translation = [200, 150];
var angleInRadians = 0;
var scale = [1, 1];
drawScene();
// Setup a ui.
webglLessonsUI.setupSlider("#x", {value: translation[0], slide: updatePosition(0), max: gl.canvas.width });
webglLessonsUI.setupSlider("#y", {value: translation[1], slide: updatePosition(1), max: gl.canvas.height});
webglLessonsUI.setupSlider("#angle", {slide: updateAngle, max: 360});
webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: updateScale(0), min: -5, max: 5, step: 0.01, precision: 2});
webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: updateScale(1), min: -5, max: 5, step: 0.01, precision: 2});
function updatePosition(index) {
return function(event, ui) {
translation[index] = ui.value;
drawScene();
};
}
function updateAngle(event, ui) {
var angleInDegrees = 360 - ui.value;
angleInRadians = angleInDegrees * Math.PI / 180;
drawScene();
}
function updateScale(index) {
return function(event, ui) {
scale[index] = ui.value;
drawScene();
};
}
// Draw the scene.
function drawScene() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Bind the attribute/buffer set we want.
gl.bindVertexArray(vao);
// Compute the matrices
var matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
matrix = m3.translate(matrix, translation[0], translation[1]);
matrix = m3.rotate(matrix, angleInRadians);
matrix = m3.scale(matrix, scale[0], scale[1]);
// Set the matrix.
gl.uniformMatrix3fv(matrixLocation, false, matrix);
// Draw the geometry.
var offset = 0;
var count = 6;
gl.drawArrays(gl.TRIANGLES, offset, count);
}
}
// Fill the buffer with the values that define a rectangle.
function setGeometry(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-150, -100,
150, -100,
-150, 100,
150, -100,
-150, 100,
150, 100,
]),
gl.STATIC_DRAW);
}
// Fill the buffer with colors for the 2 triangles
// that make the rectangle.
function setColors(gl) {
// Pick 2 random colors.
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
Math.random(), Math.random(), Math.random(), 1,
Math.random(), Math.random(), Math.random(), 1,
Math.random(), Math.random(), Math.random(), 1,
Math.random(), Math.random(), Math.random(), 1,
Math.random(), Math.random(), Math.random(), 1,
Math.random(), Math.random(), Math.random(), 1,
]),
gl.STATIC_DRAW);
}
main();
那么,考虑到这一点,为什么 WebGL2 被认为是即时的,而现代的核心 OpenGL3 被认为是保留的?两者之间的 API 调用似乎几乎是一对一的对应关系。它是否与 VBO/VAO 数据的存储方式有关(即浏览器存储这些对象并将它们每帧发送到 GPU),还是与我们在想要重新渲染显示器时必须调用 drawArrays 这一事实有关(这也是我们对核心 OpenGL3 的处理方式)?
我在这里阅读了这篇文章,但我希望能更准确地解释核心 OpenGL3 与 WebGL2。
答:
命名很糟糕,但“OpenGL 即时模式”中的即时与“即时模式渲染 API”中的术语不同。
在 OpenGL 中,即时模式通常用于旧的 glBegin/glEnd 绘图方式,而即时模式 API 意味着您自己发出渲染命令,而不是像通常使用 HTML 那样只指定内容。
- OpenGL 始终是即时模式渲染 API,因为您可以自己发出绘制命令。
- 没有保留模式 OpenGL 渲染 API
- OpenGL1/2(兼容模式)是即时模式 OpenGL 和即时模式渲染 API。
- OpenGL3(Core Profile) 和 WebGL2 都是 OpenGL 保留模式,并且都是即时模式渲染 API。
- SVG 被视为保留模式渲染 API,因为您只指定内容,而不关心内容的渲染方式/时间。
评论