LibGDX - 如何在单个着色器中渲染两个具有不同比例的纹理?

3
我正在尝试缩放由FBO生成的纹理,其比另一个纹理具有不同的比例,并在片段着色器中同时渲染这两个纹理,以便可以将它们混合在一起。我遵循了此教程 来为我的游戏生成光照贴图,并获得了完美的光照贴图,但我想在着色器中转换光照贴图,以使背景中的视差对象接收到按比例缩放的光照贴图,以使它们与光源的“3D”距离不会影响它们的照明,而它们的2D距离则会影响它们的照明。
我最好的猜测是根据视差对象的Z距离在原地缩放光照贴图。在我的游戏中,Z距离与对象到相机的2D距离成正比。
以下是示例,说明常规光照贴图发生的情况以及所需效果。这里的光照贴图由位于右侧的黄色非视差“太阳”处的黄色径向渐变组成: 光照贴图缩放的描述

当前照明效果的动画示例

预期照明效果的动画示例

通常在我的游戏中,我通过像LibGDX中的以下方式缩放视差对象,即通过缩放与SpriteBatch相连的相机来实现:

Array<Something> objects = new Array<Something>();
SpriteBatch batch = new SpriteBatch();
OrthographicCamera camera = new OrthographicCamera(screenWidth, screenHeight);

//{start loop, add objects, run logic, etc.}
...

for(int i=0;i<objects.size;i++){
   camera.zoom = objects.get(i).z;
   camera.update();
   batch.setProjectionMatrix(camera.combined);
   objects.get(i).sprite.draw(batch);
}

根据我的研究,应该有一种方法可以在顶点着色器中进行这些类型的转换。通过默认的LibGDX顶点着色器进行这样的转换非常简单,它设置为将顶点位置与相机的转换矩阵相乘,如下所示:

void main() {
    v_color = a_color;
    v_texCoords = a_texCoord0; 

    gl_Position =  u_projTrans * a_position; 
}

但我不确定如何在变换与共享顶点位置相关联时,仅缩放片段着色器中使用的两个纹理中的一个。供参考,这是从教程中实现的片段着色器:

void main() {
    vec4 diffuseColor = texture2D(u_texture, v_texCoords);

    vec2 lightCoord = (gl_FragCoord.xy / resolution.xy);
    vec4 light = texture2D(u_lightmap, lightCoord);

    vec3 ambient = ambientColor.rgb * ambientColor.a;
    vec3 intensity = ambient + light.rgb;
    vec3 finalColor = diffuseColor.rgb * intensity;

    gl_FragColor = v_color * vec4(finalColor, diffuseColor.a);
}

我认为这与转换纹理坐标有关,而不是位置,并将它们传递到光照贴图中,而不是使用屏幕分辨率获取纹理坐标。但是,由于我对OpenGL ES 2或GLSL的了解不够丰富或经验不足,我的尝试最终变成了一团糟。

是否有一种良好的方法可以在两个纹理片段着色器中以不同的方式缩放一个纹理,而不是另一个纹理?或者是否有更好的方法来实现我正在尝试使用光照贴图做的事情?

编辑:

因此,我可以通过为每个视差对象重新创建FBO光照贴图以不同的比例来实现我要做的事情,但这会为我打算使用的某些平台(Android)带来巨大的性能问题。

我找到了一些 链接,显示我想要做的事情是可行的,但它们都描述了将两个不同的纹理坐标传递到顶点着色器中,这似乎与SpriteBatch不兼容。是否可能在顶点着色器中转换纹理坐标,以便我可以传递变换后的光照贴图坐标给片段着色器?或者我对纹理坐标的工作方式有什么误解吗?

1个回答

2
首先,假设我正确地认为Something代表单个精灵:每次调用batch.setProjectionMatrix(),精灵批处理就会刷新,导致另一个绘制调用,如果你有几十个以上的精灵,这将会太多。使用您的方法将对象数组发送到精灵批处理器时,您应该按z位置对它们进行排序,然后仅在自上次调用batch.draw()以来z发生更改时才调用batch.setProjectionMatrix()。我个人会为每个视差层保留单独的对象数组,以保持简单并且不必担心排序。
从那里开始: 首先,在绘制光照图时,请为游戏支持的最小缩放进行绘制,这样您就不必缩小光照图并冒着显示光照图修剪边缘的风险。因此,在fbo.begin()之后,请确保将cam.zoom更改为场景的最小cam.zoom
要获取缩放后的光照图坐标,您需要在顶点着色器中进行计算,并将其传递给片段着色器。首先,您需要输入缩放级别,因此您需要将其作为统一变量传递。您可以像这样实现:
//including the changes explained way above, where objectLayers is an 
//Array<LayerWrapper>, where LayerWrapper contains a zoom value and 
//an Array<Something>
for(int i=0;i<objectLayers.size;i++){
    LayerWrapper layer = objectLayers.get(i);
    Array<Something> layerObjects = layer.objects;

    camera.zoom = layer.zoom;
    camera.update();
    batch.setProjectionMatrix(camera.combined);

    //update the zoom level in the shader you're using with the sprite batch
    customShader.bind();
    customShader.setUniformf("u_zoom", layer.zoom);

    for(Something object : layerObjects ){
        object.sprite.draw(batch);
    }
}

然后,请更新您的顶点着色器,声明 uniform float u_zoom;varying vec2 v_lightCoords;。基于缩放比例,将会扩大您的光照坐标。同时,只要我们传递了一个varying,我们也应该预先计算出精确的纹理坐标,这样就不必在片段着色器中进行计算,从而避免了依赖纹理读取(这是一种优化,在教程中本来就可以做到)。顶点着色器现在看起来像这样:
void main()
{
    v_color = a_color;
    v_texCoords = a_texCoord0;
    vec4 screenSpacePosition = u_projTrans * a_position;
    gl_Position =  screenSpacePosition;

    v_lightCoords = (screenSpacePosition.xy * u_zoom) * 0.5 + 0.5;
}

由于v_lightCoords已经内置了纹理坐标,因此您可以简化片段着色器,像这样进行纹理查找: vec4 Light = texture2D(u_lightmap, v_lightCoords); 并删除不再需要的uniform vec2 resolution;


非常感谢您详细的回答,您的解决方案完美地解决了我的问题。我遇到的两个小问题是screenSpacePosition需要是vec4,而对于Android,zoom / 2需要是zoom / 2.0,但是您的解决方案让我可以按照我想要的比例缩放光照贴图。另外,对于性能改进的额外建议也非常感谢;我切换到手动计算绘制精灵的宽度、高度和位置的'z因子'来创建相同的视差效果,并且性能有了明显的提升。 - user3312130
谢谢,已经修复了你的问题。我想u_zoom可以改成u_halfZoom,这样在顶点着色器中可以节省一条指令,但我怀疑这对性能的影响不够显著。 - Tenfour04

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接