Back to Content

Shader Programming in CopperCube


Since version 5, CopperCube supports creating own shaders, in order to create your own materials and effects. The system isn't very advanced, but enables to create basic extensions. This section gives a small overview on how it works. Note that shader programming isn't very easy, and assumes you are familiar with this complex topic.


The Shader system in CopperCube

Basically, you create shaders in CopperCube using the JavaScript API during runtime, using the functions ccbCreateMaterial and ccbSetShaderConstant, and set the newly created material using ccbSetSceneNodeMaterialProperty(). Shaders are platform specific, that means you write them in HLSL on Windows/D3D9, GLSL when using Windows/OpenGL or Mac/OpenGL, and GLSL ES in WebGL. On some platforms, shaders are not supported (see below). Below, you will find some examples on how to use this system, to give you a quick start.

Platform Specific Limits

For all platforms, there are some special technical limits you need to know:

Platform Shader language Comments
Windows, D3D9 HLSL the target for these shaders is always Pixel and Vertex Shader 3.0.
Windows, D3D8 - no shader programming supported on the D3D8 target
Windows and Mac OS X, OpenGL GLSL might not work on very, very old hardware with outdated drivers
WebGL GLSL ES should just work
Android GLSL ES not supported yet


Default constants set by the engine

For your convenience, the engine sets some default constants for the vertex shaders on Windows and Mac OS X so you can use them easily:
For WebGL, it offers similar features with different names:

Example codes

Here are some example shaders for beginning. Note that only the shader codes in the examples differ from platform to platform, the material creation code is always the same.


Windows / D3D9 / HLSL examples

This shows a simple vertex/pixel shader combination which basically just shows how to transform the vertex positions to be drawn correctly, and a fragment shader which brightens the colors a bit. It assumes the existance of a scene node with the name 'cubeMesh1':
var vertexShader = 
"float4x4 mWorldViewProj;  // World * View * Projection \n" + 
"float4x4 mInvWorld;       // Inverted world matrix	 	\n" + 
"float4x4 mTransWorld;     // Transposed world matrix	\n" + 
"														\n" + 
"// Vertex shader output structure						\n" + 
"struct VS_OUTPUT										\n" + 
"{														\n" + 
"	float4 Position   : POSITION;   // vertex position 	\n" + 
"	float4 Diffuse    : COLOR0;     // vertex diffuse 	\n" + 
"	float2 TexCoord   : TEXCOORD0;  // tex coords		\n" + 
"};														\n" + 
"														\n" + 
"VS_OUTPUT main      ( in float4 vPosition : POSITION,	\n" + 
"                      in float3 vNormal   : NORMAL,	\n" + 
"                      float2 texCoord     : TEXCOORD0 )\n" + 
"{														\n" + 
"	VS_OUTPUT Output;									\n" + 
"														\n" + 
"	// transform position to clip space 				\n" + 
"	Output.Position = mul(vPosition, mWorldViewProj);	\n" + 
"														\n" + 
"	// transformed normal would be this:				\n" + 
"	float3 normal = mul(vNormal, mInvWorld);			\n" + 
"														\n" + 
"	// position in world coodinates	would be this:		\n" + 
"	// float3 worldpos = mul(mTransWorld, vPosition);	\n" + 
"														\n" + 
"	Output.Diffuse = float4(1.0, 1.0, 1.0, 1.0);		\n" + 
"	Output.TexCoord = texCoord;							\n" + 
"														\n" + 
"	return Output;										\n" + 
"}														";

var fragmentShader = 
"struct PS_OUTPUT								\n" + 
"{												\n" + 
"    float4 RGBColor : COLOR0; 		  			\n" +	
"};												\n" +
"												\n" + 
"sampler2D tex0;								\n" + 
"												\n" +
"PS_OUTPUT main( float2 TexCoord : TEXCOORD0,	\n" +
"                float4 Position : POSITION,	\n" +
"                float4 Diffuse  : COLOR0 ) 	\n" +
"{ 												\n" +
"	PS_OUTPUT Output;							\n" +
"	float4 col = tex2D( tex0, TexCoord );  		\n" + 
"	Output.RGBColor = Diffuse * col;			\n" + 
"	Output.RGBColor *= 4.0;						\n" +
"	return Output;								\n" +
"}";


var newMaterial = ccbCreateMaterial(vertexShader, fragmentShader, 0, null);

var cube = ccbGetSceneNodeFromName('cubeMesh1');
ccbSetSceneNodeMaterialProperty(cube, 0, 'Type', newMaterial);

This second example is an extended version of the first one, using the ccbSetShaderConstant() function, making the material go from bright to dark every half second, by setting a pixel shader constant, which gets multiplied in the pixel shader with the color of each pixel:
var vertexShader = 
"float4x4 mWorldViewProj;  // World * View * Projection \n" + 
"float4x4 mInvWorld;       // Inverted world matrix	 	\n" + 
"float4x4 mTransWorld;     // Transposed world matrix	\n" + 
"														\n" + 
"// Vertex shader output structure						\n" + 
"struct VS_OUTPUT										\n" + 
"{														\n" + 
"	float4 Position   : POSITION;   // vertex position 	\n" + 
"	float4 Diffuse    : COLOR0;     // vertex diffuse 	\n" + 
"	float2 TexCoord   : TEXCOORD0;  // tex coords		\n" + 
"};														\n" + 
"														\n" + 
"VS_OUTPUT main      ( in float4 vPosition : POSITION,	\n" + 
"                      in float3 vNormal   : NORMAL,	\n" + 
"                      float2 texCoord     : TEXCOORD0 )\n" + 
"{														\n" + 
"	VS_OUTPUT Output;									\n" + 
"														\n" + 
"	// transform position to clip space 				\n" + 
"	Output.Position = mul(vPosition, mWorldViewProj);	\n" + 
"														\n" + 
"	// transformed normal would be this:				\n" + 
"	float3 normal = mul(vNormal, mInvWorld);			\n" + 
"														\n" + 
"	// position in world coodinates	would be this:		\n" + 
"	// float3 worldpos = mul(mTransWorld, vPosition);	\n" + 
"														\n" + 
"	Output.Diffuse = float4(1.0, 1.0, 1.0, 1.0);		\n" + 
"	Output.TexCoord = texCoord;							\n" + 
"														\n" + 
"	return Output;										\n" + 
"}														";

var fragmentShader = 
"struct PS_OUTPUT								\n" + 
"{												\n" + 
"    float4 RGBColor : COLOR0; 		  			\n" +	
"};												\n" +
"												\n" + 
"float4 pulse;									\n" +
"sampler2D tex0;								\n" + 
"												\n" +
"PS_OUTPUT main( float2 TexCoord : TEXCOORD0,	\n" +
"                float4 Position : POSITION,	\n" +
"                float4 Diffuse  : COLOR0 ) 	\n" +
"{ 												\n" +
"	PS_OUTPUT Output;							\n" +
"	float4 col = tex2D( tex0, TexCoord );  		\n" + 
"	Output.RGBColor = pulse * col;				\n" + 
"	return Output;								\n" +
"}";

myShaderCallBack = function()
{
	var time = new Date().getTime();
	var pulse = (time % 500) / 500.0;
	ccbSetShaderConstant(2, 'pulse', pulse, pulse, 0, pulse);
}

var newMaterial = ccbCreateMaterial(vertexShader, fragmentShader, 0, myShaderCallBack);

var cube = ccbGetSceneNodeFromName('cubeMesh1');
ccbSetSceneNodeMaterialProperty(cube, 0, 'Type', newMaterial);


Windows and Mac OS X OpenGL / GLSL examples

This shows a simple vertex/fragment program combination which basically just shows how to transform the vertex positions to be drawn correctly, and a fragment shader which brightens the colors a bit. It assumes the existance of a scene node with the name 'cubeMesh1':
var vertexShader = 									
"uniform mat4 mWorldViewProj;						\n" +
"uniform mat4 mInvWorld;							\n" +
"uniform mat4 mTransWorld;							\n" +
"													\n" +
"void main(void)									\n" +
"{													\n" +
"	gl_Position = mWorldViewProj * gl_Vertex;		\n" +
"													\n" +
"	// normal would be this:						\n" +
"	vec4 normal = vec4(gl_Normal, 0.0);				\n" +
"	normal = mInvWorld * normal;					\n" +
"	normal = normalize(normal);						\n" +
"													\n" +
"	// world position would be this:				\n" +
"	vec4 worldpos = gl_Vertex * mTransWorld;		\n" +
"													\n" +
"	gl_FrontColor = gl_BackColor = vec4(1.0, 1.0, 1.0, 0.0);	\n" +
"													\n" +
"	gl_TexCoord[0] = gl_MultiTexCoord0;				\n" +
"}";


var fragmentShader = 
"uniform sampler2D myTexture;							\n" +
"														\n" +
"void main (void)										\n" +
"{														\n" +
"    vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));\n" +
"    col *= gl_Color;									\n" +
"    gl_FragColor = col;								\n" +
"}";


var newMaterial = ccbCreateMaterial(vertexShader, fragmentShader, 0, null);

var cube = ccbGetSceneNodeFromName('cubeMesh1');
ccbSetSceneNodeMaterialProperty(cube, 0, 'Type', newMaterial);

This second example is an extended version of the first one, using the ccbSetShaderConstant() function, making the material go from bright to dark every half second, by setting a pixel shader constant, which gets multiplied in the pixel shader with the color of each pixel:
var vertexShader = 									
"uniform mat4 mWorldViewProj;						\n" +
"uniform mat4 mInvWorld;							\n" +
"uniform mat4 mTransWorld;							\n" +
"													\n" +
"void main(void)									\n" +
"{													\n" +
"	gl_Position = mWorldViewProj * gl_Vertex;		\n" +
"													\n" +
"	// normal would be this:						\n" +
"	vec4 normal = vec4(gl_Normal, 0.0);				\n" +
"	normal = mInvWorld * normal;					\n" +
"	normal = normalize(normal);						\n" +
"													\n" +
"	// world position would be this:				\n" +
"	vec4 worldpos = gl_Vertex * mTransWorld;		\n" +
"													\n" +
"	gl_FrontColor = gl_BackColor = vec4(1.0, 1.0, 1.0, 0.0);	\n" +
"													\n" +
"	gl_TexCoord[0] = gl_MultiTexCoord0;				\n" +
"}";


var fragmentShader = 
"uniform sampler2D myTexture;							\n" +
"uniform vec4 pulse;									\n" +
"														\n" +
"void main (void)										\n" +
"{														\n" +
"    vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));\n" +
"    col *= gl_Color;									\n" +
"    gl_FragColor = col * pulse;						\n" +
"}";

myShaderCallBack = function()
{
	var time = new Date().getTime();
	var pulse = (time % 500) / 500.0;
	ccbSetShaderConstant(2, 'pulse', pulse, pulse, 0, pulse);
}

var newMaterial = ccbCreateMaterial(vertexShader, fragmentShader, 0, myShaderCallBack);

var cube = ccbGetSceneNodeFromName('cubeMesh1');
ccbSetSceneNodeMaterialProperty(cube, 0, 'Type', newMaterial);


WebGL / GLSL examples

This shows a simple vertex/fragment program combination which basically just shows how to transform the vertex positions to be drawn correctly, and a fragment shader which brightens the colors a bit. It assumes the existance of a scene node with the name 'cubeMesh1':
var vertexShader = 									
"uniform mat4 worldviewproj;								\n" +
"															\n" +
"attribute vec4 vPosition;									\n" +
"attribute vec4 vNormal;									\n" +
"attribute vec4 vColor;										\n" +
"attribute vec2 vTexCoord1;									\n" +
"attribute vec2 vTexCoord2;									\n" +
"															\n" +
"varying vec4 v_color;										\n" +
"varying vec2 v_texCoord1;									\n" +
"varying vec2 v_texCoord2;									\n" +
"															\n" +
"void main()												\n" +
"{															\n" +
"	v_color = vColor;										\n" +
"	gl_Position = worldviewproj * vPosition;				\n" +
"	v_texCoord1 = vTexCoord1.st;							\n" +
"	v_texCoord2 = vTexCoord2.st;							\n" +
"}															";

var fragmentShader = 
"uniform sampler2D texture1;								\n" +
"uniform sampler2D texture2;								\n" +
"															\n" +
"varying vec2 v_texCoord1;									\n" +
"varying vec2 v_texCoord2;									\n" +
"															\n" +
"void main()												\n" +
"{															\n" +
"	vec2 texCoord = vec2(v_texCoord1.s, v_texCoord1.t);		\n" +
"	gl_FragColor = texture2D(texture1, texCoord) * 2.0;		\n" +
"}															\n";


var newMaterial = ccbCreateMaterial(vertexShader, fragmentShader, 0, null);

var cube = ccbGetSceneNodeFromName('cubeMesh1');
ccbSetSceneNodeMaterialProperty(cube, 0, 'Type', newMaterial);

This second example is an extended version of the first one, using the ccbSetShaderConstant() function, making the material go from bright to dark every half second, by setting a pixel shader constant, which gets multiplied in the pixel shader with the color of each pixel:
var vertexShader = 									
"uniform mat4 worldviewproj;								\n" +
"															\n" +
"attribute vec4 vPosition;									\n" +
"attribute vec4 vNormal;									\n" +
"attribute vec4 vColor;										\n" +
"attribute vec2 vTexCoord1;									\n" +
"attribute vec2 vTexCoord2;									\n" +
"															\n" +
"varying vec4 v_color;										\n" +
"varying vec2 v_texCoord1;									\n" +
"varying vec2 v_texCoord2;									\n" +
"															\n" +
"void main()												\n" +
"{															\n" +
"	v_color = vColor;										\n" +
"	gl_Position = worldviewproj * vPosition;				\n" +
"	v_texCoord1 = vTexCoord1.st;							\n" +
"	v_texCoord2 = vTexCoord2.st;							\n" +
"}															";

var fragmentShader = 
"uniform sampler2D texture1;								\n" +
"uniform sampler2D texture2;								\n" +
"uniform vec4 pulse;										\n" +
"															\n" +
"varying vec2 v_texCoord1;									\n" +
"varying vec2 v_texCoord2;									\n" +
"															\n" +
"void main()												\n" +
"{															\n" +
"	vec2 texCoord = vec2(v_texCoord1.s, v_texCoord1.t);		\n" +
"	gl_FragColor = texture2D(texture1, texCoord) * pulse;	\n" +
"}															\n";

myShaderCallBack = function()
{
	var time = new Date().getTime();
	var pulse = (time % 500) / 500.0;
	ccbSetShaderConstant(2, 'pulse', pulse, pulse, 0, pulse);
}

var newMaterial = ccbCreateMaterial(vertexShader, fragmentShader, 0, myShaderCallBack);

var cube = ccbGetSceneNodeFromName('cubeMesh1');
ccbSetSceneNodeMaterialProperty(cube, 0, 'Type', newMaterial);