简体   繁体   English

根据与相机的距离调整点精灵的大小

[英]Resizing point sprites based on distance from the camera

I'm writing a clone of Wolfenstein 3D using only core OpenGL 3.3 for university and I've run into a bit of a problem with the sprites, namely getting them to scale correctly based on distance. 我正在使用仅用于大学的核心OpenGL 3.3编写Wolfenstein 3D的克隆,我遇到了精灵的一些问题,即让它们根据距离正确缩放。

From what I can tell, previous versions of OGL would in fact do this for you, but that functionality has been removed, and all my attempts to reimplement it have resulted in complete failure. 据我所知,以前版本的OGL实际上会为你做这件事,但是这个功能已被删除,而我重新实现它的所有尝试都导致完全失败。

My current implementation is passable at distances, not too shabby at mid range and bizzare at close range. 我目前的实施方式是可以在距离上通过,在中距离不太破旧,近距离发呆。

The main problem (I think) is that I have no understanding of the maths I'm using. 主要问题(我认为)是我对我正在使用的数学没有理解。
The target size of the sprite is slightly bigger than the viewport, so it should 'go out of the picture' as you get right up to it, but it doesn't. 精灵的目标大小略大于视口,因此当你接近它时它应该“走出画面”,但事实并非如此。 It gets smaller, and that's confusing me a lot. 它变小了,这让我很困惑。
I recorded a small video of this, in case words are not enough. 我录制了一个小视频,以防单词不够。 (Mine is on the right) (我在右边)

Expected ResultActual Result

Can anyone direct me to where I'm going wrong, and explain why? 谁能指引我到我错的地方,并解释原因?

Code: 码:
C++ C ++

// setup
glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_LOWER_LEFT);
glEnable(GL_PROGRAM_POINT_SIZE);

// Drawing
glUseProgram(StaticsProg);
glBindVertexArray(statixVAO);
glUniformMatrix4fv(uStatixMVP, 1, GL_FALSE, glm::value_ptr(MVP));
glDrawArrays(GL_POINTS, 0, iNumSprites);

Vertex Shader 顶点着色器

#version 330 core

layout(location = 0) in vec2 pos;
layout(location = 1) in int spriteNum_;

flat out int spriteNum;

uniform mat4 MVP;

const float constAtten  = 0.9;
const float linearAtten = 0.6;
const float quadAtten   = 0.001;

void main() {
    spriteNum = spriteNum_;
    gl_Position = MVP * vec4(pos.x + 1, pos.y, 0.5, 1); // Note: I have fiddled the MVP so that z is height rather than depth, since this is how I learned my vectors.
    float dist = distance(gl_Position, vec4(0,0,0,1));
    float attn = constAtten / ((1 + linearAtten * dist) * (1 + quadAtten * dist * dist));
    gl_PointSize = 768.0 * attn;
}

Fragment Shader 片段着色器

#version 330 core

flat in int spriteNum;

out vec4 color;

uniform sampler2DArray Sprites;

void main() {
    color = texture(Sprites, vec3(gl_PointCoord.s, gl_PointCoord.t, spriteNum));
    if (color.a < 0.2)
        discard;
}

First of all, I don't really understand why you use pos.x + 1 . 首先,我真的不明白为什么你使用pos.x + 1

Next, like Nathan said, you shouldn't use the clip-space point, but the eye-space point. 接下来,就像内森所说,你不应该使用剪辑空间点,而应该使用眼睛空间点。 This means you only use the modelview-transformed point (without projection) to compute the distance. 这意味着您只能使用模型视图转换点(无投影)来计算距离。

uniform mat4 MV;       //modelview matrix

vec3 eyePos = MV * vec4(pos.x, pos.y, 0.5, 1); 

Furthermore I don't completely understand your attenuation computation. 此外,我不完全了解你的衰减计算。 At the moment a higher constAtten value means less attenuation. 目前,更高的constAtten数值意味着更少的衰减。 Why don't you just use the model that OpenGL's deprecated point parameters used: 为什么不使用OpenGL不推荐使用的点参数的模型:

float dist = length(eyePos);   //since the distance to (0,0,0) is just the length
float attn = inversesqrt(constAtten + linearAtten*dist + quadAtten*dist*dist);

EDIT: But in general I think this attenuation model is not a good way, because often you just want the sprite to keep its object space size, which you have quite to fiddle with the attenuation factors to achieve that I think. 编辑:但总的来说,我认为这种衰减模型不是一个好方法,因为通常你只是希望精灵保持其对象空间大小,你可以完全摆弄衰减因子来达到我的想法。

A better way is to input its object space size and just compute the screen space size in pixels (which is what gl_PointSize actually is) based on that using the current view and projection setup: 更好的方法是输入其对象空间大小,并根据使用当前视图和投影设置计算屏幕空间大小(以gl_PointSize实际为单位):

uniform mat4 MV;                //modelview matrix
uniform mat4 P;                 //projection matrix
uniform float spriteWidth;      //object space width of sprite (maybe an per-vertex in)
uniform float screenWidth;      //screen width in pixels

vec4 eyePos = MV * vec4(pos.x, pos.y, 0.5, 1); 
vec4 projCorner = P * vec4(0.5*spriteWidth, 0.5*spriteWidth, eyePos.z, eyePos.w);
gl_PointSize = screenWidth * projCorner.x / projCorner.w;
gl_Position = P * eyePos;

This way the sprite always gets the size it would have when rendered as a textured quad with a width of spriteWidth . 这样,精灵总是获得渲染为宽度为spriteWidth的纹理四边形时的spriteWidth

EDIT: Of course you also should keep in mind the limitations of point sprites. 编辑:当然你也应该记住点精灵的局限性。 A point sprite is clipped based of its center position. 点精灵根据其中心位置进行裁剪。 This means when its center moves out of the screen, the whole sprite disappears. 这意味着当它的中心移出屏幕时,整个精灵会消失。 With large sprites (like in your case, I think) this might really be a problem. 对于大精灵(就像你的情况,我认为),这可能真的是一个问题。

Therefore I would rather suggest you to use simple textured quads. 所以我宁愿建议你使用简单的纹理四边形。 This way you circumvent this whole attenuation problem, as the quads are just transformed like every other 3d object. 这样就可以避免这个整个衰减问题,因为四边形像其他每个3d对象一样被转换。 You only need to implement the rotation toward the viewer, which can either be done on the CPU or in the vertex shader. 您只需要向查看器实现旋转,这可以在CPU上或在顶点着色器中完成。

Based on Christian Rau's answer (last edit), I implemented a geometry shader that builds a billboard in ViewSpace, which seems to solve all my problems: 基于Christian Rau的回答 (最后编辑),我实现了一个几何着色器,它在ViewSpace中构建了一个广告牌,它似乎解决了我所有的问题:

Expected ResultActual Result

Here are the shaders: (Note that I have fixed the alignment issue that required the original shader to add 1 to x) 以下是着色器:(请注意,我已修复了需要原始着色器向x添加1的对齐问题)

Vertex Shader 顶点着色器

#version 330 core

layout (location = 0) in vec4 gridPos;
layout (location = 1) in int  spriteNum_in;

flat out int spriteNum;

// simple pass-thru to the geometry generator
void main() {
    gl_Position = gridPos;
    spriteNum = spriteNum_in;
}

Geometry Shader 几何着色器

#version 330 core

layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

flat in int spriteNum[];

smooth out vec3 stp;

uniform mat4 Projection;
uniform mat4 View;

void main() {
    // Put us into screen space. 
    vec4 pos = View * gl_in[0].gl_Position;

    int snum = spriteNum[0];

    // Bottom left corner
    gl_Position = pos;
    gl_Position.x += 0.5;
    gl_Position = Projection * gl_Position;
    stp = vec3(0, 0, snum);
    EmitVertex();

    // Top left corner
    gl_Position = pos;
    gl_Position.x += 0.5;
    gl_Position.y += 1;
    gl_Position = Projection * gl_Position;
    stp = vec3(0, 1, snum);
    EmitVertex();

    // Bottom right corner
    gl_Position = pos;
    gl_Position.x -= 0.5;
    gl_Position = Projection * gl_Position;
    stp = vec3(1, 0, snum);
    EmitVertex();

    // Top right corner
    gl_Position = pos;
    gl_Position.x -= 0.5;
    gl_Position.y += 1;
    gl_Position = Projection * gl_Position;
    stp = vec3(1, 1, snum);
    EmitVertex();

    EndPrimitive();
}

Fragment Shader 片段着色器

#version 330 core

smooth in vec3 stp;

out vec4 colour;

uniform sampler2DArray Sprites;

void main() {
    colour = texture(Sprites, stp);
    if (colour.a < 0.2)
        discard;
}

I don't think you want to base the distance calculation in your vertex shader on the projected position. 我认为您不希望将顶点着色器中的距离计算基于投影位置。 Instead just calculate the position relative to your view, ie use the model-view matrix instead of the model-view-projection one. 而只是计算相对于视图的位置,即使用模型视图矩阵而不是模型视图投影矩阵。

Think about it this way -- in projected space, as an object gets closer to you, its distance in the horizontal and vertical directions becomes exaggerated. 以这种方式思考 - 在投影空间中,当物体靠近你时,它在水平和垂直方向上的距离变得夸张。 You can see this in the way the lamps move away from the center toward the top of the screen as you approach them. 当你接近它们时,你可以看到灯从中心向屏幕顶部移动的方式。 That exaggeration of those dimensions is going to make the distance get larger when you get really close, which is why you're seeing the object shrink. 这些尺寸的夸大将使得距离越来越近,这就是为什么你看到物体缩小的原因。

At least in OpenGL ES 2.0, there is a maximum size limitation on gl_PointSize imposed by the OpenGL implementation. 至少在OpenGL ES 2.0中,由OpenGL实现强加的gl_PointSize存在最大大小限制。 You can query the size with ALIASED_POINT_SIZE_RANGE. 您可以使用ALIASED_POINT_SIZE_RANGE查询大小。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM