问题
I know very little about OpenGL or WebGL. I was wondering if it is possible to pass a fragment shader an argument. Specifically, I'd like to pass it a multidimensional array with the colors to set for each pixel, for example:
[0][0][0] = 1
would be the red component of the pixel at (0, 0).
回答1:
The standard way of doing this is by using a texture rather than passing an array.
回答2:
Simon's answer is right on. But took me about 6 hours to actually get it working.Here is my full working code as an HTML file. Hopefully it will save you from all the trouble I went through tonight.
How to use: Click anywhere on the canvas to set or remove a tile.
Tested In: Chrome, IE, Edge:
<!DOCTYPE HTML >
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TEXTURE AS TILEMAP</title>
<!-- AUTHOR: John Mark Isaac Madison -->
<!-- EMAIL : J4M4I5M7@hotmail.com -->
<!-- SSSSSSSSS SHADER_SECTION START SSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<style>
p{ font-size:12pt;}
h3,p,input,button,br{
padding:0px;
margin:0px;
font-family:"Andale Mono";
}
button,input{
padding:10px;
}
</style>
<script id="VERT_SHADER" type="NOT_JAVASCRIPT">
precision highp float;
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
</script>
<!-- Simpler shader for when you need to -->
<!-- simplify and hunt down a problem -->
<script id="FRAG_SHADER_SIMPLE" type="NOT_JAVASCRIPT">
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
precision highp int;
#else
precision mediump float;
precision mediump int;
#endif
uniform int HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT;
uniform sampler2D u_texture; //<-- "uSampler" here: https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html
void main(){
gl_FragColor = vec4( 0.5, 0.5, 0.5, 1.0);
}
</script>
<script id="FRAG_SHADER" type="NOT_JAVASCRIPT">
//Must declare precision before declaring
//any uniforms:
////////////////////////////////////////////////
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
precision highp int;
#else
precision mediump float;
precision mediump int;
#endif
////////////////////////////////////////////////
//:Width and height of tilemap in TILES:
uniform int CMAP_WID;
uniform int CMAP_HIG;
//SOURCE: https://stackoverflow.com/questions/36324295/webgl-access-buffer-from-shader
//: QUOTE[
//: Make sure your texture filtering
//: is set to gl.NEAREST.
//: ]QUOTE
//SDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSD//
uniform int HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT;
uniform sampler2D u_texture; //<-- "uSampler" here: https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html
vec2 textureSize = vec2(128.0, 128.0);
vec4 getValueFromTexture(float index) {
float column = mod(index, textureSize.x);
float row = floor(index / textureSize.x);
vec2 uv = vec2(
(column + 0.5) / textureSize.x,
(row + 0.5) / textureSize.y);
return texture2D(u_texture, uv);
}
//Integer version of function:
vec4 TVi( int index_i ){
return getValueFromTexture( float(index_i) );
}
//SDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSD//
#define CANVAS_WID 640.0
#define CANVAS_HIG 480.0
#define TIL_WID 64.0
#define TIL_HIG 64.0
//Uniforms exposed to HTML/JAVASCRIPT:
uniform float TIL_WID_EDIT;
uniform float TIL_HIG_EDIT;
//Time modulates between 0 and 1:
uniform float TIME_TICKER;
//Matches iTime of shadertoy website;
uniform float TIME_UPUP;
//Hard code to support a 16x16 tilemap:
uniform float TIL_MAP[256];
//uniform float TIL_MAP[8];
float til_wid;
float til_hig;
//TFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTF//
vec2 RenderTileBase(){
vec2 OUT_DATA;
//Don't render until tile-map size in tiles
//has been set by javascript:
if(CMAP_WID == 0){
return OUT_DATA;
}
if(CMAP_HIG == 0){
return OUT_DATA;
}
//If uniforms have not set by user,
//use the default values set by the #define(s)
//==========================================//
if(TIL_WID_EDIT > 0.0){
til_wid = TIL_WID_EDIT;
}else{
til_wid = TIL_WID;
}
if(TIL_HIG_EDIT > 0.0){
til_hig = TIL_HIG_EDIT;
}else{
til_hig = TIL_HIG;
}
//==========================================//
//NOTE: on "gl_FragCoord" range:
//******************************************//
//web-gl: In terms of pixel/canvas coords.
// X-AXIS: 0 to canvas.width - 1
// Y-AXIS: 0 to canvas.height- 1
//OpenGL: In terms of 0 to 1.
//******************************************//
//:Calculate number of tiles shown on screen:
//:This may be fractional:
float NUM_TIL_X = CANVAS_WID / til_wid;
float NUM_TIL_Y = CANVAS_HIG / til_hig;
//DETERMINE WHAT tile you are on:
float TC_X; //tile coordinate X
float TC_Y; //tile coordinate Y
TC_X = floor( gl_FragCoord.x / til_wid );
TC_Y = floor( gl_FragCoord.y / til_hig );
int i_X = int( TC_X ); //integer version of TC_X
int i_Y = int( TC_Y ); //integer version of TC_Y
int DEX = (i_Y * CMAP_WID) + i_X;
vec2 FC_MOD;
FC_MOD.x = gl_FragCoord.x;
FC_MOD.y = gl_FragCoord.y;
//You want all tiles to have the full range
//of colors, so you always modulate by
//CANVAS_WID and CANVAS_HIG, You scale by the
//# of tiles on each axis which means the
//gradient becomes steeper as the # of tiles
//increases.
FC_MOD.x = mod( gl_FragCoord.x*NUM_TIL_X, CANVAS_WID );
FC_MOD.y = mod( gl_FragCoord.y*NUM_TIL_Y, CANVAS_HIG );
//[N]ormalize values into range 0 to 1:
//NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN//
float norm_X = (FC_MOD.x) / CANVAS_WID;
float norm_Y = (FC_MOD.y) / CANVAS_HIG;
float NMAP_X; // norm_X value mapped.
float NMAP_Y; // norm_Y value mapped.
//NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN//
//Use [B]lue channel because why not?
//BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//
float GRAD_X = gl_FragCoord.x / CANVAS_WID;
float GRAD_Y = gl_FragCoord.y / CANVAS_HIG;
if(GRAD_X > 1.0 || GRAD_X < 0.0){ return OUT_DATA; }
if(GRAD_Y > 1.0 || GRAD_Y < 0.0){ return OUT_DATA; }
float tile_value;
float DEX_F = float(DEX);
vec4 V4 ; //tile_RGBA
if( HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT > 0 ){
V4 = getValueFromTexture( DEX_F );
tile_value = V4.x;
}
if( tile_value==0.0 ){
NMAP_X = 0.0;
NMAP_Y = 0.0;
}else{
NMAP_X = TIME_TICKER;
NMAP_Y = 1.0-TIME_TICKER;
}
//BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//
OUT_DATA.x = NMAP_X;
OUT_DATA.y = NMAP_Y;
return OUT_DATA;
}
void main() {
vec2 uv = RenderTileBase();
gl_FragColor = vec4( uv.x, uv.y, 0, 1.0);
return;
}
</script>
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSS SHADER_SECTION END SSSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<script id="BOILER_PLATE_CODE" type="text/javascript">
function ON_LOADED_FUNCTION(){
console.log("[ON_LOADED_FUNCTION]");
main();
}
//SOURCE:https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html
var HACK_CANVAS = document.createElement('canvas');
HACK_CANVAS.width = 128*128;
HACK_CANVAS.height= 1;
//Global Array Container:
var GLOBAL_GAC;
var TIME_UPUP = 0;
var TIME_0255 = 0;
var TIME_2PI = 0.0;
var TIME_TICKER = 0.0;
function UPDATE_TIMER(){
TIME_UPUP = TIME_UPUP + 0.01;
TIME_0255 += 0.75;
if(TIME_0255 > 255){ TIME_0255 = 0; }
TIME_2PI = (TIME_0255 / 255) * (Math.PI * 2);
//Use cos to modulate value between 0 and 1:
//To do so, need to offset by 1 and divide
//by two to transform [-1, 1] range
//into [0, 1] range.
TIME_TICKER = ( Math.cos(TIME_2PI) + 1) / 2;
//console.log( TIME_TICKER);
SET_ATTR("TIME_TICKER", TIME_TICKER);
SET_ATTR("TIME_UPUP" , TIME_UPUP );
}
//:Takes the gl context object, if the input
//:is null, we likely failed to get the
//:context.
function HAS_OPEN_GL_CHECK(gl){
// Only continue if WebGL is
// available and working
if (!gl) {
var msg = "";
msg += "[Unable to initialize WebGL.]";
msg += "[your browser or machine may]";
msg += "[not support it.]"
alert( msg );
return;
}
}
function GET_INPUT_BOX_VALUE( elem_id ){
var box; //DOM input box
var val; //Value in input box.
box = document.getElementById( elem_id );
val = box.value;
return (0 + val); //cast to number.
}
function PUT_WID(){
assert_program_and_gl_exist();
var val = GET_INPUT_BOX_VALUE("INPUT_WID");
var res = SET_ATTR_UI("TIL_WID_EDIT", val);
//For click listener on canvas:
if(res){ GLOBAL_TIL_WID = val; }
}
function PUT_HIG(){
assert_program_and_gl_exist();
var val = GET_INPUT_BOX_VALUE("INPUT_HIG");
var res = SET_ATTR_UI("TIL_HIG_EDIT", val);
//For click listener on canvas:
if(res){ GLOBAL_TIL_HIG = val; }
}
//Integer Version of set-attribute helper:
function SET_ATTR_INT(gl_var_name, val){
var loc; //<--location of variable
loc = gl.getUniformLocation(
program,
gl_var_name
);
gl.useProgram(program);
gl.uniform1i(loc, val );
}
//Returns TRUE if successful:
function SET_ATTR(gl_var_name, val){
var loc; //<--location of variable.
loc = gl.getUniformLocation(
program ,
gl_var_name
);
gl.useProgram(program);
gl.uniform1f(loc, val);
return true;
}
//Version of SET_ATTR used for the
//User Interface (UI):
function SET_ATTR_UI(gl_var_name, val){
if(val < 0 || val > 256 ){
alert("choose value between 0 to 256");
//Call was ignored, so return false:
return false;
}
return SET_ATTR(gl_var_name, val);
}
function assert_program_and_gl_exist(){
if(!program){慌("[NO_PROGRAM_EXISTS]");}
if(!gl ){慌("[NO_GL_EXISTS]");}
}
//慌: "disconcerted, be confused, lose one's head"
//慌: In Code: ~Panic~
function 慌( panic_message ){
console.log( panic_message );
alert ( panic_message );
throw ( panic_message );
}
function makeOpenGlContextUsingCanvas(c){
//:Try what works in chrome and all the
//:respectable browsers first:
gl = c.getContext("webgl");
if(!gl){
console.log("[Probably_In_IE]");
gl = c.getContext("experimental-webgl");
}else{
console.log("[Probably_NOT_IE]");
}
HAS_OPEN_GL_CHECK( gl );
return gl;
}
function CANVAS_CLICK_FUNCTION(event){
var mp = getMousePos(canvas,event);
//Correct to match openGL orientation:
mp.y = canvas.height - mp.y;
console.log( mp );
//Determine tile clicked on:
var tx = mp.x / GLOBAL_TIL_WID;
var ty = mp.y / GLOBAL_TIL_HIG;
tx = Math.floor(tx);
ty = Math.floor(ty);
console.log("tx:", tx, "ty:", ty);
//Convert to index:
var dex;
dex = (GLOBAL_GAC.WID * ty) + tx;
val = GLOBAL_GAC.Get_R( dex );
if(val!=0){
val = 0;
}else{
val = 1;
}
GLOBAL_GAC.Put_R( dex , val );
//Update The Shader With New TileMap:
GLOBAL_GAC.pushToGL();
}
function getMousePos(GMP_canvas, evt) {
var rect = GMP_canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
//: No "var" prefix, making them global:
function initGlobals(){
//The width and height in PIXELS of a
//single tile on our tile-map:
GLOBAL_TIL_WID = 64;
GLOBAL_TIL_HIG = 64;
canvas = document.querySelector("#glCanvas");
if(!canvas){
alert("FAILED_TO_GET_CANVAS");
}else{
console.log("[GOT_CANVAS]");
}
//Add listener to canvas:
canvas.addEventListener('click',CANVAS_CLICK_FUNCTION);
gl = makeOpenGlContextUsingCanvas(canvas);
//These dimensions are hard-coded into
//fragment shader code, so be careful
//about changing them:
canvas.width = 640;
canvas.height= 480;
buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]),
gl.STATIC_DRAW
);
//G == Global Container.
//To fix problems with rendering in I.E.
//(Internet Explorer)
//GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG//
var G = {};
G.canvas = canvas;
G.gl = gl;
G.buffer = buffer;
if( ! G.canvas ||
! G.gl ||
! G.buffer ){
慌("[Global_Container_Broken]");
}
return G;
//GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG//
}
function main(){
G = initGlobals();
HAS_OPEN_GL_CHECK( G );
gl.viewport(0,0,gl.drawingBufferWidth ,
gl.drawingBufferHeight);
setup();
render();
SETUP_TILE_MAP_DATA();
}
//Need texture/buffer container: 斗
var GLArrayContainer = function(){
var _self = this;
//web-gl texture handle:
this.TEX = null;
//Pixel Array:
this.PIX = null;
//Number of pixels in Pixel Array:
this.PIX_NUM = 0;
this.WID = 0; //width in pixels.
this.HIG = 0; //height in pixels. Sure pixels?
//Publically Exposed Functions:
//PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//
this.setPixelByIndex = _setPixelByIndex;
this.makePattern = _makePattern ;
this.solidFill = _solidFill ;
this.pushToGL = _pushToGL ;
this.Get_R = _get_R;
this.Put_R = _put_R;
//PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//
//: i == index.
function _get_R( i ){
var RED_INDEX = 0;
var r = _self.PIX[(i*4) + RED_INDEX];
return r;
}
//: i == index.
function _put_R( i, val){
var RED_INDEX = 0;
_self.PIX[(i*4) + RED_INDEX] = val;
}
function _setPixelByIndex(i, r,g,b, a){
//Multiply by 4 to get address
//of first component of pixel [i]
//Because each pixel is 4-bytes:
var b = i*4;
//RGBA (alpha last) (NOT : argb)
_self.PIX[ b + 0 ] = r;
_self.PIX[ b + 1 ] = g;
_self.PIX[ b + 2 ] = b;
_self.PIX[ b + 3 ] = a;
}
function _solidFill(){
if(_self.PIX_NUM <= 0){慌("[BAD_PIX_NUM]");}
var r,g,b;
for(var i = 0; i < _self.PIX_NUM; i++){
_setPixelByIndex(i,225,22,64,255);
}
}
//Make a checker pattern:
function _makePattern(){
var EVERY_OTHER = false;
var i_r = 0;
var i_g = 0;
var i_b = 0;
for(var i = 0; i < _self.PIX_NUM; i++){
EVERY_OTHER = (!EVERY_OTHER);
if( EVERY_OTHER ){
i_r = 255;
i_g = 255;
i_b = 255;
}else{
//alert("HEY");
i_r = 0;
i_g = 0;
i_b = 0;
}
_setPixelByIndex(
i,i_r,i_g,i_b,255
);
}
}
//Push changes to Web-GL so fragment
//shader can use the values:
function _pushToGL(){
if(!_self.TEX){慌("[TEX_PROBLEM]");}
console.log("[push_to_gl]");
gl.activeTexture( gl.TEXTURE1 );
gl.bindTexture(
gl.TEXTURE_2D,
_self.TEX
);
//Will get error:
//[ WebGL: INVALID_OPERATION: ]
//[ texParameter: no texture bound to target ]
//If no texture is bound to active slot before doing this.
//:SOURCE:https://stackoverflow.com/questions/42358462/no-texture-bound-to-the-unit-0
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);
//:will also throw error if no texture
//in active texture slot:
gl.texImage2D(
gl.TEXTURE_2D ,
0 , //LEVEL
gl.RGBA , //internalFormat,
_self.WID ,
_self.HIG ,
0 , //border
gl.RGBA , //srcFormat
gl.UNSIGNED_BYTE , //srcType
_self.PIX //<--ArrayBufferView object
);
SET_ATTR_INT(
"HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT",
1 );
//Let shader know tile-map width and height:
SET_ATTR_INT("CMAP_WID", _self.WID);
SET_ATTR_INT("CMAP_HIG", _self.HIG);
}
}
TILE_MAP_HAS_BEEN_SETUP = false;
function SETUP_TILE_MAP_DATA(){
if(TILE_MAP_HAS_BEEN_SETUP){
alert("[MAP_ALREADY_SETUP]");
return;
}
TILE_MAP_HAS_BEEN_SETUP = true;
//Decided on 128x128:
//Because it is maximum dimensions of
//a project I am working on.
const WID = 128;
const HIG = 128;
//4 components/bytes in 1 ARGB pixel:
const NUM_COM_ARGB = 4; //4;
const ARRAY_LENGTH = WID*HIG*NUM_COM_ARGB;
const pixel = new Uint8Array( ARRAY_LENGTH );
//Create Texure slot at TEXTURE1 because
//seems to be bug "there is no texture bound to unit 0"
//in chrome.
gl.activeTexture(gl.TEXTURE1);
const texture = gl.createTexture();
if(!texture){慌("[NOT_TEXTURE]");}
//Our texture sampler needs to be bound to
//the same texture slot. Hence the "1"
//https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html
gl.uniform1i(
gl.getUniformLocation(
program ,
"u_texture" //<--Sampler2D's Name.
),
1 //<--Texture Slot To Bind To.
);
//Our pixel array is 4-component, so we
//can use alignment 4. An alignment of
//1 will also work.
//SOURCE: https://webglfundamentals.org/webgl/lessons/webgl-data-textures.html
const alignment = 4;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
//Populate our helper container:
//PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//
GLOBAL_GAC = new GLArrayContainer();
GLOBAL_GAC.PIX = pixel;
GLOBAL_GAC.TEX = texture;
GLOBAL_GAC.PIX_NUM = WID*HIG;
GLOBAL_GAC.WID = WID;
GLOBAL_GAC.HIG = HIG;
GLOBAL_GAC.makePattern();
GLOBAL_GAC.pushToGL();
//setTimeout( 1, GLOBAL_GAC.pushToGL() );
//PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//
}
function setup(){
var frag_dom = document.getElementById("FRAG_SHADER");
var frag_src = frag_dom.text;
console.log( frag_src );
F = createShader(
gl,gl.FRAGMENT_SHADER, frag_src
);
var vert_dom = document.getElementById("VERT_SHADER");
var vert_src = vert_dom.text;
console.log( vert_src );
V = createShader(
gl, gl.VERTEX_SHADER, vert_src
);
//**** MAKE "program" a GLOBAL VAR ****//
program = createProgram(gl,V,F);
gl.useProgram( program );
if(!program){ 慌("[PROGRAM_IS_NULL]");}
}
function render(){
window.requestAnimationFrame(render,canvas);
// Set clear color to black, fully opaque
gl.clearColor(0.0, 0.0, 0.5, 1.0);
// Clear the color buffer with specified clear color
gl.clear(gl.COLOR_BUFFER_BIT);
//Directly before call to gl.drawArrays:
positionLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray( positionLocation );
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
UPDATE_TIMER();
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
function createShader(gl,type,source){
//:Error Check For Bad Inputs:
if(!gl ){慌("[NULL_GL]");}
if(!type ){慌("[NULL_TY]");}
if(!source){慌("[NULL_SR]");}
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var res = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if( res ){
console.log("[SHADER_COMPILED!]");
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
慌("[FAILED_TO_COMPILE_SHADER]");
}
//:gl : openGL context :
//:vert: vertex shader :
//:frag: fragment shader:
function createProgram(gl,vert, frag){
var program = gl.createProgram();
gl.attachShader(program, vert);
gl.attachShader(program, frag);
gl.linkProgram (program);
var res = gl.getProgramParameter(program, gl.LINK_STATUS);
if( res ){
console.log("[PROGRAM_CREATED!]");
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
</script>
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
</head>
<!-- HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -->
<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
<body onload="ON_LOADED_FUNCTION()" >
<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
<h3> Web-GL: Texture As Tilemap <h3>
<p> Author: John Mark Isaac Madison <p>
<p> Email : J4M4I5M7@hotmail.com <p>
<canvas id="glCanvas"></canvas>
</br>
<button onClick="PUT_WID();">TILE_WIDTH__IN_PIXELS</button>
<input type="text" id="INPUT_WID" value="32">
</br>
</br>
<button onClick="PUT_HIG();">TILE_HEIGHT_IN_PIXELS</button>
<input type="text" id="INPUT_HIG" value="32">
</br>
<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
</body>
</html>
来源:https://stackoverflow.com/questions/34873832/webgl-fragment-shader-pass-array