【问题标题】:Is there a simpler (and still performant) way to upscale a canvas render with nearest neighbor resampling?有没有一种更简单(并且仍然有效)的方法来通过最近邻重采样来放大画布渲染?
【发布时间】:2018-05-23 00:24:23
【问题描述】:

对于以最近邻格式放大画布渲染这一看似简单的任务,我有点难过,我在这里问过这个问题:

How can I properly write this shader function in JS?

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

像这样的像素艺术:

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

【问题讨论】:

    标签: javascript canvas html5-canvas


    【解决方案1】:

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

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

    (我的代码 sn-ps 使用 .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>

    【讨论】:

    • 这个(我的意思是使用 webGL 的代码,循环版本的实时性能不够)可能更好,但它并不简单,老实说,它只是如果没有简化它的库的帮助,我目前在渲染/WebGL(无)方面的经验太复杂了。你对我的努力表示赞同,但是当我看到那个代码时,我有点眼花缭乱。当我要求更简单时,我正在考虑诸如渲染到画布,禁用抗锯齿,并通过变换或类似奇怪的东西放大画布。
    • WebGL 涉及一些样板,但如果您关心的是速度,那么在网络上,目前不太可能有比硬件支持的纹理缩放更快的东西。我能做的就是提供一个模块,让它更容易使用。假设您为它提供了您一直在绘制的低分辨率画布、另一个用于放置缩放图像的画布以及一个用于缩放的因子。
    猜你喜欢
    • 1970-01-01
    • 2011-06-28
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多