将JavaScript变量发送到片段着色器

Sending JavaScript variables to fragment shader

本文关键字:片段 JavaScript 变量      更新时间:2023-09-26

我一直在拼凑在线示例来制作Mandelbrot集碎片着色器。顶点着色器基本上什么都不做,它指定gl_Position,片段着色器做一些数学运算来计算图像。

然而,我有许多#define,我想用JavaScript控制的变量替换它们,但我不知道如何做到这一点。如果在下面的代码中显示一个如何用JavaScript指定的变量替换#define MAX_ITERATIONS 200的例子,我可能会理解其余的内容。我认为我需要指定一个uniformvarying,但不确定如何管理从JavaScript到GLSL的通信。

此外,我不明白aPosition是如何在JavaScript和顶点着色器之间工作的,我所拥有的基本上与示例相同。

JavaScript,我想只有init()对SO读者很重要,其余的都会在需要时发布:

var canvas, gl, shaderProgram;
function draw() {
    window.requestAnimationFrame(draw, canvas);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function init() {
    canvas = document.getElementById("theCanvas");
    gl = initGl(canvas);
    if (!gl) {
        alert("Could not initialize WebGL");
        return;
    }
    shaderProgram = initShaders();
    if (!shaderProgram) {
        alert("Could not initialize shaders");
        return;
    }
    var vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array([
            -1.0,  -1.0,
            1.0,  -1.0,
            -1.0, 1.0,
            1.0, 1.0,
        ]),
        gl.STATIC_DRAW
    );
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.viewportWidth = canvas.width;
    gl.viewportHeight = canvas.height;
    var aPosition = gl.getAttribLocation(shaderProgram, "aPosition");
    gl.enableVertexAttribArray(aPosition);
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
    draw();
}
function initGl(inCanvas) {
    gl = false;
    try { gl = inCanvas.getContext("webgl") || inCanvas.getContext("experimental-webgl"); }
    catch (e) {}
    return !gl ? false : gl;
}
function initShaders() {
    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, document.getElementById("vertexShader").text);
    gl.compileShader(vertexShader);
    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(vertexShader));
        return false;
    }
    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, document.getElementById("fragmentShader").text);
    gl.compileShader(fragmentShader);
    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(fragmentShader));
        return false;
    }
    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);
    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) return false;
    gl.useProgram(shaderProgram);
    return shaderProgram;
}

顶点着色器

attribute vec2 aPosition;
void main() {
    gl_Position = vec4(aPosition, 0.0, 1.0);
}

片段着色器MAX_ITERATIONSXMINYMINWH应在JavaScript:中进行控制

#ifdef GL_FRAGEMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif
precision mediump int;
#define MAX_ITERATIONS 200
#define XMIN -2.5
#define YMIN -2.0
#define WH 4.0
#define LOG_TWO log(2.0)
#define LOG_MAX log(200.0)
void main() {
    // Normalized pixel position to complex plane position
    float maxPwh = max(640.0, 480.0);
    float x = XMIN+(gl_FragCoord.x/maxPwh)*WH;
    float y = YMIN+(gl_FragCoord.y/maxPwh)*WH;
    // Complex plane window offsets for pixel windows that are not square
    float halfDelta = WH/maxPwh*0.5;
    x -= min((640.0-480.0)*halfDelta, 0.0);
    y -= min((480.0-640.0)*halfDelta, 0.0);
    // Mandelbrot Set code
    float zr = x;
    float zi = y;
    int iterations = 0;
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        iterations = i;
        float sqZr = zr*zr;
        float sqZi = zi*zi;
        float twoZri = 2.0*zr*zi;
        zr = sqZr-sqZi+x;
        zi = twoZri+y;
        if (sqZr+sqZi > 16.0) break;
    }
    if (iterations == MAX_ITERATIONS-1) gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    else {
        float fn = float(iterations)+1.0-log(log(sqrt(zr*zr+zi*zi)))/LOG_TWO;
        float logVal = log(fn)/LOG_MAX;
        gl_FragColor = vec4(logVal, logVal, logVal, 1.0);
    }
}

简单的答案是基本上有两个选项

  1. 通过uniform将值从JavaScript传递到GLSL。

    例如,如果你想通过一个浮动创建一个浮动统一

    uniform float foo;
    

    在JavaScript中编译并链接该着色器,然后查找统一的位置

    var locationOfFoo = gl.getUniformLocation(someProgram, "foo");
    

    您现在可以使用将值传递给GLSL

    gl.useProgram(someProgram)
    gl.uniform1f(locationOfFoo, valueToPass);
    
  2. 编译着色器之前操纵字符串

    #define MAX_INTERATIONS %maxIterations%
    #define XMIN %xMin%
    

    var maxIterations = 123;
    var xMin = 4.5;
    shaderSource = shaderSource.replace(/%maxIterations%/g, maxIterations);
    shaderSource = shaderSource.replace(/%xMin%/g, xMin);
    

(1) 以上内容用于传递经常更改的内容#2用于在编译着色器之前更改着色器#1是几乎100%WebGL程序中使用的一种技术#2经常在动态生成着色器时使用,许多游戏引擎都是这样做的。

我花了大约45分钟来实现gman的答案,因为我一直在犯愚蠢的小错误。因此,这里有一个完整的工作代码示例,用于创建可调整的平铺贴图。

测试范围:Chrome、Internet Explorer和Edge:

    <!DOCTYPE HTML >
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>         GL_TILE_TESTBED           </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>
    
    
    <script id="FRAG_SHADER" type="NOT_JAVASCRIPT">
    
      //Must declare precision before declaring
      //any uniforms:
      ////////////////////////////////////////////////
      #ifdef GL_FRAGMENT_PRECISION_HIGH
        precision highp float;
      #else
        precision mediump float;
      #endif
      precision mediump int;
      ////////////////////////////////////////////////
    
      #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;
     
      float til_wid;
      float til_hig;
      
    
      void main() {
    
        //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.
        //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;
    
        
        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;
        //NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN//
        
        //Use [B]lue channel because why not?
        //BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//
        float GRAD_X = gl_FragCoord.x / CANVAS_WID;
        //BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//
     
        //Set the final [F]ragment colors:
        //FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF//
        gl_FragColor = vec4(norm_X, norm_Y, GRAD_X, 1.0);
        //FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF//
      }
    </script>
    <!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
    <!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
    <!-- SSSSSSSSSS SHADER_SECTION END  SSSSSSSSSS -->
    </head> 
    <!-- HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -->
             
    <!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->                    
    <body onload="ON_LOADED_FUNCTION()" >
    <!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
    
        <h3> Open GL Tile TestBed            <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="45">
        </br>
        
        </br>
        <button onClick="PUT_HIG();">TILE_HEIGHT_IN_PIXELS</button> 
        <input type="text" id="INPUT_HIG" value="45">
        </br>
        
        
         
    <!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->   
    <script id="BOILER_PLATE_CODE">
        function ON_LOADED_FUNCTION(){
            console.log("[ON_LOADED_FUNCTION]");
            main();
        }
        
        //: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");
            SET_ATTR("TIL_WID_EDIT", val);
        }
        
        function PUT_HIG(){
            assert_program_and_gl_exist();
            var val = GET_INPUT_BOX_VALUE("INPUT_HIG");
            SET_ATTR("TIL_HIG_EDIT", val); 
        }
        
        function SET_ATTR(gl_var_name, val){
            if(val < 0 || val > 256 ){
                alert("choose value between 0 to 256");
                return;
            }
         
            var loc; //<--location of variable.
            loc = gl.getUniformLocation(
                program    , 
                gl_var_name
            );
            gl.useProgram(program);
            gl.uniform1f(loc, 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;
        }
        
        //: No "var" prefix, making them global:
        function initGlobals(){
            canvas = document.querySelector("#glCanvas");
            if(!canvas){
                alert("FAILED_TO_GET_CANVAS");
            }else{
                console.log("[GOT_CANVAS]");
            }
            
            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();
        }
        
        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);
          
          
          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 -->
         
    <!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
    </body>
    </html>