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

时间:2014-02-15 10:54:07

标签: java opengl-es-2.0 libgdx

我正在尝试使用与另一个纹理不同的比例缩放FBO中生成的纹理,并在片段着色器中同时渲染两个纹理,以便我可以将它们混合在一起。

我跟着this tutorial为我的游戏制作了一个光照贴图,我得到了一个完美的光照贴图,但我想在着色器中变换光照贴图,以便背景中的视差对象接收按比例缩放的光照贴图,所以它们与光源的“3D”距离似乎不会影响它们的光照,但它们的2D距离确实如此。

我最好的猜测是如何根据视差对象的Z距离缩放光照贴图。我游戏中的Z距离与物体与相机的2D距离成正比。

以下是描述常规光照贴图会发生什么以及所需效果的示例。这里的光照贴图由一个黄色的径向渐变组成,位于右边的黄色非视差“太阳”上:

Description of lightmap scaling

Animated example of current lighting

Animated example of intended lighting

通常在我的游戏中,我通过在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)带来巨大的性能问题。

我发现some links表明我正在尝试做的事情是可能的,但它们都描述了将两个不同的纹理坐标传递给顶点着色器,这似乎不起作用使用SpriteBatch。是否可以在顶点着色器中变换纹理坐标,以便我可以将光照贴图的变换坐标传递给片段着色器?或者我误解了纹理坐标是如何工作的?

1 个答案:

答案 0 :(得分:1)

首先,以防我假设Something代表一个精灵:每当你调用batch.setProjectionMatrix()时,精灵批量被刷新,导致另一个绘制调用如果你有超过几十个精灵,这将太多了。使用将对象数组发送到精灵批处理的方法,您应该按z位置对它们进行排序,然后如果自上次调用batch.setProjectionMatrix()后z已更改,则只调用batch.draw()。我个人会为每个视差层保留一个单独的对象数组,以保持简单,不用担心排序。

从那里出发: 首先,在绘制光照贴图时,请执行游戏支持的最小缩放,这样您就不必缩小光照贴图并冒险显示光照贴图的边缘。因此,在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;。您的光坐标将根据缩放比例放大。只要我们通过变化,我们也应该预先计算出精确的纹理坐标,这样你就不必在片段着色器中进行,这会导致依赖纹理读取(优化)可以在本教程中开始完成)。

顶点着色器现在看起来像这样:

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;