目錄
基礎:以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
防止連結失效,發兩個