EDIT #2:
Re uploaded the download link. After 30 days of download inactivity the download is automatically deleted.
If anyone tries the download link and it doesn't work. Send me a PM or post a message and I'll try re uploading the demo again.
Toon Shader Demo
Even though I've changed this one a lot. Here's the link to the original shader. Just in case someone wants to start over with the original.
http://www.ogre3d.org/tikiwiki/tiki-ind ... CelShading
Here's a quick list of the changes.
1.The original example had 7 files. There are now only 3 files that are required.
2.You also had to copy several small images and save them to the necessary folders. Now the images are created and saved to PureBasic's 3D texture folder.
3.I changed the old material script to a new easier setup.
4.I had a request to make the toons receive light from the camera position instead of the light position. Which is now possible and I believe it's bug free.
It's real simple and I'll explain how when we get to that part of the code. By default it still works of off the light position and not the camera position.
OK, as always this shader requires NVIDIA's Cg-toolkit to be installed on your computer. Here's the link to the toolkit information.
https://developer.nvidia.com/cg-toolkit
The download link is at the bottom of that page.
If you intend to release your apps with this shader. Make sure you include the cg.dll with your app.
Here's a new picture of the shader in action. You'll probably notice that graphically not much has changed from the last version. It's just a lot easier to work with.

Now lets take care of the new files. There's a cg, program, and material file that need to be saved to the same location for later use.
Also make sure they are saved exactly as stated. So, the shader can find them.
EDIT:
I forgot to mention that you can save these files in pretty much any text editor. I personally use Notepad. Just be sure that the files have the proper extension
and not .txt or something similar.
First is the cg file.
Toon.CG
Code: Select all
//
// The maximum number of lights per-object. Decrease this value if you need some extra performance.
//
// If you change this, then you must enter 'Toon.program' and change the '$numLights'
// variable to match this definition.
#define NUM_LIGHTS 6
//
// Vertex program
float4 main_vp(
float4 iPos : POSITION,
float3 iNorm : NORMAL,
float2 iUV : TEXCOORD0,
out float3 oPos : TEXCOORD0,
out float3 oNorm : TEXCOORD1,
out float2 oUV : TEXCOORD2,
uniform float4x4 worldViewProj ) : POSITION
{
oPos = iPos.xyz;
oNorm = iNorm;
oUV = iUV;
return mul(worldViewProj, iPos);
}
//
// Decal + Specular Mapping fragment program
float4 mainDecalSpec_fp(
float3 iPos : TEXCOORD0,
float3 iNorm : TEXCOORD1,
float3 iUV : TEXCOORD2,
uniform float3 eyePosition,
uniform float4 ambientColor,
uniform float4 diffuseColor,
uniform float4 specularColor,
uniform float4 emissiveColor,
uniform float shininess,
uniform float3 ambientLight,
// Light params
uniform float4 lightDiffuse[NUM_LIGHTS],
uniform float4 lightSpecular[NUM_LIGHTS],
uniform float4 lightPosition[NUM_LIGHTS],
uniform float4 lightAttenuation[NUM_LIGHTS],
// Texture params
uniform sampler1D diffuseRamp : register(s0),
uniform sampler1D specRamp : register(s1),
uniform sampler2D decalTex : register(s2),
uniform sampler2D specMap : register(s3)) : COLOR
{
float4 surfaceColor = float4(ambientLight,1)*ambientColor;
float4 specularSurface = 0;
float4 texColor = tex2D(decalTex, iUV);
float4 specMapColor = tex2D(specMap, iUV);
for (int i = 0; i < NUM_LIGHTS; i++)
{
float3 norm = normalize(iNorm);
//
// Calculate light vector and distance
float3 lightVec = 0;
if (lightPosition[i].w == 1)
lightVec = lightPosition[i].xyz - iPos*lightPosition[i].w;
else
lightVec = lightPosition[i].xyz;
float lightDist = length(lightVec);
lightVec = normalize(lightVec);
//
// Calculate the direction in-between the light direction and the camera's direction.
float3 eyeVec = normalize(eyePosition - iPos);
float3 halfDir = normalize(lightVec + eyeVec);
//
// Calculate luminosity based on light attenuation
float luminosity = 1.f;
if(lightAttenuation[i].x > lightDist && lightPosition[i].w == 1)
luminosity = 1.f /
( lightAttenuation[i].y + lightAttenuation[i].z*lightDist +
lightAttenuation[i].w*(lightDist*lightDist) );
//
// Get diffuse component based on the dot product of the normal and the light direction,
// multiplied by the luminosity component.
float diffComponent = max(dot(norm, lightVec), 0)*luminosity;
//
// Get specular component based on the dot product of the normal and the half-camera-light
// direction, multiplied by the luminosity component.
float specComponent = pow(max(dot(norm, halfDir), 0), shininess)*luminosity;
float diffuse = tex1D(diffuseRamp, diffComponent).r;
float specular = tex1D(specRamp, specComponent).r;
surfaceColor += diffuse*diffuseColor*lightDiffuse[i];
specularSurface += specular*lightSpecular[i]*specularColor;
}
return texColor*(emissiveColor + surfaceColor) + specMapColor*specularSurface;
}
void EdgeVP(
float4 position : POSITION,
float4 normal : NORMAL0,
out float4 oPos : POSITION,
out float4 oColor : COLOR,
uniform float4 edgeColor,
uniform float4 eyePosition,
uniform float sinkScale,
uniform float edgeScale,
uniform float4x4 worldViewProj )
{
float4 E = normalize(eyePosition);
position = mul(worldViewProj, position - sinkScale*E);
normal.w = 0;
normal = normalize(mul(worldViewProj, normal));
position += ((sinkScale/8.0f)+1.0) * edgeScale * float4(normal.xy, 0, 0);
oPos=position;
oColor=edgeColor;
}
Next is the program file. If you wish to swap the light to the camera position. This is the file to do it in otherwise just leave it as is.
Toon.PROGRAM
Code: Select all
// The maximum number of lights per-object (for now we can go no higher than 6). Decrease this value if you need some extra performance.
//
// If you change this, then you must enter 'Toon.cg' and change the 'NUM_LIGHTS' definition to match this variable.
set $numLights 6
vertex_program CelShadingVP cg
{
source Toon.cg
entry_point main_vp
profiles vs_1_1 arbvp1
default_params
{
param_named_auto worldViewProj worldviewproj_matrix
}
}
fragment_program CelShadingDecalSpecFP cg
{
source Toon.cg
entry_point mainDecalSpec_fp
profiles ps_2_x arbfp1
default_params
{
param_named_auto eyePosition camera_position_object_space
param_named_auto ambientColor surface_ambient_colour
param_named_auto diffuseColor surface_diffuse_colour
param_named_auto specularColor surface_specular_colour
param_named_auto emissiveColor surface_emissive_colour
param_named_auto shininess surface_shininess
param_named_auto ambientLight ambient_light_colour
param_named_auto lightDiffuse light_diffuse_colour_array $numLights
param_named_auto lightSpecular light_specular_colour_array $numLights
//#####################################################################################################
//Alexi, Comment Out This Next Parameter And Uncomment The One Below It To Swap From Light To Camera Position.
param_named_auto lightPosition light_position_object_space_array $numLights
//param_named_auto lightPosition camera_position_object_space $numLights
//#####################################################################################################
param_named_auto lightAttenuation light_attenuation_array $numLights
}
}
vertex_program OutlineExperimentVP cg
{
source Toon.cg
entry_point EdgeVP
profiles vs_1_1 arbvp1
default_params
{
param_named_auto eyePosition camera_position_object_space
param_named_auto worldViewProj worldViewProj_matrix
param_named_auto edgeColor surface_diffuse_colour
}
}
Code: Select all
//#####################################################################################################
//Alexi, Comment Out This Next Parameter And Uncomment The One Below It To Swap From Light To Camera Position.
param_named_auto lightPosition light_position_object_space_array $numLights
//param_named_auto lightPosition camera_position_object_space $numLights
//#####################################################################################################
Code: Select all
//#####################################################################################################
//Alexi, Comment Out This Next Parameter And Uncomment The One Below It To Swap From Light To Camera Position.
//param_named_auto lightPosition light_position_object_space_array $numLights
param_named_auto lightPosition camera_position_object_space $numLights
//#####################################################################################################
Otherwise leave the program file alone.
The Material file.
Toon.MATERIAL
Code: Select all
abstract material TemplateToon
{
technique
{
pass
{
specular 1 1 1 256
vertex_program_ref CelShadingVP {}
fragment_program_ref CelShadingDecalSpecFP {}
texture_unit diffRamp {
texture cel_shading_diffuse.png 1d
tex_address_mode clamp }
texture_unit specRamp {
texture cel_shading_specular.png 1d
tex_address_mode clamp }
texture_unit decal {
texture $Texture }
texture_unit specMap {
texture $SpecMap }
}
pass
{
vertex_program_ref OutlineExperimentVP
{
param_named edgeColor float4 $outlinecolor
param_named edgeScale float $edgeScale
param_named sinkScale float $sinkScale
}
}
}
}
material SinbadToon : TemplateToon
{
set $outlinecolor "0 0.2 0"
set $edgeScale 0.1
set $sinkScale 1
set $Texture darkgreen.png
set $SpecMap darkgreen.png
}
material PureBasicToon : TemplateToon
{
set $outlinecolor "0 0 0"
set $edgeScale 0.05
set $sinkScale 3
set $Texture lightred.png
set $SpecMap lightred.png
}
It's pretty simple to use. You'll add the following code for every material you create.
Don't use this exact code because of the comments. It's just to give you the general idea.
Code: Select all
material SinbadToon : TemplateToon
{
set $outlinecolor "0 0.2 0"
set $edgeScale 0.1
set $sinkScale 1
set $Texture darkgreen.png
set $SpecMap darkgreen.png
}
#### Let us break down each one of those steps ####
First we have [material SinbadToon : TemplateToon]. The first part [SinbadToon] is the name of the material that we call in PureBasic using GetScriptMaterial(#MaterialHandle,"SinbadToon"). The second part [TemplateToon] is the actual material script that will use the variables that we assign next.
Our first variable [$outlinecolor] sets the outline to a dark green color ["0 0.2 0"]. If you could use RGB values it would be RGB(0,51,0).
Next two are [$edgeScale] and [$sinkScale]. These both have to do with the outline size. You'll have to experiment with these values. Because most meshes need different values depending of the shape of it. You can try 1 for both values at first and then adjust them a bit to fit your needs.
Now we have [$Texture darkgreen.png]. This is the texture that your entity will wear. It's pretty self explanatory.
Last we have [$SpecMap darkgreen.png]. This texture is for picking what part of the texture will show specular lighting. In my case I use darkgreen.png for both the texture and the specular. A true spec map is in gray scale. The lighter the gray the more specular and the darker the gray the less specular it will show. So, for example with a spec map you could create eyes that show specular, but clothing that does not. While all on the same texture.
You'll have to set the path to the 3 files you previously saved.
Also, there are 5 textures that this code will create and then save. For this example you can leave them where they were saved. For your own examples and apps you'll have to include these three images and whatever entity textures you use.
The three images needed are
cel_shading_diffuse.png
cel_shading_edge.png
cel_shading_specular.png
Code: Select all
;#### You Must Enable CG For The Engine When Using A CG Shader ####
InitEngine3D(#PB_Engine3D_EnableCG | #PB_Engine3D_DebugLog)
InitSprite()
InitKeyboard()
InitMouse()
ExamineDesktops()
Width=DesktopWidth(0)
Height=DesktopHeight(0)
R.f = 0
Move.f = 0.025
Enumeration
#Window
#CreateImage
#PlaneTexture
#PlaneMaterial
#SinbadMaterial
#PureBasicMaterial
#SinbadMesh
#PureBasicMesh
#PlaneMesh
#Sinbad
#PureBasic1
#PureBasic2
#PureBasic3
#Plane
#Camera
#Light1
EndEnumeration
UsePNGImageDecoder()
UsePNGImageEncoder()
UseJPEGImageDecoder()
Declare BuildTextures()
;#### Build The Required Textures For This Shader. Will Save Them In PureBasic's 3D Texture Folder. ####
BuildTextures()
If OpenWindow(#Window, 0, 0, Width, Height, "Cel or Toon Shader")
;#### Comment Out Antialiasing Mode If Slow ####
AntialiasingMode(#PB_AntialiasingMode_x6)
If OpenWindowedScreen(WindowID(#Window), 0, 0, Width, Height, 0, 0, 0)
;#### Set This Archive To The Location Of The Shader Files ####
Add3DArchive("C:\Toon Shader Files", #PB_3DArchive_FileSystem)
;############################################
Add3DArchive(#PB_Compiler_Home+"\Examples\3D\Data\Packs\Sinbad.zip",#PB_3DArchive_Zip)
Add3DArchive(#PB_Compiler_Home+"\Examples\3D\Data\Models",#PB_3DArchive_FileSystem)
;#### Archive To The 5 Previously Saved Textures ####
Add3DArchive(#PB_Compiler_Home+"\Examples\3D\Data\Textures",#PB_3DArchive_FileSystem)
;######################################
Parse3DScripts()
;#### Load Script Materials ####
GetScriptMaterial(#SinbadMaterial,"SinbadToon")
;#### Set Sinbad Material Shininess Size To 32 ####
MaterialShininess(#SinbadMaterial,32)
GetScriptMaterial(#PureBasicMaterial,"PureBasicToon")
;#### Turn Specular Lighting Off For PureBasic Material By Setting Specular Color To Black ####
SetMaterialColor(#PureBasicMaterial,#PB_Material_SpecularColor,RGB(0,0,0))
;#### Load Plane Texture ####
LoadTexture(#PlaneTexture,"grass.jpg")
CreateMaterial(#PlaneMaterial,TextureID(#PlaneTexture))
;#### Load Meshes and Create Entities ####
LoadMesh(#SinbadMesh,"Sinbad.mesh")
LoadMesh(#PureBasicMesh,"PureBasic.mesh")
CreatePlane(#PlaneMesh,100,100,1,1,5,5)
CreateEntity(#Plane,MeshID(#PlaneMesh),MaterialID(#PlaneMaterial),0,-5,0)
EntityRenderMode(#Plane,0)
CreateEntity(#Sinbad, MeshID(#SinbadMesh), MaterialID(#SinbadMaterial),0,0,0)
CreateEntity(#PureBasic1, MeshID(#PureBasicMesh), MaterialID(#PureBasicMaterial),5,0,0)
ScaleEntity(#PureBasic1,0.1,0.1,0.1,#PB_Absolute)
CreateEntity(#PureBasic2, MeshID(#PureBasicMesh), MaterialID(#PureBasicMaterial),-5,0,0)
ScaleEntity(#PureBasic2,0.1,0.1,0.1,#PB_Absolute)
CreateEntity(#PureBasic3, MeshID(#PureBasicMesh), MaterialID(#PureBasicMaterial),0,3,-5)
ScaleEntity(#PureBasic3,0.4,0.4,0.4,#PB_Absolute)
;#### Build Camera and Light ####
CreateCamera(#Camera, 0,0,100,100)
MoveCamera(#Camera, 7, 0, 25)
CameraLookAt(#Camera, 0, 0, 0)
CameraBackColor(#Camera, RGB(100, 100, 100))
CreateLight(#Light1, RGB( 0, 0, 0), 20, 20, 20)
SetLightColor(#Light1,#PB_Light_DiffuseColor,RGB(200,200,200))
SetLightColor(#Light1,#PB_Light_SpecularColor,RGB(0,255,0))
;#### Set Texture Additive Shadows. Quailty Must Not Be Higher Then 4096 ####
WorldShadows(#PB_Shadow_TextureAdditive, 50, RGB(100,100,100),4096)
;#### Start Sinbad's Idle Animation ####
StartEntityAnimation(#Sinbad, "IdleTop", #PB_EntityAnimation_Manual)
Repeat
;#### Camera Controls ####
If ExamineKeyboard()
If KeyboardPushed(#PB_Key_Up)
MoveCamera(#Camera, 0, 0, -0.2)
ElseIf KeyboardPushed(#PB_Key_Down)
MoveCamera(#Camera, 0, 0, 0.2)
EndIf
If KeyboardPushed(#PB_Key_Left)
MoveCamera(#Camera, -0.2, 0, 0)
CameraLookAt(#Camera, 0, 0, 0)
ElseIf KeyboardPushed(#PB_Key_Right)
MoveCamera(#Camera, 0.2, 0, 0)
CameraLookAt(#Camera, 0, 0, 0)
EndIf
If KeyboardReleased(#PB_Key_W)
CameraRenderMode(#Camera, #PB_Camera_Wireframe)
ElseIf KeyboardReleased(#PB_Key_E)
CameraRenderMode(#Camera, #PB_Camera_Textured)
EndIf
EndIf
;#### PureBasic Entity Rotation ####
R=R+Move
If R>4
Move=-0.025
ElseIf R<-4
Move=0.025
EndIf
RotateEntity(#PureBasic1,0,R,0,#PB_Relative)
RotateEntity(#PureBasic2,0,R*-1,0,#PB_Relative)
RotateEntity(#PureBasic3,0,0,R,#PB_Relative)
AddEntityAnimationTime(#Sinbad, "IdleTop", LastFrame * 2)
LastFrame=RenderWorld()
FlipBuffers()
Until KeyboardPushed(#PB_Key_Escape) Or WindowEvent() = #PB_Event_CloseWindow
EndIf
EndIf
End
Procedure BuildTextures()
CreateImage(#CreateImage,8,8,24,RGB(76,102,46))
SaveImage(#CreateImage,#PB_Compiler_Home+"\Examples\3D\Data\Textures\darkgreen.png",#PB_ImagePlugin_PNG)
CreateImage(#CreateImage,8,8,24,RGB(255,102,102))
SaveImage(#CreateImage,#PB_Compiler_Home+"\Examples\3D\Data\Textures\lightred.png",#PB_ImagePlugin_PNG)
CreateImage(#CreateImage,16,1,24,RGB(255,255,255))
StartDrawing(ImageOutput(#CreateImage))
Box(0,0,5,1,RGB(102,102,102))
StopDrawing()
SaveImage(#CreateImage,#PB_Compiler_Home+"\Examples\3D\Data\Textures\cel_shading_diffuse.png",#PB_ImagePlugin_PNG)
CreateImage(#CreateImage,16,1,24,RGB(255,255,255))
StartDrawing(ImageOutput(#CreateImage))
Box(0,0,5,1,RGB(0,0,0))
StopDrawing()
SaveImage(#CreateImage,#PB_Compiler_Home+"\Examples\3D\Data\Textures\cel_shading_edge.png",#PB_ImagePlugin_PNG)
SaveImage(#CreateImage,#PB_Compiler_Home+"\Examples\3D\Data\Textures\cel_shading_specular.png",#PB_ImagePlugin_PNG)
FreeImage(#CreateImage)
EndProcedure
EDIT:
One more thing and this is only directed towards people who edited the program file in order to swap the light to camera position.
You still need to have a light source somewhere in your world. Even though your now using the camera position you still need that light information in order for the graphics to be displayed properly. So, I'd just create a light that's pointing off into space or one that's out of the way.
If you need more info on this don't hesitate to ask.