OpenGL - textury

V této kapitole se naučíte zobrazovat obrázky. Přesněji řečeno, naučíte se, jak použit obrázek jako texturu trojúhelníka nebo krychle.

Použití textury

Použití textury je v zásadě jednoduché. Jen zase musíte udělat asi bambilión úkonů. Stručně sepsáno, jde o následující kroky:

  1. Vytvoříte buffer pro každou texturu, kterou chcete použít.
  2. Do bufferů nahrajete obrázek ve formátu, kterému OpenGL rozumí.
  3. Definujete si souřadnice na textuře, které odpovídají vrcholům trojúhelníků, které chcete otexturovat.
  4. Souřadnice textury (texture coordinate) nahrajete do bufferu pro attribute proměnnou z GLSL shaderu, obdobně, jako jakákoliv jiná vertex data.
  5. V fragment shaderu si vytvoříte uniform proměnnou typu sampler2D (pro každou texturu). Tyto proměnné budou obsahovat ID bufferu textury.
  6. V fragment shaderu použijete interní funkci texture(id,textureCoord) pro získání barvy fragmentu z textury. S touto barvou si pak můžete dělat co chcete.
Ve WebGL musí mít textura ve většině případů výšku a šířku délky mocniny dvou, tj. např. 128, 256, 512, 1024 2048 pixelů (větší texturu už ne každé zařízení zvládne). Více informací viz https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL. Pro OpenGL toto omezení neplatí.

Nahrání obrázku do bufferu

Začněme dvěma prvními body. Potřebujete vytvořit buffer pro texturu a nějaký obrázek v běžném formátu (JPG, PNG, GIF …) do bufferu nahrát ve formátu vhodném pro OpenGL.

Tyto dva kroky udělám ve funkci initBuffers(). Formát vhodný pro OpenGL je nekomprimovaný formát. Interní strukturu vám popisovat nebudu. Ke konverzi běžných obrázků použiji knihovnu stb_image.h.

Knihovna stb_image.h zvládne nahrát běžné formáty obrázků a zkonvertovat je do potřebného formátu pro OpenGL. Není ale úplně dokonalá. Občas se stane, že si s nějakým formátem úplně nerozumí a výsledek je takový divný, šedivý, pruhovaný obrázek, který vypadá jak z rozladěné televize. V takovém případě mi pomohlo obrázek otevřít v gimpu nebo v malování z Windows, uložit ho (bez jakékoliv změny) a najednou vše fungovalo jak má.

Další problém s touto knihovnou je, že při jejím překladu překladač hlásí velké množství varování. A protože se jedná jen o hlavičkový soubor, tak se tato varování vypisují při každém překladu zdrojového souboru, který tento hlavičkový soubor includuje. Z tohoto důvodu jsem vytvořil soubory stb-image.h a stb-image.c.

S novou verzí stb_image.h už se žádné varování neukazuje, ale soubory stb-image.h a stb-image.c jsem už ve zdrojácích ponechal.

Soubor stb-image.h vypadá takto:

#define STB_IMAGE_IMPLEMENTATION
#ifdef STB_IMAGE_STATIC
#define STBIDEF static
#else
#define STBIDEF extern
#endif
typedef unsigned char stbi_uc;

STBIDEF stbi_uc *sx_stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp);
STBIDEF void sx_stbi_image_free (void *retval_from_stbi_load);

První řádek definuje makro, které se má definovat, podle dokumentace k stb_image.h. Nevím proč, bez řečí dokumentaci poslouchám. (Má se includovat v jednom souboru, aby se definovala jakási implementace.)

Další definovaná makra tu jsou kvůli kompatiblitě s gcc a Visual Studiem.

Následuje deklarace funkcí, které jsem definoval v stb-image.c:

#include "stb-image.h"
#include "stb_image.h"

STBIDEF stbi_uc *sx_stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp)
{
        return stbi_load (filename, x, y, comp, req_comp);
}

STBIDEF void sx_stbi_image_free (void *retval_from_stbi_load) {
        stbi_image_free (retval_from_stbi_load);
}

První funkce volá stbi_load() z stb_image.h, která je zodpovědná na načtení a zkonvertování obrázku do formátu vhodného pro OpenGL. Vrací ukazatel na alokovanou paměť, kde je zkonvertovaný obrázek uložen. Druhá funkce se stará o uvolnění této alokované paměti.

První argument funkce je cesta v souborovém systému k obrázku. Do druhého a třetího argumentu funkce uloží šířku a výšku obrázku. Do čtvrtého argumentu uloží počet složek barvy, ze které se skládá jeden pixel. PNG a GIF obrázky mívají uložen alfa kanál (průhlednost), takže mají obvykle 4 složky (Red, Green, Blue, Alpha). JPEG průhlednost neukládá, takže má většinou 3 složky (RGB). (Ale může jich mít i méně.)

Tak to sme si udělali odbočku ke knihovně stbi_load.h a teď zpátky k našemu příkladu, k funkci initBuffer().

  1. void initBuffers(Shader *program)
  2. {
  3.         glGenVertexArrays(sizeof(VAO) / sizeof(VAO[0]), VAO);
  4.         glGenBuffers(sizeof(BO) / sizeof(BO[0]), BO);
  5.         glGenTextures(sizeof(TEX) / sizeof(TEX[0]), TEX);
  6.  
  7.         glBindTexture(GL_TEXTURE_2D, TEX[0]);
  8.         {
  9.                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  10.                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  11.  
  12.                 int width, height, n;
  13.                 unsigned char *image = sx_stbi_load("logo.png", &width, &height, &n, 0);
  14.                 printf("w = %i, h = %i, n = %i\n", width, height, n);
  15.                 if (n == 4) {
  16.                         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
  17.                 }
  18.                 else {
  19.                         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
  20.                 }
  21.                 sx_stbi_image_free(image);
  22.         }
  23.         glBindTexture(GL_TEXTURE_2D, 0);
  24.  

Na začátku se generují buffery pro (jeden) VAO a (dva) BO, jak už znáte z předchozích kapitol. Navíc se generuje buffer pro texturu pomocí funkce glGenTextures(). Globální proměnná TEX je definována jako GLuint TEX[1]; (prozatím si vystačíme s jednou texturou). To je tedy krok 1.

Následuje krok 2. Nejdříve se načte obrázek ze souboru do paměti, pak se pomocí funkce glTexImage2D() nahraje do bufferu grafické karty a nakonec se uvolní již nepotřebná paměť.

Buffery fungují, jako obvykle, jako stavové automaty. Nejdříve se tedy musí přibindovat voláním glBindTexture().

OpenGL podporuje mnoho druhů textur. Textura GL_TEXTURE_2D se používá pro 2D obrázek. Později v této kapitole vám ještě představím GL_TEXTURE_CUBE_MAP.
Jaké další existují můžete vidět v nápovědě k funkci glTexImage2D().

Než se dostanu k popisu této funkce, musím zmínit glTexParameteri(). Tato funkce se používá k nastavování všelijakých vlastností zrovna přibindované textury. Můžete mít přibindováno více textur různých druhů (ale vždy jen jednu texturu pro jeden druh), proto má jako první argument typ textury, na kterou se má nastavovavná vlastnost aplikovat. V příkladu se nastavuje funkce, která se má použít pri zmenšování (GL_TEXTURE_MIN_FILTER), resp. zvětšování obrázku (GL_TEXTURE_MAG_FILTER). V obou případech nastavuji funkci (resp. identifikátor GL funkce) na GL_LINEAR. Další možná funkce je např. GL_NEAREST. Jak se funce liší, najdete v dokumentaci (rozdíly jsou hlavně v kvalitě vs rychlosti). Nebo si je vyzkoušejte a uvidíte jak zvětšná/zmenšená textura vypadá. Jen vězte, že bez tohoto nastavení se vám textura nezobrazí.

A teď k argumentům funkce glTexImage2D().

První argument určuje, do bufferu jakého typu textury se obrázek nahraje. Jak už jsem psal, můžete mít přibindováno více bufferů pro různé typy textur, ale pro daný typ textury vždy jen jeden buffer. Takže určením typu textury jednoznačně určíte, do jakého bufferu se data nahrají.

Druhý argument se používá pro nahrávání tzv. mipmap. Zmenšování obrázku je výpočetně náročná operace a také zapříčiňuje ztrátu kvality. OpenGL můžete trochu ulehčit vygenerováním tzv. mipmap. OpenGL si předgeneruje obrázek v různých velikostech, takže pak při vykreslení textury použije nejbližší předgenerovanou mipmpapu a zmenšení/zvětšení už není tak náročné.

Zavoláním funkce glTexImage2D() s druhým argumentem 0 nahrajete „defaultní“ obrázek, ze kterého se počítají všechny další velikosti. Zavolání glTexImage2D() s druhým argumentem n > 0 vám umožňuje nahrát n-tou mipmapu. To vám umožňuje vylepšit kvalitu zmenšené textury, nebo při zmenšení použít úplně jinou texturu :-).

Třetí argument určuje interní formát, ve kterém bude textura uložena. Když necháme stranou některé exotické možnosti, tak nám zbývá GL_RGB nebo GL_RGBA. Pokud vás průhlednost nezajímá, respektive nahráváte obrázek, který průhlednost neobsahuje, pak použijte GL_RGB, které interně zabírá méně místa.

Techničtější informace o interním formátu naleznete na www.opengl.org/wiki/Image_Format.

Další parametry popisují formát nahrávaného obrázku. Nejdříve se určuje šířka a výška obrázku, další argument, border, musí být vždy 0 (to je nějaký historický nesmysl). Další argument říká, jak jsou definované barvy pixelu (nejpoužívanější jsou zase GL_RGB nebo GL_RGBA) a poslední argument je odkaz na vlastní data obrázku.

Pokud změníte interní formát obrázku z G_RGBA na G_RGB, přijdete o průhlednost (místo průhledných oblastí se vám zobrazí nejspíše černá barva). Pokud ale změníte formát pixelů nahrávaného obrázku, tak se vám obrázek nahraje blbě a uvidíte „efekt rozladěné televize“. Vyzkoušejte za domácí úkol :-).

Definování souřadnic textury

V druhé části funkce initBuffers() se nahrávají vertex data – vrcholy trojúhelníků a souřadnice textury odpovídající jednotlivým vrcholům. To už znáte z předchozích kapitol.

  1.         glBindVertexArray(VAO[0]);
  2.         {
  3.                 //vertices
  4.                 glBindBuffer(GL_ARRAY_BUFFER, BO[0]);
  5.                 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  6.                 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
  7.                 glEnableVertexAttribArray(0);
  8.                 //textures
  9.                 glBindBuffer(GL_ARRAY_BUFFER, BO[2]);
  10.                 glBufferData(GL_ARRAY_BUFFER, sizeof(texCoord), texCoord, GL_STATIC_DRAW);
  11.                 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
  12.                 glEnableVertexAttribArray(2);
  13.         }
  14.         glBindVertexArray(0); // Unbind VAO
  15. }

Vertex data jsem zase definoval v souboru vertex-data.h. Vrcholy trojúhelníků takto:

#ifndef  VERTEX_DATA_H
#define VERTEXT_DATA_H
#include <GLFW/glfw3.h>

GLfloat vertices[] = {
    // right triangle
    1.25, 1.0, 0.0,   // Vrchol A
    1.25, -1.0, 0.0,  //        B
    -0.75, -1.0, 0.0, //        C
    // left triangle
    -1.0, 1.0, 0.0,   //        D
    1.0, 1.0, 0.0,    //        E
    -1.0, -1.0, 0.0   //        F
};
 

Trojúhelníky jsem od sebe malinko odsadil, aby byly lépe rozeznatelné. Pro lepší představu jsem vám tentorkát nakreslil i obrázek:

Vertices

Vertex data – vertices

Souřadnice textury mají počátek [0,0] v levém dolním rohu. Pravý horní roh má souřadnice [1,1]. Viz následující obrázek.

TexCoord

Vertex data – TexCoord

Souřadnice textury, kde vrcholy obrázku odpovídají vrcholům trojúhelníků, vypadají takto:

GLfloat texCoord[] = {
        // right triangle
        1.0, 1.0,
        0.0, 1.0,
        0.0, 0.0,
        // left triangle
        1.0, 0.0,
        1.0, 1.0,
        0.0, 0.0
};
#endif

Výsledné natočení obrázku je dáno nejen tím, jak definujete souřadnice textury, ale i tím, jak jsou definovány vrcholy trojúhelníků. Je rozdíl v tom, jestli jsou definované po směru nebo proti směru hodinových ručiček, protože v jednom případě se díváte na zadní stranu a v druhém na přední stranu trojúhelníka. Konec konců, v programu můžete scénu myší otáčet a tím si nastavit polohu trojúhelníka – pozici textury – interaktivně. Zkrátka, občas je to docela alchimie nastavit pozici textury tak, jak chcete.

Definice shaderů

Začněme vertex shaderem:

#version 330 core
layout (location = 0) in vec3 vertexPosition;
layout (location = 2) in vec2 textureCoord;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
 
out vec4 vColor;
out vec2 texCoord;

void main(void) {
    vec4 mvPosition = uMVMatrix * vec4(vertexPosition, 1.0);
    gl_Position = uPMatrix * mvPosition;
    vColor = vec4(0.0, 0.0, 1.0, 1.0);
    texCoord = vec2(textureCoord.x, textureCoord.y);
}

V shaderu můžete vidět nějaké pohrobky z kapitoly o osvětlení, těch si nemusíte všímat. Důležitá je jen definice textureCoord, kam se budou sypat souřadnice textury, dále out vec2 texCoord, což je proměnná, kterou se budou souřadnice předávat do fragment shaderu. A na konec poslední řádek, kde se souřadnice předávají do fragment shaderu skrze texCoord.

Souřadnice jsem mohl předat jednoduše takto: texCoord = textureCoord;. Proč jsem tak neučinil, to se dozvíte v zápětí.

A teď, o trochu zajímavější, fragment shader:

#version 330 core

uniform vec3 uAmbientColor;
uniform sampler2D texture1;

in vec4 vColor;
in vec2 texCoord;

void main(void) {
        vec3 vLightIntenzity = uAmbientColor;
        vec4 c = vec4(vColor.rgb * vLightIntenzity, vColor.a);
        if (uAmbientColor.r + uAmbientColor.g + uAmbientColor.b > 0.0) {
                gl_FragColor = mix(texture(texture1, texCoord), c, 0.5);
        }
        else {
                gl_FragColor = texture(texture1, texCoord);
        }
}

Uniform proměnná texture1 obsahuje index odkazující na naší texturu. Jak je to ale možné, když jsem nikde tuto proměnnou v main.c nenastavoval?

Pokud uniform nenastavíte, defaultně je 0. Ve chvíli, kdy texturu přibindujete (voláním funkce glBindTexute()), tak se textura spojí s tzv. texture unit. Defaultní texture unit je GL_TEXTURE0, které odpovídá index 0.
Podrobněji se k tomuto tématu vrátím v další části.

Získání barvy z textury je pak jen otázkou zavolání funkce texture(), tak jak vidíte v příkladu.

Funkce texture() je přetížená funkce, což znamená, že si dokáže poradit s různými typy textur. Ve starších verzích OpenGL a ve WebGL tato funkce neexistuje a musíte použít funkci dle typu textury. Např. pro naší GL_TEXTURE_2D je to texture2D(), pro cubemap, popisovanou níže, je to textureCube() atp.

Jako takový bombónek na konec najdete v fragment shaderu kus kódu, který, v případě že uAmbientColor není černá barva, smíchá barvu s barvou textury pomocí funkce mix(). Tato funkce smíchá své dva první argumenty v poměru daném třetím argumentem (v příkladu 50:50). Poměr může být zadán číslem od 0.0 do 1.0.

Výsledek můžete vidět zde:

Textura

Animace textury. V dalších záložkách můžete vidět WebGL použité pro tuto animaci.

<script id="shader-vs-textura" type="x-shader/x-vertex">
    attribute vec3 vertexPosition;
    attribute vec2 textureCoord;
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec4 vColor;
    varying vec2 texCoord;

    void main(void) {
        vec4 mvPosition = uMVMatrix * vec4(vertexPosition, 1.0);
        gl_Position = uPMatrix * mvPosition;
        vColor = vec4(0.0, 0.0, 1.0, 1.0);
        texCoord = vec2(textureCoord.x, textureCoord.y);
    }
</script>

<script id="shader-fs-textura" type="x-shader/x-fragment">
    precision mediump float;

    uniform vec3 uAmbientColor;
    uniform sampler2D texture1;

    varying vec4 vColor;
    varying vec2 texCoord;

    void main(void) {
        vec3 vLightIntenzity = uAmbientColor;
        vec4 c = vec4(vColor.rgb * vLightIntenzity, vColor.a);
        if (uAmbientColor.r + uAmbientColor.g + uAmbientColor.b > 0.0) {
                gl_FragColor = mix(texture2D(texture1, texCoord), c, 0.5);
        }
        else {
                gl_FragColor = texture2D(texture1, texCoord);
        }
    }
</script>
var vertices = [
    // right triangle
    1.25, 1.0, 0.0,
    1.25, -1.0, 0.0,
    -0.75, -1.0, 0.0,
    // left triangle
    -1.0, 1.0, 0.0,
    1.0, 1.0, 0.0,
    -1.0, -1.0, 0.0
];

var texCoord = [
    // right triangle
    1.0, 1.0,
    0.0, 1.0,
    0.0, 0.0,
    // left triangle
    1.0, 0.0,
    1.0, 1.0,
    0.0, 0.0
];
var variables = {};
var buffers = {};
var useColor = {};
useColor.ambient = 0;

function initVariables(gl, shaderProgram) {
    variables.vertexPositionLoc = gl.getAttribLocation(shaderProgram, "vertexPosition");
    variables.textureCoordLoc = gl.getAttribLocation(shaderProgram, "textureCoord");
    gl.enableVertexAttribArray(variables.vertexPositionLoc);
    gl.enableVertexAttribArray(variables.textureCoordLoc);

    variables.uMVMatrixLoc = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    variables.uPMatrixLoc = gl.getUniformLocation(shaderProgram, "uPMatrix");
    variables.uAmbientColorLoc = gl.getUniformLocation(shaderProgram, "uAmbientColor");
    variables.texture1 = gl.getUniformLocation(shaderProgram, "texture1");
}
 
var initBuffers = function(gl) {
    buffers.positionBuffer = gl.createBuffer();
    buffers.texCoordBuffer = gl.createBuffer();
    buffers.texture1 = gl.createTexture();

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoord), gl.STATIC_DRAW);

    var logoImage = new Image();
    logoImage.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture1);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, logoImage);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    };
    logoImage.src = '/rc/sally/images/c/opengl/logo.gif';

}
    $(function() {
        var lastX, lastY;
        var canvas1 = document.getElementById('textura-canvas');
        var lastDownTarget;
        $(document).on('mousedown', function(event) {
            lastDownTarget = event.target;
            lastX = event.pageX; lastY = event.pageY;
        });

        $(document).on('mousemove', function(event) {
            if(lastDownTarget != canvas1)
                return;
            var buttonDown = event.buttons == null ?event.wich : event.buttons;
            if(!buttonDown) return;

            x = event.pageY - lastY;
            y = event.pageX - lastX;
            lastX = event.pageX;
            lastY = event.pageY;
               
            var xAngle = WebGLUtils.degToRad(x/5.0);
            var yAngle = WebGLUtils.degToRad(y/5.0);
            updateAngles(xAngle, yAngle);
            event.preventDefault();
            return false;
        });
    });
    var rotationMatrix = mat4.create();
    mat4.identity(rotationMatrix);

    function updateAngles(xAngle, yAngle) {
         var newRotationMatrix = mat4.create();
         mat4.identity(newRotationMatrix);
         mat4.rotate(newRotationMatrix, newRotationMatrix, xAngle, [1, 0, 0]);
         mat4.rotate(newRotationMatrix, newRotationMatrix, yAngle, [0, 1, 0]);
         mat4.multiply(rotationMatrix, newRotationMatrix, rotationMatrix);
    }

    function drawScene(gl, canvas) {
        gl.viewport(0, 0, canvas.width, canvas.height);
       
        gl.clearColor(1, 0,945, 0,8925, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        var uPMatrix = mat4.create();
        var uMVMatrix = mat4.create();

        mat4.identity(uPMatrix);
        mat4.identity(uMVMatrix);

        /********************************************/
        mat4.perspective(uPMatrix, WebGLUtils.degToRad(45), canvas.width / canvas.height, 1.0, 100.0);
        mat4.translate(uMVMatrix, uMVMatrix, [0.0, 0.0, -10.0]);
        mat4.multiply(uMVMatrix, uMVMatrix, rotationMatrix);
        /********************************************/
        gl.uniformMatrix4fv(variables.uMVMatrixLoc, false, uMVMatrix);
        gl.uniformMatrix4fv(variables.uPMatrixLoc, false, uPMatrix);

        if (useColor.ambient) {
                gl.uniform3f(variables.uAmbientColorLoc, 0.4, 0.4, 0.4);
        }
        else {
                gl.uniform3f(variables.uAmbientColorLoc, 0.0, 0.0, 0.0);
        }

        // vykresli logo
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture1);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
        gl.vertexAttribPointer(variables.vertexPositionLoc, 3, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
        gl.vertexAttribPointer(variables.textureCoordLoc, 2, gl.FLOAT, false, 0, 0);
        gl.drawArrays(gl.TRIANGLES, 0, vertices.length/3);
    }

    function animate() {
    }
function tick(gl, canvas) {
    drawScene(gl, canvas);
    animate();
    requestAnimFrame(function() { tick(gl, canvas);});
}


function webGLStart() {
    var canvas = document.getElementById("textura");
    var gl = WebGLUtils.setupWebGL(canvas,{ antialias: true });
    if(!gl) return;
    var shaderProgram = WebGLUtils.initShaders(gl, "shader-vs-textura","shader-fs-textura");
    gl.useProgram(shaderProgram);
    initVariables(gl,shaderProgram);
    initBuffers(gl);
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    tick(gl, canvas);
}

webGLStart();

Pozorný čtenář si jistě všiml, že je obrázek hlavou dolů. To je dáno tím, že data pro obrázek jsou nahraná od horního levého rohu a ne od dolního, jak se zadávají souřadnice. Otoční obrázku je jen otázkou změny y-ové souřadnice v vertex shaderu:

    //...
    texCoord = vec2(textureCoord.x, -textureCoord.y);
    //...

Můžete také použít funkci z stb_image.h, kterou stačí zavolat před načtením obrázku:

stbi_set_flip_vertically_on_load(true);

To už vám nechám za domácí úkol.

Více textur

V dalším příkladu se vám pokusím vysvětlit použití texture units. V příkladu budou použity dvě textury (v jednom shader programu), takže se použití texture units nedá vyhnout.

První obrázek je smějící se kuci.gif, druhý obrázek rozzuřený kuci2.gif.

V příkladu zopakuji obrázek na textuře 4x (v ose x i y). A navíc se rozzuřený „Kučí“ bude zobrazovat jen pokud bude myš nad oknem.

Jde tedy celkem o 3 změny, jejichž výsledek si můžete prohlédnout zde:

Textury
<script id="shader-vs-textury" type="x-shader/x-vertex">
    attribute vec3 vertexPosition;
    attribute vec2 textureCoord;
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec2 texCoord;
    varying vec4 position;

    void main(void) {
        vec4 mvPosition = uMVMatrix * vec4(vertexPosition, 1.0);
        gl_Position = uPMatrix * mvPosition;
        position = gl_Position;
        texCoord = vec2(textureCoord.x, textureCoord.y);
    }
</script>

<script id="shader-fs-textury" type="x-shader/x-fragment">
    precision mediump float;

    uniform sampler2D texture1;
    uniform sampler2D texture2;
    uniform bool mouseIn;

    varying vec2 texCoord;
    varying vec4 position;

    void main(void) {
        if(!mouseIn) {
                gl_FragColor = texture2D(texture1, texCoord);
                return;
        }
        if(texCoord.x > 1.0 && texCoord.x < 3.0 && texCoord.y > 1.0 && texCoord.y < 3.0) {
                gl_FragColor = texture2D(texture2, texCoord);
        }
        else {
                gl_FragColor = texture2D(texture1, texCoord);
        }
    }
</script>
var vertices = [
    // right triangle
    1.0, 1.0, 0.0,
    1.0, -1.0, 0.0,
    -1.0, -1.0, 0.0,
    // left triangle
    -1.0, 1.0, 0.0,
    1.0, 1.0, 0.0,
    -1.0, -1.0, 0.0
];

var texCoord = [
        // right triangle
        4.0, 0.0,
        4.0, 4.0,
        0.0, 4.0,
        // left triangle
        0.0, 0.0,
        4.0, 0.0,
        0.0, 4.0
];
var variables = {};
var buffers = {};

function initVariables(gl, shaderProgram) {
    variables.vertexPositionLoc = gl.getAttribLocation(shaderProgram, "vertexPosition");
    variables.textureCoordLoc = gl.getAttribLocation(shaderProgram, "textureCoord");
    gl.enableVertexAttribArray(variables.vertexPositionLoc);
    gl.enableVertexAttribArray(variables.textureCoordLoc);

    variables.uMVMatrixLoc = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    variables.uPMatrixLoc = gl.getUniformLocation(shaderProgram, "uPMatrix");
    variables.mouseIn = gl.getUniformLocation(shaderProgram, "mouseIn");
    variables.texture1 = gl.getUniformLocation(shaderProgram, "texture1");
    variables.texture2 = gl.getUniformLocation(shaderProgram, "texture2");
}
 
var initBuffers = function(gl) {
    buffers.positionBuffer = gl.createBuffer();
    buffers.texCoordBuffer = gl.createBuffer();
    buffers.texture1 = gl.createTexture();
    buffers.texture2 = gl.createTexture();

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoord), gl.STATIC_DRAW);

    var kuciImage = new Image();
    kuciImage.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture1);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, kuciImage);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    };
    kuciImage.src = '/rc/sally/images/c/opengl/kuci.gif';

    var kuci2Image = new Image();
    kuci2Image.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture2);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, kuci2Image);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    };
    kuci2Image.src = '/rc/sally/images/c/opengl/kuci2.gif';
}
    var mouseIn = 0;
    $(function() {
        var lastX, lastY;
        var canvas1 = document.getElementById('textury-canvas');
        var lastDownTarget;
        $(document).on('mousedown', function(event) {
            lastDownTarget = event.target;
            lastX = event.pageX; lastY = event.pageY;
        });

        $(document).on('mousemove', function(event) {
            if(lastDownTarget != canvas1)
                return;
            var buttonDown = event.buttons == null ?event.wich : event.buttons;
            if(!buttonDown) return;

            x = event.pageY - lastY;
            y = event.pageX - lastX;
            lastX = event.pageX;
            lastY = event.pageY;
               
            var xAngle = WebGLUtils.degToRad(x/5.0);
            var yAngle = WebGLUtils.degToRad(y/5.0);
            updateAngles(xAngle, yAngle);
            event.preventDefault();
            return false;
        });
        $(canvas1).on('mouseenter', function(event) { mouseIn = 1; });
        $(canvas1).on('mouseleave', function(event) { mouseIn = 0; });
    });
    var rotationMatrix = mat4.create();
    mat4.identity(rotationMatrix);

    function updateAngles(xAngle, yAngle) {
         var newRotationMatrix = mat4.create();
         mat4.identity(newRotationMatrix);
         mat4.rotate(newRotationMatrix, newRotationMatrix, xAngle, [1, 0, 0]);
         mat4.rotate(newRotationMatrix, newRotationMatrix, yAngle, [0, 1, 0]);
         mat4.multiply(rotationMatrix, newRotationMatrix, rotationMatrix);
    }

    function drawScene(gl, canvas) {
        gl.viewport(0, 0, canvas.width, canvas.height);
       
        gl.clearColor(1, 0,945, 0,8925, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        var uPMatrix = mat4.create();
        var uMVMatrix = mat4.create();

        mat4.identity(uPMatrix);
        mat4.identity(uMVMatrix);

        /********************************************/
        mat4.perspective(uPMatrix, WebGLUtils.degToRad(45), canvas.width / canvas.height, 1.0, 100.0);
        mat4.translate(uMVMatrix, uMVMatrix, [0.0, 0.0, -2.5]);
        mat4.multiply(uMVMatrix, uMVMatrix, rotationMatrix);
        /********************************************/
        gl.uniformMatrix4fv(variables.uMVMatrixLoc, false, uMVMatrix);
        gl.uniformMatrix4fv(variables.uPMatrixLoc, false, uPMatrix);

        gl.uniform1i(variables.texture1, 0);
        gl.uniform1i(variables.texture2, 1);
        gl.uniform1i(variables.mouseIn, mouseIn);

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture1);
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture2);

        // vykresli
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
        gl.vertexAttribPointer(variables.vertexPositionLoc, 3, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
        gl.vertexAttribPointer(variables.textureCoordLoc, 2, gl.FLOAT, false, 0, 0);
        gl.drawArrays(gl.TRIANGLES, 0, vertices.length/3);
    }

    function animate() {
    }
function tick(gl, canvas) {
    drawScene(gl, canvas);
    animate();
    requestAnimFrame(function() { tick(gl, canvas);});
}


function webGLStart() {
    var canvas = document.getElementById("textura", { antialias: true });
    var gl = WebGLUtils.setupWebGL(canvas);
    if(!gl) return;
    var shaderProgram = WebGLUtils.initShaders(gl, "shader-vs-textury","shader-fs-textury");
    gl.useProgram(shaderProgram);
    initVariables(gl,shaderProgram);
    initBuffers(gl);
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    tick(gl, canvas);
}

webGLStart();

Texture units

Pokud chcete použít více textur v jednom shaderu, musíte každou z nich připojit k jiné texture unit. To se dělá tak, že se nejdříve příslušná texutre unit aktivuje (pomocí glActiveTexture()) a pak se zavolá glBindTexture().

Funkce glActiveTexture() má jako argument id texture unit. První je GL_TEXTURE0, druhá GL_TEXTURE1 atd. Máte zaručeno, že existují alespoň dvě texture units.

Následující kus kódu určuje, kolik jich máte k dispozici:

        GLint max_combined_texture_image_units;
        glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_combined_texture_image_units);
        printf("Max texture units = %i\n", max_combined_texture_image_units);

Texture units můžete indexovat i jako GL_TEXTURE0 + i (kde i < max_combined_texture_image_units).

Prakticky to tedy znamená, že, oproti předchozímu příkladu, se ve funkci initBuffers() vytvoří dva TEX buffery, do každého se nahraje jeden obrázek (kuci.gif a kuci2.gif) a pak se zavolá následující:

//...
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, TEX[0]);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, TEX[1]);
//...

V rámci optimalizace můžete zavolat glActiveTexture() už před bindováním textur při jejich nahrávání. První volání s GL_TEXTURE0 není úplně nutné, protože tato texture unit je zapnutá defaultně, ale je to dobrý zvyk, jak se vyhnout chybám v případě, že v nějaké jiné části kódu se už glActiveTexture() někde volalo.

Ve fragment shaderu se vytvoří potřebné uniform proměnné:

//...
uniform sampler2D texture1;
uniform sampler2D texture2;
//...

A ve funkci draw() (nebo kde se vám to hodí), se uniform proměnné inicializují:

//...
glUniform1i(variables.texture1, 0);
glUniform1i(variables.texture2, 1);
//...

Ač to hodně svádí k tomu, abyste místo čísel 0 a 1 použili konstanty GL_TEXTURE0 a GL_TEXTURE1, tak tyto konstanty rozhodně nejsou definovány jako 0 a 1. Je to možná šokující, ale je to tak.

A to je vše, co je potřeba pro použití více textur v jednom shader programu.

Opakování textury

Pokud určíte souřadnice textury větší než 1.0, například 4.0, pak textura vyplní na trojúhelníku vše, co odpovídá prostoru 0.0 až 1.0 (první čtvrtině). Čím se vyplní zbytek, prostor (1.0 až 4.0)? To se nastavuje pomccí parametru GL_TEXTURE_WRAP_S (pro směr x) a GL_TEXTURE_WRAP_T (pro směr y). Parametry se nastavují funkcí glTexParameteri() a mohou nabývat jedné z následujících hodnot: GL_REPEAT, GL_MIRRORED_REPEAT, GL_CLAMP_TO_EDGE a GL_CLAMP_TO_BORDER.

V příkladu chci texturu opakovat, takže souřadnice struktury vynásobím čtyřmi a někde po přibindování textury zavolám následující kód:

    //...
    glBindTexture(GL_TEXTURE_2D, TEX[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //...
    glBindTexture(GL_TEXTURE_2D, TEX[1]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //...
GL_REPEAT
říká, že se má obrázek opakovat
GL_MIRRORED_REPEAT
říká, že se má obrázek opakovat a každý druhý zrcadlově otočit.
GL_CLAMP_TO_EDGE
říká, že se má okraj obrázku protáhnout, jako duha, do konce vyplňované plochy.
GL_CLAMP_TO_BORDER
Zbytek plochy se vyplní border barvou.

Pokud použijete poslední možnost, musíte nastavit border barvu. Třeba nějak takto:

//...
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
//...

Obrázky Kučího, které jsem použil dříve, mají na okraji jen průhledné pozadí. Proto by se na nich GL_CLAMP_TO_EDGE moc hezky neukázalo. Přidal jsem tedy barevné pozadí:

GL_REPEAT GL_MIRRORED_REPEAT GL_CLAMP_TO_EDGE GL_CLAMP_TO_BORDER

Události myši

Druhá textura se zobrazí jen při najetí myši nad okno. Událostem vstupu/výstupu myši z okna se říká enter / leave events.

Ve funkci main() se zaregistruje funkce pro obsluhu těchto událostí:

glfwSetCursorEnterCallback(window, cursor_enter_callback);

Funkci cursor_enter_callback() jsem definoval v souboru events.h, spolu s globální proměnnou mouseIn, která udržuje informaci o tom, zda je myš nad oknem nebo ne:

//...
static GLuint mouseIn = 0;
//...
static void cursor_enter_callback(GLFWwindow* window, int entered)
{
        mouseIn = entered;
}

Tuto globální proměnnou pak předám pomocí uniform proměnné do fragment shaderu (ve funkci draw()):

 //...
 glUniform1i(variables.mouseIn, mouseIn);
 //...

A to je vše (vytvoření a inicializaci variables.mouseIn zvládnete sami). Toto je výsledný fragment shader, který používá dvě textury:

#version 330 core

uniform sampler2D texture1;
uniform sampler2D texture2;
uniform bool mouseIn;

in vec2 texCoord;
in vec4 position;

void main(void) {
        if(!mouseIn) {
                gl_FragColor = texture(texture1, texCoord);
                return;
        }
        if(texCoord.x > 1.0 && texCoord.x < 3.0 && texCoord.y > 1.0 && texCoord.y < 3.0) {
                gl_FragColor = texture(texture2, texCoord);
        }
        else {
                gl_FragColor = texture(texture1, texCoord);
        }
}

Souřadnice textury byly definované od 0.0 do 4.0. Podmínka v shaderu zajistí, že se druhá textura použije uprostřed otexturovaného čtverce (pokud je mouseIn true), tj v rozsahu od 1.0 do 3.0.

Za domácí úkol můžete přepsat podmínku s pomocí position, aby se druhá textura používala uprostřed okna (bez ohledu na natočení čtverce).

Podmínce v shaderu s mouseIn se dá vyhnout. Stačí ve funkci draw() nahrát do variables.texture2 ten správný index dle mouseIn proměnné. To bude výhodné, pokud bude myš většinu času nad oknem. Jinak je to nevýhodné, protože se bude častěji provádět složitější podmínka s texCoord

Skybox

Skybox

Skybox

Doposud všechny zobrazované objekty plavaly ve vzduchoprázdnu. Skybox je jedna z možností, jak simulovat prostor.

Na obrázku je příklad jednoho takového skyboxu. Jedná se o šest čtverců, které se namapují na krychli, uvnitř které se budou zobrazovat další objekty.

Přiklad vychází z prvního příkladu této kapitoly. Navíc jen definuje vertex data pro krychli a souřadnice textury.

Jednotlivé díly textury jsou v skyboxu rozdělené do tří řádků a čtyř sloupců. Takže souřadnice textur pro jednotivé strany krychle vypadají nějak takto:

GLfloat boxTexCoord[] = {
        //back
        0.25, 1.0/3,
         .5,  1.0/3,
        0.25, 2.0/3,
         .5,  1.0/3,
         .5,  2.0/3,
        0.25, 2.0/3,
        //left
        0.0,  1.0/3,
         .25, 1.0/3,
        0.0,  2.0/3,
        0.25, 1.0/3,
         .25, 2.0/3,
        0.0,  2.0/3,
        //...
};

Shadery vypadají stejně jako v prvním příkladu. Shader program se jen zavolá 2x. Jednou pro krychli se skyboxem a podruhé pro čtverec s logem.

Rozměry krychle jsem udělal od -1000.0 do 10000.0, takže se do krychle leccos vejde :-). Aby se krychle zobrazila, musil jsem zvětšit zorné pole perspektivy:

...
mat4x4_perspective(uPMatrix, degToRad(90.0), ((GLfloat)width) / height, 1.0, 2000.0);
...

V tomto příkladu nebyla potřeba žádná nová dovednost. Ale je to hezká ukázka toho, že můžete použít jednu texturu pro otexturování více povrchů (a ještě nám na textuře zbylo volné (čeré) místo).

Skybox
<script id="shader-vs-skybox" type="x-shader/x-vertex">
    attribute vec3 vertexPosition;
    attribute vec2 textureCoord;
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec4 vColor;
    varying vec2 texCoord;

    void main(void) {
        vec4 mvPosition = uMVMatrix * vec4(vertexPosition, 1.0);
        gl_Position = uPMatrix * mvPosition;
        vColor = vec4(0.0, 0.0, 1.0, 1.0);
        texCoord = vec2(textureCoord.x, -textureCoord.y);
    }
</script>

<script id="shader-fs-skybox" type="x-shader/x-fragment">
    precision mediump float;

    uniform vec3 uAmbientColor;
    uniform sampler2D texture1;

    varying vec4 vColor;
    varying vec2 texCoord;

    void main(void) {
        vec3 vLightIntenzity = uAmbientColor;
        vec4 c = vec4(vColor.rgb * vLightIntenzity, vColor.a);
        if (uAmbientColor.r + uAmbientColor.g + uAmbientColor.b > 0.0) {
                gl_FragColor = mix(texture2D(texture1, texCoord), c, 0.5);
        }
        else {
                gl_FragColor = texture2D(texture1, texCoord);
        }
    }
</script>
var vertices = [
    // right triangle
    1.0, 1.0, 0.0,
    1.0, -1.0, 0.0,
    -1.0, -1.0, 0.0,
    // left triangle
    -1.0, 1.0, 0.0,
    1.0, 1.0, 0.0,
    -1.0, -1.0, 0.0
];

var texCoord = [
        // right triangle
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        // left triangle
        0.0, 0.0,
        1.0, 0.0,
        0.0, 1.0
];

var boxVertices = [
        // back
        -1000.0,  -1000.0, -1000.0,
         1000.0,  -1000.0, -1000.0,
        -1000.0,  1000.0, -1000.0,
         1000.0,  -1000.0, -1000.0,
         1000.0,  1000.0, -1000.0,
        -1000.0,  1000.0, -1000.0,
        // left
        -1000.0,  -1000.0,  1000.0,
        -1000.0,  -1000.0, -1000.0,
        -1000.0,  1000.0,  1000.0,
        -1000.0,  -1000.0, -1000.0,
        -1000.0,  1000.0, -1000.0,
        -1000.0,  1000.0,  1000.0,
        // right
        1000.0,  -1000.0,  1000.0,
        1000.0,  -1000.0, -1000.0,
        1000.0,  1000.0,  1000.0,
        1000.0,  -1000.0, -1000.0,
        1000.0,  1000.0, -1000.0,
        1000.0,  1000.0,  1000.0,
        // front
        -1000.0, -1000.0, 1000.0,
         1000.0, -1000.0, 1000.0,
        -1000.0,  1000.0, 1000.0,
         1000.0, -1000.0, 1000.0,
         1000.0,  1000.0, 1000.0,
        -1000.0,  1000.0, 1000.0,
        // bottom
        -1000.0, -1000.0,  1000.0,
        -1000.0, -1000.0, -1000.0,
         1000.0, -1000.0,  1000.0,
         1000.0, -1000.0,  1000.0,
        -1000.0, -1000.0, -1000.0,
         1000.0, -1000.0, -1000.0,
        // top
        -1000.0, 1000.0,  1000.0,
        -1000.0, 1000.0, -1000.0,
         1000.0, 1000.0,  1000.0,
        -1000.0, 1000.0, -1000.0,
         1000.0, 1000.0,  1000.0,
         1000.0, 1000.0, -1000.0
];

var boxTexCoord = [
        //back
        0.25, 1.0/3,
         .5,  1.0/3,
        0.25, 2.0/3,
         .5,  1.0/3,
         .5,  2.0/3,
        0.25, 2.0/3,
        //left
        0.0,  1.0/3,
         .25, 1.0/3,
        0.0,  2.0/3,
        0.25, 1.0/3,
         .25, 2.0/3,
        0.0,  2.0/3,
        //right
        0.75, 1.0/3,
         .5,  1.0/3,
        0.75, 2.0/3,
        0.5,  1.0/3,
         .5,  2.0/3,
        0.75, 2.0/3,
        //front
        1.0, 1.0/3,
        .75, 1.0/3,
        1.0, 2.0/3,
        0.75, 1.0/3,
        .75,  2.0/3,
        1.0,  2.0/3,
        // bottom
        0.5, 1.0/3,
        0.5,  0.0,
        0.25, 1.0/3,
        0.25, 1.0/3,
        0.5, 0.0,
        0.25, 0.0,
        // top
        0.25, 1.0,
        0.25, 2.0/3,
        0.5, 1.0,
        0.25, 2.0/3,
        0.5, 1.0,
        0.5, 2.0/3

];
var variables = {};
var buffers = {};
var useColor = {};
useColor.ambient = 0;

function initVariables(gl, shaderProgram) {
    variables.vertexPositionLoc = gl.getAttribLocation(shaderProgram, "vertexPosition");
    variables.textureCoordLoc = gl.getAttribLocation(shaderProgram, "textureCoord");
    gl.enableVertexAttribArray(variables.vertexPositionLoc);
    gl.enableVertexAttribArray(variables.textureCoordLoc);

    variables.uMVMatrixLoc = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    variables.uPMatrixLoc = gl.getUniformLocation(shaderProgram, "uPMatrix");
    variables.uAmbientColorLoc = gl.getUniformLocation(shaderProgram, "uAmbientColor");
    variables.texture1 = gl.getUniformLocation(shaderProgram, "texture1");
}
 
var initBuffers = function(gl) {
    //logo
    buffers.positionBuffer = gl.createBuffer();
    buffers.texCoordBuffer = gl.createBuffer();
    buffers.texture1 = gl.createTexture();

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoord), gl.STATIC_DRAW);

    var logoImage = new Image();
    logoImage.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture1);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, logoImage);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    };
    logoImage.src = '/rc/sally/images/c/opengl/logo.gif';

    //skybox
    buffers.sPositionBuffer = gl.createBuffer();
    buffers.sTexCoordBuffer = gl.createBuffer();
    buffers.sTexture1 = gl.createTexture();

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.sPositionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxVertices), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.sTexCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxTexCoord), gl.STATIC_DRAW);

    var sboxImage = new Image();
    sboxImage.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, buffers.sTexture1);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sboxImage);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    };
    sboxImage.src = '/rc/sally/images/c/opengl/miramar_large.jpg';
}
    $(function() {
        var lastX, lastY;
        var canvas1 = document.getElementById('skybox-canvas');
        var lastDownTarget;
        $(document).on('mousedown', function(event) {
            lastDownTarget = event.target;
            lastX = event.pageX; lastY = event.pageY;
        });

        $(document).on('mousemove', function(event) {
            if(lastDownTarget != canvas1)
                return;
            var buttonDown = event.buttons == null ?event.wich : event.buttons;
            if(!buttonDown) return;

            x = event.pageY - lastY;
            y = event.pageX - lastX;
            lastX = event.pageX;
            lastY = event.pageY;
               
            var xAngle = WebGLUtils.degToRad(x/5.0);
            var yAngle = WebGLUtils.degToRad(y/5.0);
            updateAngles(xAngle, yAngle);
            event.preventDefault();
            return false;
        });
    });
    var rotationMatrix = mat4.create();
    mat4.identity(rotationMatrix);

    function updateAngles(xAngle, yAngle) {
         var newRotationMatrix = mat4.create();
         mat4.identity(newRotationMatrix);
         mat4.rotate(newRotationMatrix, newRotationMatrix, xAngle, [1, 0, 0]);
         mat4.rotate(newRotationMatrix, newRotationMatrix, yAngle, [0, 1, 0]);
         mat4.multiply(rotationMatrix, newRotationMatrix, rotationMatrix);
    }

    function drawScene(gl, canvas) {
        gl.viewport(0, 0, canvas.width, canvas.height);
       
        gl.clearColor(1.0, 1.0, 1.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        var uPMatrix = mat4.create();
        var uMVMatrix = mat4.create();

        mat4.identity(uPMatrix);
        mat4.identity(uMVMatrix);

        /********************************************/
        mat4.perspective(uPMatrix, WebGLUtils.degToRad(45), canvas.width / canvas.height, 1.0, 2000.0);
        mat4.translate(uMVMatrix, uMVMatrix, [0.0, 0.0, -10.0]);
        mat4.multiply(uMVMatrix, uMVMatrix, rotationMatrix);
        /********************************************/
        gl.uniformMatrix4fv(variables.uMVMatrixLoc, false, uMVMatrix);
        gl.uniformMatrix4fv(variables.uPMatrixLoc, false, uPMatrix);

        if (useColor.ambient) {
                gl.uniform3f(variables.uAmbientColorLoc, 0.4, 0.4, 0.4);
        }
        else {
                gl.uniform3f(variables.uAmbientColorLoc, 0.0, 0.0, 0.0);
        }


        // vykresli skybox
        gl.bindTexture(gl.TEXTURE_2D, buffers.sTexture1);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.sPositionBuffer);
        gl.vertexAttribPointer(variables.vertexPositionLoc, 3, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.sTexCoordBuffer);
        gl.vertexAttribPointer(variables.textureCoordLoc, 2, gl.FLOAT, false, 0, 0);
        gl.drawArrays(gl.TRIANGLES, 0, boxVertices.length/3);

        // vykresli logo
        gl.bindTexture(gl.TEXTURE_2D, buffers.texture1);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
        gl.vertexAttribPointer(variables.vertexPositionLoc, 3, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
        gl.vertexAttribPointer(variables.textureCoordLoc, 2, gl.FLOAT, false, 0, 0);

        gl.drawArrays(gl.TRIANGLES, 0, vertices.length/3);
    }

    function animate() {
    }
function tick(gl, canvas) {
    drawScene(gl, canvas);
    animate();
    requestAnimFrame(function() { tick(gl, canvas);});
}


function webGLStart() {
    var canvas = document.getElementById("textura");
    var gl = WebGLUtils.setupWebGL(canvas,{ antialias: true });
    if(!gl) return;
    var shaderProgram = WebGLUtils.initShaders(gl, "shader-vs-skybox","shader-fs-skybox");
    gl.useProgram(shaderProgram);
    initVariables(gl,shaderProgram);
    initBuffers(gl);
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    tick(gl, canvas);
}

webGLStart();

Toto není jediný způsob, jak vytvořit iluzi prostoru. Můžete si vystačit i s jednou plochou, na kterou vykreslíte nějaké pozadí, a kterou budete otáčet vždy tak, aby čelila pohledu kamery. Nebo můžete vytvořit z mnoha trojúhleníků velkou kopuli, na kterou vykreslíte příslušnou texturu (vytvoření vhodné textury pro takovou polokouli není ale zrovna snadné).

Cubemap

Cubemap (GL_TEXTURE_CUBE_MAP) je další z několika druhů textury.

Používá se, jak název napovídá, k otexturování krychle. Tedy k něčemu podobnému, jako jsem popisoval u skyboxu. Jen se data nahrávají trochu jinak a i souřadnice textury se určují jinak.

Souřadnice textury se určují pomocí vektorů, viz obrázek níže. Jde o vektor, jehož počátek je uprostřed pomyslné krychle. Definováním vektoru (pomocí tří souřadnic) určíte směr. Místo, kde se vektor protne se stěnou krychle, určuje výslednou barvu.

Cubemap

Cubemap

Nejpřirozenější je asi vykreslovat cubemap na krychli, ale nikde není dáno, že tento druh textury nemůžete použít na cokoliv jiného.

Souřadnice cubemap textury jsou tedy trojrozměrné. Pro cubemap texturu se musí každá strana nahrávat zvlášť (viz ukázka zdrojového kódu dále), takže je potřeba skybox rozřezat na 6 samostatných obrázků.

Pro inicializování cubemap můžete použít následující funkci:

  1. /*------------------------------------------------*/
  2. /* 08textury/cubemap/load-cubemap.h                */
  3. #include <GLFW/glfw3.h>
  4.  
  5. static GLuint load_cubemap(const GLuint textureID, const char * const *faces)
  6. {
  7.     int width,height,n;
  8.     unsigned char* image;
  9.     GLuint i;
  10.        
  11.     glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
  12.     for(i = 0; i < 6; i++)
  13.     {
  14.         image = sx_stbi_load(faces[i], &width, &height, &n, 0);
  15.         if (n == 4) {
  16.                 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
  17.         }
  18.         else {
  19.                 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
  20.         }
  21.         sx_stbi_image_free(image);
  22.     }
  23.     glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  24.     glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  25.     glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  26.     glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  27.     glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
  28.     glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
  29.  
  30.     return textureID;
  31. }  

Funkce dostane jako první argument ID texture bufferu a jako druhý argument pole s názvy 6-ti souborů.

Funkce dělá v zásadě jen to, že přibinduje buffer s typem textury GL_TEXTURE_CUBE_MAP, nastavuje některé, vám již známé, parametry textury a dále nahraje obsah šesti obrázků do tzv. texture targets, které se adresují pomocí konstant z následující tabulky:

GL_TEXTURE_CUBE_MAP_POSITIVE_X= GL_TEXTURE_CUBE_MAP_POSITIVE_X + 0
GL_TEXTURE_CUBE_MAP_NEGATIVE_X= GL_TEXTURE_CUBE_MAP_POSITIVE_X + 1
GL_TEXTURE_CUBE_MAP_POSITIVE_Y= GL_TEXTURE_CUBE_MAP_POSITIVE_X + 2
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y= GL_TEXTURE_CUBE_MAP_POSITIVE_X + 3
GL_TEXTURE_CUBE_MAP_POSITIVE_Z= GL_TEXTURE_CUBE_MAP_POSITIVE_X + 4
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z= GL_TEXTURE_CUBE_MAP_POSITIVE_X + 5

Tím se inicializuje všech šest stran krychle. Funkci můžete zavolat např. takto:

  1. void initBuffers(Shader *program) {
  2.         //...
  3.         glGenTextures(sizeof(TEX) / sizeof(TEX[0]), TEX);
  4.  
  5.         const char * const faces[] = {
  6.                 "miramar_large/right.jpg",
  7.                 "miramar_large/left.jpg",
  8.                 "miramar_large/top.jpg",
  9.                 "miramar_large/bottom.jpg",
  10.                 "miramar_large/back.jpg",
  11.                 "miramar_large/front.jpg",
  12.         };
  13.  
  14.         glActiveTexture(GL_TEXTURE0);
  15.         load_cubemap(TEX[0], faces);
  16.         //...
  17. }

Vertex data pro krychli mohou vypadat nějak takto:

  1. GLfloat boxVertices[] = {
  2.     -1000.0f,  1000.0f, -1000.0f,
  3.     -1000.0f, -1000.0f, -1000.0f,
  4.      1000.0f, -1000.0f, -1000.0f,
  5.      1000.0f, -1000.0f, -1000.0f,
  6.      1000.0f,  1000.0f, -1000.0f,
  7.     -1000.0f,  1000.0f, -1000.0f,
  8.     //...
  9. };
  10.  
  11. GLfloat boxTexCoord[] = {
  12.     -1.0f,  1.0f, -1.0f,
  13.     -1.0f, -1.0f, -1.0f,
  14.     1.0f, -1.0f, -1.0f,
  15.     1.0f, -1.0f, -1.0f,
  16.     1.0f,  1.0f, -1.0f,
  17.     -1.0f,  1.0f, -1.0f,
  18.     //...
  19. }

Dále je potřeba upravit vertex shader, protože souřadnice textury už nejsou 2D vektory, ale 3D.

Kvůli úpravě shaderů nezobrazuji v příkladu čtverec s logem, protože bych musel použít dva shader programy a tím příklad zbytečně kompikoval. (V přechozím příkladě se skyboxem jsem mohl použít stejné shadery na vykreslení skyboxu i loga.)

#version 330 core
layout (location = 0) in vec3 vertexPosition;
layout (location = 1) in vec3 textureCoord;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
 
out vec3 texCoord;

void main(void) {
    vec4 mvPosition = uMVMatrix * vec4(vertexPosition, 1.0);
    texCoord = textureCoord;
    gl_Position = uPMatrix * mvPosition;
}

Fragment shader se také musí upravit kvůli 3D souřadnicím textury, ale hlavně uniform proměnná pro texturu je nyní typu sampleCube.

#version 330 core

uniform bool useAmbient;
uniform vec3 uAmbientColor;
uniform samplerCube texture1;

in vec3 texCoord;

void main(void) {
        if (useAmbient) {
                vec4 vColor = vec4(0.0, 0.0, 1.0, 1.0);
                vec4 c = vec4(vColor.rgb * uAmbientColor, vColor.a);
                gl_FragColor = mix(texture(texture1, texCoord), c, 0.5);
        }
        else {
                gl_FragColor = texture(texture1, texCoord);
        }
}

Ve WebGL musíte místo funkce texture() použít funkci textureCube().

A zde je výsledek:

Cubemap
<script id="shader-vs-cubemap" type="x-shader/x-vertex">
    attribute vec3 vertexPosition;
    attribute vec3 textureCoord;
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec3 texCoord;

    void main(void) {
        vec4 mvPosition = uMVMatrix * vec4(vertexPosition, 1.0);
        texCoord = textureCoord;
        gl_Position = uPMatrix * mvPosition;
    }
</script>

<script id="shader-fs-cubemap" type="x-shader/x-fragment">
    precision mediump float;

    uniform bool useAmbient;
    uniform vec3 uAmbientColor;
    uniform samplerCube texture1;

    varying vec3 texCoord;

    void main(void) {
        if (useAmbient) {
                vec4 vColor = vec4(0.0, 0.0, 1.0, 1.0);
                vec4 c = vec4(vColor.rgb * uAmbientColor, vColor.a);
                gl_FragColor = mix(textureCube(texture1, texCoord), c, 0.5);
        }
        else {
                gl_FragColor = textureCube(texture1, texCoord);
        }
    }
</script>
var boxVertices = [
 // Positions          
    -1000.0,  1000.0, -1000.0,
    -1000.0, -1000.0, -1000.0,
     1000.0, -1000.0, -1000.0,
     1000.0, -1000.0, -1000.0,
     1000.0,  1000.0, -1000.0,
    -1000.0,  1000.0, -1000.0,

    -1000.0, -1000.0,  1000.0,
    -1000.0, -1000.0, -1000.0,
    -1000.0,  1000.0, -1000.0,
    -1000.0,  1000.0, -1000.0,
    -1000.0,  1000.0,  1000.0,
    -1000.0, -1000.0,  1000.0,

     1000.0, -1000.0, -1000.0,
     1000.0, -1000.0,  1000.0,
     1000.0,  1000.0,  1000.0,
     1000.0,  1000.0,  1000.0,
     1000.0,  1000.0, -1000.0,
     1000.0, -1000.0, -1000.0,

    -1000.0, -1000.0,  1000.0,
    -1000.0,  1000.0,  1000.0,
     1000.0,  1000.0,  1000.0,
     1000.0,  1000.0,  1000.0,
     1000.0, -1000.0,  1000.0,
    -1000.0, -1000.0,  1000.0,

    -1000.0,  1000.0, -1000.0,
     1000.0,  1000.0, -1000.0,
     1000.0,  1000.0,  1000.0,
     1000.0,  1000.0,  1000.0,
    -1000.0,  1000.0,  1000.0,
    -1000.0,  1000.0, -1000.0,

    -1000.0, -1000.0, -1000.0,
    -1000.0, -1000.0,  1000.0,
     1000.0, -1000.0, -1000.0,
     1000.0, -1000.0, -1000.0,
    -1000.0, -1000.0,  1000.0,
     1000.0, -1000.0,  1000.0
];

var boxTexCoord = [
        // Positions          
        -1.0,  1.0, -1.0,
        -1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0,  1.0, -1.0,
        -1.0,  1.0, -1.0,
 
        -1.0, -1.0,  1.0,
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
        -1.0, -1.0,  1.0,
 
         1.0, -1.0, -1.0,
         1.0, -1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, -1.0,
         1.0, -1.0, -1.0,
   
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,
 
        -1.0,  1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
 
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0
];
var variables = {};
var buffers = {};
var useColor = {};
useColor.ambient = 0;

function initVariables(gl, shaderProgram) {
    variables.vertexPositionLoc = gl.getAttribLocation(shaderProgram, "vertexPosition");
    variables.textureCoordLoc = gl.getAttribLocation(shaderProgram, "textureCoord");
    gl.enableVertexAttribArray(variables.vertexPositionLoc);
    gl.enableVertexAttribArray(variables.textureCoordLoc);

    variables.uMVMatrixLoc = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    variables.uPMatrixLoc = gl.getUniformLocation(shaderProgram, "uPMatrix");
    variables.uAmbientColorLoc = gl.getUniformLocation(shaderProgram, "uAmbientColor");
    variables.texture1 = gl.getUniformLocation(shaderProgram, "texture1");
    variables.useAmbientLoc = gl.getUniformLocation(shaderProgram, "useAmbient");
}
 
var initBuffers = function(gl) {
    //logo
    buffers.positionBuffer = gl.createBuffer();
    buffers.texCoordBuffer = gl.createBuffer();
    buffers.texture1 = gl.createTexture();

    var faces = [
                "miramar_large/right.jpg",
                "miramar_large/left.jpg",
                "miramar_large/top.jpg",
                "miramar_large/bottom.jpg",
                "miramar_large/back.jpg",
                "miramar_large/front.jpg"
    ];
 
    load_cubemap(gl, buffers.texture1, faces);

    //cubemap
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxVertices), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(boxTexCoord), gl.STATIC_DRAW);
}
    $(function() {
        var lastX, lastY;
        var canvas1 = document.getElementById('cubemap-canvas');
        var lastDownTarget;
        $(document).on('mousedown', function(event) {
            lastDownTarget = event.target;
            lastX = event.pageX; lastY = event.pageY;
        });

        $(document).on('mousemove', function(event) {
            if(lastDownTarget != canvas1)
                return;
            var buttonDown = event.buttons == null ?event.wich : event.buttons;
            if(!buttonDown) return;

            x = event.pageY - lastY;
            y = event.pageX - lastX;
            lastX = event.pageX;
            lastY = event.pageY;
               
            var xAngle = WebGLUtils.degToRad(x/5.0);
            var yAngle = WebGLUtils.degToRad(y/5.0);
            updateAngles(xAngle, yAngle);
            event.preventDefault();
            return false;
        });
    });
    var rotationMatrix = mat4.create();
    mat4.identity(rotationMatrix);

    function updateAngles(xAngle, yAngle) {
         var newRotationMatrix = mat4.create();
         mat4.identity(newRotationMatrix);
         mat4.rotate(newRotationMatrix, newRotationMatrix, xAngle, [1, 0, 0]);
         mat4.rotate(newRotationMatrix, newRotationMatrix, yAngle, [0, 1, 0]);
         mat4.multiply(rotationMatrix, newRotationMatrix, rotationMatrix);
        var x = '';
        for(i = 0; i < rotationMatrix.length; i++)
                x += rotationMatrix[i]+',';
                if(!(i+1 % 4)) x+="<br />";
        $('#x').html(x);
    }

    function drawScene(gl, canvas) {
        gl.viewport(0, 0, canvas.width, canvas.height);
       
        gl.clearColor(1.0, 1.0, 1.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        var uPMatrix = mat4.create();
        var uMVMatrix = mat4.create();

        mat4.identity(uPMatrix);
        mat4.identity(uMVMatrix);

        /********************************************/
        mat4.perspective(uPMatrix, WebGLUtils.degToRad(90), canvas.width / canvas.height, 1.0, 2000.0);
        //mat4.translate(uMVMatrix, uMVMatrix, [0.0, 0.0, -5.0]);
        //mat4.scale(uMVMatrix, uMVMatrix, [0.002, 0.002, 0.002]);
        mat4.multiply(uMVMatrix, uMVMatrix, rotationMatrix);
        /********************************************/
        gl.uniformMatrix4fv(variables.uMVMatrixLoc, false, uMVMatrix);
        gl.uniformMatrix4fv(variables.uPMatrixLoc, false, uPMatrix);
        gl.uniform3f(variables.uAmbientColorLoc, 0.4, 0.4, 0.4);
        gl.uniform1i(variables.useAmbientLoc, useColor.ambient);

        // vykresli cubemap
        gl.bindTexture(gl.TEXTURE_CUBE_MAP, buffers.texture1);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positionBuffer);
        gl.vertexAttribPointer(variables.vertexPositionLoc, 3, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.texCoordBuffer);
        gl.vertexAttribPointer(variables.textureCoordLoc, 3, gl.FLOAT, false, 0, 0);
        gl.drawArrays(gl.TRIANGLES, 0, boxVertices.length/3);
    }

    function animate() {
    }
function tick(gl, canvas) {
    drawScene(gl, canvas);
    animate();
    requestAnimFrame(function() { tick(gl, canvas);});
}


function webGLStart() {
    var canvas = document.getElementById("textura");
    var gl = WebGLUtils.setupWebGL(canvas, { antialias: true });
    if(!gl) return;
    var shaderProgram = WebGLUtils.initShaders(gl, "shader-vs-cubemap","shader-fs-cubemap");
    gl.useProgram(shaderProgram);
    initVariables(gl,shaderProgram);
    initBuffers(gl);
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    tick(gl, canvas);
}

webGLStart();

Poznámka na závěr: ve zdrojových kódech k tomuto příkladu jsem vám nechal zakomentované tyto dvě řádky:

mat4x4_translate(uMVMatrix, 0.0, 0.0, -5.0);
mat4x4_scale_aniso(uMVMatrix, uMVMatrix, 0.002, 0.002, 0.002);

Pokud je odkomentujete, krychle se zmenší natolik, že se nani nebudete dívat zevnitř, ale z venku.

Fullscreen

Abyste si příklad s cubemap vychutnali, spouštím program ve fullscrennu. K tomu mi slouží následující kus zdrojového kódu:

        /* Create a windowed mode window and its OpenGL context */
        GLFWmonitor* monitor = glfwGetPrimaryMonitor();        
        const GLFWvidmode* mode = glfwGetVideoMode(monitor);
        glfwWindowHint(GLFW_RED_BITS, mode->redBits);
        glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
        glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
        glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
        window = glfwCreateWindow(mode->width, mode->height, "08 - cubemap", monitor, NULL);

V kódu nejdříve zjistím informace o aktuálním nastavení monitoru a pak je použiji pro nastavení vytvářeného nového okna. Díky čtvrtému argumentu funkce glfwCreateWindow() se okno rozplácne přes celý monitor (fullscreen). (Počítač může být připojen k více monitorům, funkce glfwGetPrimaryMonitor() vrací odkaz na ten, který považuje systém za primární.)

Závěr

Použití textur přináší do OpenGL nový stupeň zábavy :-). Představil jsem vám zde dva základní typy textur a některé jejich vlastnosti.

Na internetu můžete najít spousty cubemap textur (stačí vygooglit cubemap nebo skybox).

Ve zdrojových kódech ke stažení najdete v příkladu s cubemap stažené obrázky ze stránky www.custommapmakers.org/skyboxes.php. Jsou ve formátu TGA. Jedná se o nekomprimované obrázky, takže jejich načtení je mnohem, mnohem rychlejší než třeba načítání JPG, PNG nebo GIF. Vyzkoušejte, uvidíte :-).

Komentář Hlášení chyby
Created: 24.12.2016
Last updated: 25.11.2017
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..