我可以在我的第二个立方体上添加纹理吗?

Can I get a texture onto my second cube?

我想征求意见:我目前有以下程序,由两个多色立方体组成,可以通过一个 html 按钮选择它们,并通过按键盘上的相应键来旋转它们。

这是我的 HTML 文件:

<html>
<script id="vertex-shader" type="x-shader/x-vertex">

attribute vec4 vPosition;
attribute vec4 vColor;
varying vec4 fColor;

uniform vec3 theta;
uniform vec4 posiz;

void main()
{
    // Compute the sines and cosines of theta for each of the three axes in one computation.
    vec3 angles = radians( theta );
    vec3 c = cos( angles );
    vec3 s = sin( angles );

    // Remember: these matrices are column-major
    mat4 rx = mat4( 1.0,  0.0,  0.0, 0.0,
                    0.0,  c.x,  s.x, 0.0,
                    0.0, -s.x,  c.x, 0.0,
                    0.0,  0.0,  0.0, 1.0 );

    mat4 ry = mat4( c.y, 0.0, -s.y, 0.0,
                    0.0, 1.0,  0.0, 0.0,
                    s.y, 0.0,  c.y, 0.0,
                    0.0, 0.0,  0.0, 1.0 );


    mat4 rz = mat4( c.z, -s.z, 0.0, 0.0,
                    s.z,  c.z, 0.0, 0.0,
                    0.0,  0.0, 1.0, 0.0,
                    0.0,  0.0, 0.0, 1.0 );

    // position matrix
    mat4 posMat = 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,
                        posiz[0], posiz[1], posiz[2],  1.0 );

    // size matrix
    mat4 sizMat = mat4( posiz[3],  0.0,  0.0,  0.0,
                        0.0,  posiz[3],  0.0,  0.0,
                        0.0,  0.0,  posiz[3],  0.0,
                        0.0,  0.0,  0.0,  1.0 );

    fColor = vColor;
    gl_Position = sizMat * posMat * rz * ry * rx * vPosition;
} 
</script>

<script id="fragment-shader" type="x-shader/x-fragment">

precision mediump float;

varying vec4 fColor;

void
main()
{
    gl_FragColor = fColor;
}
</script>

<script type="text/javascript" src="../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../Common/initShaders.js"></script>
<script type="text/javascript" src="../Common/MV.js"></script>
<script type="text/javascript" src="cubev-1.js"></script>

<body>
<canvas id="gl-canvas" width="512"" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>

<br/>

<input type = "button" value = "First" id = "switchButton" ></input>

</body>
</html>

文件 webgl-utils.js、initShaders.js 和 MV.js 可以在这里找到:

http://www.cs.unm.edu/~angel/WebGL/7E/Common/

这是随附的 javascript 文件:

var canvas;
var gl;

var numVertices  = 36;

var xAxis = 0;
var yAxis = 1;
var zAxis = 2;

var axis1 = 0;
var axis2 = 0;

var rot1 = 1.0; // rate of rotation
var rot2 = 1.0;

var theta1 = [ 0, 0, 0 ];
var theta2 = [ 0, 0, 0 ];

// cube position along x, y, and z axis and size
var posiz1 = [ 0, 0, 0, 1 ];
var posiz2 = [ 0, 0, 0, 1 ];

// used to send info back to html, I think
var thetaLoc;
var posLoc;

var firstCube = true;

    var vertices = [
        vec3( -0.5, -0.5,  0.5 ),
        vec3( -0.5,  0.5,  0.5 ),
        vec3(  0.5,  0.5,  0.5 ),
        vec3(  0.5, -0.5,  0.5 ),
        vec3( -0.5, -0.5, -0.5 ),
        vec3( -0.5,  0.5, -0.5 ),
        vec3(  0.5,  0.5, -0.5 ),
        vec3(  0.5, -0.5, -0.5 )
    ];

    var vertexColors = [
        vec4( 0.0, 0.0, 0.0, 1.0 ),  // black
        vec4( 1.0, 0.0, 0.0, 1.0 ),  // red
        vec4( 1.0, 1.0, 0.0, 1.0 ),  // yellow
        vec4( 0.0, 1.0, 0.0, 1.0 ),  // green
        vec4( 0.0, 0.0, 1.0, 1.0 ),  // blue
        vec4( 1.0, 0.0, 1.0, 1.0 ),  // magenta
        vec4( 1.0, 1.0, 1.0, 1.0 ),  // white
        vec4( 0.0, 1.0, 1.0, 1.0 )   // cyan
    ];

// indices of the 12 triangles that comprise the cube
var indices = [
    1, 0, 3, 3, 2, 1, 2, 3, 7, 7, 6, 2,
    3, 0, 4, 4, 7, 3, 6, 5, 1, 1, 2, 6,
    4, 5, 6, 6, 7, 4, 5, 4, 0, 0, 1, 5
];

window.onload = function init()
{
    canvas = document.getElementById( "gl-canvas" );

    gl = WebGLUtils.setupWebGL( canvas );
    if ( !gl ) { alert( "WebGL isn't available" ); }

    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.clearColor( 1.0, 1.0, 1.0, 1.0 );

    gl.enable(gl.DEPTH_TEST);

    //  Load shaders and initialize attribute buffers
    var program = initShaders( gl, "vertex-shader", "fragment-shader" );
    gl.useProgram( program );

    // array element buffer    
    var iBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array(indices), gl.STATIC_DRAW);

    // color array attribute buffer
    var cBuffer = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, cBuffer );
    gl.bufferData( gl.ARRAY_BUFFER, flatten(vertexColors), gl.STATIC_DRAW );

    var vColor = gl.getAttribLocation( program, "vColor" );
    gl.vertexAttribPointer( vColor, 4, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vColor );

    // vertex array attribute buffer
    var vBuffer = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, vBuffer );
    gl.bufferData( gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW );

    var vPosition = gl.getAttribLocation( program, "vPosition" );
    gl.vertexAttribPointer( vPosition, 3, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vPosition );

    // connect location to variable in html
    thetaLoc = gl.getUniformLocation(program, "theta"); 
    posizLoc = gl.getUniformLocation(program, "posiz");

    //event listeners for buttons
    document.getElementById( "switchButton" ).onclick = function ()
    {
        firstCube = !firstCube; // switch between cubes

        if (firstCube) document.getElementById("switchButton").value = "First";
        else document.getElementById("switchButton").value = "Second";
    };

    window.onkeydown = function(event)
    {
        var key = String.fromCharCode(event.keyCode);

        if (firstCube)
        {
            if (key == 'R') posiz1[0] += .1;
            else if (key == 'L') posiz1[0] -= .1;
            else if (key == 'U') posiz1[1] += .1;
            else if (key == 'D') posiz1[1] -= .1;
            else if (key == 'I') posiz1[2] += .1;
            else if (key == 'O') posiz1[2] -= .1;
            else if (key == 'G') posiz1[3] += .1;
            else if (key == 'S') posiz1[3] -= .1;

            if (event.shiftKey == 0)
            {
                if (key == 'X' || key == 'Y' || key == 'Z') rot1 = -1.0;
            }
            else
            {
                if (key == 'X' || key == 'Y' || key == 'Z') rot1 = 1.0;
            }

            if (key == 'X') axis1 = xAxis;
            if (key == 'Y') axis1 = yAxis;
            if (key == 'Z') axis1 = zAxis;
        }
        else
        {
            if (key == 'R') posiz2[0] += .1;
            else if (key == 'L') posiz2[0] -= .1;
            else if (key == 'U') posiz2[1] += .1;
            else if (key == 'D') posiz2[1] -= .1;
            else if (key == 'I') posiz2[2] += .1;
            else if (key == 'O') posiz2[2] -= .1;
            else if (key == 'G') posiz2[3] += .1;
            else if (key == 'S') posiz2[3] -= .1;

            if (event.shiftKey == 0)
            {   
                if (key == 'X' || key == 'Y' || key == 'Z') rot2 = -1.0;
            }
            else
            {
                if (key == 'X' || key == 'Y' || key == 'Z') rot2 = 1.0;
            }

            if (key == 'X') axis2 = xAxis;
            if (key == 'Y') axis2 = yAxis;
            if (key == 'Z') axis2 = zAxis;
        }
    };

    render();
}

function render()
{
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // handle rendering of first cube
    theta1[axis1] += rot1;

    gl.uniform3fv(thetaLoc, theta1);
    gl.uniform4fv(posizLoc, posiz1);

    gl.drawElements( gl.TRIANGLES, numVertices, gl.UNSIGNED_BYTE, 0 );

    // handle rendering of second cube
    theta2[axis2] += rot2;

    gl.uniform3fv(thetaLoc, theta2);
    gl.uniform4fv(posizLoc, posiz2);

    gl.drawElements( gl.TRIANGLES, numVertices, gl.UNSIGNED_BYTE, 0 );

    requestAnimFrame( render );
}

我对 WebGL 没有什么经验,我正在尝试将纹理映射到第二个立方体而不影响第一个立方体的颜色。我正在尝试按照以下说明进行操作:

https://developer.mozilla.org/en-US/docs/Web/WebGL/Using_textures_in_WebGL#The_fragment_shader

指示我将 html 中的片段着色器更新为我认为会干扰第一个立方体正常渲染的值。

我是不是走错了路?将纹理映射到第二个立方体而不影响第一个立方体的颜色是否现实可行?我想要一个意见,因为我不想追逐可能是死胡同的事情。

顺便说一下,如果您想自己 运行 程序,这里是控件:

u - 上,d - 下,l - 左,r - 右,i - 内,o - 外,g - 增长,s - 收缩

x/y/z - 沿立方体的 x/y/z 轴旋转

shift + x/y/z - 同上但方向相反

按钮在立方体之间切换。

非常感谢您的帮助和提前时间。

你问的是一个相当高层次的问题,所以这里是一个高层次的答案:

当然可以有一个有纹理的对象和一个没有纹理的对象。有两种基本方法:

  1. 使用两个单独的片段着色器,一个执行纹理查找,另一个使用您已经使用的顶点颜色。您会执行 initShaders 两次,并获得两个程序对象。然后只需使用 gl.useProgram 在它们之间切换。

    请注意,uniform 的值特定于一个程序 — 因此,如果您想对两者使用相同的值,则必须为每个程序调用一次 gl.uniform...

  2. 在片段着色器中使用条件并用uniform变量控制:

    varying vec4 fColor;
    varying vec2 texcoord;
    uniform sampler2D texture;
    uniform bool useTexture;
    void main(void) {
      gl_FragColor = useTexture ? texture2D(texture, texcoord) : fColor;
    }
    

    那么您可以根据您是否需要纹理给 useTexture 一个值 0 或 1,而不是像第一个选项那样切换程序。

在这两个选项中,第一个可以有更好的性能,因为它不需要您向数组添加未使用的属性(纹理时没有颜色,着色时没有纹理坐标)并且不需要 GPU 评估条件。

第二种,使用条件,优点是简单。此外,如果您有多个不同的渲染选项,则不需要您为所有可能的选项组合编译着色器(这可能非常慢)。

在冗长的 运行 中,我建议您在工具箱中同时使用这两种技术,并使用最适合问题的一种。在这种特殊情况下,您使用的顶点非常少,着色器非常简单,因此使用哪个并不重要。

我想你可能真的会找到 these tutorials helpful. In particular this one about matrix math

特别是在顶点着色器中创建如此多的矩阵并不常见。通常,所有关于 rx、ry、rz、posMat 和 sizeMat 的代码通常会在 JavaScript 中完成,并且只会将结果发送到着色器。这使得着色器更加灵活,因为您可以在 JavaScript 中执行任何您想要的矩阵数学运算,而不仅仅是您在着色器中执行的 5 个硬编码操作。当然,您的操作方式在技术上也没有任何问题。什么对你有用。我只是指出这不是一种常见的方法。

至于纹理与当前着色器的顶点颜色,对于一个大型程序来说,通常有数百甚至数千个着色器,每个着色器具有不同的功能。事实上,对于 3D 引擎或游戏引擎,根据特定对象所需的功能生成着色器是非常常见的。

同时对于您的特定情况,另一种方法可能是混合颜色然后将一个设置为白色,而不是 2 个着色器

varying vec4 fColor;
varying vec2 texcoord;
uniform sampler2D texture;

void main(void) {
  gl_FragColorl = texture2D(texture, texcoord) * fColor; 
}

所以当你想用纹理绘制时

// set attributes for texcoord
gl.bindBuffer(..... texcoordBuffer);
gl.enableVertexAttribArray(texcoordLocation);
gl.vertexAttribPointer(texcoordLocation, ....);

// set the texture
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(...., texture);

// tell the shader which unit you put the texture on
gl.uniform1i(textureLocation, unit);

// turn OFF the attribute for vertex colors
gl.disableVertexAttribArray(fColorLocation);

// set the attribute so it returns white
gl.vertexAttrib4f(fColorLocation, 1, 1, 1, 1);

当你想用顶点颜色绘制时,你可以做相反的事情

// set attributes for vertex colors
gl.bindBuffer(..... fColorBuffer);
gl.enableVertexAttribArray(fColorLocation);
gl.vertexAttribPointer(fColorLocation, ....);

// turn OFF the attribute for texcoords
gl.disableVertexAttribArray(texcoordLocation);

// bind a white texture
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(...., white1x1PixelTexture);

// tell the shader which unit you put the texture on
gl.uniform1i(textureLocation, unit);

在初始时,您将创建一个 1x1 白色像素纹理

var white1x1PixelTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, white1x1PixelTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
     gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255,255,255,255]));

使用这种样式还意味着您可以同时使用纹理和顶点颜色,这是为地形图等对象添加多样性的常用方法。只需设置纹理和顶点颜色,它们就会相乘。