Hey folks!
I’m not sure wether this post belongs here or in the contributers forum, but as I do not identify myself as a contributer yet (not one in italic anyway) I decided to go with the user code forum.
I have been interested in the general idea of shaders for a while now but never dared to try writing them myself. Through some other post on the forum here I stumbled upon this and truly, I love the idea of games that look like they were drawn while actually being 3D. As there was an example attached I decided to give this thing a shot and now, 5 hours later, I finished my first shader, and I still can’t believe how easy that went
Anyhow, to show the results I got (yes, I implemented it in jME3 right away, how cool is that?), here are some pics:
Anime image on a box with black lines
Same anime image with colors leaking through
My avatar with different color settings for both lines and background
An image of a test game I was making with an early version of the filter applied
I think that when applied correctly with a nice cartoonedge filter and some anime style models we could very well create an anime style drawn effect, as shown in the first 2 pictures.As I doubt I will use this shader much myself, I sincerely hope this shader will be included in the jME packages as part of the standard library so it is of use to someone
Please test this shader on any system you have and report whether it works, I had 0,0 experience writing these things until this afternoon and it all seems so logical, I can’t believe it’s that simple… I’m sure I must have made ahuge mistake, just need to find it yet
The files I created:
CrossHatchFilter.java
[java]package shaders;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
/*
- A Post Processing filter that makes the screen look like it was drawn as
- diagonal lines with a pen.
- Try combining this with a cartoon edge filter to obtain manga style visuals.
*
- Based on an article from Geeks3D:
-
(Shader Library) Crosshatching GLSL Filter | Geeks3D
*
-
@author: Roy Straver a.k.a. Baal Garnaal
/
public class CrossHatchFilter extends Filter {
private ColorRGBA lineColor = ColorRGBA.Black.clone();
private ColorRGBA paperColor = ColorRGBA.White.clone();
private float colorInfluenceLine = 0.0f;
private float colorInfluencePaper = 0.0f;
private float fillValue = 0.9f;
private float luminance1 = 0.9f;
private float luminance2 = 0.7f;
private float luminance3 = 0.5f;
private float luminance4 = 0.3f;
private float luminance5 = 0.0f;
private int lineThickness = 1;
private int lineDistance = 4;
public CrossHatchFilter() {
super("CrossHatchFilter");
}
public CrossHatchFilter(ColorRGBA lineColor, ColorRGBA paperColor) {
this();
this.lineColor = lineColor;
this.paperColor = paperColor;
}
@Override
public boolean isRequiresDepthTexture() {
return false;
}
@Override
public void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
material = new Material(manager, "/MatDefs/Post/CrossHatch.j3md");
material.setColor("LineColor", lineColor);
material.setColor("PaperColor", paperColor);
material.setFloat("ColorInfluenceLine", colorInfluenceLine);
material.setFloat("ColorInfluencePaper", colorInfluencePaper);
material.setFloat("FillValue", fillValue);
material.setFloat("Luminance1", luminance1);
material.setFloat("Luminance2", luminance2);
material.setFloat("Luminance3", luminance3);
material.setFloat("Luminance4", luminance4);
material.setFloat("Luminance5", luminance5);
material.setInt("LineThickness", lineThickness);
material.setInt("LineDistance", lineDistance);
}
@Override
public Material getMaterial() {
return material;
}
@Override
public void preRender(RenderManager renderManager, ViewPort viewPort) {
}
@Override
public void cleanUpFilter(Renderer r) {
}
/
- Sets color used to draw lines
/
public void setLineColor(ColorRGBA lineColor){
this.lineColor = lineColor;
if (material != null)
material.setColor("LineColor", lineColor);
}
/
- Sets color used as background
/
public void setPaperColor(ColorRGBA paperColor){
this.paperColor = paperColor;
if (material != null)
material.setColor("PaperColor", paperColor);
}
/
- Sets color influence of original image on lines drawn
/
public void setColorInfluenceLine(float colorInfluenceLine){
this.colorInfluenceLine = colorInfluenceLine;
if (material != null)
material.setFloat("ColorInfluenceLine", colorInfluenceLine);
}
/
- Sets color influence of original image on non-line areas
/
public void setColorInfluencePaper(float colorInfluencePaper){
this.colorInfluencePaper = colorInfluencePaper;
if (material != null)
material.setFloat("ColorInfluencePaper", colorInfluencePaper);
}
/
- Sets line/paper color ratio for areas with values < luminance5,
- really dark areas get no lines but a filled blob instead
/
public void setFillValue(float fillValue){
this.fillValue = fillValue;
if (material != null)
material.setFloat("FillValue", fillValue);
}
/
- Sets minimum luminance levels for lines drawn
- Luminance1: Top-left to down right 1
- Luminance2: Top-right to bottom left 1
- Luminance3: Top-left to down right 2
- Luminance4: Top-right to bottom left 2
- Luminance5: Blobs
/
public void setLuminanceLevels(float luminance1, float luminance2, float luminance3, float luminance4, float luminance5){
this.luminance1 = luminance1;
this.luminance2 = luminance2;
this.luminance3 = luminance3;
this.luminance4 = luminance4;
this.luminance5 = luminance5;
if (material != null){
material.setFloat("Luminance1", luminance1);
material.setFloat("Luminance2", luminance2);
material.setFloat("Luminance3", luminance3);
material.setFloat("Luminance4", luminance4);
material.setFloat("Luminance5", luminance5);
}
}
/
- Sets the thickness of lines drawn
/
public void setLineThickness(int lineThickness){
this.lineThickness = lineThickness;
if (material != null)
material.setInt("LineThickness", lineThickness);
}
/
- Sets minimum distance between lines drawn
- Primary lines are drawn at 2*lineDistance
- Secondary lines are drawn at lineDistance
/
public void setLineDistance(int lineDistance){
this.lineDistance = lineDistance;
if (material != null)
material.setInt("LineDistance", lineDistance);
}
/
- Returns line color
/
public ColorRGBA getLineColor() {
return lineColor;
}
/
- Returns paper background color
/
public ColorRGBA getPaperColor() {
return paperColor;
}
/
- Returns current influence of image colors on lines
/
public float getColorInfluenceLine(){
return colorInfluenceLine;
}
/
- Returns current influence of image colors on paper background
/
public float getColorInfluencePaper(){
return colorInfluencePaper;
}
/
- Returns line/paper color ratio for blobs
/
public float getFillValue() {
return fillValue;
}
/
- Returns the thickness of the lines drawn
/
public int getLineThickness() {
return lineThickness;
}
/
- Returns minimum distance between lines
/
public int getLineDistance() {
return lineDistance;
}
/
- Returns treshold for lines 1
/
public float getLuminance1() {
return luminance1;
}
/
- Returns treshold for lines 2
/
public float getLuminance2() {
return luminance2;
}
/
- Returns treshold for lines 3
/
public float getLuminance3() {
return luminance3;
}
/
- Returns treshold for lines 4
/
public float getLuminance4() {
return luminance4;
}
/
- Returns treshold for blobs
/
public float getLuminance5() {
return luminance5;
}
}[/java]
CrossHatch.jm3d
[java]MaterialDef CrossHatch {
MaterialParameters {
Int NumSamples
Texture2D Texture;
Vector4 LineColor;
Vector4 PaperColor;
Float ColorInfluenceLine;
Float ColorInfluencePaper;
Float FillValue;
Float Luminance1;
Float Luminance2;
Float Luminance3;
Float Luminance4;
Float Luminance5;
Int LineThickness;
Int LineDistance;
}
Technique {
VertexShader GLSL150: Common/MatDefs/Post/Post15.vert
FragmentShader GLSL150: MatDefs/Post/CrossHatch15.frag
WorldParameters {
WorldViewProjectionMatrix
}
}
Technique {
VertexShader GLSL100: Common/MatDefs/Post/Post.vert
FragmentShader GLSL100: MatDefs/Post/CrossHatch.frag
WorldParameters {
WorldViewProjectionMatrix
}
}
Technique FixedFunc {
}
}[/java]
CrossHatch15.frag
[java]#import "Common/ShaderLib/MultiSample.glsllib"
uniform COLORTEXTURE m_Texture;
in vec2 texCoord;
uniform vec4 m_LineColor;
uniform vec4 m_PaperColor;
uniform float m_ColorInfluenceLine;
uniform float m_ColorInfluencePaper;
uniform float m_FillValue;
uniform float m_Luminance1;
uniform float m_Luminance2;
uniform float m_Luminance3;
uniform float m_Luminance4;
uniform float m_Luminance5;
uniform int m_LineDistance;
uniform int m_LineThickness;
void main() {
vec4 texVal = getColor(m_Texture, texCoord);
float linePixel = 0;
float lum = texVal.r0.2126 + texVal.g0.7152 + texVal.b0.0722;
if (lum < m_Luminance1){
if (mod(gl_FragCoord.x + gl_FragCoord.y, m_LineDistance * 2) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance2){
if (mod(gl_FragCoord.x - gl_FragCoord.y, m_LineDistance * 2) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance3){
if (mod(gl_FragCoord.x + gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance4){
if (mod(gl_FragCoord.x - gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance5){ // No line, make a blob instead
linePixel = m_FillValue;
}
// Mix line color with existing color information
vec4 lineColor = mix(m_LineColor, texVal, m_ColorInfluenceLine);
// Mix paper color with existing color information
vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper);
gl_FragColor = mix(paperColor, lineColor, linePixel);
}[/java]
CrossHatch.frag
[java]uniform sampler2D m_Texture;
varying vec2 texCoord;
uniform vec4 m_LineColor;
uniform vec4 m_PaperColor;
uniform float m_ColorInfluenceLine;
uniform float m_ColorInfluencePaper;
uniform float m_FillValue;
uniform float m_Luminance1;
uniform float m_Luminance2;
uniform float m_Luminance3;
uniform float m_Luminance4;
uniform float m_Luminance5;
uniform int m_LineDistance;
uniform int m_LineThickness;
void main() {
vec4 texVal = texture2D(m_Texture, texCoord);
float linePixel = 0;
float lum = texVal.r0.2126 + texVal.g0.7152 + texVal.b*0.0722;
if (lum < m_Luminance1){
if (mod(gl_FragCoord.x + gl_FragCoord.y, m_LineDistance * 2) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance2){
if (mod(gl_FragCoord.x - gl_FragCoord.y, m_LineDistance * 2) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance3){
if (mod(gl_FragCoord.x + gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance4){
if (mod(gl_FragCoord.x - gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)
linePixel = 1;
}
if (lum < m_Luminance5){ // No line, make a blob instead
linePixel = m_FillValue;
}
// Mix line color with existing color information
vec4 lineColor = mix(m_LineColor, texVal, m_ColorInfluenceLine);
// Mix paper color with existing color information
vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper);
gl_FragColor = mix(paperColor, lineColor, linePixel);
}[/java]
Update: Added lineThickness option and now updates shader after initialization wehn changes setters are used