Modul 4.2: Teksturering, avansert

Introduksjon

Hittil har vi sett hvordan man kan teskturere en enkel flate med et enkelt teksturbilde. Dette notatet viser hvordan man kan teksturere 3D-modeller og hvordan man kan bruke ulike deler av en teksturer til å kle ulike deler av en 3D-modell. Vi skal også se på såkalt texture tiling og hvordan man kan blande teksturer.

Teksturert kube

Følgende kodeeksempel tegner følgende:

Tektsturert kube
Tektsturert kube

Her brukes samme tekstur, bricks.png, på kubens seks sider. Legg merke til at verdier knyttet til CW/CCW og culling er satt eksplisitt i draw, slik:

// Setter verdier for culling: // EKsperiementer med verdiene, test f.eks. med renderInfo.gl.FRONT renderInfo.gl.frontFace(renderInfo.gl.CCW); // Angir vertekser CCW. renderInfo.gl.enable(renderInfo.gl.CULL_FACE); // Aktiverer culling. renderInfo.gl.cullFace(renderInfo.gl.BACK); // Skjuler baksider. }

Her er de viktigste delene av cubetextured.js eksemplet:

Tilhørende index.html

Culling er som standard avslått. I koden over aktiveres culling vha. renderInfo.gl.enable(renderInfo.gl.CULL_FACE). Endrer man f.eks. til renderInfo.gl.FRONT i kallet til renderInfo.gl.cullFace(), vil flere av kubens sider ikke vises. Man vil imidlertid se kubens "innersider" som vender mot kamera, som vist under:

Tester med renderInfo.gl.cullFace(renderInfo.gl.FRONT);
Tester med renderInfo.gl.cullFace(renderInfo.gl.FRONT);

Legg merke til at vi nå ser innsiden av kubens venstreside, bakside og bunn.

Bruke deler av teksturen

En tekstur kan fordeles utover modellen vha. uv-koordinatene. Anta at følgende tekstur skal brukes til å kle sidene av en kube. Her er teksturkoordinatene som må brukes angitt for å kunne teksturere kubens ulike sider.

Terningtekstur
Terningtekstur (fra Koilz)

Ved hjelp av denne teksturen skal kuben tekstureres som vist i følgende figur:

Bruk av terningtekstur
Bruk av terningtekstur

Kubesiden som skal vise 1-tallet vil bestå av to trekanter. Verteksposisjonene til denne siden (forsiden) er definert i forhold til følgende figur:

Forsidens verteksposisjoner
Forsidens verteksposisjoner

Under ser vi utsnitt fra kodeeksemplet modul4/dicetextured:

. . .
//Definerer to trekanter per side:
let pos = new Float32Array([
  //Forsiden (1-tallet):
  -1, 1, 1,     //v1
  -1,-1, 1,     //v2
   1,-1, 1,     //v3
  -1,1,1,       //v4
   1, -1, 1,    //v5
   1,1,1,       //v6
  //Høyre side:
   1,1,1,
   . . .
   . . .
]);  

Her er de 6 verteksposisjonene til kubens forside definert (tilsvarende for resten av kubens sider). Verteksene definerer to trekanter som skal kles med den delen av teksturen som inneholder 1-tallet. Siden 1-tallet er en del av teksturen må vi bruke fire ulike uv-koordinater og knytte disse til de to trekantenes seks vertekser. Uv-koordinatene assosieres med de ulike verteksene som vist i figuren under:

Forsidens verteksposisjoner og uv-koordinater
Forsidens verteksposisjoner og uv-koordinater

Dette gir følgende kopling mellom verteksposisjon og uv-koordinater:

Verteks

Posisjon

Uv-koordinat

v1

-1,1,1

0,1

v2

-1,-1,1

0,0.5

v3

1,-1,1

0.33,0.5

v4

-1,1,1

0,1

v5

1,-1,1

0.33,0.5

v6

1,1,1

0.33,1

Du finner komplett eksempel blant kodeeksemplene.

Oppgave

Finn/lag din egen terningtekstur med tall, terningprikker e.l. La teksturen bestå av to kolonner og tre rader, f.eks. slik:

Alternativ terningtekstur
Alternativ terningtekstur

Kle terningens ulike sider med dette.

Teksture wrapping / tiling

Hittil har sagt at teksturkoordinatene til et bilde er lik 0,0 i nedre venstre hjørne og 1,1 i øvre høyre hjørne. Dette gjelder selv om bildet ikke er kvadratisk. Det er også mulig å angi teksturkoordinater utenfor dette området, f.eks. slik:

//Setter opp uv-buffer for tekstur:
this.uvs = new Float32Array([
  0, 0,
  5, 0,
  0, 5,
  5, 5
]);	  

Bruker dette sammen med gl.REPEAT:, slik:

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);  

Figuren under viser vertekser med tilhørende UV-parametre:

Texture tiling
Texture tiling

Eksempel med følgende tekstur:

Flagg-tekstur
Flagg-tekstur

vil det gi følgende resultat:

Flagg-teksturi bruk
Flagg-tekstur i bruk

Samme tekstur legges nå på rektanglet i et 5x5 rutenett. NB! Her må vi bruke en bildefil med bredde og høyde lik 2n piksler. Teksturparametrene som vist over er standard og man får samme resultat uten å ha satt dem som vist.

Denne teknikken kan være veldig besparende dersom man har store flater som skal kles med «ensformig» tekstur, som f.eks. en gresslette, en murvegg o.l. Man kan laste en liten .png som kan «kle» store flater. Her er et eksempel med en murvegg der vi bruker følgende tekstur:

Natursteintekstur
Natursteintekstur

Resultat:

Natursteintekstur i bruk
Natursteintekstur i bruk

Ser vi nøye på denne flaten ser vi fortsatt at flaten er dekt av 5x5 teksturer. For å unngå dette er det viktig at teksturen er laget slik at den «sømløst» kan plasseres side ved side.

Brukes denne teksturen:

Mursteintekstur
Mursteintekstur

får vi følgende resultat:

Mursteintekstur i bruk
Mursteintekstur i bruk

Det er også mulig å speile annenhver «teksturcelle» ved å bruke følgende parametre:

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,gl.MIRRORED_REPEAT); 
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,gl.MIRRORED_REPEAT);  

år vi følgende resultat på teksturen som ble vist tidligere:

Flagg-tekstur med mirror repeat
Flagg-tekstur med mirror repeat

Her speiles teksturen annenhver gang.

Blande teksturer

I foregående eksempler ble fargen til hvert fragment bestemt av teksturen vha. shaderfunksjonen texture(uSampler, vec2(vTextureCoordinate.s, vTextureCoordinate.t)). Shadervariabelen uSampler settes fra Javascript-koden og er assosiert med teksturen som skal brukes til å gi fragmentet farge.

Variabelen vTextureCoordinate er interpolert på veien fra verteks- til fragmentshaderen og tilsvarer uv / st -posisjonen for aktuelt fragment (dvs. fragmentet som fragmentshaderen kjører for).

Funksjonens texture() henter med andre ord farge fra aktuell tekstur (uSampler) vha. vTextureCoordinate.s og vTextureCoordinate.t og tilordner farge til fragColor som returneres fra fragmentshaderen.

Vi har også sett hvordan vi kan blande teksturfarge med verteksfarge. WebGL har også muligheten til å blande farge fra flere teksturer. For å få til dette må man laste antall bildefiler som skal brukes og tilsvarende antall texture-objekter opprettes. I tillegg må man bruke flere «sampler» variabler. Hver av disse assosieres med et texture-objekt. Fragmentshaderen må endres slik at den mottar flere sampler-verdier og returnerer produktet av fargene fra teksturene, slik:

Følgende er utdrag fra kodeeksemplet under modul4 som demonstrerer multiteksturering:

#version 300 es
in highp vec2 vTextureCoordinate;
uniform sampler2D uSampler0;
uniform sampler2D uSampler1;
out lowp vec4 fragColor;

void main() {
    vec4 color0 = texture(uSampler0, vec2(vTextureCoordinate.s, vTextureCoordinate.t));
    vec4 color1 = texture(uSampler1, vec2(vTextureCoordinate.s, vTextureCoordinate.t));
    fragColor = color0 * color1;
}  

Legg merke til følgende:

  • To bilder laster før kall på initCubeTextureAndBuffers(canvas.gl, textureImage0, textureImage1).
  • Her bruker vi to teksturer og dermed to textureobjekter i initCubeTextureAndBuffers().
  • Fragmentshaderen er endret som vist over.
  • To samplervariabler sendes til fragmentshaderen.

Det nå definert to «samplere» i fragmentshaderen, uSampler0 og uSampler1. Disse brukes videre for å hente texels (farge) fra de to teksturene, slik:

void main() {   
  vec4 color0 = texture2D(uSampler0, vec2(vTextureCoordinate.s, vTextureCoordinate.t));
  vec4 color1 = texture2D(uSampler1, vec2(vTextureCoordinate.s, vTextureCoordinate.t));
  gl_FragColor = color0 * color1;
}  

Koplingen til uSampler0 og uSampler1 gjøres som en del av oppstartskoden, f.eks. i initTextureShaders(), som vist tidligere:

function initTextureShaders(gl) {
  // Leser shaderkode fra HTML-fila: Standard/enkel shader (posisjon og farge):
  let vertexShaderSource = document.getElementById('texture-vertex-shader').innerHTML;
  let fragmentShaderSource = document.getElementById('texture-fragment-shader').innerHTML;

  // Initialiserer  & kompilerer shader-programmene;
  const glslShader = new WebGLShader(gl, vertexShaderSource, fragmentShaderSource);

  // Samler all shader-info i ET JS-objekt, som returneres.
  return  {
    program: glslShader.shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(glslShader.shaderProgram, 'aVertexPosition'),
      vertexColor: gl.getAttribLocation(glslShader.shaderProgram, 'aVertexColor'),
      vertexTextureCoordinate: gl.getAttribLocation(glslShader.shaderProgram, 'aVertexTextureCoordinate'),
    },
    uniformLocations: {
      sampler0: gl.getUniformLocation(glslShader.shaderProgram, 'uSampler0'),
      sampler1: gl.getUniformLocation(glslShader.shaderProgram, 'uSampler1'),
      projectionMatrix: gl.getUniformLocation(glslShader.shaderProgram, 'uProjectionMatrix'),
      modelViewMatrix: gl.getUniformLocation(glslShader.shaderProgram, 'uModelViewMatrix'),
    },
  };
}

Verdiene til uSampler0 og uSampler1 settes i connectTextureAttribute() via draw():

gl.uniform1i(textureShader.uniformLocations.sampler0, 0);
gl.uniform1i(textureShader.uniformLocations.sampler1, 1);   

Her gir vi også verdien 0 til uSampler0 og 1 til uSampler1. Videre aktiveres gl.TEXTURE0 og teksturen som ligger i textureObject0 bindes med gl.TEXTURE0. Tilsvarende for gl.TEXTURE1 og textureObject1.

I fragmentshaderen kalles først funksjonen texture(…) med uSampler0 som første paremeter. Denne har nå verdien 0 og indikerer at texture() skal bruke teksturen som er assosiert med gl.TEXTURE0. Tilsvarende for kall på texture(uSampler1, …) som nå henter farge fra teksturen knyttet til gl.TEXTURE1.

Resultatet fra dette eksemplet ser slik ut:

Multiteksturert kube
Multiteksturert kube

Fargene som hentes fra de to teksturene multipliseres og resultatet tilordnes fragColor.

Mipmapping

Teknikken går ut på å bruke teksturer/bildefiler med ulik størrelse. Bildet som man tar utgangspunkt i, er mipmap level 0 (et av argumentene til texImage2D() metoden). Fra dette genereres det flere mindre bilder, enten automatisk av WebGL eller manuelt ved hjelp av et grafisk verktøy.

Utgangsbildet (level 0) må ha størrelse tilsvarende 2n x 2n piksler der n = 0,1,2,3 osv.

1 kommentar:

  1. Savner litt mer informasjon om MIP mapping.
    Hvorfor brukes dette?
    Hva er Moiré-Patterns?
    Hvorfor oppstår Moiré-Patterns?
    Hvordan velger texture2D hvilken level som skal brukes?

    SvarSlett