ETH官方钱包

前往
大廳
主題

【程式】OpenGL 3.3 使用貼圖

Shark | 2021-07-18 11:42:16 | 巴幣 1228 | 人氣 1148

前篇:OpenGL 3.3 架設(shè)基本繪圖管線
前一篇還沒(méi)有使用uniform buffer、texture和sampler,這次要加上這三種物件,並且寫比較複雜的shader。

整個(gè)系列可以看這篇目錄,「Direct3D與OpenGL」的部分。
Shark流程式教學(xué)一覽



本篇使用的圖檔,可以下載回去用,把檔名改成「char1.png」放在與exe檔相同資料夾。

用這個(gè)軟體做的
キャラクターなんとか機(jī)  http://khmix.sakura.ne.jp/download.shtml

#define GL_GLEXT_PROTOTYPES
#define GLX_GLXEXT_PROTOTYPES
#include<GL/gl.h> //間接引用GL/glext.h
#include<GL/glx.h> //間接引用X11/Xlib.h

#include<stdio.h>
#include<time.h> //使用clock_gettime()和nanosleep()
#include<fcntl.h> //使用open()
#include<sys/stat.h> //使用fstat()
#include<gdk-pixbuf/gdk-pixbuf.h> //讀取圖檔
#include<x86intrin.h>
//使用_rotr()和__bswapd()

const int WINDOW_W=400,WINDOW_H=400;
//OpenGL global狀態(tài)
Display* dsp;
Window window;
GLXContext context;
//OpenGL物件
uint32_t programID;
uint32_t vertexData;
uint32_t vertexArrayObj;
//本篇新增的物件
uint32_t uniformBuffer;
uint32_t texture;
uint32_t sampler;

//要傳給GPU的資料在下面說(shuō)明

//這些函式在下面說(shuō)明

static int initGL();
static int initSettings();
static void nextFrame();
static void deinitSettings();
static void deinitGL();
static char* loadWholeFile(const char* fileName, uint32_t* outFileSize);
static int loadShader(uint32_t shaderID, const char* fileName);
static void initVertexData1(uint32_t* outVertexData, uint32_t* outVao);
static uint32_t loadTexture(const char* fileName);

//這個(gè)函式留待之後介紹layout時(shí)解說(shuō)
static void initVertexData2(uint32_t* outVertexData, uint32_t* outVao);

int main(){
  dsp = XOpenDisplay( NULL );
  window = XCreateSimpleWindow(dsp,
    DefaultRootWindow(dsp),
    0, 0,WINDOW_W, WINDOW_H, //xywh
    0, 0, 0);

  if(initGL()){
    printf("Can not initialize OpenGL\n");
    return 0;
  }
  if(initSettings()){
    return 0;
  }

  //設(shè)標(biāo)題
  XStoreName(dsp, window, "simplepipeline");
  //設(shè)定事件mask
  Atom wmDelete = XInternAtom(dsp, "WM_DELETE_WINDOW", True);
  XSetWMProtocols(dsp, window, &wmDelete, 1);
  XMapWindow( dsp, window );

  XEvent evt;
  int isEnd=0;
  struct timespec prevTime,nextTime;
  while(!isEnd){
    clock_gettime(CLOCK_MONOTONIC, &prevTime);
    //接收事件
    while(XPending(dsp)){
      XNextEvent(dsp, &evt);
      switch(evt.type){
      case ClientMessage:
        if(evt.xclient.data.l[0]== wmDelete){ isEnd=1; }
        break;
      }
    }

    //正式寫遊戲時(shí),遊戲邏輯放在此處

    nextFrame(); //更新畫面

    clock_gettime(CLOCK_MONOTONIC, &nextTime); //單位為奈秒(10^-9秒)
    int64_t elapsedTime = (nextTime.tv_sec-prevTime.tv_sec)*1000000000
      +(nextTime.tv_nsec-prevTime.tv_nsec); //求出經(jīng)過(guò)的奈秒數(shù)
    struct timespec sleepTime={0, 16000000-elapsedTime};
    if(sleepTime.tv_nsec>0){ nanosleep(&sleepTime, NULL); }
  }

  XDestroyWindow( dsp, window );
  XFlush(dsp);
  deinitSettings();
  deinitGL();
  XCloseDisplay( dsp );
  return 0;
}
將視窗尺寸設(shè)得比較大以配合圖檔,且多了一些header用來(lái)讀檔案。

這次要傳給GPU的資料如下
//uniform buffer
const float WINDOW_SIZE[]={WINDOW_W, WINDOW_H};

//頂點(diǎn)資料
typedef struct{
  float pos[2];
  short texCoord[2];
  uint32_t color;
} VertexData1;
VertexData1 VERTEX_DATA1[4]={
  {100,0,  0,  0,  0xffffffff},
  {100,400,0,  400,0xffffffff},
  {400,0,  300,0,  0xffffffff},
  {400,400,300,400,0xffffffff},
};

typedef struct{
  float pos[8];
  short texCoord[8];
  uint32_t color[4];
} VertexData2;
VertexData2 VERTEX_DATA2={
  {100,0,100,400,400,0,400,400},
  {0,0,0,400,300,0,300,400},
  {0xffffffff,0xffffffff,0xffffffff,0xffffffff},
};
資料跟D3D篇一樣,只是那篇使用C++而本篇使用C,純C用struct定義資料型態(tài)必須寫成typedef struct。
說(shuō)明照搬那邊的:
VERTEX_DATA1和VERTEX_DATA2是相同頂點(diǎn)資料用不同layout儲(chǔ)存,本篇只用1,2等之後寫layout的教學(xué)再使用。
這次頂點(diǎn)資料有三項(xiàng):位置、貼圖坐標(biāo)、顏色,之前說(shuō)過(guò)D3D和OpenGL的畫面坐標(biāo)是-1~1、貼圖坐標(biāo)是0~1,但2D畫面習(xí)慣上以畫面左上角為原點(diǎn)、以像素為單位,這裡頂點(diǎn)資料就這樣給,在shader裡換算成D3D和OpenGL標(biāo)準(zhǔn)。
雖然本篇故意在vertex shader裡算,這裡只有4個(gè)頂點(diǎn),在CPU把4個(gè)頂點(diǎn)全部算好才傳給GPU其實(shí)效能不會(huì)差多少,但如果一次畫很多個(gè)三角形(如tilemap和粒子系統(tǒng)),在vertex shader裡算比較方便。

坐標(biāo)是這樣,右圖的ABCD是頂點(diǎn)順序。

多數(shù)繪圖軟體和函式庫(kù)以左上角為原點(diǎn),但上傳貼圖的函式glTexImage2D()規(guī)定給它的資料以左下角為原點(diǎn),所以圖在OpenGL內(nèi)部是倒立的狀態(tài)。不過(guò)OpenGL讀取貼圖也以左下角為原點(diǎn),兩次顛倒抵消,能畫出正立的圖片。
但如果用framebuffer object把圖畫在記憶體內(nèi)的點(diǎn)陣圖,再把它當(dāng)成貼圖畫在另一張點(diǎn)陣圖,就要對(duì)坐標(biāo)特別處理了。

shader這次採(cǎi)用讀外部檔的方式,由於OpenGL 3.3沒(méi)有提供事先編譯的方式,主程式要把shader原始碼載入,在執(zhí)行時(shí)編譯。
準(zhǔn)備這兩個(gè)檔案,因?yàn)镚LSL程式起點(diǎn)固定叫做main(),不能把兩個(gè)shader放在同一個(gè)檔案。
//usetexture.vert.glsl
#version 330
layout(std140) uniform uniform1{
  vec2 windowSize;
};
uniform sampler2D sampler1; //GLSL的sampler物件包含sampler和貼圖

//頂點(diǎn)資料,對(duì)應(yīng)到struct VertexData1
layout(location=0) in vec2 inPos;
layout(location=1) in vec2 inTexCoord;
layout(location=2) in vec4 inColor;

//vertex傳給fragment shader的資料
varying vec2 varTexCoord;
varying vec4 varColor;

void main(){
  vec2 outPos=inPos*vec2(2,-2)/windowSize+vec2(-1,1);
  gl_Position=vec4(outPos, 0, 1);
  ivec2 texSize=textureSize(sampler1, 0);
  varTexCoord=inTexCoord/texSize;
  varColor=inColor;
}
//usetexture.frag.glsl
#version 330
uniform sampler2D sampler1;

varying vec2 varTexCoord;
varying vec4 varColor;

void main(){
  vec4 texColor=texture(sampler1, varTexCoord);
  gl_FragColor=texColor*varColor;
}
shader的輸入與輸出」提到的輸出入在這裡出現(xiàn)了,看本篇時(shí)可以跟那篇對(duì)照著看。

shader裡面的處理方式跟D3D篇一樣,以下是從D3D篇照抄:
vertex shader坐標(biāo)轉(zhuǎn)換的算式怎麼求出,回想一下學(xué)校學(xué)過(guò)的二元一次方程式。
畫面坐標(biāo)要把左邊的換算成右邊

二元一次方程式的標(biāo)準(zhǔn)式「x'=xa+b, y'=yc+d」,把兩組坐標(biāo)代進(jìn)去,得到兩組聯(lián)立方程式
X坐標(biāo)
-1 = 0 + b
1 = windowW×a + b
Y坐標(biāo)
1 = 0 + d
-1 = windowH×c + d

解出a,b,c,d如下
  x' =( 2/windowW)x - 1
  y' =(-2/windowH)y + 1
因?yàn)閟hader可以一次計(jì)算四維向量,可以寫成這樣
  outPos = inPos×(2,-2)/(windowW, windowH) + (-1,1)

貼圖坐標(biāo)只要除以貼圖寬高即可,內(nèi)建函式textureSize()可以取得貼圖寬高。
顏色就原封不動(dòng)傳給rasterizer內(nèi)插。

fragment shader讀取貼圖裡的像素,跟頂點(diǎn)資料裡的顏色相乘。

OpenGL 3.3雖然不能事先編譯,但有一個(gè)工具glslangValidator可以在把shader包進(jìn)主程式前檢查語(yǔ)法。Fedora要裝glslang的套件;Ubuntu要20.04版,Mint要20版以後才有這個(gè)套件,名稱是glslang-tools。
Windows的話就自己去官網(wǎng)找下載的地方。
https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/

像這樣打可檢查語(yǔ)法錯(cuò)誤
glslangValidator usetexture.vert.glsl
glslangValidator usetexture.frag.glsl
它會(huì)根據(jù)副檔名判斷這是哪一階段的shader、語(yǔ)言是GLSL還是HLSL,把檔案命名為.vert.glsl和.frag.glsl目的在此。

如果要查uniform block裡各變數(shù)的位置作為寫C struct的參考,好像必須用「glslangValidator -i 檔名」再?gòu)难e面找出offset資訊,不知道有沒(méi)有更簡(jiǎn)單的方法。
另外如果想在OpenGL比較新的版本用SPIR-V,將程式碼編譯成SPIR-V也是用這個(gè)工具。

static int initGL(){
  const int fbConfigAttr[]={
    GLX_X_RENDERABLE,True,
    GLX_DOUBLEBUFFER,True,
    GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
    GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE,8,
    None
  };
  int fbCount;
  GLXFBConfig* fbc = glXChooseFBConfig(dsp, DefaultScreen(dsp),fbConfigAttr,&fbCount);
  if(fbc==NULL){ return 1; }

  //產(chǎn)生OpenGL 3.3 context
  const int contextAttribs[] = {
    GLX_CONTEXT_MAJOR_VERSION_ARB,3,
    GLX_CONTEXT_MINOR_VERSION_ARB,3,
    GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
    None,
  };
  context = glXCreateContextAttribsARB(dsp, fbc[0], 0,True, contextAttribs);
  XFree(fbc);
  if(context==0){ return 1; }
  glXMakeCurrent(dsp, window, context);

  //設(shè)定Vsync
  PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA=
    (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddress("glXSwapIntervalMESA");
  if(glXSwapIntervalMESA){
    glXSwapIntervalMESA(0);
  }
  PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI=
    (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddress("glXSwapIntervalSGI");
  if(glXSwapIntervalSGI){
    glXSwapIntervalSGI(0);
  }
  return 0;
}

static void deinitGL(){
  glXDestroyContext(dsp, context);
}
initGL()、deinitGL和之前一樣。

static void nextFrame(){
  glClear(GL_COLOR_BUFFER_BIT);
  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  glXSwapBuffers(dsp, window);
}
glDrawArrays()第一參數(shù)的幾何形狀改成GL_TRIANGLE_STRIP,第三參數(shù)的頂點(diǎn)數(shù)量改成4。
本篇要講的東西很多,先不介紹各種幾何形狀。雖然OpenGL 3.0以後把四邊形列為deprecated,OpenGL ES也一開(kāi)始就沒(méi)支援四邊形,只有四個(gè)頂點(diǎn)的情況下用triangle strip可以畫四邊形。

static void deinitSettings(){
  glDeleteProgram(programID);
  uint32_t buffers[]={vertexData, uniformBuffer};
  glDeleteBuffers(2, buffers);
  glDeleteVertexArrays(1, &vertexArrayObj);

  glDeleteTextures(1, &texture);
  glDeleteSamplers(1, &sampler);
}
要?jiǎng)h除的物件增加3個(gè)。
buffer物件有兩個(gè),這裡利用glDeleteBuffers()可以一次刪除多個(gè)物件的特性,傳入長(zhǎng)度為2的陣列。



接下來(lái)的initSettings()是主題,用到的輔助函式會(huì)在旁邊解說(shuō)。

-Vertex & Fragment Shader-

//傳回來(lái)的指標(biāo)要用free()釋放
static char* loadWholeFile(const char* fileName, uint32_t* outFileSize){
  int file=open(fileName, O_RDONLY);
  if(file==-1){
    *outFileSize=0;
    return NULL;
  }
  struct stat buf;
  fstat(file, &buf);
  long fileSize=buf.st_size;
  char* data=(char*)malloc(fileSize);
  read(file, data, fileSize);
  close(file);
  *outFileSize=fileSize;
  return data;
}

//傳回0代表成功、非0代表失敗
static int loadShader(uint32_t shaderID, const char* fileName){
  uint32_t fileSize;
  const char* shaderCode=loadWholeFile(fileName, &fileSize);
  if(shaderCode==NULL){
    return 1;
  }
  glShaderSource(shaderID, 1, &shaderCode, &fileSize);
  free((void*)shaderCode);
  glCompileShader(shaderID);
  //檢查shader錯(cuò)誤
  int ret;
  glGetShaderiv(shaderID, GL_COMPILE_STATUS, &ret); //ret=0代表有錯(cuò)誤
  if(!ret){
    int infoLen;
    glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &infoLen); //取得訊息長(zhǎng)度
    char info[infoLen]; //配置空間
    glGetShaderInfoLog(shaderID, infoLen, NULL, info); //取得訊息
    printf("%s:\n%s",fileName, info);
  }
  return !ret;
}

//傳回0代表成功,非0代表失敗
static int initSettings(){
  //compile vertex shader
  uint32_t vsID=glCreateShader(GL_VERTEX_SHADER);
  loadShader(vsID, "usetexture.vert.glsl");
  //compile fragment shader
  uint32_t fsID=glCreateShader(GL_FRAGMENT_SHADER);
  loadShader(fsID, "usetexture.frag.glsl");

  //create program object
  programID=glCreateProgram();
  glAttachShader(programID, vsID);
  glAttachShader(programID, fsID);
  glLinkProgram(programID);
  glDetachShader(programID, vsID);
  glDetachShader(programID, fsID);
  glDeleteShader(vsID); //之後不會(huì)再用到shader物件,可刪除
  glDeleteShader(fsID);
  //check program error
  int ret;
  glGetProgramiv(programID,GL_LINK_STATUS, &ret);
  if(!ret){
    int infoLen;
    glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLen);
    char info[infoLen];
    glGetProgramInfoLog(programID, infoLen, NULL, info);
    printf("program linking:\n%s", info);
    glDeleteProgram(programID);
    return 1;
  }
  glUseProgram(programID);
loadWholeFile()用到的Linux API函式請(qǐng)參照這篇:檔案操作—Linux篇,作業(yè)系統(tǒng)沒(méi)有直接提供「?jìng)魅霗n名→讀取整個(gè)檔案」的函式,但這功能有時(shí)候會(huì)用到,寫一個(gè)函式做這件事。

把shader程式碼讀入記憶體之後,按照「OpenGL 3.3 架設(shè)基本繪圖管線」的方法建立shader與program物件、檢查error。

-Vertex buffer & Input assembler-

static void initVertexData1(uint32_t* outVertexData, uint32_t* outVao){
  //vertex buffer
  glGenBuffers(1, outVertexData);
  glBindBuffer(GL_ARRAY_BUFFER, *outVertexData);
  glBufferData(GL_ARRAY_BUFFER, sizeof(VERTEX_DATA1),VERTEX_DATA1, GL_STATIC_DRAW);

  //vertex array object
  glGenVertexArrays(1,outVao);
  glBindVertexArray(*outVao);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT,0,
    sizeof(VertexData1), 0);
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 2, GL_SHORT,0,
    sizeof(VertexData1), (void*)offsetof(VertexData1, texCoord));
  glEnableVertexAttribArray(2);
  glVertexAttribPointer(2, GL_BGRA, GL_UNSIGNED_BYTE,1,
    sizeof(VertexData1), (void*)offsetof(VertexData1, color));
}

//initSettings()內(nèi)容
  //vertex data & vertex array object

  initVertexData1(&vertexData, &vertexArrayObj);
建buffer和vertex array物件的方法跟「OpenGL 3.3 架設(shè)基本繪圖管線」一樣,本篇頂點(diǎn)有三項(xiàng)資料,要呼叫三次glEnableVertexAttribArray()和glVertexAttribPointer()設(shè)定格式。glVertexAttribPointer()的參數(shù)是什麼意思留待下一篇介紹。

-Uniform buffer-

  //uniform buffer
  glGenBuffers(1, &uniformBuffer);
  glBindBuffer(GL_UNIFORM_BUFFER, uniformBuffer);
  glBufferData(GL_UNIFORM_BUFFER, sizeof(WINDOW_SIZE), WINDOW_SIZE, GL_STATIC_DRAW);

  //將這個(gè)物件與shader裡的buffer物件對(duì)應(yīng)
  uint32_t uniformIndex=glGetUniformBlockIndex(programID, "uniform1");
  glUniformBlockBinding(programID, uniformIndex, 2);
  glBindBufferBase(GL_UNIFORM_BUFFER, 2, uniformBuffer);
本篇新增的東西之一。與vertex buffer一樣是buffer所以建立的方法很像,只是bind的第一參數(shù)換成GL_UNIFORM_BUFFER。

-Sampler-

  //sampler
  glGenSamplers(1, &sampler);
  glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //default
  glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_REPEAT); //default
  glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_REPEAT); //default

  //將這個(gè)物件與shader裡的sampler物件對(duì)應(yīng)

  int location=glGetUniformLocation(programID, "sampler1");
  glUniform1i(location, 2);
  glBindSampler(2, sampler);
本篇新增的東西之二。跟大部分OpenGL物件一樣用Gen函式產(chǎn)生識(shí)別碼,不過(guò)修改sampler屬性不需要先bind,因?yàn)椴僮鱯ampler的函式用第一參數(shù)指定要操作哪個(gè)sampler。
FILTER和WRAP是什麼請(qǐng)參照這篇貼圖的部分:shader的輸入與輸出,MAG_FILTER和MIN_FILTER分別設(shè)定放大和縮小要做什麼處理,WRAP_S和WRAP_T分別是貼圖的X坐標(biāo)和Y坐標(biāo),兩個(gè)方向可以用不同鋪排方式。
OpenGL wiki: Sampler Object
有幾行有標(biāo)示「//default」,sampler物件建好之後就會(huì)填入一些預(yù)設(shè)值,這幾行剛好跟預(yù)設(shè)的值相同,把這幾行拿掉執(zhí)行結(jié)果不會(huì)變。

-Texture-

//讀取圖檔、解碼、產(chǎn)生texture物件
//成功傳回OpenGL物件識(shí)別碼,失敗傳回0

static uint32_t loadTexture(const char* fileName){
  //用gdk-pixbuf讀取圖檔,把RGBA值存在一個(gè)叫pixels的變數(shù)
  GError* error=NULL;
  GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file(fileName,&error);
  if(pixbuf==NULL){
    return 0;
  }
  int w = gdk_pixbuf_get_width(pixbuf);
  int h = gdk_pixbuf_get_height(pixbuf);
  uint32_t* pixels = (uint32_t*)malloc(w*h*4);
  GdkPixbuf* outPixbuf=gdk_pixbuf_new_from_data((uint8_t*)pixels,
    GDK_COLORSPACE_RGB, 1,8,w,h,w*4,NULL,0);
  gdk_pixbuf_copy_area(pixbuf,0,0,w,h,outPixbuf,0,0);
  g_object_unref(pixbuf);
  g_object_unref(outPixbuf);
  //RGBA轉(zhuǎn)BGRA
  uint32_t* tempP=pixels;
  for(int i=0; i<w*h; i++,tempP++){
    *tempP=_rotr(__bswapd(*tempP), 8);
  }

  //建立texture物件
  uint32_t textureID;
  glGenTextures(1, &textureID);
  glBindTexture(GL_TEXTURE_2D, textureID);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w,h,0, GL_BGRA,GL_UNSIGNED_BYTE, pixels);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_LEVEL, 0); //設(shè)mipmap層數(shù)
  free(pixels);
  return textureID;
}
//initSettings()內(nèi)容
  //texture

  glActiveTexture(GL_TEXTURE2); //目前要操作2號(hào)slot
  texture = loadTexture("char1.png");
本篇新增的東西之三,用到一個(gè)輔助函式loadTexture()。用一個(gè)函式庫(kù):gdk-pixbuf讀取圖檔和解碼,方法參照這篇:讀取圖檔的方法-Linux篇
再抄一段D3D篇的過(guò)來(lái):PNG、JPG、webP這些編碼過(guò)的格式不能給GPU使用,因?yàn)镚PU讀取貼圖需要迅速找到任意位置的像素(即隨機(jī)存取,random access),這些格式必須完全解碼才能得知每個(gè)像素的值。用在GPU的壓縮格式必須設(shè)計(jì)成能隨機(jī)存取,D3D和OpenGL有支援一些壓縮格式,以後用到再介紹。

gdk-pixbuf解碼出來(lái)的byte順序是RGBA,之後把它轉(zhuǎn)換成BGRA。R,G,B,A四個(gè)分量,每個(gè)分量8bit剛好形成一個(gè)32位元整數(shù),四個(gè)byte的順序D3D和OpenGL可以支援RGBA(最低位元組是R)和BGRA(最低位元組是B),筆者選用BGRA的理由是大部分繪圖軟體的十六進(jìn)位顏色值都是BGR,可以直接把軟體裡的值複製貼上到程式裡。
只用C/C++語(yǔ)法轉(zhuǎn)換byte順序有點(diǎn)麻煩,這裡用兩個(gè)組合語(yǔ)言指令做這工作:bswap和ror,

__bswapd()和_rotr()是所謂的intrinsic function,有些組合語(yǔ)指令沒(méi)有直接對(duì)應(yīng)的C/C++語(yǔ)法,C/C++只能用函式的型式提供功能,這些函式編譯後會(huì)直接轉(zhuǎn)換成組合語(yǔ)言指令,不會(huì)產(chǎn)生函式呼叫。

之後建立貼圖物件,老樣子的Gen、Bind兩步驟,再用glTexImage2D()設(shè)定貼圖格式並上傳資料。參數(shù)如下:
1:target。glBindTexture()把貼圖ID設(shè)給一個(gè)叫GL_TEXTURE_2D的變數(shù),然後glTexImage2D()讀取這個(gè)變數(shù)得知要操作哪個(gè)貼圖物件。
  貼圖種類有很多,除了本篇用的2D貼圖以外,還有1D、3D、cube map、貼圖陣列等等。
2:如果有使用mipmap且建立物件時(shí)就要上傳mipmap,要用這個(gè)參數(shù)指定層數(shù),不使用mipmap就填0。
3:在顯示記憶體裡以何種格式儲(chǔ)存。
4、5:寬高。
6:border。是早期版本的功能,但現(xiàn)在的OpenGL取消了,必須填0。
7、8:你傳進(jìn)去的資料(第9參數(shù))是什麼格式,7是有幾個(gè)顏色分量和byte順序,8是各分量是什麼型態(tài)。
9:要上傳的資料,byte數(shù)會(huì)從寬高和格式求出。

3,7,8全部可以填什麼格式要看官方文件
OpenGL wiki: GLAPI/glTexImage2D
內(nèi)部格式和第7,8參數(shù)如果是不同格式,GPU會(huì)做轉(zhuǎn)換,如果不能轉(zhuǎn)換就產(chǎn)生error code,取得error code的方法以前提過(guò),是用glGetError()。
內(nèi)部格式可以填這篇文件裡的Base Internal Formats讓GPU自行決定各分量bit數(shù),也可以填Sized Internal Formats指定bit數(shù);不需要指定byte順序,GPU會(huì)看情況自行決定。
下面還有Sized Depth and Stencil Internal Formats和Compressed Internal Formats兩個(gè)表,要求GPU配置一塊depth或stencil buffer也是用這個(gè)函式,文件裡有列出壓縮格式但上傳壓縮過(guò)的貼圖要用另一個(gè)函式:glCompressedTexImage2D()。

下一行「glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_LEVEL, 0)」絕對(duì)不能少,本篇沒(méi)有用到mipmap的功能,但貼圖物件被建出來(lái)時(shí)是設(shè)定成要套用mipmap,如果沒(méi)準(zhǔn)備好mipmap就不能使用這個(gè)物件,所以要用這一行告訴OpenGL這個(gè)貼圖不用mipmap。
(筆者有一次因?yàn)闆](méi)打這行卡了好幾個(gè)小時(shí))

-Rasterizer, depth, stencil, and blend-

  //rasterizer
  glDisable(GL_CULL_FACE); //default
  glDisable(GL_MULTISAMPLE);
  glViewport(0, 0, WINDOW_W, WINDOW_H);

  //depth and stencil
  glDisable(GL_DEPTH_TEST); //default
  glDisable(GL_STENCIL_TEST); //default

  //blend

  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_ADD); //default
  glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

  const float color[]={1,1,1,1};
  glClearColor(color[0],color[1],color[2],color[3]);
  return 0;
}
//initSettings()結(jié)束
rasterizer、depth和stencil跟以前一樣,只有blend換了算式,改成正式繪圖軟體使用的。
  (Sc, Sa):pixel shader輸出的顏色和alpha,數(shù)值範(fàn)圍0~1
  (Dc, Da):畫面上的顏色和alpha
    color = ScSa + Dc(1-Sa)
    alpha = screen(Sa, Da) = Sa + Da(1-Sa)
  screen()是圖層模式的濾色模式

glClearColor()把背景設(shè)成白色,且把a(bǔ)lpha設(shè)成1簡(jiǎn)化blend的算式。如果畫面的alpha不是1,那其實(shí)D3D和OpenGL的blend設(shè)定湊不出正確的alpha blend算式,要用premultiply alpha的技巧,讀取圖檔時(shí)做一點(diǎn)特別處理。
以前有寫過(guò)一篇筆記:premultiply alpha的妙用



如果檔名是usetexture.c,用這個(gè)指令build
gcc usetexture.c -o usetexture -s -Os -lX11 -lGL `pkg-config --cflags --libs gdk-pixbuf-2.0`
比上次多了`pkg-config --cflags --libs gdk-pixbuf-2.0`是因?yàn)橛胓dk-pixbuf讀取圖檔。

執(zhí)行的樣子。




uniform buffer、texture、sampler這三種物件如何將主程式和shader的物件對(duì)應(yīng),可以想成顯卡少女的工作臺(tái)有個(gè)置物櫃,有很多格子。

跟D3D11不一樣的是,OpenGL所有shader階段共用一個(gè)置物櫃,且把貼圖和sampler放在同一個(gè)格子。program物件裡有一張表把變數(shù)名稱和格子編號(hào)對(duì)應(yīng),由於OpenGL要把全部階段的shader連結(jié)成一個(gè)program才能使用,這個(gè)對(duì)應(yīng)表是全部階段共用。
(註:有兩個(gè)擴(kuò)充:ARB_shading_language_420pack和ARB_separate_shader_objects會(huì)改變本節(jié)介紹的規(guī)則,因?yàn)榉謩e是4.2與4.1版才列為標(biāo)準(zhǔn)配備,本篇不介紹)

本篇的shader裡有這兩段,其中的uniform1和sampler1是變數(shù)名稱,兩階段同名的變數(shù)會(huì)視為同一個(gè)物件。
//vertex shader
layout(std140) uniform uniform1{
  vec2 windowSize;
};
uniform sampler2D sampler1;
//fragment shader
uniform sampler2D sampler1;
link之後program會(huì)記住裡面有"uniform1"和"sampler1"兩個(gè)變數(shù),留待之後對(duì)應(yīng)到格子。

變數(shù)名稱與格子的對(duì)應(yīng),以及指示顯卡少女把物件放進(jìn)格子,是在主程式裡做。
這一段是設(shè)定uniform buffer
//填對(duì)應(yīng)表,將名稱"uniform1"對(duì)應(yīng)到2號(hào)
uint32_t uniformIndex=glGetUniformBlockIndex(programID, "uniform1");
glUniformBlockBinding(programID, uniformIndex, 2);
//將物件"uniformBuffer"放入2號(hào)格子
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uniformBuffer);
格子的索引是從0開(kāi)始,本篇故意用2。
不知為何不用一個(gè)函式直接填入(programID, "uniform1", 2)就好,還要取得uniformIndex再用另一個(gè)函式設(shè)定,但這就是OpenGL的規(guī)定。

上面說(shuō)對(duì)應(yīng)表是存在program物件裡,如果有3個(gè)program物件,要呼叫3次glGetUniformBlockIndex()和glUniformBlockBinding()分別設(shè)定對(duì)應(yīng),但只要呼叫一次glBindBufferBase()就可以3個(gè)program共用這個(gè)uniform buffer。

這一段是設(shè)定sampler物件
//填對(duì)應(yīng)表,將名稱"sampler1"對(duì)應(yīng)到2號(hào)
int location=glGetUniformLocation(programID, "sampler1");
glUniform1i(location, 2);
//將物件"sampler"放入2號(hào)格子
glBindSampler(2, sampler);
同樣要用一個(gè)暫時(shí)變數(shù)location。
glGetUniformLocation()用第一參數(shù)指定要操作哪個(gè)program,但glUniform1i()沒(méi)有programID的參數(shù),它用前面的glUseProgram()決定要操作的program。OpenGL有個(gè)討厭的地方是有些函式只由參數(shù)決定行為,但有些函式會(huì)讀取內(nèi)部的全域變數(shù),規(guī)則不一致。

texture物件的話,因?yàn)镚LSL的sampler物件也包含貼圖,sampler部分的前兩行也同時(shí)設(shè)定texture的格子對(duì)應(yīng)。
把物件放到2號(hào)格子要這樣做
//設(shè)全域變數(shù),設(shè)定之後要操作2號(hào)格子
glActiveTexture(GL_TEXTURE2);
//將物件"textureID"放入2號(hào)格子,本篇是在loadTexture()裡呼叫此
glBindTexture(GL_TEXTURE_2D, textureID);
格子編號(hào)不是填數(shù)字,而是用常數(shù)GL_TEXTURE0、GL_TEXTURE1……,這些是連續(xù)整數(shù),所以也可以用「GL_TEXTURE0+i」的方式。

可以做到這樣,在vertex和fragment shader用不同的變數(shù)名稱,但是對(duì)應(yīng)到相同格子。
但這會(huì)讓人寫程式混亂,同一個(gè)slot最好還是用相同名稱。
//vertex shader
uniform sampler2D sampler1;
//fragment shader
uniform sampler2D sampler2;
//主程式
int location = glGetUniformLocation(programID, "sampler1");
glUniform1i(location, 2);
location = glGetUniformLocation(programID, "sampler2");
glUniform1i(location, 2);

具體有幾個(gè)格子隨硬體、驅(qū)動(dòng)程式和作業(yè)系統(tǒng)而異,要用glGetIntegerv()查詢以下的值:
GL_MAX_VERTEX_UNIFORM_BLOCKS
GL_MAX_FRAGMENT_UNIFORM_BLOCKS
GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS
GL_MAX_TEXTURE_IMAGE_UNITS
  …………
在命令列打「glxinfo -B -l」會(huì)列出目前環(huán)境中OpenGL各功能的上限,其中有這些值。
參考 OpenGL wiki: Resource limitations
OpenGL規(guī)格會(huì)要求晶片廠商支援一定數(shù)量以上,照這篇說(shuō)明,3.3版至少有12格uniform buffer(編號(hào)0~11),16格sampler。

sampler物件是OpenGL 3.3版新增的東西,3.2版以前的做法是每個(gè)貼圖物件都內(nèi)附一個(gè)sampler,想設(shè)定取樣方式要修改貼圖屬性,如果看OpenGL比較早版本的教學(xué)會(huì)看到這種用法。
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
查這兩個(gè)函式的說(shuō)明會(huì)看到很多相同的設(shè)定值
glTexParameter
glSamplerParameter

創(chuàng)作回應(yīng)

相關(guān)創(chuàng)作

更多創(chuàng)作