概述
坐标属性插值
对于三角形中的点的属性,可以使用插值的方式,来获取平滑过渡的值。比如点的纹理坐标,点的颜色,点的法向量等等。
如上图所示,光栅化之后,我们可以获取到三角形内的点的坐标(x,y)。然后给三角形中的顶点VA指定红色,VB指定绿色,VC指定蓝色,然后通过插值的方式,三角形内就可以得到这种平滑过渡的颜色了。
我们在程序中,只是指定三角形三个顶点的坐标,纹理坐标,颜色,法向量等属性。如:
GLfloat vVertices[] {
-0.5f, 0.5f, 0.0f, 1.5f, // 顶点坐标
0.0f, 0.0f, // 顶点的纹理坐标
-0.5f, -0.5f, 0.0f, 1.1f,
0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.1f,
1.0f, 1.0f,
0.5f, 0.5f, 0.0f, 1.5f,
1.0f, 0.0f
};
而三角形内的点的纹理坐标,颜色,法向量等属性,就要通过插值的方式来获取了。
这一步进行的插值,是为了获取点的纹理坐标;而下面纹理放大所进行的插值,是为了获取这个纹理坐标所对应的纹理值。
综上:光栅化 -> 三角形内的点的坐标 -> 三角形内插值 -> 三角形内的点的纹理坐标 -> 纹理放大进行插值 -> 纹理坐标所对应的纹理值
三角形的重心坐标
我们一般会通过三角形的重心坐标来对三角形内的点来做插值。
1. 通过顶点坐标来计算三角形的面积
两种方式:梯形面积求出来,以及叉积的方式求出来
2. 重心坐标的定义和约束条件
重心坐标的定义:三角平面中的任何点都可以表示为顶点的加权平均值,这些加权称为重心坐标。
两个约束条件:设重心坐标为(b1,b2,b3),则:
- b1 + b2 + b3 = 1
- b1 > 0,b2 > 0,b3 > 0
3. 计算重心坐标
重心坐标的值等于各个顶点所对应的子三角形的面积与总三角形面积的比值。
也可以通过叉积来计算重心坐标:
4. 重心坐标的使用
设三角形ABC的三个顶点的颜色值为A(ra,ga,ba),B(rb,gb,bb),C(rc,gc,bc)
三角形ABC内的点P的颜色值为P(rx,gy,bz),重心坐标为(b1,b2,b3)则:
rx = b1 * ra + b2 * rb + b3 * rc
gy = b1 * ga + b2 * gb + b3 * gc
bz = b1 * ba + b2 * bb + b3 * bc
纹理放大
纹理放大发生在屏幕上投影的多边形大于纹理尺寸的时候,就是低分辨率的图片投到大屏幕上
纹理放大的时候,可以使用最近值和双线性插值的方式来进行采样。
1. 最近值
比如一个纹理坐标被映射到了上面的红点上,最近值采样就是选择离红点最近的纹理坐标的值,那么最终返回的就是u11所对应的纹理值
2. 双线性插值
最近采样和双线性插值采样的对比图:明显双线性插值更加清晰
纹理缩小
纹理缩小发生在屏幕上投影的多边形小于纹理尺寸的时候。就是高清图片投影到低分率屏幕上
纹理缩小的时候,可以使用mipmap贴图的方式。
当纹理缩小的时候,如果采用最近采样或者双线性采样的话,图片就会产生形变,如下图所示:
这是由于纹理缩小的时候,屏幕上一个像素点,会占据纹理中很大的一片区域,这时候,纹理坐标就会有很大的跳跃,就会造成锯齿伪像。
怎么解决?核心思路:只要求这个像素点所对应的一大片区域的平均值即可;mipmap采样就是这样做的。
Mipmap采样
1. 生成mip贴图链
mipmap采样首先会准备一系列的图片,叫做mip贴图链,首先level0为原始图片,level1为level0的一半,level2又为level1的一半,一直到最后的1x1纹理。
level1中的每个像素为level0中相同位置的4个像素的平均值计算得到。
2. 计算当前纹理坐标取哪个level的贴图的值
取当前像素坐标,及其上边和右边的像素坐标;然后计算其对应的纹理坐标之间的距离,然后距离取对数,就为所求的level值了。
求到level数,再求其对应的纹理坐标的值即可。
3. 求得的level为小数咋办
先对level D和level D+1的纹理进行双线性插值,然后对level D和level D+1所取得的值,进行一次单线性插值即可。
效果图:
建立mipmap链-源码
GLboolean GenMipMap2D(GLubyte *src, GLubyte **dst, int srcWidth, int srcHeight, int *dstWidth, int *dstHeight)
{
int x, y;
int texelSize = 3;
// 新图片的宽和高都为原图片的一半
*dstWidth = srcWidth / 2;
if (*dstWidth <= 0) {
*dstWidth = 1;
}
*dstHeight = srcHeight / 2;
if (*dstHeight <= 0) {
*dstHeight = 1;
}
*dst = (GLubyte *)malloc(sizeof(GLubyte)*texelSize*(*dstWidth)*(*dstHeight));
if (*dst == NULL) {
return GL_FALSE;
}
for (y = 0; y < *dstHeight; y++) {
for (x = 0; x < *dstWidth; x++) {
int srcIndex[4];
float r = 0.0f, g = 0.0f, b = 0.0f;
int sample;
// 扫描一行,y对应一行
srcIndex[0] = (((y*2)*srcWidth)+(x*2))*texelSize;
srcIndex[1] = (((y*2)*srcWidth)+(x*2+1))*texelSize;
srcIndex[2] = ((((y*2)+1)*srcWidth)+(x*2))*texelSize;
srcIndex[3] = ((((y*2)+1)*srcWidth)+(x*2+1))*texelSize;
for (sample = 0; sample < 4; sample++) {
r += src[srcIndex[sample]];
g += src[srcIndex[sample]+1];
b += src[srcIndex[sample]+2];
}
// 4个像素取平均值
r /= 4.0;
g /= 4.0;
b /= 4.0;
// 这里也是扫描一行,y对应一行
(*dst)[(y*(*dstWidth)+x)*texelSize] = (GLubyte)(r);
(*dst)[(y*(*dstWidth)+x)*texelSize + 1] = (GLubyte)(g);
(*dst)[(y*(*dstWidth)+x)*texelSize + 2] = (GLubyte)(b);
}
}
return GL_TRUE;
}
GLuint CreateMipMappedTexture2D()
{
GLuint textureId;
int width = 4096,
height = 4096;
int level;
GLubyte * pixels;
GLubyte *prevImage;
GLubyte *newImage;
// 生成图片的
pixels = GenCheckImage(width, height, 8);
if (pixels == NULL) {
return 0;
}
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
// 传入level0的图片
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
level = 1;
prevImage = &pixels[0];
while (width > 1 && height > 1) {
int newWidth;
int newHeight;
GenMipMap2D(prevImage, &newImage, width, height, &newWidth, &newHeight);
// 传入level 1 2 3 4 . . .直到变为1x1的图片
glTexImage2D(GL_TEXTURE_2D, level, GL_RGB,
newWidth, newHeight, 0, GL_RGB,
GL_UNSIGNED_BYTE, newImage);
free(prevImage);
prevImage = newImage;
level++;
width = newWidth;
height = newHeight;
}
free(newImage);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return textureId;
}
源码解析
#include <stdlib.h>
#include "esUtil.h"
typedef struct
{
GLuint programObject;
GLint samplerLoc;
GLint offsetLoc;
GLuint textureId;
} myUserData;
GLboolean GenMipMap2D(GLubyte *src, GLubyte **dst, int srcWidth, int srcHeight, int *dstWidth, int *dstHeight)
{
int x, y;
int texelSize = 3;
*dstWidth = srcWidth / 2;
if (*dstWidth <= 0) {
*dstWidth = 1;
}
*dstHeight = srcHeight / 2;
if (*dstHeight <= 0) {
*dstHeight = 1;
}
*dst = (GLubyte *)malloc(sizeof(GLubyte)*texelSize*(*dstWidth)*(*dstHeight));
if (*dst == NULL) {
return GL_FALSE;
}
for (y = 0; y < *dstHeight; y++) {
for (x = 0; x < *dstWidth; x++) {
int srcIndex[4];
float r = 0.0f, g = 0.0f, b = 0.0f;
int sample;
srcIndex[0] = (((y*2)*srcWidth)+(x*2))*texelSize;
srcIndex[1] = (((y*2)*srcWidth)+(x*2+1))*texelSize;
srcIndex[2] = ((((y*2)+1)*srcWidth)+(x*2))*texelSize;
srcIndex[3] = ((((y*2)+1)*srcWidth)+(x*2+1))*texelSize;
for (sample = 0; sample < 4; sample++) {
r += src[srcIndex[sample]];
g += src[srcIndex[sample]+1];
b += src[srcIndex[sample]+2];
}
r /= 4.0;
g /= 4.0;
b /= 4.0;
(*dst)[(y*(*dstWidth)+x)*texelSize] = (GLubyte)(r);
(*dst)[(y*(*dstWidth)+x)*texelSize + 1] = (GLubyte)(g);
(*dst)[(y*(*dstWidth)+x)*texelSize + 2] = (GLubyte)(b);
}
}
return GL_TRUE;
}
GLubyte *GenCheckImage(int width, int height, int checkSize)
{
int x, y;
GLubyte *pixels = (GLubyte *)malloc(width * height * 3);
if (pixels == NULL) {
return NULL;
}
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
GLubyte rColor = 0;
GLubyte bColor = 0;
if ((x/checkSize)%2 == 0) {
rColor = 255 * ((y/checkSize) % 2);
bColor = 255 * (1 - ((y/checkSize) % 2));
} else {
bColor = 255 * ((y/checkSize) % 2);
rColor = 255 * (1 - ((y/checkSize) % 2));
}
pixels[(y*width + x)*3] = rColor;
pixels[(y*width + x)*3 + 1] = 0;
pixels[(y*width + x)*3 + 2] = bColor;
}
}
return pixels;
}
GLuint CreateMipMappedTexture2D()
{
GLuint textureId;
int width = 4096,
height = 4096;
int level;
GLubyte * pixels;
GLubyte *prevImage;
GLubyte *newImage;
pixels = GenCheckImage(width, height, 8);
if (pixels == NULL) {
return 0;
}
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
level = 1;
prevImage = &pixels[0];
while (width > 1 && height > 1) {
int newWidth;
int newHeight;
GenMipMap2D(prevImage, &newImage, width, height, &newWidth, &newHeight);
glTexImage2D(GL_TEXTURE_2D, level, GL_RGB,
newWidth, newHeight, 0, GL_RGB,
GL_UNSIGNED_BYTE, newImage);
free(prevImage);
prevImage = newImage;
level++;
width = newWidth;
height = newHeight;
}
free(newImage);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return textureId;
}
int Init(MYESContext *myesContext)
{
myUserData *userData = (myUserData *)myesContext->userData;
char vShaderStr[] =
"#version 300 es \n"
"uniform float u_offset; \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec2 a_texCoord; \n"
"out vec2 v_texCoord; \n"
"void main() \n"
"{ \n"
" gl_Position = a_position; \n"
" gl_Position.x += u_offset; \n"
" v_texCoord = a_texCoord; \n"
"} \n";
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"in vec2 v_texCoord; \n"
"layout(location = 0) out vec4 outColor; \n"
"uniform sampler2D s_texture; \n"
"void main() \n"
"{ \n"
" outColor = texture(s_texture, v_texCoord); \n"
"} \n";
userData->programObject = myesLoadProgram(vShaderStr, fShaderStr);
userData->samplerLoc = glGetUniformLocation(userData->programObject, "s_texture");
userData->offsetLoc = glGetUniformLocation(userData->programObject, "u_offset");
userData->textureId = CreateMipMappedTexture2D();
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
return GL_TRUE;
}
void Draw(MYESContext *myesContext)
{
myUserData *userData = (myUserData *)myesContext->userData;
GLfloat vVertices[] {
-0.5f, 0.5f, 0.0f, 1.5f, // (x,y,z,w),w不指定时默认为1
0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.1f,
0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.1f,
1.0f, 1.0f,
0.5f, 0.5f, 0.0f, 1.5f,
1.0f, 0.0f
};
GLushort indices[] = {0,1,2,0,2,3};
glViewport(0, 0, myesContext->width, myesContext->height);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(userData->programObject);
glVertexAttribPointer(0, 4, GL_FLOAT,
GL_FALSE, 6*sizeof(GLfloat), vVertices);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), &vVertices[4]);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, userData->textureId);
glUniform1i(userData->samplerLoc, 0);
// 这里先设置为线性采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// 两幅图相隔0.6,可以错开
glUniform1f(userData->offsetLoc, -0.6f);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
// 然后设置为mipmap采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glUniform1f(userData->offsetLoc, 0.6f);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
}
void ShutDown(MYESContext *myesContext)
{
myUserData *userData = (myUserData *)myesContext->userData;
glDeleteTextures(1, &userData->textureId);
glDeleteProgram(userData->programObject);
}
int myesMain(MYESContext *myesContext)
{
myesContext->userData = malloc(sizeof(myUserData));
myesCreateWindow(myesContext, "9_1_simple_texture2D", 320, 240, MY_ES_WINDOW_RGB);
if (!Init(myesContext))
{
return GL_FALSE;
}
esRegisterDrawFunc(myesContext, Draw);
esRegisterShutdownFunc(myesContext, ShutDown);
return GL_TRUE;
}
效果图:
参考
1. 3d数学基础书籍 -- 第九章 集合图元中的三角形
2. GAMES101-现代计算机图形学入门-闫令琪 -- 第九节 纹理和mipmap贴图
https://www.bilibili.com/video/BV1X7411F744?p=4
3. 一篇文章为你讲透双线性插值
https://zhuanlan.zhihu.com/p/110754637
4. 双线性插值
https://zh.wikipedia.org/wiki/%E5%8F%8C%E7%BA%BF%E6%80%A7%E6%8F%92%E5%80%BC