单元格着色 LibGdx

Cell Shading LibGdx

我正在使用 LibGdx 制作我的 3D Game
在查看了一些其他线程和 posts 以及一些非常好的教程之后,我得到了第一个着色器。我现在的问题是让 Cel/Outline/Toon 着色器工作。 因此,我还找到了一个教程和一个项目,但它们没有用。

在阅读了一些如何解决这个着色问题的帖子后(渲染对象两次,...)我尝试了这个方法但是有一些副作用。

实际上我得到了一个错误的渲染场景。 我现在的问题是,如果我的模型只需要一些其他的 Material 或者为什么我会得到这些结果。

我写了一个基于 KBAL tutorial 的 cel 着色器,它产生了像上面那样的渲染。我一直想在上面写点东西,因为从那时起图书馆发生了很大变化。您好像卡在了深度着色器上,它是原始教程中最需要更新的部分之一。

除了兼容性更新之外,我通过修改 LibGDX 附带的超级着色器删除了一个渲染通道,以便在几何体的初始渲染期间而不是在 KBAL 教程的 toonify() 函数中执行离散化post 传球。除此之外,它遵循相同的模式。

下面的代码是我的 cel 着色器代码的基本实现。 class 派生扩展了 AbstractScreen,它实现了 LibGDX 的 Screen 接口的一些基本功能。详细了解 Screenhere and see the CelTutorialScreen source within a full project context here

package com.hh.ghoststory.screen;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.utils.Array;
import com.hh.ghoststory.GhostStory;
import com.hh.ghoststory.render.shaders.CelDepthShaderProvider;
import com.hh.ghoststory.render.shaders.CelLineShaderProgram;

public class CelTutorialScreen extends AbstractScreen {
    private PerspectiveCamera camera = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

    private AssetManager assetManager = new AssetManager();
    private Array<ModelInstance> instances = new Array<ModelInstance>();

    private FrameBuffer fbo;
    private TextureRegion textureRegion;
    private ShaderProgram lineShader = new CelLineShaderProgram();

    private SpriteBatch spriteBatch = new SpriteBatch();
    private ModelBatch modelBatch = new ModelBatch(Gdx.files.classpath("com/badlogic/gdx/graphics/g3d/shaders/default.vertex.glsl").readString(), Gdx.files.internal("shaders/cel.main.fragment.glsl").readString());
    private ModelBatch depthBatch = new ModelBatch(new CelDepthShaderProvider());
    private Environment environment = new Environment();

    public CelTutorialScreen(GhostStory game) {
        super(game);

        Gdx.gl.glClearColor(1.0f, 0.0f, 1.0f, 0.0f);
        Gdx.gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT);

        // setup camera
        camera.position.set(5, 5, 5);
        camera.lookAt(0, 0, 0);
        camera.near = 1;
        camera.far = 1000;
        camera.update();

        // add a light
        environment.add(new DirectionalLight().set(0.8f, 0.8f, 1.8f, -1f, -0.8f, 0.2f));

        // load our model
        assetManager.load("models/spider.g3dj", Model.class);
        loading = true;
    }
    @Override
    public void render(float delta) {
        if (loading && assetManager.update())
            doneLoading();

        camera.update();
        Gdx.gl.glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        Gdx.gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT);

        // render depth map to fbo
        captureDepth();
        // draw the scene
        renderScene();
        // put fbo texture in a TextureRegion and flip it
        prepTextureRegion();
        // draw the cel outlines
        drawOutlines();
    }
    /*
     * Draws the cel outlines using the CelLineShaderProgram
     */
    protected void drawOutlines() {
        spriteBatch.setShader(lineShader);
        lineShader.setUniformf("u_size", Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        spriteBatch.begin();
        spriteBatch.draw(textureRegion, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        spriteBatch.end();
        spriteBatch.setShader(null);
    }
    /*
     * Stores fbo texture in a TextureRegion and flips it vertically.
     */
    protected void prepTextureRegion() {
        textureRegion = new TextureRegion(fbo.getColorBufferTexture());
        textureRegion.flip(false, true);
    }
    /*
     * Draws the depth pass to an fbo, using a ModelBatch created with CelDepthShaderProvider()
     */
    protected void captureDepth() {
        fbo.begin();
        Gdx.gl.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        Gdx.gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT);
        depthBatch.begin(camera);
        depthBatch.render(instances);
        depthBatch.end();
        fbo.end();
    }
    /*
     * Renders the scene.
     */
    protected void renderScene() {
        Gdx.gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT);
        modelBatch.begin(camera);
        modelBatch.render(instances, environment);
        modelBatch.end();
    }
    @Override
    protected void doneLoading() {
        loading = false;
        instances.add(new ModelInstance(assetManager.get("models/spider.g3dj", Model.class)));
    }
    /*
     * Set camera width and height, SpriteBatch projection matrix, and reinit the FBOs
     */
    @Override
    public void resize(int width, int height) {
        camera.position.set(camera.position);
        camera.viewportWidth = width;
        camera.viewportHeight = height;
        camera.update();

        if (fbo != null) fbo.dispose();
        fbo = new FrameBuffer(Pixmap.Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);

        spriteBatch.setProjectionMatrix(new Matrix4().setToOrtho2D(0, 0, width, height));
    }
    @Override
    public void dispose() {
        assetManager.dispose();
        modelBatch.dispose();
        depthBatch.dispose();
        spriteBatch.dispose();
        fbo.dispose();
        lineShader.dispose();
    }
}

render 执行 3 遍以创建最终产品。

第一个包含在 captureDepth() 函数中。

    protected void captureDepth() {
        fbo.begin();
        Gdx.gl.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        Gdx.gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT);
        depthBatch.begin(camera);
        depthBatch.render(instances);
        depthBatch.end();
        fbo.end();
    }

启动帧缓冲区,调用 glClear,然后 depthBatch ModelBatch() 在帧缓冲区结束之前渲染模型实例(在这种情况下只有一个)。

depthBatch 是使用 CelDepthShaderProvider, which provides a CelDepthShader. CellDepthShaderProvider is a small class that extends BaseShaderProvider and overrides createShader to return an instance of CelDepthShader, which registers and sets u_near and u_far uniforms as well as sets up the use of the cel depth vertex and fragment GLSL 着色器的 ModelBatch

我猜 GLSL 文件是您 运行 遇到问题的地方。我链接到的顶点着色器与 KBAL 顶点着色器相同,除了第 125 行,它删除了 cel 边缘上的一些伪像:

v_depth = (pos.z + u_near) / (u_far - u_near);

片段着色器与KBAL教程中的非常相似,但实际上是从LibGDX复制而来的built in depth fragment shader. It's quite possible that the current LigGDX DepthShader可以用来代替我的CelDepthShader,但我还没来得及研究这个。

第一次通过后,FBO 捕获了打包的深度图。第二遍准备运行,会用LibGDXs绘制场景default vertex shader and a slightly modified version of its fragment shader.

默认片段着色器的变化在行 140-150 中,其中高光值在添加到 gl_FragColor 之前被离散化:

if (specIntensity > 0.6)
    specFactor = 1.0;
else if (specIntensity > 0.3)
    specFactor = 0.5;
else
    specFactor = 0.1;

specular *= specFactor;

173-182整体gl_FragColor离散化:

float intensity = max(gl_FragColor.r, max(gl_FragColor.g, gl_FragColor.b));
float factor;
if (intensity > 0.8)
    factor = 1.0;
else if (intensity > 0.5)
    factor = 0.8;
else if (intensity > 0.25)
    factor = 0.3;
else
    factor = 0.1;

这就是主要的 cel 通道。

接下来在 render() 中调用 prepTextureRegion() 函数。这只是将捕获到我们的 fbo 的深度纹理放入纹理区域并垂直翻转它,然后在最终通道中使用它绘制 cel 轮廓。

最后一关在 drawOutlines() 中执行并使用 SpriteBatch since we're drawing a 2d texture instead of geometry. The call to spriteBatch.setShader(lineshader) sets the SpriteBatch to use an instance of CelLineShaderProgram, another class that extends ShaderProgram. It sets a u_size uniform and uses cel.line.vertex.glsl and cel.line.fragment.glsl.

这个着色器程序运行是拉普拉斯过滤器。 vertex shader 从 KBAL 边缘着色器复制并更新为与新版本的 LibGDX 一起使用,它将深度图的采样坐标及其顶部、底部、左侧和右侧的相邻纹素传递给片段着色器作为变量。

fragment shader uses an updated method of unpacking the depth values based on code from the getShadowness() function here as recommended by Xoppa.

可以对此过程进行一些改进。一方面,我没有在原始教程中实现超级采样。

此外,在这张静止图像中它并不是很明显,但是一旦场景中有可控相机,或四处移动的几何体,您会注意到每像素照明看起来有点奇怪,尤其是在有限的多边形中你的几何。 LibGDX 阴影系统测试中有一个 per-pixel lighting fragment shader 可以用作使用 cel 着色实现此功能的基础。阴影系统甚至可能是创建用于 cel 着色的多通道渲染系统的良好基础。毫无疑问,可以从我使用的修改后的 LibGDX 着色器以及其他优化和清理中删除代码。

希望这对您或任何其他正在寻找有关多通道 cel 着色的信息的人有所帮助。