使用天空盒程序生成星星

2021-12-19 00:00:00 opengl c++ procedural-generation

我正在尝试在 OpenGL 中以程序方式生成一个充满星星的背景.

我采用的方法是创建一个带有立方体贴图纹理的天空盒.立方体贴图纹理的每一面基本上由一个 2048x2048 的黑色图像组成,其中随机选择的纹素设置为白色.结果如下:

我不确定它从图像中的表现有多明显,但是当在一个非常明显的盒子形状周围移动时,可以看到靠近盒子边缘的星星显得更小更靠近.我怎样才能防止这种情况?我是否需要放弃天空盒方法并使用类似天空球的东西?

这是我将立方体贴图映射到天空的方式.

//创建并绑定纹理.glGenTextures(1, &texture_);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_CUBE_MAP,纹理_);for (unsigned int i = 0; i <6; ++i) {std::vector图像 = generateTexture(TEXTURE_WIDTH, TEXTURE_HEIGHT);glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, TEXTURE_WIDTH, TEXTURE_HEIGHT,0, GL_RGB, GL_UNSIGNED_BYTE, image.data());}//设置纹理参数.glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

这里是generateTexture函数的定义:

std::vector星星::生成纹理(GLsizei 宽度,GLsizei 高度){std::vector图像(static_cast(3 * width * height));add_stars(图像,NUM_STARS);返回图像;}void Stars::add_stars(std::vector<std::uint8_t>& image, unsigned int nStars) {std::default_random_engine 工程;std::uniform_int_distributiondist(0, image.size()/3 - 1);而(nStars--){std::size_t 索引 = 3 * dist(eng);图像[索引++] = 255;图像[索引++] = 255;图像[索引++] = 255;}}

这里是用于渲染天空的绘制函数.

void Stars::draw(const Camera& camera) const {//Skybox 将最后渲染.为了保证星星在后面渲染//场景,深度缓冲区填充了天空盒的 1.0 值――这是在//顶点着色器.我们需要确保天空盒通过深度 te3t 的值//小于或等于深度缓冲区.glDepthFunc(GL_LEQUAL);program_.enable();//计算视图-投影矩阵并设置相应的uniform.视图矩阵必须是//去除平移组件,以便天空盒跟随相机.glm::mat4 视图 = glm::mat4(glm::mat3(camera.viewMatrix()));glm::mat4 投影 = camera.projectionMatrix();glm::mat4 VP = 投影 * 视图;glUniformMatrix4fv(program_.uniformLocation("VP"), 1, GL_FALSE, glm::value_ptr(VP));//将缓冲区对象和纹理绑定到当前上下文并绘制.glBindVertexArray(vao_);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);glBindTexture(GL_TEXTURE_CUBE_MAP,纹理_);glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(INDICES.size()), GL_UNSIGNED_INT,reinterpret_cast<GLvoid *>(0));glBindVertexArray(0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);glBindTexture(GL_TEXTURE_CUBE_MAP, 0);program_.disable();glDepthFunc(GL_LESS);}

解决方案

  1. 在某个立方体积内均匀地生成恒星

    x=2.0*Random()-1.0;//<-1,+1>y=2.0*Random()-1.0;//<-1,+1>z=2.0*Random()-1.0;//<-1,+1>

  2. 将它们投影到单位球体

    所以只需计算向量 (x,y,z) 的长度并将坐标除以它.

  3. 将结果投影到立方体贴图上

    立方体的每一面都由平面定义,因此找到从 (0,0,0) 投射到笛卡尔星位置和平面的光线的交点.取到 (0,0,0) 最短距离的交点,并将其作为最终星位.

实现可能是这样的 OpenGL&C++ 代码:

 glClearColor(0.0,0.0,0.0,0.0);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);int i,n=10000;浮动 a,b,x,y,z;//兰德种子=8123456789;n=obj.pnt.num;//三角化球体点列表glDepthFunc(GL_LEQUAL);glEnable(GL_BLEND);glBlendFunc(GL_ONE,GL_ONE);glPointSize(2.0);glBegin(GL_POINTS);对于 (i=0;i=fabs(y))&&(fabs(x)>=fabs(z))){y/=x;z/=x;如果 (x>=0) x=1.0;否则 x=-1.0;}否则 if ((fabs(y)>=fabs(x))&&(fabs(y)>=fabs(z))){x/=y;z/=y;如果 (y>=0) y=1.0;否则 y=-1.0;}否则 if ((fabs(z)>=fabs(x))&&(fabs(z)>=fabs(y))){ x/=z;y/=z;如果 (z>=0) z=1.0;否则 z=-1.0;}//蓝色立方体贴图glColor3f(0.0,0.3,0.6);glVertex3f(x,y,z);}glEnd();glPointSize(1.0);glDisable(GL_BLEND);glFlush();交换缓冲区(hdc);

看起来它应该在这里预览(混合球体/立方体贴图):

虽然看起来有洞,但没有洞(可能是一些混合错误)如果我禁用球体贴图渲染,那么映射中就没有可见的洞或扭曲.

用于测试的球体三角剖分mesh obj取自此处:

  • 所以只需根据您的纹理生成器调整代码...

    [Edit2] 随机星星

    glClearColor(0.0,0.0,0.0,0.0);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);国际我;浮动 x,y,z,d;兰德种子=8123456789;glDepthFunc(GL_LEQUAL);glEnable(GL_BLEND);glBlendFunc(GL_ONE,GL_ONE);glPointSize(2.0);glBegin(GL_POINTS);对于 (i=0;i<1000;i++){//立方体内的均匀随机笛卡尔星x=(2.0*Random())-1.0;y=(2.0*Random())-1.0;z=(2.0*Random())-1.0;//投影到单位球体上d=sqrt((x*x)+(y*y)+(z*z));如果 (d<1e-3) { i--;继续;}d=1.0/d;x*=d;y*=d;z*=d;//红色球体贴图glColor3f(0.6,0.3,0.0);glVertex3f(x,y,z);//立方体 half size=1 使用相似性不失真:y/x = y'/x'如果 ((fabs(x)>=fabs(y))&&(fabs(x)>=fabs(z))){y/=x;z/=x;如果 (x>=0) x=1.0;否则 x=-1.0;}否则 if ((fabs(y)>=fabs(x))&&(fabs(y)>=fabs(z))){x/=y;z/=y;如果 (y>=0) y=1.0;否则 y=-1.0;}否则 if ((fabs(z)>=fabs(x))&&(fabs(z)>=fabs(y))){ x/=z;y/=z;如果 (z>=0) z=1.0;否则 z=-1.0;}//蓝色立方体贴图glColor3f(0.0,0.3,0.6);glVertex3f(x,y,z);}glEnd();glPointSize(1.0);glDisable(GL_BLEND);glFlush();交换缓冲区(hdc);

    此处混合展位(1000 颗星):

    这里只有立方体贴图(10000颗星)

    [Edit3] 混合问题解决了

    这是由于忘记了fabs这里固定代码:

     glClearColor(0.0,0.0,0.0,0.0);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);国际我;浮动 x,y,z,d;兰德种子=8123456789;glDepthFunc(GL_ALWAYS);//glDepthFunc(GL_LEQUAL);glEnable(GL_BLEND);glBlendFunc(GL_ONE,GL_ONE);glPointSize(2.0);glBegin(GL_POINTS);对于 (i=0;i<25000;i++){//立方体内的均匀随机笛卡尔星x=(2.0*Random())-1.0;y=(2.0*Random())-1.0;z=(2.0*Random())-1.0;//投影到单位球体上d=sqrt((x*x)+(y*y)+(z*z));如果 (d<1e-3) { i--;继续;}d=1.0/d;x*=d;y*=d;z*=d;//红色球体贴图glColor3f(0.6,0.3,0.0);glVertex3f(x,y,z);//立方体 half size=1 使用相似性不失真:y/x = y'/x'如果 ((fabs(x)>=fabs(y))&&(fabs(x)>=fabs(z))){y/=fabs(x);z/=fabs(x);如果 (x>=0) x=1.0;否则 x=-1.0;}否则 if ((fabs(y)>=fabs(x))&&(fabs(y)>=fabs(z))){ x/=fabs(y);z/=fabs(y);如果 (y>=0) y=1.0;否则 y=-1.0;}否则 if ((fabs(z)>=fabs(x))&&(fabs(z)>=fabs(y))){ x/=fabs(z);y/=fabs(z);如果 (z>=0) z=1.0;否则 z=-1.0;}//蓝色立方体贴图glColor3f(0.0,0.3,0.6);glVertex3f(x,y,z);}glEnd();glPointSize(1.0);glDisable(GL_BLEND);glFlush();交换缓冲区(hdc);

    这里的混合结果最终颜色应该是这样的,所以从 (0,0,0) 观看时,球体和立方星完美重叠(白色):

    I am attempting to procedurally generate a star-filled background in OpenGL.

    The approach I am taking is to create a skybox with a cubemap texture. Each side of the cubemap texture essentially consists of a 2048x2048 black image with randomly selected texels set to White. Here is the result:

    I'm not sure how obvious it is from the image, but when moving around a very distinct box shape can be made out as stars close to the edge of the box appear smaller and closer together. How can I prevent this? Do I need to abandon the skybox approach and use something like a skysphere instead?

    EDIT: here is how I am mapping the cubemap onto the sky.

    // Create and bind texture.
    glGenTextures(1, &texture_);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture_);
    
    for (unsigned int i = 0; i < 6; ++i) {
        std::vector<std::uint8_t> image = generateTexture(TEXTURE_WIDTH, TEXTURE_HEIGHT);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, TEXTURE_WIDTH, TEXTURE_HEIGHT,
                     0, GL_RGB, GL_UNSIGNED_BYTE, image.data());
    }
    
    // Set texture parameters.
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    

    Here is the definition of the generateTexture function:

    std::vector<std::uint8_t> Stars::generateTexture(GLsizei width, GLsizei height) {
        std::vector<std::uint8_t> image(static_cast<std::size_t>(3 * width * height));
    
        add_stars(image, NUM_STARS);
    
        return image;
    }
    
    void Stars::add_stars(std::vector<std::uint8_t>& image, unsigned int nStars) {
        std::default_random_engine eng;
        std::uniform_int_distribution<std::size_t> dist(0, image.size() / 3 - 1);
    
        while (nStars--) {
            std::size_t index = 3 * dist(eng);
    
            image[index++] = 255;
            image[index++] = 255;
            image[index++] = 255;
        }
    }
    

    EDIT2: here is the draw function used to render the sky.

    void Stars::draw(const Camera& camera) const {
        // Skybox will be rendered last. In order to ensure that the stars are rendered at the back of
        // the scene, the depth buffer is filled with values of 1.0 for the skybox -- this is done in
        // the vertex shader. We need to make sure that the skybox passes the depth te3t with values
        // less that or equal to the depth buffer.
        glDepthFunc(GL_LEQUAL);
    
        program_.enable();
    
        // Calculate view-projection matrix and set the corresponding uniform. The view matrix must be
        // stripped of translation components so that the skybox follows the camera.
        glm::mat4 view       = glm::mat4(glm::mat3(camera.viewMatrix()));
        glm::mat4 projection = camera.projectionMatrix();
    
        glm::mat4 VP = projection * view;
        glUniformMatrix4fv(program_.uniformLocation("VP"), 1, GL_FALSE, glm::value_ptr(VP));
    
        // Bind buffer objects and texture to current context and draw.
        glBindVertexArray(vao_);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
        glBindTexture(GL_TEXTURE_CUBE_MAP, texture_);
    
        glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(INDICES.size()), GL_UNSIGNED_INT,
                       reinterpret_cast<GLvoid *>(0));
    
        glBindVertexArray(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
        program_.disable();
    
        glDepthFunc(GL_LESS);
    }
    

    解决方案

    1. generate stars uniformly in some cubic volume

      x=2.0*Random()-1.0; // <-1,+1>
      y=2.0*Random()-1.0; // <-1,+1>
      z=2.0*Random()-1.0; // <-1,+1>
      

    2. project them on unit sphere

      So just compute the length of vector (x,y,z) and divide the coordinates by it.

    3. project the result onto the cube map

      Each side of cube is defined by the plane so find intersection of ray casted from (0,0,0) through Cartesian star position and the planes. Take the intersection with shortest distance to (0,0,0) and use that as final star position.

    The implementation could be something like this OpenGL&C++ code:

        glClearColor(0.0,0.0,0.0,0.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        int i,n=10000;
        float a,b,x,y,z;
        //RandSeed=8123456789;
        n=obj.pnt.num;  // triangulated sphere point list
    
        glDepthFunc(GL_LEQUAL);
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE,GL_ONE);
    
        glPointSize(2.0);
        glBegin(GL_POINTS);
        for (i=0;i<n;i++)
            {
            // equidistant points instead of random to test this
            x=obj.pnt[i].p[0];
            y=obj.pnt[i].p[1];
            z=obj.pnt[i].p[2];
    /*
            // random star spherical position
            a=2.0*M_PI*Random();
            b=M_PI*(Random()-0.5);
            // spherical 2 cartessian r=1;
            x=cos(a)*cos(b);
            y=sin(a)*cos(b);
            z=       sin(b);
    */
            // redish sphere map
            glColor3f(0.6,0.3,0.0); glVertex3f(x,y,z);
            // cube half size=1 undistort // using similarities like: yy/xx = y/x
                 if ((fabs(x)>=fabs(y))&&(fabs(x)>=fabs(z))){ y/=x; z/=x; if (x>=0) x=1.0; else x=-1.0; }
            else if ((fabs(y)>=fabs(x))&&(fabs(y)>=fabs(z))){ x/=y; z/=y; if (y>=0) y=1.0; else y=-1.0; }
            else if ((fabs(z)>=fabs(x))&&(fabs(z)>=fabs(y))){ x/=z; y/=z; if (z>=0) z=1.0; else z=-1.0; }
            // bluish cube map
            glColor3f(0.0,0.3,0.6); glVertex3f(x,y,z);
            }
        glEnd();
        glPointSize(1.0);
        glDisable(GL_BLEND);
        glFlush();
        SwapBuffers(hdc);
    

    Looks like it works as it should here preview (of the blended sphere/cube map):

    Although it looks like there are holes but there are none (it is may be some blend error) if I disable the sphere map render then there are no visible holes or distortions in the mapping.

    The sphere triangulation mesh obj used to test this is taken from here:

    • Sphere triangulation

    [Edit1] yes there was a silly blending error

    I repaired the code ... but the problem persists anyway. does not matter this mapping is working as should here the updated code result:

    So just adapt the code to your texture generator ...

    [Edit2] Random stars

    glClearColor(0.0,0.0,0.0,0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    int i;
    float x,y,z,d;
    RandSeed=8123456789;
    
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE,GL_ONE);
    
    glPointSize(2.0);
    glBegin(GL_POINTS);
    for (i=0;i<1000;i++)
        {
        // uniform random cartesian stars inside cube
        x=(2.0*Random())-1.0;
        y=(2.0*Random())-1.0;
        z=(2.0*Random())-1.0;
        // project on unit sphere
        d=sqrt((x*x)+(y*y)+(z*z));
        if (d<1e-3) { i--; continue; }
        d=1.0/d;
        x*=d; y*=d; z*=d;
        // redish sphere map
        glColor3f(0.6,0.3,0.0); glVertex3f(x,y,z);
        // cube half size=1 undistort using similarities like: y/x = y'/x'
             if ((fabs(x)>=fabs(y))&&(fabs(x)>=fabs(z))){ y/=x; z/=x; if (x>=0) x=1.0; else x=-1.0; }
        else if ((fabs(y)>=fabs(x))&&(fabs(y)>=fabs(z))){ x/=y; z/=y; if (y>=0) y=1.0; else y=-1.0; }
        else if ((fabs(z)>=fabs(x))&&(fabs(z)>=fabs(y))){ x/=z; y/=z; if (z>=0) z=1.0; else z=-1.0; }
        // bluish cube map
        glColor3f(0.0,0.3,0.6); glVertex3f(x,y,z);
        }
    glEnd();
    glPointSize(1.0);
    glDisable(GL_BLEND);
    glFlush();
    SwapBuffers(hdc);
    

    Here Blend of booth (1000 stars):

    And Here only the cube-map (10000 stars)

    [Edit3] The Blend problem solved

    It was caused by Z-fighting and occasional changing of sign for some coordinates during the projection due to forgotten fabs here fixed code:

        glClearColor(0.0,0.0,0.0,0.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        int i;
        float x,y,z,d;
        RandSeed=8123456789;
    
        glDepthFunc(GL_ALWAYS);
    //  glDepthFunc(GL_LEQUAL);
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE,GL_ONE);
    
        glPointSize(2.0);
        glBegin(GL_POINTS);
        for (i=0;i<25000;i++)
            {
            // uniform random cartesian stars inside cube
            x=(2.0*Random())-1.0;
            y=(2.0*Random())-1.0;
            z=(2.0*Random())-1.0;
            // project on unit sphere
            d=sqrt((x*x)+(y*y)+(z*z));
            if (d<1e-3) { i--; continue; }
            d=1.0/d;
            x*=d; y*=d; z*=d;
            // redish sphere map
            glColor3f(0.6,0.3,0.0); glVertex3f(x,y,z);
            // cube half size=1 undistort using similarities like: y/x = y'/x'
                 if ((fabs(x)>=fabs(y))&&(fabs(x)>=fabs(z))){ y/=fabs(x); z/=fabs(x); if (x>=0) x=1.0; else x=-1.0; }
            else if ((fabs(y)>=fabs(x))&&(fabs(y)>=fabs(z))){ x/=fabs(y); z/=fabs(y); if (y>=0) y=1.0; else y=-1.0; }
            else if ((fabs(z)>=fabs(x))&&(fabs(z)>=fabs(y))){ x/=fabs(z); y/=fabs(z); if (z>=0) z=1.0; else z=-1.0; }
            // bluish cube map
            glColor3f(0.0,0.3,0.6); glVertex3f(x,y,z);
            }
        glEnd();
        glPointSize(1.0);
        glDisable(GL_BLEND);
        glFlush();
        SwapBuffers(hdc);
    

    And here the Blend result finally the colors are as should be so the sphere and cube stars overlaps perfectly (white) while viewing from (0,0,0):

相关文章