是否有更简单(且仍然高性能)的方法来通过最近邻重采样来升级画布渲染?

2023-12-01

我对这个看似简单的以最近邻格式升级画布渲染的任务感到有点困惑,我在这里问:

如何在 JS 中正确编写这个着色器函数?

目标是像这样转换 3D 渲染输出:

enter image description here

像这样的像素艺术:

enter image description here

但在这个问题中我问的是如何实施我选择的解决方案(本质上使用着色器来处理放大)正确。也许我应该问:有没有更简单(并且仍然高性能)的方法来做到这一点?


我可以提供两种方法,它们都可以使用最近邻有效地放大或缩小图像。

要手动执行此操作,您应该迭代新缩放图像的每个像素,并使用旧尺寸与新尺寸的比率来计算应使用原始图像中的哪些像素。

(我的代码片段使用 .toDataURL() 因此它们可能无法在 Chrome 中工作。)

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<style>
			#input {
				display: none;
			}
		
			body {
				background-color: black;
			}
			
			body > * {
				display: block;
				margin-top: 10px;
				margin-left: auto;
				margin-right: auto;
			}
			
			img {
				background-color: gray;
				border: solid 1px white;
				border-radius: 10px;
				image-rendering: optimizeSpeed;
			}
			
			label {
				transition: 0.1s;
				cursor: pointer;
				text-align: center;
				font-size: 15px;
				-webkit-touch-callout: none;
				-webkit-user-select: none;
				-khtml-user-select: none;
				-moz-user-select: none;
				-ms-user-select: none;
				user-select: none;
				
				width: 130px;
				height: 40px;
				line-height: 40px;
				border-radius: 10px;
				
				color: white;
				background-color: #005500;
				box-shadow: 0px 4px #555555;
			}
			
			label:hover {
				background-color: #007700;
			}
			
			label:active {
				box-shadow: 0px 1px #555555;
				transform: translateY(3px);
			}
			
			script {
				display: none;
			}
		</style>
	</head>
	
	<body>
		<img id="unscaledImage"></img>
		<img id="scaledImage"></img>
		<input id="scale" type="range" min="1" max="100" value="50"></input>
		<label for="input">Upload Image</label>
		<input id="input" type="file"></input>
		
		<script type="application/javascript">
		
			void function() {
			
				"use strict";
				
				var unscaledImage = null;
				var scaledImage = null;
				var scale = null;
				var input = null;
				var canvas = null;
				var ctx = null;
				var hasImage = false;
				
				function scaleImage(img,scale) {
					var newWidth = (img.width * scale) | 0;
					var newHeight = (img.height * scale) | 0;
				
					canvas.width = img.width;
					canvas.height = img.height;
					ctx.drawImage(img,0,0);
					
					var unscaledData = ctx.getImageData(0,0,img.width,img.height);
					var scaledData = ctx.createImageData(newWidth,newHeight);
					var unscaledBitmap = unscaledData.data;
					var scaledBitmap = scaledData.data;
					
					var xScale = img.width / newWidth;
					var yScale = img.height / newHeight;
					
					for (var x = 0; x < newWidth; ++x) {
						for (var y = 0; y < newHeight; ++y) {
							var _x = (x * xScale) | 0;
							var _y = (y * yScale) | 0;
							var scaledIndex = (x + y * newWidth) * 4;
							var unscaledIndex = (_x + _y * img.width) * 4;
							
							scaledBitmap[scaledIndex] = unscaledBitmap[unscaledIndex];
							scaledBitmap[scaledIndex + 1] = unscaledBitmap[unscaledIndex + 1];
							scaledBitmap[scaledIndex + 2] = unscaledBitmap[unscaledIndex + 2];
							scaledBitmap[scaledIndex + 3] = 255;
						}
					}
					
					ctx.clearRect(0,0,canvas.width,canvas.height);
					canvas.width = newWidth;
					canvas.height = newHeight;
					ctx.putImageData(scaledData,0,0);
					
					return canvas.toDataURL();
				}
				
				function onImageLoad() {
					URL.revokeObjectURL(this.src);
					scaledImage.src = scaleImage(this,scale.value * 0.01);
					scaledImage.style.width = this.width + "px";
					scaledImage.style.height = this.height + "px";
					hasImage = true;
				}
				
				function onImageError() {
					URL.revokeObjectURL(this.src);
				}
				
				function onScaleChanged() {
					if (hasImage) {
						scaledImage.src = scaleImage(unscaledImage,this.value * 0.01);
					}
				}
				
				function onImageSelected() {
					if (this.files[0]) {
						unscaledImage.src = URL.createObjectURL(this.files[0]);
					}
				}
				
				onload = function() {
					unscaledImage = document.getElementById("unscaledImage");
					scaledImage = document.getElementById("scaledImage");
					scale = document.getElementById("scale");
					input = document.getElementById("input");
					canvas = document.createElement("canvas");
					ctx = canvas.getContext("2d");
					
					ctx.imageSmoothingEnabled = false;
					unscaledImage.onload = onImageLoad;
					unscaledImage.onerror = onImageError;
					scale.onmouseup = onScaleChanged;
					input.oninput = onImageSelected;
				}
			
			}();
		
		</script>
	</body>
</html>

或者,使用着色器的一种更快的方法是将图像添加到设置为使用最近邻过滤的纹理并将其绘制到四边形上。四边形的大小可以在绘制之前通过 gl.viewport 控制。

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<style>
			#file {
				display: none;
			}
		
			body {
				background-color: black;
			}
			
			body > * {
				display: block;
				margin-top: 10px;
				margin-left: auto;
				margin-right: auto;
			}
			
			img {
				background-color: gray;
				border: solid 1px white;
				border-radius: 10px;
				image-rendering: optimizeSpeed;
			}
			
			label {
				transition: 0.1s;
				cursor: pointer;
				text-align: center;
				font-size: 15px;
				-webkit-touch-callout: none;
				-webkit-user-select: none;
				-khtml-user-select: none;
				-moz-user-select: none;
				-ms-user-select: none;
				user-select: none;
				
				width: 130px;
				height: 40px;
				line-height: 40px;
				border-radius: 10px;
				
				color: white;
				background-color: #005500;
				box-shadow: 0px 4px #555555;
			}
			
			label:hover {
				background-color: #007700;
			}
			
			label:active {
				box-shadow: 0px 1px #555555;
				transform: translateY(3px);
			}
			
			script {
				display: none;
			}
		</style>
	</head>
	
	<body>
		<img id="unscaledImage"></img>
		<img id="scaledImage"></img>
		<input id="scale" type="range" min="1" max="100" value="50"></input>
		<input id="file" type="file"></input>
		<label for="file">Upload Image</label>
		<script type="application/javascript">
		
			void function() {
				
				"use strict";
				
				// DOM
				var unscaledImage = document.getElementById("unscaledImage");
				var scaledImage = document.getElementById("scaledImage");
				var scale = document.getElementById("scale");
				var file = document.getElementById("file");
				var imageUploaded = false;
				
				function onScaleChanged() {
					if (imageUploaded) {
						scaledImage.src = scaleOnGPU(this.value * 0.01);
					}
				}
				
				function onImageLoad() {
					URL.revokeObjectURL(this.src);
					uploadImageToGPU(this);
					
					scaledImage.src = scaleOnGPU(scale.value * 0.01);
					scaledImage.style.width = this.width + "px";
					scaledImage.style.height = this.height + "px";
					
					imageUploaded = true;
				}
				
				function onImageError() {
					URL.revokeObjectURL(this.src);
				}
				
				function onImageSubmitted() {
					if (this.files[0]) {
						unscaledImage.src = URL.createObjectURL(this.files[0]);
					}
				}
				
				// GL
				var canvas = document.createElement("canvas");
				var gl = canvas.getContext("webgl",{ preserveDrawingBuffer: true }) 
				var program = null;
				var buffer = null;
				var texture = null;
				
				function uploadImageToGPU(img) {
					gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,img);
				}
				
				function scaleOnGPU(scale) {
					canvas.width = (unscaledImage.width * scale) | 0;
					canvas.height = (unscaledImage.height * scale) | 0;
					gl.viewport(0,0,canvas.width,canvas.height);
					gl.drawArrays(gl.TRIANGLES,0,6);
					
					return canvas.toDataURL();
				}
				
				// Entry point
				onload = function() {
					// DOM setup
					unscaledImage.onload = onImageLoad;
					unscaledImage.onerror = onImageError;
					scale.onmouseup = onScaleChanged;
					file.oninput = onImageSubmitted;
					
					// GL setup
					
					// Program (shaders)
					var vertexShader = gl.createShader(gl.VERTEX_SHADER);
					var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
					program = gl.createProgram();
					
					gl.shaderSource(vertexShader,`
						precision mediump float;
						
						attribute vec2 aPosition;
						attribute vec2 aUV;
						
						varying vec2 vUV;
						
						void main() {
							vUV = aUV;
							gl_Position = vec4(aPosition,0.0,1.0);
						}
					`);
					
					gl.shaderSource(fragmentShader,`
						precision mediump float;
						
						varying vec2 vUV;
						
						uniform sampler2D uTexture;
						
						void main() {
							gl_FragColor = texture2D(uTexture,vUV);
						}
					`);
					
					gl.compileShader(vertexShader);
					gl.compileShader(fragmentShader);
					gl.attachShader(program,vertexShader);
					gl.attachShader(program,fragmentShader);
					gl.linkProgram(program);
					gl.deleteShader(vertexShader);
					gl.deleteShader(fragmentShader);
					gl.useProgram(program);
					
					// Buffer
					buffer = gl.createBuffer();
					
					gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
					gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([
						
						 1.0, 1.0,	1.0, 0.0,
						-1.0, 1.0,	0.0, 0.0,
						-1.0,-1.0,	0.0, 1.0,
						 
						 1.0, 1.0,	1.0, 0.0,
						-1.0,-1.0,	0.0, 1.0,
						 1.0,-1.0,	1.0, 1.0
						
					]),gl.STATIC_DRAW);
					
					gl.vertexAttribPointer(0,2,gl.FLOAT,gl.FALSE,16,0);
					gl.vertexAttribPointer(1,2,gl.FLOAT,gl.FALSE,16,8);
					gl.enableVertexAttribArray(0);
					gl.enableVertexAttribArray(1);
					
					// Texture
					texture = gl.createTexture();
					
					gl.activeTexture(gl.TEXTURE0);
					gl.bindTexture(gl.TEXTURE_2D,texture);
					gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST);
					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);
				}
				
				onunload = function() {
					gl.deleteProgram(program);
					gl.deleteBuffer(buffer);
					gl.deleteTexture(texture);
				}
				
			}();
		
		</script>
	</body>
</html>

编辑: 为了更好地说明这在实际渲染器中的样子,我创建了另一个示例,它将场景绘制到低分辨率帧缓冲区,然后将其缩放到画布(关键是设置 min & mag 过滤器到最近的邻居)。

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<style>
    
			body {
				background-color: black;
			}
			
			.center {
				display: block;
				margin-top: 30px;
				margin-left: auto;
				margin-right: auto;
				border: solid 1px white;
				border-radius: 10px;
			}
			
			script {
				display: none;
			}
		</style>
	</head>
	
	<body>
		<canvas id="canvas" class="center"></canvas>
		<input id="scale" type="range" min="1" max="100" value="100" class="center"></input>
		<script type="application/javascript">
			
			void function() {
				
				"use strict";
				
				// DOM
				var canvasWidth = 180 << 1;
				var canvasHeight = 160 << 1;
				var canvas = document.getElementById("canvas");
				var scale = document.getElementById("scale");
				
				function onScaleChange() {
					var scale = this.value * 0.01;
					
					internalWidth = (canvasWidth * scale) | 0;
					internalHeight = (canvasHeight * scale) | 0;
					
					gl.uniform1f(uAspectRatio,1.0 / (internalWidth / internalHeight));
					
					gl.deleteFramebuffer(framebuffer);
					gl.deleteTexture(framebufferTexture);
					
					[framebuffer,framebufferTexture] = createFramebuffer(internalWidth,internalHeight);
				}
				
				// GL
				var internalWidth = canvasWidth;
				var internalHeight = canvasHeight;
				var currentCubeAngle = -0.5;
				
				var gl = canvas.getContext("webgl",{ preserveDrawingBuffer: true, antialias: false }) || console.warn("WebGL Not Supported.");
				
				var cubeProgram = null; // Shaders to draw 3D cube
				var scaleProgram = null; // Shaders to scale the frame
				var uAspectRatio = null; // Aspect ratio for projection matrix
				var uCubeRotation = null; // uniform location for cube program
				
				var cubeBuffer = null; // cube model (attributes)
				var scaleBuffer = null; // quad position & UV's
				
				var framebuffer = null; // render target
				var framebufferTexture = null; // textured that is rendered to. (The cube is drawn on this)
				
				function createProgram(vertexCode,fragmentCode) {
					var vertexShader = gl.createShader(gl.VERTEX_SHADER);
					var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
					
					gl.shaderSource(vertexShader,vertexCode);
					gl.shaderSource(fragmentShader,fragmentCode);
					gl.compileShader(vertexShader);
					gl.compileShader(fragmentShader);
					
					try {
						if (!gl.getShaderParameter(vertexShader,gl.COMPILE_STATUS)) { throw "VS: " + gl.getShaderInfoLog(vertexShader); }
						if (!gl.getShaderParameter(fragmentShader,gl.COMPILE_STATUS)) { throw "FS: " + gl.getShaderInfoLog(fragmentShader); }
					} catch(error) {
						gl.deleteShader(vertexShader);
						gl.deleteShader(fragmentShader);
						console.error(error);
					}
					
					var program = gl.createProgram();
					
					gl.attachShader(program,vertexShader);
					gl.attachShader(program,fragmentShader);
					gl.deleteShader(vertexShader);
					gl.deleteShader(fragmentShader);
					gl.linkProgram(program);
					
					return program;
				}
				
				function createBuffer(data) {
					var buffer = gl.createBuffer();
					
					gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
					gl.bufferData(gl.ARRAY_BUFFER,Float32Array.from(data),gl.STATIC_DRAW);
					
					return buffer;
				}
				
				function createFramebuffer(width,height) {
					var texture = gl.createTexture();
					
					gl.bindTexture(gl.TEXTURE_2D,texture);
					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,width,height,0,gl.RGBA,gl.UNSIGNED_BYTE,null);
					
					var _framebuffer = gl.createFramebuffer();
					
					gl.bindFramebuffer(gl.FRAMEBUFFER,_framebuffer);
					gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,texture,0);
					
					gl.bindTexture(gl.TEXTURE_2D,null);
					gl.bindFramebuffer(gl.FRAMEBUFFER,null);
					
					return [_framebuffer,texture];
				}
				
				function loop() {
					//
					currentCubeAngle += 0.01;
					
					if (currentCubeAngle > 2.0 * Math.PI) {
						currentCubeAngle = 0.0;
					}
					
					//
					gl.bindFramebuffer(gl.FRAMEBUFFER,framebuffer);
					gl.bindTexture(gl.TEXTURE_2D,null);
					gl.viewport(0,0,internalWidth,internalHeight);
					gl.useProgram(cubeProgram);
					gl.uniform1f(uCubeRotation,currentCubeAngle);
					gl.bindBuffer(gl.ARRAY_BUFFER,cubeBuffer);
					gl.vertexAttribPointer(0,3,gl.FLOAT,gl.FALSE,36,0);
					gl.vertexAttribPointer(1,3,gl.FLOAT,gl.FALSE,36,12);
					gl.vertexAttribPointer(2,3,gl.FLOAT,gl.FALSE,36,24);
					gl.enableVertexAttribArray(2);
					gl.clear(gl.COLOR_BUFFER_BIT);
					gl.drawArrays(gl.TRIANGLES,0,24);
					
					gl.bindFramebuffer(gl.FRAMEBUFFER,null);
					gl.bindTexture(gl.TEXTURE_2D,framebufferTexture);
					gl.viewport(0,0,canvasWidth,canvasHeight);
					gl.useProgram(scaleProgram);
					gl.bindBuffer(gl.ARRAY_BUFFER,scaleBuffer);
					gl.vertexAttribPointer(0,2,gl.FLOAT,gl.FALSE,16,0);
					gl.vertexAttribPointer(1,2,gl.FLOAT,gl.FALSE,16,8);
					gl.disableVertexAttribArray(2);
					gl.clear(gl.COLOR_BUFFER_BIT);
					gl.drawArrays(gl.TRIANGLES,0,6);
					
					//
					requestAnimationFrame(loop);
				}
				
				// Entry Point
				onload = function() {
					// DOM
					canvas.width = canvasWidth;
					canvas.height = canvasHeight;
					scale.onmouseup = onScaleChange;
					
					// GL
					gl.clearColor(0.5,0.5,0.5,1.0);
					gl.enable(gl.CULL_FACE);
					gl.enableVertexAttribArray(0);
					gl.enableVertexAttribArray(1);
					
					cubeProgram = createProgram(`
						precision mediump float;
						
						const float LIGHT_ANGLE = 0.5;
						const vec3 LIGHT_DIR = vec3(sin(LIGHT_ANGLE),0.0,cos(LIGHT_ANGLE));
						
						const mat4 OFFSET = mat4(
							1.0,0.0,0.0,0.0,
							0.0,1.0,0.0,0.0,
							0.0,0.0,1.0,0.0,
							0.0,0.0,-5.0,1.0
						);
						
						const float FOV = 0.698132;
						const float Z_NEAR = 1.0;
						const float Z_FAR = 20.0;
						const float COT_FOV = 1.0 / tan(FOV * 0.5);
						const float Z_FACTOR_1 = -(Z_FAR / (Z_FAR - Z_NEAR));
						const float Z_FACTOR_2 = -((Z_NEAR * Z_FAR) / (Z_FAR - Z_NEAR));
						
						attribute vec3 aPosition;
						attribute vec3 aNormal;
						attribute vec3 aColour;
						
						varying vec3 vColour;
						
						uniform float uAspectRatio;
						uniform float uRotation;
						
						void main() {
							float s = sin(uRotation);
							float c = cos(uRotation);
							
							mat4 PROJ = mat4(
								COT_FOV * uAspectRatio,0.0,0.0,0.0,
								0.0,COT_FOV,0.0,0.0,
								0.0,0.0,Z_FACTOR_1,Z_FACTOR_2,
								0.0,0.0,-1.0,0.0
							);
							
							mat4 rot = mat4(
								c  ,0.0,-s ,0.0,
								0.0,1.0,0.0,0.0,
								s  ,0.0,c  ,0.0,
								0.0,0.0,0.0,1.0
							);
						
							vec3 normal = (vec4(aNormal,0.0) * rot).xyz;
						
							vColour = aColour * max(0.4,dot(normal,LIGHT_DIR));
							gl_Position = PROJ * OFFSET * rot * vec4(aPosition,1.0);
						}
					`,`
						precision mediump float;
						
						varying vec3 vColour;
						
						void main() {
							gl_FragColor = vec4(vColour,1.0);
						}
					`);
					
					uAspectRatio = gl.getUniformLocation(cubeProgram,"uAspectRatio");
					uCubeRotation = gl.getUniformLocation(cubeProgram,"uRotation");
					
					gl.useProgram(cubeProgram);
					gl.uniform1f(uAspectRatio,1.0 / (internalWidth / internalHeight));
					
					scaleProgram = createProgram(`
						precision mediump float;
						
						attribute vec2 aPosition;
						attribute vec2 aUV;
						
						varying vec2 vUV;
						
						void main() {
							vUV = aUV;
							gl_Position = vec4(aPosition,0.0,1.0);
						}
					`,`
						precision mediump float;
						
						varying vec2 vUV;
						
						uniform sampler2D uTexture;
						
						void main() {
							gl_FragColor = texture2D(uTexture,vUV);
						}
					`);
					
					cubeBuffer = createBuffer([
						// Position 	  Normal		 Colour
						
						// Front
						 1.0, 1.0, 1.0,  0.0, 0.0, 1.0,  0.0,0.0,0.6,
						-1.0, 1.0, 1.0,  0.0, 0.0, 1.0,  0.0,0.0,0.6,
						-1.0,-1.0, 1.0,  0.0, 0.0, 1.0,  0.0,0.0,0.6,
						
						-1.0,-1.0, 1.0,  0.0, 0.0, 1.0,  0.0,0.0,0.6,
						 1.0,-1.0, 1.0,  0.0, 0.0, 1.0,  0.0,0.0,0.6,
						 1.0, 1.0, 1.0,  0.0, 0.0, 1.0,  0.0,0.0,0.6,
						 
						// Back
						-1.0,-1.0,-1.0,  0.0, 0.0,-1.0,  0.0,0.0,0.6,
						-1.0, 1.0,-1.0,  0.0, 0.0,-1.0,  0.0,0.0,0.6,
						 1.0, 1.0,-1.0,  0.0, 0.0,-1.0,  0.0,0.0,0.6,
						
						 1.0, 1.0,-1.0,  0.0, 0.0,-1.0,  0.0,0.0,0.6,
						 1.0,-1.0,-1.0,  0.0, 0.0,-1.0,  0.0,0.0,0.6,
						-1.0,-1.0,-1.0,  0.0, 0.0,-1.0,  0.0,0.0,0.6,
						
						// Left
						-1.0, 1.0, 1.0,  1.0, 0.0, 0.0,  0.0,0.0,0.6,
						-1.0,-1.0,-1.0,  1.0, 0.0, 0.0,  0.0,0.0,0.6,
						-1.0,-1.0, 1.0,  1.0, 0.0, 0.0,  0.0,0.0,0.6,
						 
						-1.0, 1.0, 1.0,  1.0, 0.0, 0.0,  0.0,0.0,0.6,
						-1.0, 1.0,-1.0,  1.0, 0.0, 0.0,  0.0,0.0,0.6,
						-1.0,-1.0,-1.0,  1.0, 0.0, 0.0,  0.0,0.0,0.6,
						
						// Right
						 1.0,-1.0, 1.0, -1.0, 0.0, 0.0,  0.0,0.0,0.6,
						 1.0,-1.0,-1.0, -1.0, 0.0, 0.0,  0.0,0.0,0.6,
						 1.0, 1.0, 1.0, -1.0, 0.0, 0.0,  0.0,0.0,0.6,
						 
						 1.0,-1.0,-1.0, -1.0, 0.0, 0.0,  0.0,0.0,0.6,
						 1.0, 1.0,-1.0, -1.0, 0.0, 0.0,  0.0,0.0,0.6,
						 1.0, 1.0, 1.0, -1.0, 0.0, 0.0,  0.0,0.0,0.6
					]);
					
					scaleBuffer = createBuffer([
						// Position UV
						 1.0, 1.0,  1.0,1.0,
						-1.0, 1.0,  0.0,1.0,
						-1.0,-1.0,  0.0,0.0,
						 
						 1.0, 1.0,  1.0,1.0,
						-1.0,-1.0,  0.0,0.0,
						 1.0,-1.0,  1.0,0.0
					]);
					
					[framebuffer,framebufferTexture] = createFramebuffer(internalWidth,internalHeight);
				
					loop();
				}
				
				// Exit point
				onunload = function() {
					gl.deleteProgram(cubeProgram);
					gl.deleteProgram(scaleProgram);
					gl.deleteBuffer(cubeBuffer);
					gl.deleteBuffer(scaleBuffer);
					gl.deleteFramebuffer(framebuffer);
					gl.deleteTexture(framebufferTexture);
				}
			}();
			
		</script>
	</body>
</html>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否有更简单(且仍然高性能)的方法来通过最近邻重采样来升级画布渲染? 的相关文章

  • JavaScript onTouch 不工作

    谁能告诉我为什么这个 onTouch 处理程序没有触发 var myDiv document getElementById existingContent var myButton a href log out a myDiv append
  • 在 javascript/jquery 中将光标更改为等待

    当调用函数时 如何让光标更改为此加载图标以及如何将其更改回 javascript jquery 中的普通光标 在你的 jQuery 中使用 body css cursor progress 然后又恢复正常 body css cursor d
  • Web 串行 API - 未捕获(承诺中)DOMException:无法打开串行端口/所需成员 baudRate 未定义

    下面的代码可以在我的 Xubuntu 机器上运行 但现在我在 Kubuntu 上 它不再工作了 它不会打开端口 Arduino IDE 工作正常 可以向开发板写入代码 并且我可以在 Chrome 中选择设备 Arduino Uno 但当我尝
  • 在 Vue.js 中从父组件执行子方法

    目前 我有一个 Vue js 组件 其中包含其他组件的列表 我知道使用 vue 的常见方式是将数据传递给孩子 并从孩子向父母发出事件 但是 在这种情况下 我想在子组件中的按钮出现时执行子组件中的方法 parent被点击 哪种方法最好 一种建
  • 如何重置使用 JavaScript 更改的 CSS 属性?

    我的导航按钮的宽度从 100px 增加到 150px 当鼠标悬停在 nav li hover width 150px 但是使用 javascript 我已经做到了 无论选择哪个选项 宽度都将继续为 150px 当选择每个选项时 它会使其他选
  • 使用模数按字母顺序对列表进行排序

    我在获取元素列表并按字母顺序对它们进行排序方面没有任何问题 但我很难理解如何使用模数来做到这一点 更新 这是按我的方式工作的代码 但是 我更喜欢下面提供的答案的可重用性 因此接受了该答案
  • 检查 JavaScript 字符串是否为 URL

    JavaScript 有没有办法检查字符串是否是 URL 正则表达式被排除在外 因为 URL 很可能是这样写的stackoverflow 也就是说它可能没有 com www or http 如果你想检查一个字符串是否是有效的 HTTP UR
  • Meteor:应用程序无法在 0.9.1.1 版本上运行

    出现类似错误 Error TypeError undefined is not a function evaluating Template create anonymous function iron dynamic template j
  • Google App Engine:修改云运行环境

    我正在尝试部署一个使用自定义 Node js 服务器的 Next js 应用程序 我想将自定义构建变量注入应用程序 next config js const NODE ENV process env NODE ENV const envTy
  • 为什么是 javascript:history.go(-1);无法在移动设备上工作?

    首先 一些背景 我有一个向用户呈现搜索页面 html 表单 的应用程序 填写标准并单击 搜索 按钮后 结果将显示在标准部分下方 在结果列表中 您可以通过单击将您带到新页面的链接来查看单个结果的详细信息 在详细信息页面中 我添加了一个 返回结
  • 标签获取 href 值

    我有以下 html div class threeimages a img alt Australia src Images Services 20button tcm7 9688 gif a div class text h2 a hre
  • Meteor - 从客户端取消服务器方法

    我正在通过服务器方法执行数据库计数 用户可以选择他们希望如何执行计数 然后调用该方法 我的问题是 计数可能需要一些时间 并且用户可能会在方法运行时改变主意并请求不同的计数 有什么方法可以取消调用的方法并运行新的计数吗 我认为 this un
  • 在javascript中解析json - 长数字被四舍五入

    我需要解析一个包含长数字的 json 在 java servlet 中生成 问题是长数字被四舍五入 当执行这段代码时 var s x 6855337641038665531 var obj JSON parse s alert obj x
  • 为 illustrator 导出脚本以保存为 web jpg

    任何人都可以帮我为 illustrator CC2017 编写一个脚本 将文件以 JPG 格式导出到网络 旧版 然后保存文件并关闭 我有 700 个文件 每个文件有 2 个画板 单击 文件 gt 导出 gt 另存为 Web 旧版 然后右键文
  • 在 vue.js 中访问数组对象属性

    给定以下数组vue js packageMaps Object packageMap 0 Object Id 16 PackageType flag list ProductCode F BannerBase packageMap 1 Ob
  • 条件在反应本机生产中失败,但在开发中有效

    我创建了一个反应本机应用程序 我需要通过它进行比较 如果属实 就会执行死刑 问题是 该条件适用于 React Native 开发模式 而不适用于 React Native 生产版本 我使用 firebase 作为数据库 也使用 redux
  • 如何获取浏览器视口中当前显示的内容

    如何获取当前正在显示长文档的哪一部分的指示 例如 如果我的 html 包含 1 000 行 1 2 3 9991000 并且用户位于显示第 500 行的中间附近 那么我想得到 500 n501 n502 或类似的内容 显然 大多数场景都会比
  • 在 React.js 中编辑丰富的数据结构

    我正在尝试为数据结构创建一个简单的基于网格的编辑器 但我在使用 React js 时遇到了一些概念问题 他们的文档对此没有太大帮助 所以我希望这里有人可以提供帮助 首先 将状态从外部组件传输到内部组件的正确方法是什么 是否有可能将内部组件中
  • fullCalendar 未显示正确的结束日期

    我正在看调试页面 http jsbin com wukofacaxu edit js outputFullCalendar 官方网站的 我想安排一个活动时间为 22 09 2015 至 30 09 2015 dd mm yyyy 但它只显示
  • 如何从图像输入中获取 xy 坐标?

    我有一个输入设置为图像类型

随机推荐

  • 如何使用selenium和python查找不包含特定类名的元素

    我想找到包含某个类名的所有元素 但跳过那些除了我正在搜索的类名之外还包含另一个类名的元素 我有元素 div class examplenameA 和元素 div class examplenameA examplenameB 目前我正在这样
  • 如何使用自定义动词发出 HTTP 请求?

    为了测试 API 我希望能够使用自定义动词 例如 RECOMPUTE 来发出 HTTP 请求 而不是 GET POST PUT DELETE OPTIONS HEAD TRACE 和 CONNECT 是否有一个库已经做到了这一点 或者我是否
  • Android-Boot Completed 在 Broadcastreceiver 中不起作用

    我正在使用 android 版本 4 1 1 MeLE box SmartTv 来开发一个应用程序 我需要在设备启动时间完成时启动我的应用程序 但我的设备无法赶上 BOOT COMPLETED 操作 如果我在手机或模拟器中使用相同的应用程序
  • Python3 f 字符串:如何避免转义文字大括号?

    有没有办法避免在 python3 中转义大括号字符f string 例如 如果你想输出一个 json 字符串或一大块 CSS 规则 那么必须转换所有的内容确实很不方便 and 字符到 and 如果您想使用 f 字符串语法 我知道可以使用旧的
  • 当作业在 Databricks 中运行时,如何获取作业名称。这不是基于笔记本的工作

    我正在尝试获取正在运行的作业的名称 我想获取姓名并发送消息 示例 我将作业部署到 databricks 并运行它 我希望这个作业在松弛时发送带有作业名称的消息 这就是为什么我想获取当前作业的名称 Databricks 通过以下方式公开大量信
  • 主线程检查器:在后台线程上调用的 UI API:-[UIApplication applicationState]

    我在 Xcode 9 beta iOS 11 中使用谷歌地图 我收到输出到日志的错误 如下所示 主线程检查器 在后台线程上调用的 UI API UIApplication applicationState PID 4442 TID 8378
  • 更改 std::map 内元素的键的最快方法是什么

    我理解为什么不能这样做的原因 重新平衡之类的 iterator i m find 33 if i m end i gt first 22 但到目前为止 更改键的唯一方法 我知道 是从树中删除节点 然后使用不同的键将值插入回来 iterato
  • 如何为所有列设置 AND 条件 - php

    In MY TABLE如果我输入 floor fly 表返回No matching records因为全球搜索php 函数搜索单个列内的记录 但我希望 AND 条件适用于所有列 如果我输入floor fly表应该显示如下内容 Column1
  • 按类名划分的 JavaFX 样式

    也许是一个基本问题 但是是否可以在 JavaFX 中通过类名设置表元素的样式 例如这样 MyClassname table view column header label fx text fill F00 我希望它可以在 1 个样式表中设
  • 可以从Javascript文件访问MVC ViewBag对象吗?

    是否可以从 MVC 应用程序中的 javascript 文件执行以下操作 function alert ViewBag someValue 目前它抛出错误 引用未定义的 XML 名称 ViewBag 我不认为目前有任何方法可以做到这一点 R
  • Git Clone 在 Azure 命令行任务中生成错误

    在我的 Azure DevOps CD 管道中 我添加了一个克隆 Git 存储库的命令行任务 克隆已成功完成 但日志中出现错误 奇怪的行为是克隆与 Azure 托管代理完美配合 例如vs2017 win2016 or Windows 201
  • 如何从 Azure AD B2C 身份验证获取电子邮件地址?

    我正在尝试让 Azure AD B2C 获取用户电子邮件地址 但我在提供该地址的令牌中看不到任何内容 即使我的登录 注册策略对用户电子邮件地址进行了 声明 我如何获得电子邮件地址 更一般地说 除了示例之外 是否有任何文档可以解释此服务的工作
  • 使用 prxmatch 匹配以某个字符结尾的字符串

    匹配以某个字符结尾的字符串 我正在尝试创建一个新变量 该变量指示字符串是否以某个字符结尾 下面是我尝试过的 但是当运行此代码时 变量ending in e全为零 我希望像 Alice 和 Jane 这样的名字会与下面的代码匹配 但它们不是
  • 结构体中零长度数组的用途是什么? [复制]

    这个问题在这里已经有答案了 当我查看Linux内核代码时 发现了以下代码 struct thread info struct task struct task struct exec domain exec domain unsigned
  • 如果 isNaN,如何将变量的内容更改为零 [重复]

    这个问题在这里已经有答案了 可能的重复 当我按 Enter 时 我得到 isNaN 但该值是一个数字 我上周日交了作业 它被发回给我进行更正 因为 isNaN 值返回到 Total 文本框 我认为这就是编程要做的事情 相反 根据她的说法 i
  • 在 Seaborn 箱线图中获取值

    我想通过 Seaborn 中生成的箱线图获取具体值 即媒体 四分位数 例如 在下面的箱线图中 来源 link 有没有办法获取媒体和四分位数而不是手动估计 import numpy as np import seaborn as sns sn
  • 打印文件中的每 n 行

    我正在尝试打印每个n文件中的第 3 行 但是n不是常量而是变量 例如 我想替换sed n 1 5p 与类似的东西sed n 1 i p 这可能吗 awk还可以用更优雅的方式做到这一点 awk v n YOUR NUM NR n 1 file
  • Kubelet 使用什么来确定节点的临时存储容量?

    我在虚拟机上运行 Kubernetes 集群 安装座的简要概述如下 df h Filesystem Size Used Avail Use Mounted on dev sda1 20G 4 5G 15G 24 dev mapper vg0
  • pandas read_csv 最后一列包含逗号

    所以我有一个 csv 数据集 根据我的书 该数据集格式良好 并且我正在尝试获取pandas包以正确加载它 标题由 5 个列名组成 但最后一列由包含未转义逗号的 JSON 对象组成 例如 A B C D E 1 2 3 4 K1 V1 K2
  • 是否有更简单(且仍然高性能)的方法来通过最近邻重采样来升级画布渲染?

    我对这个看似简单的以最近邻格式升级画布渲染的任务感到有点困惑 我在这里问 如何在 JS 中正确编写这个着色器函数 目标是像这样转换 3D 渲染输出 像这样的像素艺术 但在这个问题中我问的是如何实施我选择的解决方案 本质上使用着色器来处理放大