目錄

基礎:以OpenGL為例說明,OpenGL是如何渲染到紋理的

正題:UE4如何渲染到紋理

引子:

何為渲染到紋理?通常渲染的物件是視窗緩衝區,那麼要在UI上顯示3D資訊,用的技術就是渲染到紋理。比如說起絕地求生,無人不曉。絕地求生當中,開啟生個人資訊面板,UI上的3D人物(本文的封面圖),就是渲染到紋理的具體應用。

渲染篇:渲染到紋理分析(基礎介紹)

本篇的渲染結果

OpenGL是如何渲染到紋理的

關鍵名詞解釋:

幀快取

在OpenGL渲染管線中,幾何資料和紋理經過多次轉化和多次測試,最後以二維畫素的形式顯示在螢幕上。OpenGL管線的最終渲染目的地被稱作幀快取(framebuffer),幀緩衝是一些二維陣列和OpenG所使用的儲存區的集合:顏色快取、深度快取、模板快取和累計快取。

幀快取物件

預設情況下,OpenGL將幀緩衝區作為渲染最終目的地。此幀緩衝區完全由window系統生成和管理。這個預設的幀快取被稱作“window系統生成”(window-system-provided)的幀緩衝區。 在OpenGL擴充套件中,GL_ARB_framebuffer_object提供了一種建立額外的非顯示的幀緩衝區物件的介面。此種幀緩衝區被稱為“由程式建立的幀緩衝區”。這是為了區分window系統預設建立的幀緩衝區物件。透過使用幀快取物件(FBO),OpenGL可以將顯示輸出重定向到程式幀快取物件,而不是傳統的“window系統生成”幀快取。

紋理物件

儲存紋理資料的物件

OpenGL渲染預設是直接輸出到視窗快取(雙快取時渲染到背後的快取,之後再swapbuffer到前快取)。也可以直接渲染到幀快取中,如果將紋理物件關聯到幀快取物件的顏色附件中,就能得到渲染的最終結果紋理圖。這就是本次的主題“渲染到紋理(render to texture)”。

下面一步一步的介紹OpenGL是如何渲染到紋理的。

OpenGL渲染到紋理

構造一個紋理物件並渲染上紋理資料

渲染一個立方體用來展示渲染的紋理圖

構造一個紋理物件並渲染上紋理資料

由於具體要畫什麼不是重點,但是為了充分的展示渲染到紋理的實時效果,這裡畫了300個小怪,由於程式碼過於多,所以不全部列出,只展示重點程式碼,文章結尾附上完成vs2015工程原始碼

資料定義:

// 幀快取、紋理資料

GLuint FrameBuffer;

GLuint Texture;

GLsizei TexWidth;

GLsizei TexHeight;

// RTT渲染內容

VBObject object;//具體實現如感興趣,可以在本文最後自行下載原始檔。

。。。

資料初始化:

void Ch06RTT::InitRttContent()

{

// 分配紋理空間

glGenTextures(1, &Texture);

glBindTexture(GL_TEXTURE_2D, Texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TexWidth, TexHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

// 建立幀快取,並將紋理關聯到幀快取附件

glGenFramebuffers(1, &FrameBuffer);

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FrameBuffer);

glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Texture, 0);

// 載入模型

。。。

object。LoadFromVBM(“media/armadillo_low。vbm”, 0, 1, 2);

。。。

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

}

函式首先建立了一個TexWidth * TexHeight畫素格式為

GL_RGBA8

的二維紋理。之後將該紋理透過函式

glFramebufferTexture2D

關聯到幀快取物件的顏色附件掛點上。接下來載入一個模型,以備後面將該模型作為渲染內容渲染到幀快取中。顯示如下:

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FrameBuffer);

glViewport(0, 0, TexWidth, TexHeight);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glGenerateMipmap(GL_TEXTURE_2D);

。。。。

// Render INSTANCE_COUNT objects

glClearColor(1, 1, 0, 0);

object。Render(0, INSTANCE_COUNT);

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

首先需要的是呼叫

glBindFramebuffer

繫結幀快取物件,以重定向渲染目標為該幀快取物件,而不是視窗系統的快取物件。最後透過引數0來呼叫

glBindFramebuffer

恢復渲染目標為視窗幀快取物件。

渲染一個立方體用來展示渲染的紋理圖

cube資料定義:

// 立方體資料

GLuint CubeVBO; // 立方體的頂點陣列物件

GLint ModelMatrix; // shader中使立方體能旋轉的矩陣名稱

GLuint CubeShaderHandle; // shader的program

VertexData *CubeVertices; // 頂點資料

OpenGL若要畫立方體,首先需要頂點陣列物件,為頂點陣列物件分配快取物件,初始化快取物件的資料。下面的程式碼完成這些工作

void Ch06RTT::InitCube()

{

//生成頂點陣列物件

glGenVertexArrays(1, &CubeVBO);

glBindVertexArray(CubeVBO);

GLuint cubBuffer;

CubeVertices = new VertexData[36]{

// 左面

{ { -0。5, -0。5, -0。5 },{ 0。00f, 0。00f } },

{ { -0。5, -0。5, 0。5 },{ 0。00f, 1。00f } },

{ { -0。5, 0。5, 0。5 },{ 1。00f, 1。0f } },

{ { -0。5, 0。5, 0。5 },{ 1。00f, 1。0f } } ,

{ { -0。5, 0。5, -0。5 },{ 1。0f, 0。0f } },

{ { -0。5, -0。5, -0。5 },{ 0。00f, 0。00f } },

。。。其餘幾面定義省略,檔案結尾附完成程式碼檔案

// 背面

{ { 0。5, -0。5, -0。5 },{ 1。0f, 0。00f } },

{ { -0。5, -0。5, -0。5 },{ 0。00f, 0。00f } },

{ { -0。5, 0。5, -0。5 },{ 0。00f, 1。0f } },

{ { -0。5, 0。5, -0。5 },{ 0。00f, 1。0f } },

{ { 0。5, 0。5, -0。5 },{ 1。0f, 1。0f } },

{ { 0。5, -0。5, -0。5 },{ 1。0f, 0。00f } },

};

// 分配頂點快取

glGenBuffers(1, &cubBuffer);

glBindBuffer(GL_ARRAY_BUFFER, cubBuffer);

glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData) * 36, CubeVertices, GL_STATIC_DRAW);

// 載入shader程式

ShaderInfo shaders[] = {

{ GL_VERTEX_SHADER, “shaders/06/Rtt-cube。vs。glsl” },

{ GL_FRAGMENT_SHADER, “shaders/06/Rtt-cube。fs。glsl” },

{ GL_NONE, NULL }

};

CubeShaderHandle = LoadShaders(shaders);

glUseProgram(CubeShaderHandle);

ModelMatrix = glGetUniformLocation(CubeShaderHandle, “modelMatrix”);

// 設定頂點屬性,關聯shader中的屬性

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), BUFFER_OFFSET(0));

glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), BUFFER_OFFSET(sizeof(GLfloat)*3));

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

}

其中幾個關鍵點在此稍作解釋:

glGetUniformLocation

獲取shader中宣告模型變換矩陣變數的位置,以備在顯示立方體時,設定模型變換矩陣。

glVertexAttribPointer

該函式指明瞭頂點資料結構。該函式的引數略長,大概簡述下其功能如下:在頂點著色器中(下文)我們定義了兩個in限定型別的變數 position和uv。其中position為vec3由三個浮點型資料構成,uv:vec2由兩個浮點型構成,在應用程式中我們定了頂點結構體:

//頂點資料

struct VertexData {

vec3 position;

vec2 uv;

};

Shader中:

in vec3 position;

in vec2 uv;

這樣的定義將程式中初始化的頂點資料傳給了頂點shader,其中

glVertexAttribPointer

的最後一個引數在指定uv時為BUFFER_OFFSET(sizeof(

GLfloat

)*3),而在指定position時為null,結合該函式的第二個引數sizeof(VertexData),就解釋成:在頂點快取中每個頂點的資料大小是sizeof(VertexData)個位元組,其中每個頂點屬性從第sizeof(

GLfloat

)*3個位元組開始往後數2個Glfloat大小為uv座標。解釋完這些再看下shader的實現。新的OpenGL程式,再小的OpenGL程式也需要至少一個頂點著色器和一個片元著色器。其中LoadShaders就是負責載入該立方體的shader。Shader程式很簡單:

Vertex Shader

#version 430 core

in vec3 position;

in vec2 uv;

uniform mat4 modelMatrix;

out vec2 uvCoord;

void main()

{

uvCoord = uv;

gl_Position = modelMatrix * vec4(position,1。0);

}

頂點著色器聲明瞭一個頂點紋理座標uvCoord,該變數只是將程式中指定的頂點uv座標轉發到下階段的片元著色器,同時對頂點做了一個矩陣變換。

Fragment Shader

#version 430 core

uniform sampler2D textBuffer;

in vec2 uvCoord;

out vec4 outColor;

void main()

{

outColor = texture(textBuffer,uvCoord);

}

片元著色器接受頂點傳遞過來的uv座標,注意這裡uvCoord必須是和頂點著色器型別和名稱一致,但是限定詞頂點shader為out,片元shader為in。這樣就完成了程式的紋理座標向opengl的對映。接下來片元shader,使用texture取樣獲取片元顏色。為了顯示立方體,應用程式需要做的最後的內容就是,繫結紋理,交給渲染管線顯示該紋理。具體實現如下:

void Ch06RTT::DisplayCube()

{

glViewport(0, 0, current_width, current_height);

glClearColor(0, 0, 0, 1);

glEnable(GL_TEXTURE_2D);

static int t = 0;

++t %= 100000;

float rad = t*0。01 / 180。f*3。14;

float s = sin(rad);

float cs = cos(rad);

mat4 mm =

rotate( cs* 360。0f, 1。0f, 0。0f, 0。0f)*

rotate(s * 360。0f, 0。0f, 1。0f, 0。0f);

glUseProgram(CubeShaderHandle);

glBindVertexArray(CubeVBO);

glBindTexture(GL_TEXTURE_2D, Texture);

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);//s座標包裹模式

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);//t座標包裹模式

glUniformMatrix4fv(ModelMatrix, 1, GL_FALSE, mm);

glDrawArrays(GL_TRIANGLES, 0, 36);

}

程式碼透過glBindVertexArray並呼叫glDrawArrays來畫立方體,透過glUniformMatrix4fv 設定模型變換矩陣。

做完以上工作,最後就是整合兩個顯示內容:

void Ch06RTT::Init()

{

InitRttContent();

InitCube();

glClearColor(0, 0, 0, 0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_CULL_FACE); // 開啟剪裁

glCullFace(GL_BACK); // 裁掉背面

}

void Ch06RTT::Display()

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

DisplayRttContent();

DisplayCube();

//幀快取顯示在視窗右下角

glBindFramebuffer(GL_READ_FRAMEBUFFER, FrameBuffer);

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

glBlitFramebuffer(0, 0, TexWidth, TexHeight,

current_width - 200, 0, current_width, 200, GL_COLOR_BUFFER_BIT, GL_NEAREST);

}

首先在Init初始化RTT渲染內容,之後再初始化立方體。在接下來的Display中,首先繪製Rtt內容來使得紋理物件texture被填充上資料,之後再使用該texture渲染立方體。為了明確的觀察離屏渲染的內容,我們將使用函式

glBlitFramebuffer將

幀快取物件的內容copy到視窗渲染快取的右下角。效果如下

渲染篇:渲染到紋理分析(基礎介紹)

OpenGL渲染到紋理大致就是這樣了。

第二部分就是本篇的主角——UE4如何渲染到紋理

備註:

示例程式碼:

連結:

https://

pan。baidu。com/s/1uh2xFp

Jie4U4no3vHmg9Yw

密碼:117r

防止連結失效,發兩個