------------------------------------------------------------------------
Update: Added at the end Bleach Bypass post process effect
Update: Added Cross Processing Post Process effect at page 4.
------------------------------------------------------------------------
Greetings all,
Introduction
One of the most challenging things I personally have faced with learning the UDK (apart from Unrealscript) is porting nice shaders I see around in the internet to the UDK. I started practicing couple of days back and achieved quite good results. The aim of this tutorial is not just showing how to implement toon shading, but rather learning how to port a shader to the material editor so when you come across some nice shader and the source is available, you should be able to have it as an asset.
Also note that not every shader is portable to the material editor. It has to be compatible with SM3.
The Shader
I'm going to present a snippet from the HLSL shader that we'll work with. For the purposes of this tutorial, we are only interested in the pixel shader which basically has what we want.
First, we need to understand few things that will be seen alot when working with shaders such as:Code:float4 PixelShader(float2 Tex: TEXCOORD0,float3 L: TEXCOORD1, float3 N: TEXCOORD2) : COLOR { // Calculate normal diffuse light but use Tex.x as color in stead. float4 Color = tex2D(ColorMapSampler, Tex); float Ai = 0.8f; float4 Ac = float4(0.075, 0.075, 0.2, 1.0); float Di = 1.0f; float4 Dc = float4(1.0, 1.0, 1.0, 1.0); Tex.y = 0.0f; Tex.x = saturate(dot(L, N)); float4 CelColor = tex2D(CelMapSampler, Tex); return (Ai*Ac*Color)+(Color*Di*CelColor); }
1- L is the Light Vector
2- N is usually the surface normal (normal map) unless stated otherwise.
3- Tex is the UV coordinates of a texture.
4- tex2D(Sampler, Tex): Think of this as a Texture Sample node where Sampler is the texture and Tex is the UV input in the back.
5- V is usually the view vector (camera vector) unless stated otherwise.
6- Saturate() is a function which clamps between 0,1. Exactly like the Clamp node.
It is very important to understand what each variable means/refers to. For example, I didn't how to "translate" Saturate() to the material editor's "language". After quick research I found that it's the Clamp node. Once the picture is clear to you, you can start the porting process. Let's analyze the above segment by segment:
Create a Texture Sample node containing the color map texture (Diffuse).Code:float4 Color = tex2D(ColorMapSampler, Tex);
Create 2 constants, Ai, and Di and assign them with the values you see above. Create another couple of 3d-constants and assign their respective values. I tried creating 4d-constants, but it gave me arithmetic errors while doing the calculations because of the 4th channel. So I resolved this by using 3d-constants only. Try to mimic the code as much as possible, or find alternatives. If it looks fine, then you're safe.Code:float Ai = 0.8f; float4 Ac = float4(0.075, 0.075, 0.2, 1.0); float Di = 1.0f; float4 Dc = float4(1.0, 1.0, 1.0, 1.0);
Create a Light Vector, Texture Sample (having the normal map), and plug them to a DotProduct node. Connect the output to a Clamp node. (1)Code:Tex.y = 0.0f; Tex.x = saturate(dot(L, N));
Create a constant and assign it to zero. (2)
Create an Append vector and connect (1) and (2) to it. (3)
Create a TextureCoordinate node, plug it to a Multiply node. Use (3) to connect it with the same Multiply node. Connect the output to the input of the cel sampler texture.
What I did here is basically is calculate whatever the Saturate function has, and appended it to a zero value to represent a 2-d vector which should be assigned as a texture coordinate to the cel sampler texture. Now all you have to do is multiply this vector with a TextCoord having both U and V set to 1.0. TextCoord doesn't have an input, so I had to do it this way in order to modify the UV values according to whatever I want and avoid hardcoding.
Create a TextureSample node having this:Code:float4 CelColor = tex2D(CelMapSampler, Tex);
![]()
This is the Cel Sampler. If you want to know why this texture is used, please refer to my references. Download it from the attachments.
Last step. Multiply the constants on the left with the diffuse texture. Then add it up to Diffiuse X Di X Image Provided. Pretty straightforward.Code:return (Ai*Ac*Color)+(Color*Di*CelColor);
Connect the final output to "Custom Lighting" and again to "Custom Lighting Diffuse". Make sure that you set the Lighting Model in the properties to MLM_Custom. Other minor stuff need to be done, all illustrated here along with everything we've done so far:
You can mess around with the values in the constants. In the above, I set the Di to a value of 10 and it felt right to me. Suit yourself.
Bonus: Edge Detection
Edge detection is a nice way to spice up your toon-shaded scene. I found a nice tutorial that shows how to do that as a post process effect, did some very small modifications and the outcome was good. Create a new material having this:
You need to plug this material in a postprocess chain to be able to view it. You can make a copy from the default one, assign the material in the Material Effect node, check "Show in Editor" for testing. In the World Properties, find an option named "World Post process chain" (or something similar) and assign it with the copy you created.
Play with the constants in the shader and check out how it looks.
Results
A BSP cube and some UDK stock content to put some love on:
Built with Lightmass.![]()
Conclusion
I hope you guys find this useful. If you have/saw any shaders with source code, post them here and let's all learn how to port to UDK. This would be a good practice. If this tutorial helped you doing some nice shaders, post pics and show us your work.
Have fun.
References
1- Petri Wilhelmsen's Toon Shading Tutorial for XNA
2- Dave Prout's Edge Detection Tutorial
-----------------
* NEW *
-----------------
Exercise: Bleach Bypass Post-Process Effect
Opacity in the above set to 0.8
Analyze the following HLSL code segment (source):
Port the above to the material editor, and since this is a post process effect, don't forget to:Code:float4 bypassPS(QuadVertexOutput IN, uniform sampler2D SceneSampler) : COLOR { float4 base = tex2D(SceneSampler, IN.UV); float3 lumCoeff = float3(0.25,0.65,0.1); float lum = dot(lumCoeff,base.rgb); float3 blend = lum.rrr; float L = min(1,max(0,10*(lum- 0.45))); float3 result1 = 2.0f * base.rgb * blend; float3 result2 = 1.0f - 2.0f*(1.0f-blend)*(1.0f-base.rgb); float3 newColor = lerp(result1,result2,L); float A2 = Opacity * base.a; float3 mixRGB = A2 * newColor.rgb; mixRGB += ((1.0f-A2) * base.rgb); return float4(mixRGB,base.a); }
0. DO NOT forget to tick that damn checkbox in all SceneTexture nodes. (It'll make you bang your head on the keyboard)
1. Set the Blend Mode to Transluecent
2. Set the Lighting Model to Unlit.
3. Connect the final node to the Emmisive channel.
4- Use the material in a post-process chain in order to test this.
Answer: (If you are too lazy to try doing it yourself)
Create a material instance out of that and play with the Opacity parameter (0 to 1).
I hope you find this useful and wish to see some of you guys trying out porting nice shaders to UDK.![]()











Reply With Quote







.


Bookmarks