我正在研究一种自上而下的2D HTML5游戏,该游戏使用逐像素区域采样和操作来创建波浪波纹效果。我在JavaScript中运行并运行,但是在FireFox上性能不佳并且在Chrome上完全不可接受。我考虑将我的整个原型移植到一个性能更好的平台上,但在此过程中了解了GL着色器。
我认为将我的算法调整为GL片段着色器会很简单。我连续第四天试图让我的着色器产生任何输出。我已尽最大努力将其他问题和教程中的解决方案应用到我正在做的事情中,但没有一个能够完全满足我的特定需求。
首先,我将概述我在概念上需要发生的事情。然后,我将提供代码并解释我迄今为止尝试采用的方法。如果我能理解我需要做什么,我愿意从头开始。
波浪效果算法的工作原理如here所述。它涉及通过基于每个像素的波高数据移动某些像素来从源图像渲染新图像,存储在两个矩阵中,其具有图像中每个像素的对应条目。一个用作水的当前状态,另一个用于存储前一帧的结果以用于计算电流。
每帧:waveMapCurrent通过平均值waveMapPrevious
计算得出每个像素:位移是根据waveMapCurrent中的高度计算的,并且(在伪代码中)newPixelData[current] = sourcePixelData[current+displacement]
至少,我需要片段着色器才能从当前波高矩阵和图像中访问数据以用作源。如果我理解正确,那么最小化我将新数据传递到GL管道并在着色器中执行波高计算的次数最有利于性能,但我可以在我的脚本和我的脚本中进行计算。将波高矩阵的更新版本传递给每帧的片段着色器。
在我甚至可以考虑片段着色器正在做什么之前,有一个设置实际绘制片段的对象的任务。据我所知,这需要设置顶点来表示画布并将它们设置到画布的角落,以使WebGL将其渲染为平面2D图像,但这似乎不直观。我要么将其渲染为图像以用作背景纹理,要么初始化第二个画布并将第一个画布的背景设置为透明(这是我在下面的代码中尝试做的)。如果有任何方法可以让片段着色器运行并简单地渲染其输出,每个片段与画布/图像的像素对应1:1,那将是崇高的,但我对GLSL没有任何假设。
我尝试做的是将当前波高矩阵打包为纹理并将其作为均匀采样器2D发送。在当前状态下,我的代码运行,但是WebGL告诉我,活动纹理1(我的波高矩阵包装为纹理)是不完整的,并且它的缩小/放大过滤未设置为NEAREST,即使我试图明确地将它设置为NEAREST。我不知道如何进一步调试它,因为WebGL引用我对gl.drawElements的调用作为错误的来源。
这就像我能描述的那样聪明。这就是我所拥有的:
ws.glProgram = function(gl, tex) {
var flExt = gl.getExtension("OES_texture_float");
ws.program = gl.createProgram();
var vertShader = gl.createShader(gl.VERTEX_SHADER);
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
var vertSrc = [
"attribute vec4 position;",
"void main(void) {",
"gl_Position = position;",
"}"
]
var fragSrc = [
"precision highp float;",
"uniform sampler2D canvasTex;",
"uniform sampler2D dataTex;",
"uniform vec2 mapSize;",
"uniform float dispFactor;",
"uniform float lumFactor;",
"void main(void) {",
"vec2 mapCoord = vec2(gl_FragCoord.x+1.5, gl_FragCoord.y+1.5);",
"float wave = texture2D(dataTex, mapCoord).r;",
"float displace = wave*dispFactor;",
"if (displace < 0.0) {",
"displace = displace+1.0;",
"}",
"vec2 srcCoord = vec2(gl_FragCoord.x+displace,gl_FragCoord.y+displace);",
"if (srcCoord.x < 0.0) {",
"srcCoord.x = 0.0;",
"}",
"else if (srcCoord.x > mapSize.x-2.0) {",
"srcCoord.x = mapSize.x-2.0;",
"}",
"if (srcCoord.y < 0.0) {",
"srcCoord.y = 0.0;",
"}",
"else if (srcCoord.y > mapSize.y-2.0) {",
"srcCoord.y = mapSize.y-2.0;",
"}",
"float lum = wave*lumFactor;",
"if (lum > 40.0) { lum = 40.0; }",
"else if (lum < -40.0) { lum = -40.0; }",
"gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);", // Fragment Shader is not producing output
/*"gl_FragColor = texture2D(canvasTex, srcCoord);",
"gl_FragColor.r = gl_FragColor.r + lum;",
"gl_FragColor.g = gl_FragColor.g + lum;",
"gl_FragColor.b = gl_FragColor.b + lum;",*/
"}"];
vertSrc = vertSrc.join('\n');
fragSrc = fragSrc.join('\n');
gl.shaderSource(vertShader, vertSrc);
gl.compileShader(vertShader);
gl.attachShader(ws.program, vertShader);
gl.shaderSource(fragShader, fragSrc);
gl.compileShader(fragShader);
gl.attachShader(ws.program, fragShader);
console.log(gl.getShaderInfoLog(vertShader));
gl.linkProgram(ws.program);
gl.useProgram(ws.program);
// Vertex Data for rendering surface
var vertices = [ 0,0,0, 1,0,0,
0,1,0, 1,1,0 ];
var indices = [ 0,1,2, 0,2,3 ];
ws.program.vertices = new Float32Array(vertices);
ws.program.indices = new Float32Array(indices);
gl.enableVertexAttribArray(0);
var vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferData(gl.ARRAY_BUFFER, ws.program.vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
var iBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, ws.program.indices, gl.STATIC_DRAW);
// Send texture data from tex to WebGL
var canvasTex = gl.createTexture();
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, canvasTex);
// Non-Power-of-Two Texture Dimensions
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tex.imageData);
gl.uniform1i(gl.getUniformLocation(ws.program, "canvasTex"), 2);
// Send empty wave map to WebGL
ws.activeWaveMap = new Float32Array((ws.width+2)*(ws.height+2));
ws.dataPointerGL = gl.createTexture();
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, ws.dataPointerGL);
// Non-Power-of-Two Texture Dimensions
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, ws.width+2,ws.height+2,0, gl.LUMINANCE, gl.FLOAT, ws.activeWaveMap);
gl.uniform1i(gl.getUniformLocation(ws.program, "dataTex"), 1);
// Numeric Uniforms
gl.uniform2f(gl.getUniformLocation(ws.program, "mapSize"), ws.width+2,ws.height+2);
gl.uniform1f(gl.getUniformLocation(ws.program, "dispFactor"), ws.dispFactor);
gl.uniform1f(gl.getUniformLocation(ws.program, "lumFactor"), ws.lumFactor);
return ws.program;
}
ws.render = function(gl, moves, canvas) {
//canvas.clear();
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // specify gl.clearColor?
for (g=0, fl=0; g < ws.tempWaveMap.length; g++) {
for (b=0; b < ws.tempWaveMap[g].length; b++) {
ws.tempWaveMap[g][b] = ws.activeWaveMap[fl];
fl += 1;
}
}
for (j=0; j < moves.length; j++) {
ws.setWave(moves[j],ws.tempWaveMap);
}
for (x=1; x <= ws.width; x++) {
for (y=1; y <= ws.height; y++) {
ws.resolveWaves(ws.inactiveWaveMap, ws.tempWaveMap, x,y);
}
}
for (g=0, fl=0; g < ws.inactiveWaveMap.length; g++) {
for (b=0; b < ws.inactiveWaveMap[g].length; b++) {
ws.outgoingWaveMap[fl] = ws.inactiveWaveMap[g][b];
ws.inactiveWaveMap[g][b] = ws.tempWaveMap[g][b];
fl += 1;
}
}
ws.activeWaveMap.set(ws.outgoingWaveMap);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, ws.width+2,ws.height+2,0, gl.LUMINANCE, gl.FLOAT, ws.activeWaveMap);
gl.drawElements(gl.TRIANGLES, ws.program.indices.length, gl.UNSIGNED_BYTE, 0);
}
更新:我设法使用角顶点设置我的2D绘图表面。 (教程here对于让我的基础使用VAO非常有帮助。)现在我正在努力找出上传,存储和操作数据的最佳方法。
已解决:由于gman,我的代码正常运行。 wave行为本身仍然需要调试,但GL管道方面的所有内容都在运行。除了奇怪的波浪行为之外,游戏每隔几秒就会停滞一会儿,然后以正常速度恢复。性能测试表明非增量垃圾收集是原因,当水效应被禁用时不会发生这种情况,因此它在我的代码中肯定存在,可能是数组newIndices
正在新鲜每帧初始化,但我不确定。除非它与GL的行为有关,否则超出了这个问题的范围。
这是相关代码。除了这里之外你真正需要知道的是GL上下文,用于绘制2D表面的顶点着色器和VAO是从另一个对象传入的,并且该对象每帧都运行render
函数
function waterStage(gl, vao, vShader) {
var ws = new Object();
ws.width = game.world.width; ws.height = game.world.height;
// Initialize Background Texture
ws.img = game.make.bitmapData(ws.width, ws.height);
ws.img.fill(0,10,40);
ws.img.ctx.strokeStyle = "#5050FF";
ws.img.ctx.lineWidth = 2;
ws.img.ctx.moveTo(0,0);
for (y=0; y < ws.height; y+=10) {
ws.img.ctx.beginPath();
ws.img.ctx.moveTo(0,y);
ws.img.ctx.lineTo(ws.width,y);
ws.img.ctx.closePath();
ws.img.ctx.stroke();
}
ws.img.update();
gl.flExt = gl.getExtension("OES_texture_float");
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
// Source Image
ws.srcTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, ws.srcTexture);
// Enable all texture sizes
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ws.img.imageData);
delete ws.img;
// Map Textures
ws.clearProgram = gl.createProgram();
gl.attachShader(ws.clearProgram, vShader);
var clearSrc = [
"precision highp float;",
"void main(void) {",
"gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);",
"}"
];
clearSrc = clearSrc.join("\n");
var clearShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(clearShader, clearSrc);
gl.compileShader(clearShader);
gl.attachShader(ws.clearProgram, clearShader);
gl.linkProgram(ws.clearProgram);
ws.mapTextures = [];
ws.frameBuffers = [];
for (t=0; t < 2; t++) {
var map = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, map);
// Enable all texture sizes
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);
// Initialize empty texture of the same size as the canvas
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, ws.width, ws.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
ws.mapTextures.push(map);
var fbo = gl.createFramebuffer()
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, map, 0);
ws.frameBuffers.push(fbo);
gl.useProgram(ws.clearProgram);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); // Set output to new map
gl.vao_ext.bindVertexArrayOES(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// Blank texture to be copied to in render()
ws.copyTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, ws.copyTexture);
// Enable all texture sizes
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, ws.width, ws.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// Blank texture for entering new wave values through GL
ws.nwTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, ws.nwTexture);
// Enable all texture sizes
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, ws.width, ws.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
ws.newWaves = new Array(ws.width*ws.height);
ws.nwProgram = gl.createProgram();
ws.mapProgram = gl.createProgram();
ws.displaceProgram = gl.createProgram();
gl.attachShader(ws.nwProgram, vShader);
gl.attachShader(ws.mapProgram, vShader);
gl.attachShader(ws.displaceProgram, vShader);
var nwSrc = [
"precision highp float;",
"uniform sampler2D newWaves;",
"uniform sampler2D previous;",
"uniform vec2 size;",
"void main(void) {",
"vec2 texCoord = vec2((gl_FragCoord.x/size.x),(gl_FragCoord.y/size.y));",
"float nw = texture2D(newWaves, texCoord).r;",
"if (nw == 0.0) {",
"gl_FragColor = texture2D(previous, texCoord);",
"}",
"else {",
"float current = texture2D(previous, texCoord).r;",
"nw = float(current+nw);",
"gl_FragColor = vec4(nw, nw, nw, 1.0);",
"}",
"}"
]
nwSrc = nwSrc.join("\n");
var nwShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(nwShader, nwSrc);
gl.compileShader(nwShader);
console.log(gl.getShaderInfoLog(nwShader));
gl.attachShader(ws.nwProgram, nwShader);
gl.linkProgram(ws.nwProgram);
var mapSrc = [
"precision highp float;",
"uniform sampler2D previous;",
"uniform sampler2D current;",
"uniform vec2 size;",
"uniform float damper;",
"void main(void) {",
"vec4 surrounding;",
"vec2 texCoord = vec2((gl_FragCoord.x/size.x),(gl_FragCoord.y/size.y));",
"float active = texture2D(current, texCoord).r-0.5;",
"vec2 shifted = vec2(((gl_FragCoord.x-1.0)/size.x),texCoord.y);", // x-1
"if (gl_FragCoord.x == 0.0) {",
"surrounding.x = 0.0;",
"}",
"else {",
"surrounding.x = texture2D(previous, shifted).r-0.5;",
"}",
"shifted = vec2(((gl_FragCoord.x+1.0)/size.x),texCoord.y);", // x+1
"if (gl_FragCoord.x == size.x-1.0) {",
"surrounding.z = 0.0;",
"}",
"else {",
"surrounding.z = texture2D(previous, shifted).r-0.5;",
"}",
"shifted = vec2(texCoord.x,((gl_FragCoord.y-1.0)/size.y));", // y-1
"if (gl_FragCoord.y == 0.0) {",
"surrounding.y = 0.0;",
"}",
"else {",
"surrounding.y = texture2D(previous, shifted).r-0.5;",
"}",
"shifted = vec2(texCoord.x,((gl_FragCoord.y+1.0)/size.y));", // y+1
"if (gl_FragCoord.y == size.y-1.0) {",
"surrounding.w = 0.0;",
"}",
"else {",
"surrounding.w = texture2D(previous, shifted).r-0.5;",
"}",
"active = ((surrounding.x+surrounding.y+surrounding.z+surrounding.w)/2.0)-active;",
"active = active-(active/damper);",
"gl_FragColor = vec4(active+0.5, active+0.5, active+0.5, 1.0);",
// "gl_FragColor = texture2D(current, vec2(gl_FragCoord.x/size.x),(gl_FragCoord.y/size.y));",
"}"
];
mapSrc = mapSrc.join("\n");
var mapShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(mapShader, mapSrc);
gl.compileShader(mapShader);
console.log(gl.getShaderInfoLog(mapShader));
gl.attachShader(ws.mapProgram, mapShader);
gl.linkProgram(ws.mapProgram);
var displaceSrc = [
"precision highp float;",
"uniform sampler2D current;",
"uniform sampler2D srcImg;",
"uniform vec2 size;",
"uniform float dspFactor;",
"uniform float lumFactor;",
"void main(void) {",
"vec2 texCoord = vec2((gl_FragCoord.x/size.x),(gl_FragCoord.y/size.y));",
"float wave = texture2D(current, texCoord).r-0.5;",
"float displacement = wave * dspFactor * 1.5;",
"if (displacement == 0.0) {",
"gl_FragColor = texture2D(srcImg, texCoord);",
"}",
"else {",
"if (displacement < 0.0) {",
"displacement = displacement + 1.0;",
"}",
"float lum = wave * lumFactor;",
"if (lum > 0.16) { lum = 0.16; }",
"else if (lum < -0.16) { lum = -0.16; }",
"float dspX = (gl_FragCoord.x+displacement);",
"float dspY = (gl_FragCoord.y+displacement);",
"if (dspX < 0.0) { dspX = 0.0; }",
"else if (dspX >= size.x) { dspX = size.x-1.0; }",
"if (dspY < 0.0) { dspY = 0.0; }",
"else if (dspY >= size.y) { dspY = size.y-1.0; }",
"vec2 srcCoord = vec2((dspX/size.x),(dspY/size.y));",
// Just for testing
//"gl_FragColor = texture2D(current, vec2((gl_FragCoord.x/size.x),(gl_FragCoord.y/size.y)));",
"vec4 newColor = texture2D(srcImg, srcCoord);", // srcCoord
"gl_FragColor.r = newColor.r+lum;",
"gl_FragColor.g = newColor.g+lum;",
"gl_FragColor.b = newColor.b+lum;",
"}",
"}"
];
displaceSrc = displaceSrc.join("\n");
var displaceShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(displaceShader, displaceSrc);
gl.compileShader(displaceShader);
console.log(gl.getShaderInfoLog(displaceShader));
gl.attachShader(ws.displaceProgram, displaceShader);
gl.linkProgram(ws.displaceProgram);
ws.render = function(gl, vao, moves) {
// Calculate wave values as texture data, then render to screen with displacement fragment shader
if (moves.length > 0) {
for (x=0, len=ws.width*ws.height; x < len; x++) {
ws.newWaves[x] = 0;
}
var newIndices = [];
for (m=0; m < moves.length; m++) {
newIndices.push(moves[m].y*ws.width + moves[m].x);
}
for (i=0; i < newIndices.length; i++) {
ws.newWaves[newIndices[i]] = moves[i].magnitude/1;
}
gl.useProgram(ws.nwProgram);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, ws.nwTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, ws.width, ws.height, 0, gl.LUMINANCE, gl.FLOAT, new Float32Array(ws.newWaves));
gl.uniform1i(gl.getUniformLocation(ws.nwProgram, "newWaves"), 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, ws.copyTexture);
gl.uniform1i(gl.getUniformLocation(ws.nwProgram, "previous"), 1);
gl.bindFramebuffer(gl.FRAMEBUFFER, ws.frameBuffers[0]); // Set output to previous map texture [0]
gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, ws.width, ws.height, 0); // Copy mapTextures[0] into copyTexture
gl.uniform2f(gl.getUniformLocation(ws.nwProgram, "size"), ws.width, ws.height);
gl.vao_ext.bindVertexArrayOES(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
// Map Texture Manipulation
gl.useProgram(ws.mapProgram);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, ws.mapTextures[0]);
gl.uniform1i(gl.getUniformLocation(ws.mapProgram, "previous"), 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, ws.copyTexture);
gl.uniform1i(gl.getUniformLocation(ws.mapProgram, "current"), 1);
gl.uniform2f(gl.getUniformLocation(ws.mapProgram, "size"), ws.width, ws.height);
gl.uniform1f(gl.getUniformLocation(ws.mapProgram, "damper"), 1000);
gl.bindFramebuffer(gl.FRAMEBUFFER, ws.frameBuffers[1]); // Set output to current map texture [1]
gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, ws.width, ws.height, 0); // Copy mapTextures[1] into copyTexture
gl.vao_ext.bindVertexArrayOES(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Output Texture Manipulation
gl.useProgram(ws.displaceProgram);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, ws.mapTextures[1]);
gl.uniform1i(gl.getUniformLocation(ws.displaceProgram, "current"), 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, ws.srcTexture);
gl.uniform1i(gl.getUniformLocation(ws.displaceProgram, "srcImg"), 1);
gl.uniform2f(gl.getUniformLocation(ws.displaceProgram, "size"), ws.width, ws.height);
gl.uniform1f(gl.getUniformLocation(ws.displaceProgram, "dspFactor"), 20);
gl.uniform1f(gl.getUniformLocation(ws.displaceProgram, "lumFactor"), 0.5);
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Output to canvas
gl.vao_ext.bindVertexArrayOES(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
ws.mapTextures.sort(function(a,b) { return 1; });
ws.frameBuffers.sort(function(a,b) { return 1; });
}
答案 0 :(得分:1)
您无法从着色器中读取WebGL中画布的内容,因此您需要创建纹理并将该纹理附加到帧缓冲区(帧缓冲区只是附件的集合)。这样,您可以渲染到纹理并在其他渲染中使用结果
接下来,textures, at least in WebGL 1, are always referenced by texture coordinates which go from 0 to 1所以这段代码没有多大意义
vec2 mapCoord = vec2(gl_FragCoord.x+1.5, gl_FragCoord.y+1.5);
float wave = texture2D(dataTex, mapCoord).r;
gl_FragCoord
不在纹理坐标中,它在绝对目标像素中意味着如果您在屏幕中间的矩形gl_FragCoord
将具有dest像素坐标(不是从0开始)。至于+1.5,纹理坐标中任何方向的1.5都是1.5 *纹理的宽度或高度。
如果您想要向左或向右看一个像素,您需要知道纹理的大小。
纹理坐标中的一个像素单位为1 / width
,横向为1 / height
。换句话说,如果你有一些引用像素的纹理坐标
vec2 texcoord = vec2(0.5, 0.5); // center of texture
你想要一个像素到右边
vec2 onePixelRight = texcoord + vec2(1.0 / textureWidth, 0);
{4G}中没有传递textureWidth
的宽度,因此您必须制作制服并将其传递给自己。
您可以看到an example of rendering to a texture through a framebuffer and reading from nearby pixels here
查看您发布的链接,您需要4个纹理
你需要3个帧缓冲区。每一波一个
每个框架
将newWave交换为current,current to prev,prev to next
注意:(这只是意味着更改代码中的变量,不需要移动数据)
这是基于原始代码的一些代码。由于我无法运行原版,我不知道它应该是什么样子。
function main() {
var width = 256;
var height = 256;
var gl = document.querySelector("canvas").getContext("webgl");
var flExt = gl.getExtension("OES_texture_float");
// you should always check for this unless you want confused users
if (!flExt) {
alert("no floating point textures available on your system");
return;
}
// the algoritm from the article requires all textures and the canvas
// to be the same size
gl.canvas.width = width;
gl.canvas.height = height;
// template literals or script tags are way easier than arrays of strings
var vertSrc = `
attribute vec4 position;
void main(void) {
gl_Position = position;
}
`;
var waveFragSrc = `
precision highp float;
uniform sampler2D currentSourceMap;
uniform sampler2D previousResultMap;
uniform float damp;
uniform vec2 textureSize;
void main(void) {
vec2 onePixel = 1. / textureSize;
// this only works because we're drawing a quad the size of the texture
// normally I'd pass in texture coords
vec2 xy = gl_FragCoord.xy / textureSize;
vec4 n =
(texture2D(currentSourceMap, xy + onePixel * vec2(-1, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2(-2, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+1, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+2, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,-1)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,-2)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,+1)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,+2)) +
texture2D(currentSourceMap, xy + onePixel * vec2(-1,-1)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+1,-1)) +
texture2D(currentSourceMap, xy + onePixel * vec2(-1,+1)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+1,+1))
) / 6.0 - texture2D(previousResultMap, xy);
gl_FragColor = n - n / damp;
}
`;
// need another shader to draw the result texture to the screen
var displaceFragSrc = `
precision highp float;
uniform vec2 resolution;
uniform sampler2D waveMap;
uniform sampler2D backgroundImage;
uniform float rIndex;
// this code assumes the wavemap and the image and the destination
// are all the same resolution
void main() {
vec2 onePixel = 1. / resolution;
// this only works because we're drawing a quad the size of the texture
// normally I'd pass in texture coords
vec2 xy = gl_FragCoord.xy / resolution;
float xDiff = floor(texture2D(waveMap, xy + onePixel * vec2(1, 0)) -
texture2D(waveMap, xy)).r;
float yDiff = floor(texture2D(waveMap, xy + onePixel * vec2(0, 1)) -
texture2D(waveMap, xy)).r;
float xAngle = atan( xDiff );
float xRefraction = asin( sin( xAngle ) / rIndex );
float xDisplace = floor( tan( xRefraction ) * xDiff );
float yAngle = atan( yDiff );
float yRefraction = asin( sin( yAngle ) / rIndex );
float yDisplace = floor( tan( yRefraction ) * yDiff );
if (xDiff < 0.) {
// { Current position is higher - Clockwise rotation }
if (yDiff < 0.) {
gl_FragColor = texture2D(
backgroundImage, xy + onePixel * vec2(-xDisplace, -yDisplace));
} else {
gl_FragColor = texture2D(
backgroundImage, xy + onePixel * vec2(-xDisplace, +yDisplace));
}
} else {
// { Current position is lower - Counterclockwise rotation }
if (yDiff < 0.) {
gl_FragColor = texture2D(backgroundImage, vec2(+xDisplace, -yDisplace));
} else {
gl_FragColor = texture2D(backgroundImage, vec2(+xDisplace, +yDisplace));
}
}
}
`;
// use some boilerplate. I'm too lazy to type all the code for looking
// up uniforms and setting them when a tiny piece of code can hide all
// that for me. Look up the library if it's not clear that `setUniforms`
// does lots of `gl.uniformXXX` etc...
// also Use **MUST** look up the attribute locations or assign them with
// gl.bindAttribLocation **BEFORE** linking otherwise your code
// is not portable and may not match across programs. This boilerplate
// calls gl.bindAttributeLocation for the names passed in the 3rd argument
var waveProgramInfo = twgl.createProgramInfo(gl, [vertSrc, waveFragSrc], ["position"]);
var displaceProgramInfo = twgl.createProgramInfo(gl, [vertSrc, displaceFragSrc], ["position"]);
var positionLocation = 0; // see above
// Vertex Data for rendering surface
// no reason for 3d points when drawing 2d
// Not using indices. It's several more lines of un-needed code
var vertices = new Float32Array([
-1,-1, 1,-1, -1,1,
1,-1, -1, 1, 1,1,
]);
var vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Send texture data from tex to WebGL
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
// since we don't have an image lets make one with a canvas 2d.
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = width;
ctx.canvas.height = height;
for (var y = 0; y < width; y += 16) {
for (var x = 0; x < height; x += 16) {
ctx.fillStyle = "rgb(" + x / width * 256 +
"," + y / height * 256 +
"," + (x / 16 + y / 16) % 2 * 255 +
")";
ctx.fillRect(x, y, 16, 16);
}
}
// Non-Power-of-Two Texture Dimensions
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas);
// make some data for the wave
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.strokeStyle = "rgb(1,1,1)";
ctx.lineWidth = 30;
ctx.beginPath();
ctx.arc(ctx.canvas.width / 2, ctx.canvas.height / 2,
ctx.canvas.width / 3, 0, Math.PI * 2, false);
ctx.stroke();
// You can NOT use any kind of filtering on FLOAT textures unless
// you check for and enable OES_texture_float_linear. Note that
// no mobile devices support it as of 2017/1
// create 3 wave textures and 3 framebuffers, prevs, current, and next
var waves = [];
for (var i = 0; i < 3; ++i) {
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// Non-Power-of-Two Texture Dimensions
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, ctx.canvas);
var fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D,
tex, 0);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
alert("can not render to floating point textures");
return;
}
waves.push({ texture: tex, framebuffer: fb });
}
function render() {
var previousWave = waves[0];
var currentWave = waves[1];
var nextWave = waves[2];
// while you're only drawing 1 thing at the moment if you want to draw
// more than one you'll need to set attributes before each draw if
// data changes
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw to next wave
gl.bindFramebuffer(gl.FRAMEBUFFER, nextWave.framebuffer);
gl.viewport(0, 0, width, height);
gl.useProgram(waveProgramInfo.program);
// pass current and previous textures to shader
twgl.setUniforms(waveProgramInfo, {
currentSourceMap: currentWave.texture,
previousResultMap: previousWave.texture,
textureSize: [width, height],
damp: 4,
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
// draw to canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(displaceProgramInfo.program);
// pass in the next wave to the displacement shader
twgl.setUniforms(displaceProgramInfo, {
waveMap: nextWave.texture,
backgroundImage: imageTex,
resolution: [gl.canvas.width, gl.canvas.height],
rIndex: 4,
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
// swap the buffers.
var temp = waves[0];
waves[0] = waves[1];
waves[1] = waves[2];
waves[2] = temp;
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
<script src="https://twgljs.org/dist/2.x/twgl.min.js"></script>
<canvas></canvas>
效果非常古老,因为它是基于像素和大量整数,而着色器使用浮点。我得到的印象是确切的算法并不是着色器的真正匹配。或者更确切地说,使用不同的算法可以获得更好的结果。
function main() {
var width = 256;
var height = 256;
var gl = document.querySelector("canvas").getContext("webgl");
var flExt = gl.getExtension("OES_texture_float");
// you should always check for this unless you want confused users
if (!flExt) {
alert("no floating point textures available on your system");
return;
}
// the algoritm from the article requires all textures and the canvas
// to be the same size
gl.canvas.width = width;
gl.canvas.height = height;
// template literals or script tags are way easier than arrays of strings
var vertSrc = `
attribute vec4 position;
void main(void) {
gl_Position = position;
}
`;
var waveFragSrc = `
precision highp float;
uniform sampler2D currentSourceMap;
uniform sampler2D previousResultMap;
uniform float damp;
uniform vec2 textureSize;
void main(void) {
vec2 onePixel = 1. / textureSize;
// this only works because we're drawing a quad the size of the texture
// normally I'd pass in texture coords
vec2 xy = gl_FragCoord.xy / textureSize;
vec4 n =
(texture2D(currentSourceMap, xy + onePixel * vec2(-1, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2(-2, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+1, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+2, 0)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,-1)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,-2)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,+1)) +
texture2D(currentSourceMap, xy + onePixel * vec2( 0,+2)) +
texture2D(currentSourceMap, xy + onePixel * vec2(-1,-1)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+1,-1)) +
texture2D(currentSourceMap, xy + onePixel * vec2(-1,+1)) +
texture2D(currentSourceMap, xy + onePixel * vec2(+1,+1))
) / 6.0 - texture2D(previousResultMap, xy);
gl_FragColor = n - n / damp;
}
`;
// need another shader to draw the result texture to the screen
var displaceFragSrc = `
precision highp float;
uniform vec2 resolution;
uniform sampler2D waveMap;
uniform sampler2D backgroundImage;
uniform float fudge;
// this code assumes the wavemap and the image and the destination
// are all the same resolution
void main() {
vec2 onePixel = 1. / resolution;
// this only works because we're drawing a quad the size of the texture
// normally I'd pass in texture coords
vec2 xy = gl_FragCoord.xy / resolution;
float xDiff = (texture2D(waveMap, xy + onePixel * vec2(1, 0)) -
texture2D(waveMap, xy)).r;
float yDiff = (texture2D(waveMap, xy + onePixel * vec2(0, 1)) -
texture2D(waveMap, xy)).r;
gl_FragColor = texture2D(
backgroundImage, xy + onePixel * vec2(xDiff, yDiff) * fudge);
}
`;
var pntVertSrc = `
uniform vec2 position;
uniform float pointSize;
void main(void) {
gl_Position = vec4(position, 0, 1);
gl_PointSize = pointSize;
}
`;
var pntFragSrc = `
precision mediump float;
void main(void) {
gl_FragColor = vec4(1);
}
`;
// use some boilerplate. I'm too lazy to type all the code for looking
// up uniforms and setting them when a tiny piece of code can hide all
// that for me. Look up the library if it's not clear that `setUniforms`
// does lots of `gl.uniformXXX` etc...
// also Use **MUST** look up the attribute locations or assign them with
// gl.bindAttribLocation **BEFORE** linking otherwise your code
// is not portable and may not match across programs. This boilerplate
// calls gl.bindAttributeLocation for the names passed in the 3rd argument
var waveProgramInfo = twgl.createProgramInfo(
gl, [vertSrc, waveFragSrc], ["position"]);
var displaceProgramInfo = twgl.createProgramInfo(
gl, [vertSrc, displaceFragSrc], ["position"]);
var pntProgramInfo = twgl.createProgramInfo(
gl, [pntVertSrc, pntFragSrc], ["position"]);
var positionLocation = 0; // see above
// Vertex Data for rendering surface
// no reason for 3d points when drawing 2d
// Not using indices. It's several more lines of un-needed code
var vertices = new Float32Array([
-1,-1, 1,-1, -1,1,
1,-1, -1, 1, 1,1,
]);
var vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Send texture data from tex to WebGL
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
// since we don't have an image lets make one with a canvas 2d.
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = width;
ctx.canvas.height = height;
for (var y = 0; y < width; y += 16) {
for (var x = 0; x < height; x += 16) {
ctx.fillStyle = "rgb(" + x / width * 256 +
"," + y / height * 256 +
"," + (x / 16 + y / 16) % 2 * 255 +
")";
ctx.fillRect(x, y, 16, 16);
}
}
// Non-Power-of-Two Texture Dimensions
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas);
// make some data for the wave
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.strokeStyle = "rgb(255,255,255)";
ctx.lineWidth = 30;
ctx.beginPath();
ctx.arc(ctx.canvas.width / 2, ctx.canvas.height / 2,
ctx.canvas.width / 3, 0, Math.PI * 2, false);
ctx.stroke();
// You can NOT use any kind of filtering on FLOAT textures unless
// you check for and enable OES_texture_float_linear. Note that
// no mobile devices support it as of 2017/1
// create 3 wave textures and 3 framebuffers, prevs, current, and next
var waves = [];
for (var i = 0; i < 3; ++i) {
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// Non-Power-of-Two Texture Dimensions
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, ctx.canvas);
var fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D,
tex, 0);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
alert("can not render to floating point textures");
return;
}
waves.push({ texture: tex, framebuffer: fb });
}
function render(time) {
time *= 0.001; // convert to seconds
var previousWave = waves[0];
var currentWave = waves[1];
var nextWave = waves[2];
// while you're only drawing 1 thing at the moment if you want to draw
// more than one you'll need to set attributes before each draw if
// data changes
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw to next wave
gl.bindFramebuffer(gl.FRAMEBUFFER, nextWave.framebuffer);
gl.viewport(0, 0, width, height);
gl.useProgram(waveProgramInfo.program);
// pass current and previous textures to shader
twgl.setUniforms(waveProgramInfo, {
currentSourceMap: currentWave.texture,
previousResultMap: previousWave.texture,
textureSize: [width, height],
damp: 40,
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
// draw dot to next wave add waves
gl.useProgram(pntProgramInfo.program);
twgl.setUniforms(pntProgramInfo, {
position: [ Math.sin(time * 0.71), Math.cos(time) ],
pointSize: 8,
});
gl.drawArrays(gl.POINTS, 0, 1);
// draw to canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(displaceProgramInfo.program);
// pass in the next wave to the displacement shader
twgl.setUniforms(displaceProgramInfo, {
waveMap: nextWave.texture,
backgroundImage: imageTex,
resolution: [gl.canvas.width, gl.canvas.height],
fudge: 100,
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
// swap the buffers.
var temp = waves[0];
waves[0] = waves[1];
waves[1] = waves[2];
waves[2] = temp;
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
<script src="https://twgljs.org/dist/2.x/twgl.min.js"></script>
<canvas></canvas>