Modul 4.2: Teksturering, avansert

Kube med teksturerte sider

Se kodeeksemplet som viser en teksturert kube. Legg merke til bruk av to shaderpar, et for koordinatsystemet og et for kuben. 

6nbqeZCtnfaYyTK-2WwAnUs7CD6TeRxqGCCqwyqBxx0IsXFggtoafQdwIFMU-a38uw0wfKeyKA4xW8qXwCwAOVrwnJGAeOrK3zUMOuEVO3CEiZeHg-JHBcg7TPVsSh3yhbqPn0UUPg4=w311-h261

Legg også merke til bruk av "backface culling". Dette for at ikke innersiden av kubens flater skal vises når den roterer. Slår man av culling vil kuben se slik ut:

Skjermbilde%2B2019-09-12%2Bkl

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.

CIkZFyjx8nztuZ5kKohc3IFigODYx73hmaZWCShnOGtYIoR1AW4OQmVVrVBEsitrcA5Tgj76TXRoCk2FOIJLY4BQad7eA2EKQKB3xUzGWBuThe-BF8tle8AcXIxz5YTVunOCAPqiAGs

        Terningtekstur (fra Koilz)

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

p6rkVKc1XTnah26l53LYun74ycJNR1jXvMynIebE6CsxmV7TMdbhC0oSK-7u5jcNk2SFDQ24W41VErZPXv4Ra-YuvKWHC2gIjMdwgpkqXII7fkslL7806dpjpRNdcnsrbEcnF4tawww

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

vU_HRQzgMd45oMRIl0DDH_pI6euyFH3VAe2O0KYSS7N6w3bZEWVFXylDDtRMpM49ieaZWWZAI2nWYpNx7naKcOXSmLSgj6lkGY6LMuCCcjelW5Fr-x2qnSl5FZbHgzA_qLxUH86ttxg

Under ser vi utsnitt fra kodeeksemplet som det refereres til:

. . .
//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 6 verteksene:

Disse assosieres med de ulike verteksene som vist i figuren under:

Q8oaEguWEiPkAH4ZhZp773cVdOgcEhwzCLzVmZpUBks5TPtPGXfIiNDsDEt0a2LUBthYcwn9tr0ygaktThtIzYwz6z7auknXRsunJYlfTJqh6IPGjyJAJSJO9ZlhE660gJUVDFtf9L4

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:

_XVKSd93Lg3lxW1-PU9ELX6BrIyFDYYONIDUVKHYvDRCz53qLHUcQBmyDkB3olK0ZObOT12bU8hfHWJg25LuuaaMpGYymz2omp59XjE6zfNqJD87dm0IHzXRBDlY03RyS8yBh127DNA

Sørg for å kle terningen 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:

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:

vVRD3FOhKrEBFfuda5G5JNU8A3-mnRKlpwQeGJjnoFBG28CWmwKF6cfAdpv3qQnWyqTZs7pffdtPeaojaEtsj3gYUxcklOLRVXkVnjuS3_RCLdhSOTDTyHslfzEFRj7ZZtrXvDzPacY=w672-h366

Eksempel:

Bruker vi nå følgende tekstur:

z9FpUGoLo6BeGS0_0Mcto1JlL1RoEStxHwID7E5azUrjw9Hop7bHM1CjfDC01vRvN8j2BhDwpPe9W1R9xdf2i9ibgdjcms9N8coA9hLqFn5DdT7HUNrvqFr_cG8VMexHB8P5WP3_nYg=w226-h113

vil det gi følgende resultat:

WpnAM2NjGuMADj75HD0orH2ythQmAV4j0_tGYmRgRDKhIdQRPTdJ8v8lO-9DESjDNwBNy65EGP5saEfU4sgLXeaYC1N92xw6-rUqjavx_8YFEjh1XxR9okYvhlqQXI-pMNxDM_8DUuM=w674-h430

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:

9KEAq1BupQd-sDqIV5syKpG_a6duyqoenLUdl0KncuJTMH80I7RCUXJpRtgF70IODqedAjbDtOyEWjJHp6GunH4nKxjyfJAzvyGZQ_VZEoN7fCvz2IIqouQLhF9qFzt0YutxaIkr7Hc

Resultat:

VQJ-ZoqWWtQgOYWUf2ooeRzIz2VxkdYGDhpAHawL4TfWuZQuiBYXIRe-DRfYkoxdZ0a9dGiQeRRZ_N3Ub5iuONJN8W1SVTaQSr2tGu0ci7qGQZnhyTV-T21nDI-LUP2S8TvdG7VtlRo=w643-h385

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:

tWHxehTGhAAw9Mpx7DTxsYcaLqD_PCGxzBUA_vq9ct552IiPJxNLBc_DFxOeGYzUWZEvrYT8_yjqnYTm6SZxrk_djGNMdIdejC8yh8h6L6YaqmgIjNiJtk-OPUZBWm4JMUMfp55T0T4

får vi følgende resultat:

kicTQ0u4dtMvNsuuQu_Q1QP01q3S9lZhjVa44hf42Mwg2PZ0ayQUkQgPWg3wlK9_t6rsR0b5TpwaCT_IhC0RDPN_WFcHMFOrqYjrzYeDGGSWDjQbDUhyIof6aNQEPNamZWZ5TzLk7JQ=w636-h427

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);

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

0vHSnX-4jI1mjC3NTW1WmE2SlnqqPI8XtB70hV2WA4r_FqpLSbhGDZSXL-yC2WyQuOJI-od4T5tNBaWs1kh13HeehoyYhy5_pl2ZBUeQ0rFqCqWqnx4cLyhfVTHL5wl6A3R2I5CMnO4

Her speiles teksturen annenhver gang.

Blande teksturer

I foregående eksempler ble fargen til hvert fragment bestemt av teksturen vha. shaderfunksjonen texture2D(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 texture2D() henter med andre ord farge fra aktuell tekstur (uSampler) vha. vTextureCoordinate.s og vTextureCoordinate.t og tilordner farge til gl_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 (se også Greggman, 2013). 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:

varying lowp vec2 vTextureCoordinate;
uniform sampler2D uSampler0;
uniform sampler2D uSampler1;

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

Legg merke til følgende:

  • Følgende gjør at man kan bruke bilder av vilkårlig størrelse:
// Dette gjør at bildet kan ha vilkårlig bredde og høyde.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  • To bilder laster før kall på initSquareBuffer(textureImage1, textureImage2). 
  • Det er to globale texture-variabler (squareTexture1 og squareTexture2) og dermed to textureobjekter.
  • Shaderne er endret som vist over.
  • To samplervariabler sendes til fragmentshaderen. 

Som vist over er det nå definert to «samplere» i fragmentshaderen, uSampler0 og uSampler1. Disse brukes videre for å hente 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:
  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 draw() metoden som vist tidligere:

let samplerLoc0 = gl.getUniformLocation(textureShader.program, textureShader.uniformLocations.sampler0);
gl.uniform1i(samplerLoc0, 0);
let samplerLoc1 = gl.getUniformLocation(textureShader.program, textureShader.uniformLocations.sampler1);
gl.uniform1i(samplerLoc1, 1); 

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

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

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

Mipmapping

Teknikken går ut på å bruke teksturer/bildefiler med ulik størrelse. Bildet som man tar utgangspunkt 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