[Day5] 第一章贴图

今日目标

  • 载入图片,画出第一张图

stb_image.h

第三天-驱动OpenGL这篇有稍稍提到这个玩意

Single-header file

并推荐了stb这个在github上的repo。这个repo放的是作者Sean T. Barrett的撰写C语言时的各种工具。这些工具最大的特点就是把实作跟宣告写在同一个文件,利用macro分割开来,这样可以解决C与C++中麻烦的引用问题。

专案中要使用stb_image.h是一个「使用起来」简单,载入处理一些常见的图档的函式库。

老样子,我把这个文件放在external资料夹里面,再额外包一层stb的资料夹。然後就可以开始载入图片了。

载入图片

图片我用的是Kenny上的Animal Pack Redux,这是一个很赞的网站,里面提供了许多免费的素材~

首先,定义一个结构(struct)把要的资料记录下来

// iron_types.h
typedef enum GL_TextureFmt{
    TEX_FMT_WHITE_BLACK = GL_RED,
    TEX_FMT_GRAYSCALE   = GL_RG,
    TEX_FMT_RGB         = GL_RGB,
    TEX_FMT_RGBA        = GL_RGBA,
} TextureFmt;

typedef struct Texture {
    unsigned int id;
    int w, h;
    TextureFmt gl_fmt;
} Texture;

然後,我还定义了一个enum,表示Texture的对应在OpenGL里面的格式,其实可以点实作进去看,其实就是OpenGL,定义的一系列的数字,用於识别图片格式。

再来就是载入了...

// iron_asset.c - LoadTextureFile

    int channels;
	unsigned char* pixels = stbi_load(file_name, &texture->w, &texture->h, &channels, 0);
	if (pixels == NULL) {
		stbi_image_free(pixels);
		printf("Failed to load image: %s\n", file_name);
		return RES_ERROR_LOAD_IMAGE_FILE;
	}

	int src_img_fmt;
	if (channels == 2) {
		texture->gl_fmt = TEX_FMT_GRAYSCALE;
		src_img_fmt = GL_RG8;
	} else if (channels == 3) {
		texture->gl_fmt = TEX_FMT_RGB;
		src_img_fmt = GL_RGB8;
	} else if (channels == 4) {
		texture->gl_fmt = TEX_FMT_RGBA;
		src_img_fmt = GL_RGBA8;
	} else {
		texture->gl_fmt = TEX_FMT_WHITE_BLACK;
		src_img_fmt = GL_R8;
	}
    // continue...

有用到了就两个,stbi_loadstbi_freestbi可以视为他的命名空间。

stbi_load最後会回传图片所有的像素点,存成unsigned char的阵列,可以看到的第三个参数我代入channels,这个就是图片的颜色通道,例如PNG有RGBA四个通道,那channels就会拿到「4」,最後在对应到OpenGL的格式。除了OpenGL的格式,我还存了图片的「原」格式,下面会用到

再来就是设置Texture,把他储存在OpenGL的状态机里面:

   // iron_asset.c - LoadTextureFile 
   
   // part 1
    glBindTexture(GL_TEXTURE_2D, 0);

	glGenTextures(1, &texture->id);
	glBindTexture(GL_TEXTURE_2D, texture->id);
   // part 2
	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);
   // part 3
	glTexImage2D(GL_TEXTURE_2D, 0, src_img_fmt, texture->w, texture->h, 0, texture->gl_fmt, GL_UNSIGNED_BYTE, pixels);
   // part 4
	glBindTexture(GL_TEXTURE_2D, 0);

这里分作4个Part讲解比较容易

Part1: 生成与绑定新的Texture,第一行的bind texture是为了防止之後,有正在使用的texture,被盖到才加上的
Part2: 设置Texture的环绕(?)与过滤(?)的方式,老实说我不知道中文怎麽翻译,建议可以看看这个可以更加清楚。
Part3: 设置资料到OpenGL里,第2个参数必填0 参考
Part4: 设置完成,解除绑定

这样就把图片设置在OpenGL里罗~之後可以通过Texture.id来呼叫。

画出来吧~

之前写在iron_render.c内部的shader code,需要加上两个参数
1.Texture的座标
2.Texture本身

这是更新後的样子:

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

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

然後更新iron_render模组内,预设的shader,在iron_asset.LoadShaderCode的地方加上这个


    // texture coordinate
    int texcoord_location = glGetAttribLocation(shader->id, "_Texcoords");
	if (texcoord_location < 0) {
		return RES_ERROR_GET_SHADER_ATTRIBUTE;
	}
	shader->attribs_locations[SHADER_ATTRIB_VEC2_TEXCOORD] = texcoord_location;
    
    // texture
    int texture_location = glGetUniformLocation(shader->id, "_Texture2D");
	if (texture_location < 0) {
		return RES_ERROR_GET_SHADER_ATTRIBUTE;
	}
	shader->attribs_locations[SHADER_ATTRIB_SAMPLER2D_TEXTURE] = texcoord_location;

P.S. 写的时候发现不应该没有找到这个属性就Return,不然之後有不同需求的Shader,里面没有包含这属性就GG了。

最後...利用之前DrawFirstRectangle的code,改造一下,就可以画出我们第一个图片了

    unsigned int vbo, vao, ibo;

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

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

	glGenBuffers(1, &ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 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]);

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

有个注意一下,原本的VERTICES,除了顶点位置,我也把Texture的座标放进去了,所以原本的一组顶点是2个float会变成4个float,中间还需要告诉glVertexAttribPointer顶点与贴图座标的间距。

完成!但...怎麽怪怪的

完成之後,我的输出是这样,发福的倒立兔子

原因有几个

  1. 图片没有翻转,这个可以在载入图片之前加上stbi_set_flip_vertically_on_load翻转图片
  2. 目前的座标系,是固定的,并不是依照图原本的坐标系去处理的,预计会在座标转换那边在处理
  3. Alpha为0的地方被画出来了,这个是没有把GL_BLEND打开,可以在CreateRenderer的地方补上

// ...
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// ...

今天就先这样,明天预计会写输入操作时间处理的部分

参考

今日的成果


<<:  [Day 10] Leetcode 978. Longest Turbulent Subarray (C++)

>>:  Day_03 : 让 Vite 来开启你的Vue 微谈模组化与演进(下)

Day 28 品质分数等於产品绩效吗?

我们在之前的分享中得知,品质分数多少会影响自然排序的表现,但很多人会把它和绩效挂上等号,这并不是必然...

9.MYSQL输出写入的内容

建立好自己完整的表格之後,就可以开始搜寻自己要的东西了 SELECT * (全部栏位) FROM 什...

[Lesson23] Kotlin - 回圈

while 回圈 Kotlin 的 while 回圈跟 Java 一样,每一次的循环之前会检查条件式...

Amazon Linux 2 上将 Django 与 Nginx 整合 -Day 08

Amazon Linux 2 上将 Django 与 Nginx 整合 -Day 08 先前我们都是...

[Day30] Cloud Meow Meow

喵喵喵喵喵喵喵喵!!!终於写完 30 天的文章了,这 30 天中,我们从云端的概念开始,进入了 Go...