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

标签 javascript canvas html5-canvas

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

How can I properly write this shader function in 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>

编辑: 为了更好地说明这在实际渲染器中的样子,我创建了另一个示例,它将场景绘制到低分辨率帧缓冲区,然后将其缩放到 Canvas (关键是设置 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>

关于javascript - 是否有更简单(且仍然高性能)的方法来通过最近邻重采样来升级 Canvas 渲染?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50440166/

相关文章:

canvas - 将fabricjs对象转换为PDF格式而不是简单的图像?

python - 检查 tkinter 中按钮的状态

javascript - CanvasContext2D drawImage() 问题 [onload 和 CORS]

javascript - 仅在指定字符的第一个实例上拆分字符串

css - 仅清洁 Canvas 上的局部绘图

javascript - 如何清除ajax调用后初始化函数的javascript

javascript - 如何检查Canvas中的两个图形(线)是否发生碰撞?

javascript - Ajax 工具包 : Balloon Popup Extender on html5 Canvas

javascript - React Hooks 多个带有单独倒计时的警报

javascript - 是否可以仅使用 css3 淡入新创建的元素?