我编写了一个演示,使用三角形带进行索引绘制来绘制纹理球体。 指数看起来是正确的。每行周围有 30 个点:
0, 30, 1, 31, 2, 32, ... 29, 59, 0, 30
然后每行的末尾都有退化三角形。坐标和索引对我来说很合适。 据我计算,如果在纬度线上围绕地球的每个圆周围都有 lonRes 点(在演示中,lonRes= 30),那么每行应该有 lonRes+4,因为每行都连接回起点,然后两个退化索引继续到下一行。不知怎的,计算关闭了,indexSize=2503,实际大小是2432。我将indexSize设置为实际使用和渲染的索引数。球体的大部分看起来都不错,但缺少一列,您希望在其中连接回起点。 地球旋转(在下图中,非洲正在向右移动),但是每行边缘的间隙向后旋转(它向左移动并覆盖了它。无论我的绘制命令有什么问题,我都会我预计它会一起旋转。我不明白这个机制,而且它是一个单一的绘制调用,这使得调试对我来说是一个黑匣子。
我当然会接受答案,但我想学习一些如何进行调试的策略。我已经打印了坐标和索引。他们看起来是正确的。
我包含了代码、着色器(尽管我相当确定它们不是问题)以及显示错误的屏幕截图。
代码是使用以下内容构建的:
g++ -g -std=c++20 -c common/common.cc
g++ -g -std=c++20 06b_sphere3.cc Shape.cc -o bin/06b_sphere3 common.o -lglfw -L/usr/lib64 -lGLEW -lGL -lX11 -lGLU -lwebp
06b_sphere3.cpp:
/*
Textured Sphere demo
Load a webp cylindrical projection of earth and map to the sphere
Tilt earth axis to 23.5 degrees and rotate
*/
#include <GL/glew.h>
#include "common/common.hh"
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include <numbers>
#include <iostream>
#include <iomanip>
#include <cstdint>
#include <string>
using namespace std;
using namespace glm;
using namespace std::numbers;
constexpr double PI = numbers::pi;
class Sphere {
private:
uint32_t progid; // handle to the shader code
uint32_t vao; // array object container for vbo and indices
uint32_t vbo; // handle to the point data on the graphics card
uint32_t lbo; // handle to buffer of indices for lines for wireframe sphere
uint32_t latRes, lonRes;
uint32_t resolution;
uint32_t indexSize;
public:
/**
* @brief Construct a sphere
*
* @param r radius of the sphere
* @param latRes resolution of the grid in latitude
* @param lonRes resolution of the grid in latitude
* @param texturePath path to the texture image
*/
Sphere(double r, uint32_t latRes, uint32_t lonRes);
~Sphere() { cleanup(); }
void render(mat4& trans, GLuint textureID);
void cleanup();
};
Sphere::Sphere(double r, uint32_t latRes, uint32_t lonRes) : latRes(latRes), lonRes(lonRes),
resolution((2*latRes-1)*lonRes + 2) {
progid = loadShaders("06b_texturepoints.vert", "06b_textures.frag");
// progid = loadShaders("03gouraud.vert", "03gouraud.frag");
double dlon = 2.0*PI / lonRes, dlat = PI / (2*latRes);
double z;
double lat = -PI/2 + dlat; // latitude in radians
double rcircle;
float vert[resolution*5]; // x,y,z,u,v
uint32_t c = 0;
for (uint32_t j = 0; j < 2*latRes-1; j++, lat += dlat) {
//what is the radius of hte circle at that height?
rcircle = r* cos(lat); // size of the circle at this latitude
z = r * sin(lat); // height of each circle
cout << "rcircle=" << rcircle << ", z=" << z << endl;
double t = 0;
for (uint32_t i = 0; i < lonRes; i++, t += dlon) {
vert[c++] = rcircle * cos(t),
vert[c++] = rcircle * sin(t);
cout << '(' << vert[c-2] << ", " << vert[c-1] << ") ";
vert[c++] = z;
vert[c++] = t / (2.0 * PI); // Correct u mapping
vert[c++] = (lat + PI / 2.0) / PI; // Correct v mapping
}
cout << endl;
}
// south pole
vert[c++] = 0;
vert[c++] = 0;
vert[c++] = -r;
vert[c++] = 0.5;
vert[c++] = 0;
// north pole
vert[c++] = 0;
vert[c++] = 0;
vert[c++] = r;
vert[c++] = 0.5;
vert[c++] = 1;
cout << "resolution: " << resolution << endl;
cout << "predicted num vert components: " << resolution*5 << endl;
cout << "actual num vert components: " << c << endl;
indexSize = resolution * 2 + (2*4*latRes-1);
//TODO: North and South Poles aren't used
uint32_t indices[indexSize]; // connect every point in circles or latitude and longitude
c = 0;
for (uint32_t j = 0; j < 2*latRes - 2; j++) {
uint32_t startrow = j*lonRes;
for (uint32_t i = 0; i < lonRes; i++) {
indices[c++] = startrow + i;
indices[c++] = startrow + lonRes + i;
}
indices[c++] = startrow;
indices[c++] = startrow + lonRes;
// Add degenerate triangles to connect strips
indices[c++] = (j + 1) * lonRes;
indices[c++] = (j + 1) * lonRes;
}
cout << "indexSize: " << indexSize << endl;
cout << "actual grid indices: " << c << endl;
indexSize = c; // not sure why the computaiton is off...
// Print index data
cout << "Index data: " ;
for (size_t i = 0; i < indexSize; i += 2) {
cout << '(' << indices[i] << ", " << indices[i+1] << ") ";
}
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, resolution*5*sizeof(float), vert, GL_STATIC_DRAW);
glGenBuffers(1, &lbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lbo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize*sizeof(uint32_t), indices, GL_STATIC_DRAW);
glBindVertexArray(0);
}
void Sphere::render(mat4& trans, GLuint textureID) {
glUseProgram(progid); // Use the shader
uint32_t matrixID = glGetUniformLocation(progid, "transform");
glUniformMatrix4fv(matrixID, 1, GL_FALSE, &trans[0][0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);
glUniform1i(glGetUniformLocation(progid, "textureSampler"), 0);
glBindVertexArray(vao);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); // Position
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); // Texture coordinates
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawElements(GL_TRIANGLE_STRIP, indexSize, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindVertexArray(0);
}
void Sphere::cleanup() {
glDeleteBuffers(1, &vbo); // remove vbo memory from graphics card
glDeleteBuffers(1, &lbo); // remove lbo (line indices)
glDeleteVertexArrays(1, &vao); // remove vao from graphics card
glDeleteProgram(progid);
}
using namespace std;
using namespace numbers;
void glmain() {
win = createWindow(800, 800, "Sphere demo");
glClearColor(0.0f, 0.0f, 0.4f, 0.0f); // Dark blue background
GLuint textureID = loadWebPTexture("earth.webp"); // Load the texture
Sphere sphere(1.0, 20, 30);
float rotAngle = 0, dRotAngle = 0.0052;
mat4 northup = rotate(mat4(1.0f), float(PI/2), vec3(1, 0, 0));
// mat4 northup = mat4(1.0f);
vec3 up(0, 1, 0); // normal OpenGL coordinates, x positive to right, y is up (z positive out of screen)
do {
mat trans = rotate(northup, radians(23.5f), vec3(0, 1, 0)); // tilt axis
trans = rotate(trans, rotAngle, vec3(0, 0, 1)); // spin on axis
rotAngle += dRotAngle;
glClear(GL_COLOR_BUFFER_BIT); // Clear the screen
glDisable(GL_DEPTH_TEST);
sphere.render(trans, textureID);
glfwSwapBuffers(win); // double buffer
glfwPollEvents();
} while (glfwGetKey(win, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
glfwWindowShouldClose(win) == 0);
glDeleteTextures(1, &textureID); // Clean up the texture
}
常见.hh
#include <GL/glew.h> // OpenGL API
#include <GLFW/glfw3.h> // Window API
#include <glm/glm.hpp> // Matrix and vector math for OpenGL
#include <glm/ext.hpp>
// all demos use a window, declared globally in common.cc
extern GLFWwindow* win;
GLFWwindow* createWindow(uint32_t w, uint32_t h, const char title[]);
GLuint loadShaders(const char vertexPath[], const char * fragmentPath);
void glmain();
/*
provide a standardized main, because it's always the same
It catches exceptions and quits if there is a problem
you write glmain instead
*/
int main(int argc, char* argv[]);
void dump(glm::mat4& mat);
void transpt(glm::mat4& m, double x, double y, double z);
GLuint loadWebPTexture(const char* filePath);
GLuint build_prog(const char vertex_shader[], const char fragment_shader[]);
const uint32_t INVALID_UNIFORM_LOCATION = 0xFFFFFFFF;
// TODO: eventually move all hardcoded, prebuilt shaders into strings
/*
generically render a textured object composed of a VAO, containing a vertex buffer, an index buffer,
a texture, and a texture unit
*/
void render_textured_indexed(GLuint program, GLuint vao, GLuint vert, GLuint index, GLuint texture);
/*
generically render a textured object composed of a VAO, containing a vertex buffer with a color per vertex,
an index buffer,
*/
void render_indexed_colored(GLuint program, GLuint vao, GLuint vert, GLuint index);
/*
generically render a surface composed of a VAO, containing a vertex buffer with a value per vertex looking up in a 1D-texture
*/
void render_indexed_heatmap(GLuint program, GLuint vao, GLuint vert, GLuint index, GLuint texture);
通用.cpp
#include <stdio.h>
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <algorithm>
#include <sstream>
#include <webp/decode.h>
using namespace std;
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include "common.hh"
GLFWwindow* win = nullptr;
void check_status( GLuint obj, bool isShader ) {
GLint status = GL_FALSE, log[ 1 << 11 ] = { 0 };
( isShader ? glGetShaderiv : glGetProgramiv )( obj, isShader ? GL_COMPILE_STATUS : GL_LINK_STATUS, &status );
if( status == GL_TRUE ) return;
( isShader ? glGetShaderInfoLog : glGetProgramInfoLog )( obj, sizeof( log ), nullptr, (GLchar*)log );
std::cerr << (GLchar*)log << "\n";
std::exit( EXIT_FAILURE );
}
void attach_shader( GLuint program, GLenum type, const char* src ) {
GLuint shader = glCreateShader( type );
glShaderSource( shader, 1, &src, NULL );
glCompileShader( shader );
check_status( shader, true );
glAttachShader( program, shader );
glDeleteShader( shader );
}
// build a vertex and fragment shader program from constants in source code
// this hardcoded version is provided for all the standard shaders that you want for common graphics
// you can always load from files but I will build a number of the basic ones in here
GLuint build_prog(const char vertex_shader[], const char fragment_shader[]) {
GLuint prog = glCreateProgram();
attach_shader( prog, GL_VERTEX_SHADER, vertex_shader );
attach_shader( prog, GL_FRAGMENT_SHADER, fragment_shader );
glLinkProgram( prog );
check_status( prog, false );
return prog;
}
GLuint loadShaders(const char vertexPath[], const char * fragmentPath) {
// Create the shaders
GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
// Read the Vertex Shader code from the file
std::string VertexShaderCode;
std::ifstream VertexShaderStream(vertexPath, std::ios::in);
if(VertexShaderStream.is_open()){
std::stringstream sstr;
sstr << VertexShaderStream.rdbuf();
VertexShaderCode = sstr.str();
VertexShaderStream.close();
}else{
printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !\n", vertexPath);
getchar();
return 0;
}
// Read the Fragment Shader code from the file
std::string FragmentShaderCode;
std::ifstream FragmentShaderStream(fragmentPath, std::ios::in);
if(FragmentShaderStream.is_open()){
std::stringstream sstr;
sstr << FragmentShaderStream.rdbuf();
FragmentShaderCode = sstr.str();
FragmentShaderStream.close();
}
GLint Result = GL_FALSE;
int InfoLogLength;
// Compile Vertex Shader
printf("Compiling shader : %s\n", vertexPath);
char const * VertexSourcePointer = VertexShaderCode.c_str();
glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
glCompileShader(VertexShaderID);
// Check Vertex Shader
glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
printf("%s\n", &VertexShaderErrorMessage[0]);
}
// Compile Fragment Shader
printf("Compiling shader : %s\n", fragmentPath);
char const * FragmentSourcePointer = FragmentShaderCode.c_str();
glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
glCompileShader(FragmentShaderID);
// Check Fragment Shader
glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
printf("%s\n", &FragmentShaderErrorMessage[0]);
}
// Link the program
printf("Linking program\n");
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
glLinkProgram(ProgramID);
// Check the program
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
printf("%s\n", &ProgramErrorMessage[0]);
}
glDetachShader(ProgramID, VertexShaderID);
glDetachShader(ProgramID, FragmentShaderID);
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
return ProgramID;
}
GLFWwindow* createWindow(uint32_t w, uint32_t h, const char title[]) {
// Initialise GLFW
if( !glfwInit() ) {
throw "Failed to initialize GLFW";
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
GLFWwindow* win = glfwCreateWindow(w, h, title, nullptr, nullptr);
if (win == nullptr) {
glfwTerminate();
throw "Failed to open GLFW window";
}
glfwMakeContextCurrent(win); // create OpenGL context
// Initialize GLEW
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
throw "Failed to initialize GLEW";
}
// Ensure we can capture the escape key to quit
glfwSetInputMode(win, GLFW_STICKY_KEYS, GL_TRUE);
return win;
}
/*
standardized main to catch errors.
In this simplified version each error is just reported as a string
It would be better to also track which file and line number the error
happened in, but that would take an exception object.
For now, keeping it simple
*/
int main(int argc, char* argv[]) {
try {
glmain();
glfwTerminate(); // Close OpenGL window and terminate GLFW
} catch (const char* msg) {
cerr << msg << '\n';
exit(-1);
}
return 0;
}
void dump(glm::mat4& mat) {
// TODO: I suspect we are printing the matrix transposed
const float* m = &mat[0][0];
cerr << setprecision(7);
for (int i = 0, c = 0; i < 4; i++) {
for (int j = 0; j < 4; j++, c++)
cerr << setw(14) << m[c];
cerr << '\n';
}
}
void transpt(glm::mat4& m, double x, double y, double z) {
cerr << "orig=(" << x << "," << y << "," << z << ") transformed: (" <<
(m[0][0] * x + m[1][0] * y + m[2][0] * z + m[3][0]) << "," <<
(m[0][1] * x + m[1][1] * y + m[2][1] * z + m[3][1]) << "," <<
(m[0][2] * x + m[1][2] * y + m[2][2] * z + m[3][2]) << ")\t(";
cerr <<
(m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]) << "," <<
(m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]) << "," <<
(m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]) << ")\n";
}
GLuint loadWebPTexture(const char* filePath) {
// Read the file into a buffer
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "Failed to open WebP file: " << filePath << std::endl;
return 0;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
if (!file.read(buffer.data(), size)) {
std::cerr << "Failed to read WebP file: " << filePath << std::endl;
return 0;
}
// Decode the WebP image
int width, height;
uint8_t* data = WebPDecodeRGBA(reinterpret_cast<uint8_t*>(buffer.data()), size, &width, &height);
if (!data) {
std::cerr << "Failed to decode WebP image: " << filePath << std::endl;
return 0;
}
// Generate and bind a texture
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
// Upload the texture data
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
// Set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Free the image data
WebPFree(data);
return textureID;
}
void transform_dump(glm::mat4& mat, double x, double y, double z) {
glm::vec4 point = glm::vec4(x, y, z, 1.0f);
glm::vec4 tp = mat * point;
cerr << "Transformed point: (" << tp.x << ", " << tp.y << ", " << tp.z << ")\n";
}
06b_texturepoints.vert
#version 330 core
layout(location = 0) in vec3 pos; // Position (x, y, z)
layout(location = 1) in vec2 texCoord; // Texture coordinates (u, v)
uniform mat4 transform;
out vec2 TexCoord;
void main() {
gl_Position = transform * vec4(pos, 1.0);
TexCoord = texCoord;
}
06b_textures.frag
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D textureSampler;
void main() {
FragColor = texture(textureSampler, TexCoord);
}
这是试图显示问题的屏幕截图:
为了便于讨论,我们假设您将
lonRes
设置为 10。
根据上面的代码,vert
中的单个水平条带的左侧顶点的u
设置为 0, 0.1, ..., 0.9。
在索引计算代码中,将每个顶点与其后面的顶点连接,然后将最终顶点与行中的第一个顶点连接,因此 u=0.9 的顶点将连接到 u=0 的顶点。这意味着最终的正方形将包含整个地图的 90% (
1-1/lonRes
),水平翻转,因为你从 u=0.9 变为 u=0。
相反,使经度循环更长,以便在
u=1
处生成顶点。还要调整 indices
上的循环,使其上升到 并包括 lonRes
,并将显式重置降至 startrow
。
一些不相关的建议:
struct Vertex { float x,y,z,u,v; }
,这样您就可以按字段分配值或一次创建整个顶点,而不是一直执行 vert[c++] = ...
。std::vector
!使用 emplace_back
和合理的容量提示与普通数组一样快,无需预先计算顶点数。