WebGL 基础
WebGL 可以在您的浏览器中显示惊人的实时 3D 图形,但很多人不知道的是,WebGL 实际上是一个 2D API,而不是一个 3D API。 让我解释。
WebGL 只关心两件事。 二维和颜色中的裁剪空间坐标。 作为一名使用 WebGL 的程序员,您的工作是为 WebGL 提供这两样东西。 您提供 2 个“着色器”来执行此操作。 提供剪辑空间坐标的顶点着色器和提供颜色的片段着色器。
无论您的画布有多大,剪辑空间坐标总是从 -1 到 +1。 这是一个简单的 WebGL 示例,以最简单的形式展示了 WebGL。
// Get A WebGL context var canvas = document.getElementById("canvas"); var gl = canvas.getContext("experimental-webgl"); // setup a GLSL program var vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader"); var fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader"); var program = createProgram(gl, [vertexShader, fragmentShader]); gl.useProgram(program); // look up where the vertex data needs to go. var positionLocation = gl.getAttribLocation(program, "a_position"); // Create a buffer and put a single clipspace rectangle in // it (2 triangles) var buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]), gl.STATIC_DRAW); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // draw gl.drawArrays(gl.TRIANGLES, 0, 6);
这是 2 个着色器
<script type="x-shader/x-vertex"> attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0, 1); } </script> <script type="x-shader/x-fragment"> void main() { gl_FragColor = vec4(0,1,0,1); // green } </script>
这将绘制一个与画布整个大小相同的绿色矩形。
不是很令人兴奋:-p
同样,无论画布的大小如何,剪辑空间坐标总是从 -1 到 +1。 在上面的例子中,你可以看到我们什么都不做,只是直接传递我们的位置数据。 由于位置数据已经在剪辑空间中,因此无需做任何工作。 如果您想要 3D,则由您提供从 3D 转换为 2D 的着色器,因为 WebGL 是 2D API!!!
对于 2D 内容,您可能更愿意在像素而不是剪辑空间中工作,所以让我们更改着色器,以便我们可以以像素为单位提供矩形,并为我们将其转换为剪辑空间。 这是新的顶点着色器
<script type="x-shader/x-vertex"> attribute vec2 a_position; uniform vec2 u_resolution; void main() { // convert the rectangle from pixels to 0.0 to 1.0 vec2 zeroToOne = a_position / u_resolution; // convert from 0->1 to 0->2 vec2 zeroToTwo = zeroToOne * 2.0; // convert from 0->2 to -1->+1 (clipspace) vec2 clipSpace = zeroToTwo - 1.0; gl_Position = vec4(clipSpace, 0, 1); } </script>
现在我们可以将数据从剪辑空间更改为像素
// set the resolution var resolutionLocation = gl.getUniformLocation(program, "u_resolution"); gl.uniform2f(resolutionLocation, canvas.width, canvas.height); // setup a rectangle from 10,20 to 80,30 in pixels gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 10, 20, 80, 20, 10, 30, 10, 30, 80, 20, 80, 30]), gl.STATIC_DRAW);
您可能会注意到矩形靠近该区域的底部。 WebGL 认为左下角为 0,0。 为了让它成为用于 2d 图形 API 的更传统的左上角,我们只需翻转 y 坐标。
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
现在我们的矩形就是我们期望的地方。
让我们将定义矩形的代码编写成一个函数,以便我们可以为不同大小的矩形调用它。 在此期间,我们将使颜色可设置。
首先我们让片段着色器接受颜色统一输入。
<script type="x-shader/x-fragment"> precision mediump float; uniform vec4 u_color; void main() { gl_FragColor = u_color; } </script>
这是在随机位置和随机颜色绘制 50 个矩形的新代码。
... var colorLocation = gl.getUniformLocation(program, "u_color"); ... // Create a buffer var buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // draw 50 random rectangles in random colors for (var ii = 0; ii < 50; ++ii) { // Setup a random rectangle setRectangle( gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300)); // Set a random color. gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1); // Draw the rectangle. gl.drawArrays(gl.TRIANGLES, 0, 6); } } // Returns a random integer from 0 to range - 1. function randomInt(range) { return Math.floor(Math.random() * range); } // Fills the buffer with the values that define a rectangle. function setRectangle(gl, x, y, width, height) { var x1 = x; var x2 = x + width; var y1 = y; var y2 = y + height; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2]), gl.STATIC_DRAW); }
我希望你能看到 WebGL 实际上是一个非常简单的 API。 虽然做 3D 会变得更复杂,但您,程序员,以更复杂的着色器的形式添加了复杂性。 WebGL API 本身是二维的,而且相当简单。
type="x-shader/x-vertex" 和 type="x-shader/x-fragment" 是什么意思?
<script>
标签默认包含 JavaScript。 你可以不放类型或者你可以放 type="javascript"
或者 type="text/javascript"
浏览器会将内容解释为 JavaScript。 如果您放置其他任何内容,浏览器将忽略脚本标记的内容。
我们可以使用此功能将着色器存储在脚本标签中。 更好的是,我们可以组成我们自己的类型,并在我们的 javascript 中查找它来决定是否将着色器编译为顶点着色器或片段着色器。
在这种情况下,函数 createShaderFromScriptElement
寻找指定的脚本 id
然后看着 type
决定要创建的着色器类型。
WebGL 图像处理
WebGL 中的图像处理很容易。 有多容易? 参见下文。
要在 WebGL 中绘制图像,我们需要使用纹理。 与 WebGL 在渲染时期望裁剪空间坐标而不是像素的方式类似,WebGL 在读取纹理时期望纹理坐标。 无论纹理的尺寸如何,纹理坐标都会从 0.0 到 1.0。
由于我们只绘制了一个矩形(好吧,2 个三角形),我们需要告诉 WebGL 矩形中的每个点对应于纹理中的哪个位置。 我们将使用一种称为“可变”的特殊变量将这些信息从顶点着色器传递到片段着色器。 它被称为变化,因为它变化。 当 WebGL 使用片段着色器绘制每个像素时,它会插入我们在顶点着色器中提供的值。
使用上一节末尾的顶点着色器,我们需要添加一个属性来传递纹理坐标,然后将它们传递给片段着色器。
attribute vec2 a_texCoord; ... varying vec2 v_texCoord; void main() { ... // pass the texCoord to the fragment shader // The GPU will interpolate this value between points v_texCoord = a_texCoord; }
然后我们提供一个片段着色器来从纹理中查找颜色。
<script type="x-shader/x-fragment"> precision mediump float; // our texture uniform sampler2D u_image; // the texCoords passed in from the vertex shader. varying vec2 v_texCoord; void main() { // Look up a color from the texture. gl_FragColor = texture2D(u_image, v_texCoord); } </script>
最后,我们需要加载图像,创建纹理并将图像复制到纹理中。 因为我们在浏览器中异步加载图像,所以我们必须稍微重新安排我们的代码以等待纹理加载。 一旦加载,我们将绘制它。
function main() { var image = new Image(); image.src = "http://someimage/on/our/server"; // MUST BE SAME DOMAIN!!! image.onload = function() { render(image); } } function render(image) { ... // all the code we had before. ... // look up where the texture coordinates need to go. var texCoordLocation = gl.getAttribLocation(program, "a_texCoord"); // provide texture coordinates for the rectangle. var texCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]), gl.STATIC_DRAW); gl.enableVertexAttribArray(texCoordLocation); gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); // Create a texture. var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // Set the parameters so we can render any size image. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); // Upload the image into the texture. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); ... }
不太令人兴奋,所以让我们操纵该图像。 只交换红色和蓝色怎么样?
... gl_FragColor = texture2D(u_image, v_texCoord).bgra; ...
如果我们想要进行实际查看其他像素的图像处理怎么办? 由于 WebGL 在纹理坐标中引用了从 0.0 到 1.0 的纹理,因此我们可以通过简单的数学计算计算 1 像素移动多少 onePixel = 1.0 / textureSize
.
这是一个片段着色器,它对纹理中每个像素的左右像素进行平均。
<script type="x-shader/x-fragment"> precision mediump float; // our texture uniform sampler2D u_image; uniform vec2 u_textureSize; // the texCoords passed in from the vertex shader. varying vec2 v_texCoord; void main() { // compute 1 pixel in texture coordinates. vec2 onePixel = vec2(1.0, 1.0) / u_textureSize; // average the left, middle, and right pixels. gl_FragColor = ( texture2D(u_image, v_texCoord) + texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) + texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0; } </script>
然后我们需要从 JavaScript 传入纹理的大小。
... var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize"); ... // set the size of the image gl.uniform2f(textureSizeLocation, image.width, image.height); ...
与上面未模糊的图像进行比较。
现在我们知道如何引用其他像素,让我们使用卷积核来做一堆常见的图像处理。 在这种情况下,我们将使用 3×3 内核。 卷积核只是一个 3×3 矩阵,其中矩阵中的每个条目代表我们正在渲染的像素周围的 8 个像素相乘多少。 然后我们将结果除以内核的权重(内核中所有值的总和)或 1.0,以较大者为准。 这是一篇关于它的相当不错的文章 。 而 这里的表现出一些实际的代码,如果你是手工用C写这篇++的另一篇文章 。
在我们的例子中,我们将在着色器中完成这项工作,所以这里是新的片段着色器。
<script type="x-shader/x-fragment"> precision mediump float; // our texture uniform sampler2D u_image; uniform vec2 u_textureSize; uniform float u_kernel[9]; // the texCoords passed in from the vertex shader. varying vec2 v_texCoord; void main() { vec2 onePixel = vec2(1.0, 1.0) / u_textureSize; vec4 colorSum = texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] + texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] + texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] + texture2D(u_image, v_texCoord + onePixel * vec2(-1, 0)) * u_kernel[3] + texture2D(u_image, v_texCoord + onePixel * vec2( 0, 0)) * u_kernel[4] + texture2D(u_image, v_texCoord + onePixel * vec2( 1, 0)) * u_kernel[5] + texture2D(u_image, v_texCoord + onePixel * vec2(-1, 1)) * u_kernel[6] + texture2D(u_image, v_texCoord + onePixel * vec2( 0, 1)) * u_kernel[7] + texture2D(u_image, v_texCoord + onePixel * vec2( 1, 1)) * u_kernel[8] ; float kernelWeight = u_kernel[0] + u_kernel[1] + u_kernel[2] + u_kernel[3] + u_kernel[4] + u_kernel[5] + u_kernel[6] + u_kernel[7] + u_kernel[8] ; if (kernelWeight <= 0.0) { kernelWeight = 1.0; } // Divide the sum by the weight but just use rgb // we'll set alpha to 1.0 gl_FragColor = vec4((colorSum / kernelWeight).rgb, 1.0); } </script>
在 JavaScript 中,我们需要提供一个卷积核。
... var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]"); ... var edgeDetectKernel = [ -1, -1, -1, -1, 8, -1, -1, -1, -1 ]; gl.uniform1fv(kernelLocation, edgeDetectKernel); ...
使用下拉列表选择不同的内核。
我希望这已经让您确信 WebGL 中的图像处理非常简单。 接下来,我将讨论如何将多个效果应用于图像。
GLSL 中 from 变量中的 a_、u_ 和 v_ 前缀是什么?
这只是一个命名约定。 a_ 用于属性,它是缓冲区提供的数据。 u_ 用于作为着色器输入的制服,v_ 用于变量,它们是从顶点着色器传递到片段着色器并在绘制的每个像素的顶点之间插入(或变化)的值。
应用多种效果
图像处理的下一个最明显的问题是如何应用多重效果?
好吧,您可以尝试动态生成着色器。 提供一个 UI,让用户可以选择他想要使用的效果,然后生成执行所有效果的着色器。 尽管该技术通常用于 但这可能并不总是可行 为实时图形创建效果 。
更灵活的方法是使用另外 2 个纹理并依次渲染每个纹理,来回切换并每次应用下一个效果。
Original Image -> [Blur] -> Texture 1 Texture 1 -> [Sharpen] -> Texture 2 Texture 2 -> [Edge Detect] -> Texture 1 Texture 1 -> [Blur] -> Texture 2 Texture 2 -> [Normal] -> Canvas
为此,我们需要创建帧缓冲区。 在 WebGL 和 OpenGL 中,Framebuffer 实际上是一个糟糕的名字。 WebGL/OpenGL 帧缓冲区实际上只是状态的集合,实际上并不是任何类型的缓冲区。 但是,通过将纹理附加到帧缓冲区,我们可以渲染到该纹理中。
首先让我们把旧的纹理创建代码变成一个函数
function createAndSetupTexture(gl) { var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // Set up texture so we can render any size image and so we are // working with pixels. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); return texture; } // Create a texture and put the image in it. var originalImageTexture = createAndSetupTexture(gl); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
现在让我们使用该函数制作另外 2 个纹理并将它们附加到 2 个帧缓冲区。
// create 2 textures and attach them to framebuffers. var textures = []; var framebuffers = []; for (var ii = 0; ii < 2; ++ii) { var texture = createAndSetupTexture(gl); textures.push(texture); // make the texture the same size as the image gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); // Create a framebuffer var fbo = gl.createFramebuffer(); framebuffers.push(fbo); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); // Attach a texture to it. gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); }
现在让我们制作一组内核,然后列出要应用的内核。
// Define several convolution kernels var kernels = { normal: [ 0, 0, 0, 0, 1, 0, 0, 0, 0 ], gaussianBlur: [ 0.045, 0.122, 0.045, 0.122, 0.332, 0.122, 0.045, 0.122, 0.045 ], unsharpen: [ -1, -1, -1, -1, 9, -1, -1, -1, -1 ], emboss: [ -2, -1, 0, -1, 1, 1, 0, 1, 2 ] }; // List of effects to apply. var effectsToApply = [ "gaussianBlur", "emboss", "gaussianBlur", "unsharpen" ];
最后让我们应用每一个,乒乓我们正在渲染的纹理
// start with the original image gl.bindTexture(gl.TEXTURE_2D, originalImageTexture); // don't y flip images while drawing to the textures gl.uniform1f(flipYLocation, 1); // loop through each effect we want to apply. for (var ii = 0; ii < effectsToApply.length; ++ii) { // Setup to draw into one of the framebuffers. setFramebuffer(framebuffers[ii % 2], image.width, image.height); drawWithKernel(effectsToApply[ii]); // for the next draw, use the texture we just rendered to. gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]); } // finally draw the result to the canvas. gl.uniform1f(flipYLocation, -1); // need to y flip for canvas setFramebuffer(null, canvas.width, canvas.height); drawWithKernel("normal"); function setFramebuffer(fbo, width, height) { // make this the framebuffer we are rendering to. gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); // Tell the shader the resolution of the framebuffer. gl.uniform2f(resolutionLocation, width, height); // Tell webgl the viewport setting needed for framebuffer. gl.viewport(0, 0, width, height); } function drawWithKernel(name) { // set the kernel gl.uniform1fv(kernelLocation, kernels[name]); // Draw the rectangle. gl.drawArrays(gl.TRIANGLES, 0, 6); }
这是一个工作版本,其 UI 稍微灵活一些。 检查效果以将其打开。 拖动效果以重新排列它们的应用方式。
有些事情我应该过去。
调用 gl.bindFramebuffer
和 null
告诉 WebGL 您想要渲染到画布而不是渲染到您的帧缓冲区之一。
WebGL 必须从剪辑空间转换回像素。 它根据以下设置执行此操作 gl.viewport
. 的设置 gl.viewport
默认为我们初始化 WebGL 时画布的大小。 由于我们渲染的帧缓冲区大小不同,因此我们需要适当设置画布视口。
最后,在 WebGL 基础示例中,我们在渲染时翻转了 Y 坐标,因为 WebGL 显示画布的左下角为 0,0,而不是更传统的 2D 左上角。 渲染到帧缓冲区时不需要。 因为从不显示帧缓冲区,所以顶部和底部的哪一部分是无关紧要的。 重要的是帧缓冲区中的像素 0,0 对应于我们计算中的 0,0。 为了解决这个问题,我可以通过在着色器中再添加一个输入来设置是否翻转。
<script type="x-shader/x-vertex"> ... uniform float u_flipY; ... void main() { ... gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1); ... } </script>
然后我们可以在渲染时设置它
... var flipYLocation = gl.getUniformLocation(program, "u_flipY"); ... // don't flip gl.uniform1f(flipYLocation, 1); ... // flip gl.uniform1f(flipYLocation, -1);
我通过使用可以实现多种效果的单个 GLSL 程序来使这个示例保持简单。 如果您想对图像进行全面处理,您可能需要许多 GLSL 程序。 用于色调、饱和度和亮度调整的程序。 另一个用于亮度和对比度。 一个用于反转,另一个用于调整级别等。您需要更改代码以切换 GLSL 程序并更新该特定程序的参数。 我曾考虑编写该示例,但最好留给读者练习,因为多个 GLSL 程序每个都有自己的参数需求,这可能意味着进行一些重大的重构,以防止它们变成一团糟的意大利面。
我希望这个和前面的例子让 WebGL 看起来更平易近人,我希望从 2D 开始有助于使 WebGL 更容易理解。 如果我有时间,我会试着写更多关于如何做 3D 的文章,以及更多关于 WebGL 在幕后真正做的事情的细节。
WebGL 和 Alpha
我注意到一些 OpenGL 开发人员在 WebGL 如何处理后台缓冲区(即画布)中的 alpha 方面存在问题,所以我认为回顾一下 WebGL 和 OpenGL 之间与 alpha 相关的一些差异可能会很好。
OpenGL 和 WebGL 之间的最大区别在于,OpenGL 渲染到一个后台缓冲区,它没有与任何东西复合,或者实际上没有与操作系统窗口管理器的任何东西复合,所以你的 alpha 是什么并不重要。
WebGL 由浏览器与网页合成,默认使用预乘 alpha 与 .png <img> 标签相同,带有透明度和 2d canvas 标签。
WebGL 有几种方法可以使它更像 OpenGL。
#1) 告诉 WebGL 你希望它与非预乘 alpha 合成
gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});
默认值为 true。
当然,结果仍然会在页面上合成,无论背景颜色最终在画布下(画布的背景颜色,画布的容器背景颜色,页面的背景颜色,如果画布有 z-index,画布后面的东西) > 0 等...)换句话说,CSS 为网页的该区域定义的颜色。
找到是否有任何 alpha 问题的好方法是将画布的背景设置为明亮的颜色,如红色。 你会立即看到发生了什么。
<canvas style="background: red;"></canvas>
您也可以将其设置为黑色,这将隐藏您遇到的任何 alpha 问题。
#2) 告诉 WebGL 你不想在后台缓冲区中使用 alpha
gl = canvas.getContext("experimental-webgl", {alpha: false});
这将使它的行为更像 OpenGL,因为后台缓冲区只有 RGB。 这可能是最好的选择,因为一个好的浏览器可以看到您没有 alpha 并且实际上优化了 WebGL 的合成方式。 当然,这也意味着它实际上不会在后台缓冲区中包含 alpha,因此如果您在后台缓冲区中使用 alpha 用于某些可能对您不起作用的目的。 据我所知,很少有应用程序在后台缓冲区中使用 alpha。 我认为可以说这应该是默认设置。
#3) 在渲染结束时清除 alpha
.. renderScene(); .. // Set the backbuffer's alpha to 1.0 gl.clearColor(1, 1, 1, 1); gl.colorMask(false, false, false, true); gl.clear(gl.COLOR_BUFFER_BIT);
清除通常非常快,因为在大多数硬件中都有一种特殊情况。 我在大多数演示中都这样做了。 如果我很聪明,我会切换到上面的方法#2。 也许我会在发布后立即这样做。 似乎大多数 WebGL 库都应该默认使用这种方法。 那些实际使用 alpha 进行合成效果的开发人员可以要求它。 其余的只会获得最好的性能和最少的惊喜。
#4) 清除 alpha 一次然后不再渲染它
// At init time. Clear the back buffer. gl.clearColor(1,1,1,1); gl.clear(gl.COLOR_BUFFER_BIT); // Turn off rendering to alpha gl.colorMask(true, true, true, false);
当然,如果您要渲染到自己的帧缓冲区,则可能需要将渲染重新打开为 alpha,然后在切换到画布渲染时再次将其关闭。
#5) 处理图像
此外,如果您将带有 alpha 的 PNG 文件加载到纹理中,默认情况下它们的 alpha 是预乘的,这通常不是大多数游戏的处理方式。 如果你想阻止这种行为,你需要告诉 WebGL
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
#6) 使用适用于预乘 alpha 的混合方程。
几乎所有我编写或使用过的 OpenGL 应用程序
gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);
这适用于非预乘 alpha 纹理。
如果你真的想使用预乘的 alpha 纹理,那么你可能想要
gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);
这些是我知道的方法。 如果您知道更多,请在下面发布它们。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 混合位置音频和 WebGL
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论