iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
自我挑戰組

翻車機率極高的2D平台遊戲(2D Platformer)製作系列 第 9

[Day9] 預設貼圖

今日目標

設計簡單的2D渲染器

iron_render這裡就是放任何跟渲染相關功能的地方。2D渲染器,剛開始相對簡單(我想像中了啦)。

我的設計會是,在初始化(CreateRenderer)之後,接下來只要在Game Loop裡面呼叫DrawXXXX就可以畫出相對應的想要的圖案畫貼圖,之後會像視窗功能那樣在Game Loop中有StartDrawEndDraw之類的記錄這一Frame的狀態,DrawXXXX會在StartDrawEndDraw之間呼叫。

// This is a example

while(1) {
    StartDraw();

        DrawRectangle();

        DrawTexture();

    EndDraw();
}

會說簡單只要還有一點,主要是大部分得圖形,甚至是全部,都可以視為是矩形。最多就4個頂點與,然後會有使用貼圖與顏色的不同,所以我會把預設的Shader直接寫在iron_render.c文件裡面,基本的一些繪製就可以應付了,除非之後有甚麼特殊效果,需要用不同的Shader來製作,就再說吧~;)

預設的Shader

預設的shader沒甚麼變,還是長這樣(如果不知道甚麼是shader的話,可以參考這篇)

// vertex
static const char* DEFAULT_2D_VERTEX_SHADER_CODE = "#version 330 core\n"
"in vec2 _Pos;\n"
"in vec2 _Texcoords;\n"
"out vec2 Texcoords;\n"
"void main() {\n"
"   gl_Position = vec4(_Pos, 0.0, 1.0);\n"
"   Texcoords = _Texcoords;\n"
"}\n\0";

// fragment
static const char* DEFAULT_2D_FRAGMENT_SHADER_CODE = "#version 330 core\n"
"in vec2 Texcoords;\n"
"uniform vec4 _Color;\n"
"uniform sampler2D _Texture2D;"
"out vec4 _FragColor;\n"
"void main() {\n"
"   _FragColor = _Color * texture(_Texture2D, Texcoords);\n"
"}\n\0";

之前沒有說,shader當中設置的變數,前面的inoutuniform是甚麼意思?

首先來講uniform,可以看到在Shader中標示uniform的變數,在DrawXXXX中用glUniform4f設定了變數,uniform其實就是Shader裡的常數,透過外部去設置這個變數,如同之前所寫的,就是把顏色設定到shader裡。

inout兩個關鍵字比較新,之前OpenGL2.0時的GLSL是用varying,這是用在shader管線之間傳遞參數用的,不會提供給外部,inout是視作一對的,例如: 傳遞_Pos

// In vertex shader
out vec2 _Pos;

然後經過管線傳到fragment shader,那就要是:

// In fragment shader
in vec2 _Pos

如上,連參數名稱也要一樣。

但看看vertex shader的_Pos_Texcoords,開頭就是in那這是哪來的呢?這些在vertex shader的就是attribute了,在OpenGL2.0的GLSL也是用這個關鍵字,這個要搭配

glGetAttribLocation(...)
glVertexAttribPointer(...)
glEnableVertexAttribArray(...)

一起使用,還有如果有點進上面參考資料的,裡面的shader在attribute前面有加上layout(location = 0)其實就是直接標示了attrubute的位置,不用使用glGetAttribLocation

註: 這邊也證實我之前說錯了,可編程管線,也就是Shader,其語言不是3.0才有的,2.0就有了

第一個矩形第一章貼圖

CreateRenderer的地方,加寫這一段,等於是把之前設置頂點數據的地方,換句話說,創建VertexBufferObjectVertexArrayObjectIndexBufferObject的地方((如果不知道這些是甚麼的話,也請參考這篇),初始化一份存起來即可

...
    glGenVertexArrays(1, &RENDER_2D_CONTEXT.vao);
	glBindVertexArray(RENDER_2D_CONTEXT.vao);

	glGenBuffers(1, &RENDER_2D_CONTEXT.vbo);
	glBindBuffer(GL_ARRAY_BUFFER, RENDER_2D_CONTEXT.vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES), VERTICES, GL_STATIC_DRAW);

	glGenBuffers(1, &RENDER_2D_CONTEXT.ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, RENDER_2D_CONTEXT.ibo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(INDICES), INDICES, GL_STATIC_DRAW);

	// set position attribute
	    glVertexAttribPointer(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_POS], 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
            glEnableVertexAttribArray(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_POS]);

	// set texture coordinate attribute
        glVertexAttribPointer(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_TEXCOORD], 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
        glEnableVertexAttribArray(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_TEXCOORD]);

	glBindVertexArray(0);
...

然後DrawFirstTriangleDrawFirstTexture的地方只要留下呼叫要使用的shader與VAO綁上要的屬性繪製圖形這三個功能就好了。

// Draw Rect
    glBindVertexArray(RENDER_2D_CONTEXT.vao);

	glUseProgram(RENDER_2D_CONTEXT.default_shader.id);

	V4f v = ColorToVec4f(c);
	glUniform4f(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC4_COLOR], v.r, v.g, v.b, v.a);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, RENDER_2D_CONTEXT.default_texture.id);

	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

	glBindTexture(GL_TEXTURE_2D, 0);
	glBindVertexArray(0);
// DrawTexture
    glBindVertexArray(RENDER_2D_CONTEXT.vao);

	glUseProgram(RENDER_2D_CONTEXT.default_shader.id);

	// set shader color
	V4f v = ColorToVec4f(c);
	glUniform4f(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC4_COLOR], v.r, v.g, v.b, v.a);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture.id);

	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

	glBindTexture(GL_TEXTURE_2D, 0);
	glBindVertexArray(0);

完成後,如果呼叫DrawFirstRectangle的話會看到烏漆麻黑一片,這是因為預設的shader有這段:

_FragColor = _Color * texture(_Texture2D, Texcoords);

這表示強制需要使用一張貼圖。

那就在CreateRenderer的地方,產生一張***1x1的白色像素***當作預設的貼圖,方便在之後如果Shader或貼圖載入錯誤了,就會直接使用預設的資源...

// create default texture, a pixel 1x1 white squad
	unsigned char white_pixel[4] = { 255, 255, 255, 255 };

	glBindTexture(GL_TEXTURE_2D, 0);

	glGenTextures(1, &RENDER_2D_CONTEXT.default_texture.id);
	glBindTexture(GL_TEXTURE_2D, RENDER_2D_CONTEXT.default_texture.id);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, white_pixel);

	glBindTexture(GL_TEXTURE_2D, 0);

這樣就完成了!

參考

最後這是今天的成果,可以到我們github


上一篇
[Day 8] 2D世界中的數學 (一)
下一篇
[Day10] 2D的數學世界(二) - 座標系轉換
系列文
翻車機率極高的2D平台遊戲(2D Platformer)製作33

尚未有邦友留言

立即登入留言