我在 OpenGL 中绘制立方体。正交投影效果很好,但透视投影会出现黑屏。
所以我尝试了两种投影类型之间的插值,以了解透视失败的原因。
投影 = alpha*persp + (1-alpha)*ortho.
对于 alpha=0(纯正交),它工作正常,但在 alpha=.48 附近,立方体的远端开始被 zFar 处的墙剪裁。这会随着 alpha 的增加而继续,到 alpha = .508 时,整个立方体将被剪裁并且根本不会出现。
我预计增加平截头体的 zFar 会解决这个问题,但它根本没有影响。尽管增加了 zFar,立方体总是消失在 alpha=.48 和 alpha=.51 之间的后墙后面。
要看我说的效果就下载代码,编译,运行。它要求您输入 alpha。在 alpha=0 时它是纯正字法,在 alpha=1 时它是纯透视。但是从 alpha=.48 开始,立方体开始消失在裁剪空间的后墙后面,到 alpha=.51 时,整个立方体都被裁剪掉了。改变 zFar=frustum[5] 没有任何区别,这是这里的大惊喜。
这是完整的代码、着色器和所有内容。将其复制到文件 vertex.glsl、fragment.glsl 和 test.c 中。之后它可以被编译 gcc -g -o 测试 test.c
pkg-config --cflags --libs gtk+-3.0
-lepoxy
这里是着色器 vertex.glsl 和 fragment.glsl
/*----------------------------- vertex.glsl ------------------------------*/
#version 330 core
layout (location = 0) in vec3 point;
layout (location = 1) in vec3 colorIn;
uniform mat4 proj;
out vec3 colorOut;
void main (void)
{
gl_Position = proj*vec4(point, 1.0);
colorOut=colorIn;
}
/*------------------------------- fragment.glsl -----------------------------*/
#version 330 core
out vec4 FragColor;
in vec3 colorOut;
void main(void)
{
FragColor = vec4(colorOut, 1.0);
}
这是代码 test.c,尽可能简单。只有 388 行,其中大部分是坚如磐石的标准内容。谜团在于后墙剪裁不响应 zFar=frustum[5] 的任何变化。
//gcc -g -o test test.c `pkg-config --cflags --libs gtk+-3.0` -lepoxy
#include <epoxy/gl.h>
#include <gtk/gtk.h>
static void realize(GtkGLArea *gl_area);
static void render (GtkGLArea *gl_area);
static float *chessboard_vba(void);
static float *cube_vba(void);
static void interpolate_proj1_and_proj2(GLfloat *proj1, GLfloat *proj2, GLfloat alpha);
static int get_perspective_from_frustum(GLfloat *persp, GLfloat *frustum);
static int get_orthography_from_frustum(GLfloat *ortho, GLfloat *frustum);
static void print_vba(float *vba, int nvertex, int span);
static int simulate_vshader_and_gl_position(float *point, float *proj);
static void let_A_equal_Bmatrix_times_C_4c(float *A, float *B, float *C);
static void let_A_equal_Bmatrix_times_C_3c(float *A, float *B, float *C);
static void print_matrix_4c(float *M);
static GLuint shader_load_program(const char *vertex, const char *fragment);
static GLuint shader_load_file(const char *filename, GLenum type);
static void shader_print_log(GLuint object);
GLuint program;
GLuint vao, vbo;
GLint uni_proj;
GtkGLArea *gl_area;
GLfloat frustum[] = {-0.01, 0.01, -0.009, 0.009, 0.02, 1000};
GLfloat proj[16], ortho[16], persp[16];
/*------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *gl_area;
gtk_init(&argc, &argv);
// Initialize Window
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Shader Test");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 640, 640);
gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_UTILITY);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
//Initialize GTK GL Area
gl_area = gtk_gl_area_new();
gtk_widget_set_vexpand(gl_area, TRUE);
gtk_widget_set_hexpand(gl_area, TRUE);
g_signal_connect(gl_area, "realize", G_CALLBACK(realize), NULL);
g_signal_connect(gl_area, "render" , G_CALLBACK(render) , NULL);
gtk_container_add(GTK_CONTAINER(window), gl_area);
glClearColor(.5, .5, .5, 1);
gtk_widget_set_can_focus(GTK_WIDGET(gl_area), TRUE);
gtk_widget_grab_focus(GTK_WIDGET(gl_area));
// Show widgets
gtk_widget_show_all(window);
gtk_main();
return 0;
}
/*------------------------------------------------------------------------*/
static void realize(GtkGLArea *gl_area)
{
int err;
GLuint vao;
GLfloat *vba, alpha;
gtk_gl_area_make_current(gl_area);
glClearColor(.5, .5, .5, 1);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
const char *vs = "vertex.glsl";
const char *fs = "fragment.glsl";
program = shader_load_program(vs, fs);
glUseProgram(program);
vba=cube_vba();//"vertex buffer array"
//ortho or persp. Maybe decide based on user input.
get_perspective_from_frustum(proj, frustum);
get_orthography_from_frustum(ortho, frustum);
printf("Enter alpha\n");
scanf("%f", &alpha);
interpolate_proj1_and_proj2(proj, ortho, alpha);
//see if corner 5 really does go beyond z=1
simulate_vshader_and_gl_position(vba+72, proj);
uni_proj = glGetUniformLocation(program, "proj");
glUniformMatrix4fv(uni_proj, 1, GL_FALSE, proj);
while ((err = glGetError()))
printf("WARNING: glError A errno=%i, err\n", err);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 2*8*8*6*sizeof(GLfloat), vba, GL_STATIC_DRAW);
//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);//might not be needed here
glFlush();
free(vba);
while ((err = glGetError()))
printf("WARNING: glError B errno=%i, err\n", err);
}
/*------------------------------------------------------------------------*/
static void render(GtkGLArea *gl_area)
{
int i, j, err;
int lseg=8, span=2*3, stride=6*sizeof(GLfloat);
long rpointer=0, cpointer=3*sizeof(GLfloat);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void *)rpointer);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void *)cpointer);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glLineWidth(1);
glDrawArrays(GL_LINES, 0, 2*12);
while ((err = glGetError()))
printf("WARNING: glError B errno=%i, err\n", err);
}
/*------------------------------------------------------------------------*/
static float *cube_vba()
{
int i, j, k, l, m, ibase, ineigh[3], span=2*3, offset, ibw, dum;
GLfloat *vba, size=.05, *r[2], color[]={0,.5,0}, corner[8][3];
GLfloat Re[] ={0.98039, 0.01960, -0.19607,
0.01960, 0.98039, 0.19607,
0.19607, -0.19607, 0.96078};//small rotation about x, y axes
for (k=0; k<2; k++)
for (j=0; j<2; j++)
for (i=0; i<2; i++) {
offset=4*k+2*j+i;
corner[offset][0] = 2*(i-.5)*size;
corner[offset][1] = 2*(j-.5)*size;
corner[offset][2] = 2*(k-.5)*size + .04;//z has to be positive
let_A_equal_Bmatrix_times_C_3c(corner[offset], Re, corner[offset]);//so you can see all corners in ortho mode
}
vba=(GLfloat *)malloc(2*12*span*sizeof(GLfloat));
memset(vba, 0, 2*12*span*sizeof(GLfloat));
for (m=ibase=0; m<4; m++) {
//Indices of base corners. Neighbors of corners 0,3,5,6 cover all sides
i=(m==1||m==2); j=(m==1||m==3); k=(m==2||m==3);
ibase = 4* k + 2* j + i;
ineigh[2] = 4*!k + 2* j + i; //
ineigh[1] = 4* k + 2*!j + i; //neighboring corners, ie, one different index
ineigh[0] = 4* k + 2* j + !i; //
for (l=0; l<3; l++) {
offset=3*m*2*span + l*2*span;
r[0] = corner[ibase ];
r[1] = corner[ineigh[l]];
memcpy(vba+offset , r[0] , 3*sizeof(GLfloat));
memcpy(vba+offset+3, color , 3*sizeof(GLfloat));
memcpy(vba+offset+6, r[1] , 3*sizeof(GLfloat));
memcpy(vba+offset+9, color , 3*sizeof(GLfloat));
dum=1;
}
dum=1;
}
//print_vba(vba, 12, 6);
return vba;
}
/*------------------------------------------------------------------------*/
static float *chessboard_vba()
{
int i, j, offset, ibw, *iba, dum;
GLfloat *vba, size=.1, r[2][3], z=1, bw[2][3]={{0,0,0},{1,1,1}};
vba=(GLfloat *)malloc(2*8*8*6*sizeof(GLfloat));
for (j=0; j<8; j++) {
ibw=j%2;
for (i=0; i<8; i++) {
ibw=!ibw;
offset=2*(8*j+i)*2*3;
r[0][0]=(i -4)*size; r[0][1]=(j-4)*size; r[0][2]=z;
r[1][0]=(i+1-4)*size; r[1][1]=(j-4)*size; r[1][2]=z;
memcpy(vba+offset , r[0] , 3*sizeof(GLfloat));
memcpy(vba+offset+3, bw+ibw , 3*sizeof(GLfloat));
memcpy(vba+offset+6 , r[1] , 3*sizeof(GLfloat));
memcpy(vba+offset+9, bw+ibw , 3*sizeof(GLfloat));
dum=1;
}
}
return vba;
}
/*------------------------------------------------------------------------*/
static void interpolate_proj1_and_proj2(GLfloat *proj1, GLfloat *proj2, GLfloat alpha)
{
int i, dum;
for (i=0; i<16; i++)
{proj[i] = alpha*proj1[i] + (1-alpha)*proj2[i];}
dum=1;
}
/*------------------------------------------------------------------------*/
//letters stand for: left, right, bottom, top, near, far, width, height, depth
static int get_perspective_from_frustum(GLfloat *proj, GLfloat *frustum)
{
float l=frustum[0], r=frustum[1], b=frustum[2], t=frustum[3], n=frustum[4];
float f=frustum[5], w=r-l, h=t-b, d=f-n;
memset(proj, 0, 16*sizeof(float));
proj[ 0]= 2*n/w; proj[ 1]= 0 ; proj[ 2]= (r+l)/w; proj[ 3]= 0 ;
proj[ 4]= 0 ; proj[ 5]= 2*n/h; proj[ 6]= (t+b)/h; proj[ 7]= 0 ;
proj[ 8]= 0 ; proj[ 9]= 0 ; proj[10]=-(f+n)/d; proj[11]=-2*f*n/d;
proj[12]= 0 ; proj[13]= 0 ; proj[14]= -1 ; proj[15]= 0 ;
return 1;
}
/*------------------------------------------------------------------------*/
//letters stand for w=width, h=height, d=depth
static int get_orthography_from_frustum(GLfloat *ortho, GLfloat *frustum)
{
float w=frustum[1]-frustum[0], h=frustum[3]-frustum[2], d=frustum[5]-frustum[4];
memset(ortho, 0, 16*sizeof(GLfloat));
ortho[ 0] = .2/w;
ortho[ 5] = .2/h;
ortho[10] = .2/d;
ortho[15] = 1;
return 1;
}
/*------------------------------------------------------------------------*/
static void print_vba(float *vba, int Nvertex, int span)
{
int i, j, dum;
for (j=0; j<Nvertex; j++) {
for (i=0; i<2*span; i++)
printf("%6.3f ", vba[2*j*span+i]);
printf("\n");
dum=1;
}
}
/*------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
static int simulate_vshader_and_gl_position(float *point, float *proj)
{
int i, j;
float point4[]={point[0], point[1], point[2], 1}, p_point[]={0,0,0,0}, view_point[4];
//This gives the output of the vertex shader to the GPU
let_A_equal_Bmatrix_times_C_4c(p_point, proj, point4);
//This is where gl will put the point in the gl_area (?)
for (i=0; i<3; i++)
view_point[i] = p_point[i]/p_point[2];
return 1;
}
/*------------------------------------------------------------------------*/
static void print_matrix_4c(float *M)
{
int i, j;
if (!M) return;
for (j=0; j<4; j++) {
for (i=0; i<4; i++)
printf("%12.8f ", M[4*j+i]);
printf("\n");
}
}
/*------------------------------------------------------------------------*/
static void let_A_equal_Bmatrix_times_C_4c(float *A, float *B, float *C)
{
float Ct[4];
memcpy(Ct, C, 4*sizeof(float));
A[0] = B[ 0]*Ct[0] + B[ 1]*Ct[1] + B[ 2]*Ct[2] + B[ 3]*Ct[3];
A[1] = B[ 4]*Ct[0] + B[ 5]*Ct[1] + B[ 6]*Ct[2] + B[ 7]*Ct[3];
A[2] = B[ 8]*Ct[0] + B[ 9]*Ct[1] + B[10]*Ct[2] + B[11]*Ct[3];
A[3] = B[12]*Ct[0] + B[13]*Ct[1] + B[14]*Ct[2] + B[15]*Ct[3];
}
/*------------------------------------------------------------------------*/
static void let_A_equal_Bmatrix_times_C_3c(float *A, float *B, float *C)
{
float Ct[3];
memcpy(Ct, C, 3*sizeof(float));
A[0] = B[0]*Ct[0] + B[1]*Ct[1] + B[2]*Ct[2];
A[1] = B[3]*Ct[0] + B[4]*Ct[1] + B[5]*Ct[2];
A[2] = B[6]*Ct[0] + B[7]*Ct[1] + B[8]*Ct[2];
}
/*------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
static GLuint shader_load_program(const char *vertex, const char *fragment)
{
GLint link_ok;
GLuint program;
GLuint vs = shader_load_file(vertex, GL_VERTEX_SHADER);
GLuint fs = shader_load_file(fragment, GL_FRAGMENT_SHADER);
if(vs == 0 || fs == 0) {
return 0;
}
program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
glDetachShader(program, vs);
glDetachShader(program, fs);
return program;
}
/*------------------------------------------------------------------------*/
static GLuint shader_load_file(const char *filename, GLenum type)
{
FILE *fp;
unsigned int file_len;
char *source;
GLuint shader;
GLint compile_ok;
fp = fopen(filename, "r");
if(fp == NULL) {
fprintf(stderr, "Could not open %s for reading\n", filename);
return 0;
}
fseek(fp, 0, SEEK_END);
file_len = ftell(fp);
fseek(fp, 0, SEEK_SET);
source = malloc(file_len + 1);
fread(source, file_len, sizeof(char), fp);
fclose(fp);
source[file_len] = '\0';
const GLchar *sources[] = { source };
shader = glCreateShader(type);
glShaderSource(shader, 1, sources, NULL);
glCompileShader(shader);
free(source);
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_ok);
return shader;
}