单元格着色 LibGdx
Cell Shading LibGdx
我正在使用 LibGdx 制作我的 3D Game。
在查看了一些其他线程和 posts 以及一些非常好的教程之后,我得到了第一个着色器。我现在的问题是让 Cel/Outline/Toon 着色器工作。
因此,我还找到了一个教程和一个项目,但它们没有用。
在阅读了一些如何解决这个着色问题的帖子后(渲染对象两次,...)我尝试了这个方法但是有一些副作用。
实际上我得到了一个错误的渲染场景。
我现在的问题是,如果我的模型只需要一些其他的 Material 或者为什么我会得到这些结果。
我写了一个基于 KBAL tutorial 的 cel 着色器,它产生了像上面那样的渲染。我一直想在上面写点东西,因为从那时起图书馆发生了很大变化。您好像卡在了深度着色器上,它是原始教程中最需要更新的部分之一。
除了兼容性更新之外,我通过修改 LibGDX 附带的超级着色器删除了一个渲染通道,以便在几何体的初始渲染期间而不是在 KBAL 教程的 toonify()
函数中执行离散化post 传球。除此之外,它遵循相同的模式。
下面的代码是我的 cel 着色器代码的基本实现。 class 派生扩展了 AbstractScreen
,它实现了 LibGDX 的 Screen
接口的一些基本功能。详细了解 Screen
的 here 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 着色的信息的人有所帮助。
我正在使用 LibGdx 制作我的 3D Game。
在查看了一些其他线程和 posts 以及一些非常好的教程之后,我得到了第一个着色器。我现在的问题是让 Cel/Outline/Toon 着色器工作。
因此,我还找到了一个教程和一个项目,但它们没有用。
在阅读了一些如何解决这个着色问题的帖子后(渲染对象两次,...)我尝试了这个方法但是有一些副作用。
实际上我得到了一个错误的渲染场景。 我现在的问题是,如果我的模型只需要一些其他的 Material 或者为什么我会得到这些结果。
除了兼容性更新之外,我通过修改 LibGDX 附带的超级着色器删除了一个渲染通道,以便在几何体的初始渲染期间而不是在 KBAL 教程的 toonify()
函数中执行离散化post 传球。除此之外,它遵循相同的模式。
下面的代码是我的 cel 着色器代码的基本实现。 class 派生扩展了 AbstractScreen
,它实现了 LibGDX 的 Screen
接口的一些基本功能。详细了解 Screen
的 here 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 着色的信息的人有所帮助。