Documentation


All tutorials

CopperLicht Tutorial: Custom materials and shaders


This tutorial demonstrates how to create custom materials using shaders in CopperLicht.
The final result of this tutorial will look about like this:

copperlicht tutorial 5
The wood texture is made brighter and drawn by a fragment shader


Writing CopperLicht code

For this tutorial we don't need the 3d editor CopperCube, everything we are doing here is writing code. To keep this simple and short, the following is the full code of this example. I'll explain it in detail below.
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">	
		<script type="text/javascript" src="copperlicht.js"></script>	
	</head>

	<body>	
		<b>Tutorial 05: Custom materials and shaders.</b><br/>
	Demonstrates how to create custom materials and shaders.<br/><br/>
	<div style="width:640px; margin:auto; position:relative; font-size: 9pt; color: #777777;">
		<canvas id="3darea" width="640" height="480" style="background-color:#000000">
		</canvas>
		<div style="display:block; color:#ffffff; padding:5px; position:absolute; left:20px; top:420px; background-color:#000000; height:37px; width:300px; border-radius:5px; border:1px solid #777777; opacity:0.5;" id="helptext"> 
			Look with the mouse, move with the cursor keys or WASD. 
		</div> 
	</div>
	<script type="text/javascript">
	<!--
	main = function()
	{
		// create the 3d engine
		var engine = new CL3D.CopperLicht('3darea');
		
		if (!engine.initRenderer())
			return; // this browser doesn't support WebGL
			
		// add a new 3d scene
		
		var scene = new CL3D.Scene();
		engine.addScene(scene);
		
		scene.setBackgroundColor(CL3D.createColor(1, 0, 0, 0));
		scene.setRedrawMode(CL3D.Scene.REDRAW_WHEN_SCENE_CHANGED);
		
		// add a sky box
		var skybox = new CL3D.SkyBoxSceneNode();
		scene.getRootSceneNode().addChild(skybox);
		
		// set texture sides of the skybox
		for (var i=0; i<6; ++i)
			skybox.getMaterial(i).Tex1 = engine.getTextureManager().getTexture("stars.jpg", true);
			
		// add a cube to test out
		var cubenode = new CL3D.CubeSceneNode();
		scene.getRootSceneNode().addChild(cubenode);
		cubenode.getMaterial(0).Tex1 = engine.getTextureManager().getTexture("crate_wood.jpg", true);
									
		// add a user controlled camera with a first person shooter style camera controller
		var cam = new CL3D.CameraSceneNode();
		cam.Pos.X = 20;
		cam.Pos.Y = 15;
		
		var animator = new CL3D.AnimatorCameraFPS(cam, engine);										
		cam.addAnimator(animator);										
		animator.lookAt(new CL3D.Vect3d(0,0,0));			
		
		scene.getRootSceneNode().addChild(cam);
		scene.setActiveCamera(cam);				
		
		// now, we want to use a custom material for our cube, lets write
		// a vertex and a fragment shader:
		
		  var vertex_shader_source = "\
		   #ifdef GL_ES                    \n\
		   precision highp float;          \n\
		   #endif                          \n\
		   uniform mat4 worldviewproj;     \
		   attribute vec4 vPosition;       \
		   attribute vec4 vNormal;         \
		   attribute vec2 vTexCoord1;      \
		   attribute vec2 vTexCoord2;      \
		   varying vec2 v_texCoord1;       \
		   varying vec2 v_texCoord2;       \
		   void main()                     \
		   {                               \
			gl_Position = worldviewproj * vPosition;\
			v_texCoord1 = vTexCoord1.st;   \
			v_texCoord2 = vTexCoord2.st;   \
		   }";
		   
		  var fragment_shader_source = "\
		   #ifdef GL_ES                    \n\
		   precision highp float;          \n\
		   #endif                          \n\
		   uniform sampler2D texture1;     \
		   uniform sampler2D texture2;     \
			                               \
		   varying vec2 v_texCoord1;       \
		   varying vec2 v_texCoord2;       \
						                   \
		   void main()                     \
		   {                               \
			vec2 texCoord = vec2(v_texCoord1.s, v_texCoord1.t);  \
			gl_FragColor = texture2D(texture1, texCoord) * 2.0;  \
		   }";
		
		// create a solid material using the shaders. For transparent materials, take a look
		// at the other parameters of createMaterialType
		
		var newMaterialType = engine.getRenderer().createMaterialType(vertex_shader_source, fragment_shader_source);
		if (newMaterialType != -1)
			cubenode.getMaterial(0).Type = newMaterialType;
		else
			alert('could not create shader'); //copperLicht will write the exact error line in the html
	}
	
	main();
	-->
	</script>
	</body>
</html>
Create a .html file and paste this code into there, place the copperlicht.js file from the CopperLicht SDK in the same directory, and add two .jpg files named crate_wood.jpg and stars.jpg which are used in this example as textures.

What the code does

The first few lines of the html code are creating a canvas element inside a container. Inside this container, there is a <div>>, used for the 2d overlay. The style of these divs with its absolute positioning is making it possible to move them over the canvas:
<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
      <script type="text/javascript" src="copperlicht.js"></script>   
   </head>

   <body>   
      <b>Tutorial 05: Custom materials and shaders.</b><br/>
   Demonstrates how to create custom materials and shaders.<br/><br/>
   <div style="width:640px; margin:auto; position:relative; font-size: 9pt; color: #777777;">
      <canvas id="3darea" width="640" height="480" style="background-color:#000000">
      </canvas>
      <div style="display:block; color:#ffffff; padding:5px; position:absolute; left:20px; top:420px; background-color:#000000; height:37px; width:300px; border-radius:5px; border:1px solid #777777; opacity:0.5;" id="helptext"> 
         Look with the mouse, move with the cursor keys or WASD. 
      </div> 
   </div>
So let's start with the javascript code. First, we simply initialize the 3d engine and create a scene, and set it's background color and redraw mode:
  <script type="text/javascript">
   <!--
   main = function()
   {
       // create the 3d engine
      var engine = new CL3D.CopperLicht('3darea');
      
      if (!engine.initRenderer())
         return;  // this browser doesn't support WebGL
         
       // add a new 3d scene
      
      var scene = new CL3D.Scene();
      engine.addScene(scene);
      
      scene.setBackgroundColor(CL3D.createColor(1, 0, 0, 0));
      scene.setRedrawMode(CL3D.Scene.REDRAW_WHEN_SCENE_CHANGED);
	
Of course, we need a camera from which the 3d scene is rendered. We place it at 20, 15 and add an animator to it so that we can move the camera using the cursor keys and the mouse. In addition, we also add a simple sky box and a cube scene node in the middle of the scene. The material of that cube will then later have the material we create.
     // add a sky box
      var skybox = new CL3D.SkyBoxSceneNode();
      scene.getRootSceneNode().addChild(skybox);
      
      // set texture sides of the skybox
      for (var i=0; i<6; ++i)
         skybox.getMaterial(i).Tex1 = engine.getTextureManager().getTexture("stars.jpg", true);
         
      // add a cube to test out
      var cubenode = new CL3D.CubeSceneNode();
      scene.getRootSceneNode().addChild(cubenode);
      cubenode.getMaterial(0).Tex1 = engine.getTextureManager().getTexture("crate_wood.jpg", true);
                           
      // add a user controlled camera with a first person shooter style camera controller
      var cam = new CL3D.CameraSceneNode();
      cam.Pos.X = 20;
      cam.Pos.Y = 15;
      
      var animator = new CL3D.AnimatorCameraFPS(cam, engine);                              
      cam.addAnimator(animator);                              
      animator.lookAt(new Vect3d(0,0,0));         
      
      scene.getRootSceneNode().addChild(cam);
      scene.setActiveCamera(cam);           
	
And now for the more interesting part: Create our own material. In WebGL, you create materials using so called shaders. They are ususally consisting of a vertex and a fragment shader. The vertex shader is a little program which calculates the final position of each 3d vertex on the screen, and the fragment shader is a program which calculates the color of the final pixel. You can write these programs in a language called GLSL. The following two strings in javascript represent our vertex and fragment programs:
// now, we want to use a custom material for our cube, lets write
      // a vertex and a fragment shader:
      
       var vertex_shader_source = "\
		   #ifdef GL_ES                    \n\
		   precision highp float;          \n\
		   #endif                          \n\
		   uniform mat4 worldviewproj;     \
		   attribute vec4 vPosition;       \
		   attribute vec4 vNormal;         \
		   attribute vec2 vTexCoord1;      \
		   attribute vec2 vTexCoord2;      \
		   varying vec2 v_texCoord1;       \
		   varying vec2 v_texCoord2;       \
		   void main()                     \
		   {                               \
			gl_Position = worldviewproj * vPosition;\
			v_texCoord1 = vTexCoord1.st;   \
			v_texCoord2 = vTexCoord2.st;   \
		   }";
		   
		  var fragment_shader_source = "\
		   #ifdef GL_ES                    \n\
		   precision highp float;          \n\
		   #endif                          \n\
		   uniform sampler2D texture1;     \
		   uniform sampler2D texture2;     \
			                               \
		   varying vec2 v_texCoord1;       \
		   varying vec2 v_texCoord2;       \
						                   \
		   void main()                     \
		   {                               \
			vec2 texCoord = vec2(v_texCoord1.s, v_texCoord1.t);  \
			gl_FragColor = texture2D(texture1, texCoord) * 2.0;  \
		   }";
	
The programs have been written in GLSL, directly inside the javascript code, but you can do this how ever you like, like loading it from an element in the html code or similar.
Note that the vertex_shader program is using some constants like worldviewproj, which is the World-View-Projection matrix. This is a variable provided by CopperLicht. If you want to set own variables, you can do this using the normal WebGL functions, and the Renderer.getGLProgramFromMaterialType() function. The program multiplies the current vertx position (vPosition) with this World-View-Projection matrix to store the final vertex position into gl_Position. This is what basically all vertex programs should do. Also, it saves the texture coordinates (vTexCoord1.st and vTexCoord2.st) so it can be used in the fragment shader below.
The fragment shader, the program which writes the pixels is accessing the texture coordinate using texture2D and writing it out to the screen, gl_FragColor. Before this, it is multiplying the color by two, making it brighter. Feel free to change the * 2 statement to something different to try it out.

If you don't understand all this, don't worry. Writing GLSL shaders is quite complicated and there are books and tutorials out there covering this, but it is out of the scope of this tutorial explaining in detail how they work.

Now, we only need to tell CopperLicht to use these shader programs. We create a new Material type using these two shader programs in the last few lines of code:
      // create a solid material using the shaders. For transparent materials, take a look
      // at the other parameters of createMaterialType
      
      var newMaterialType = engine.getRenderer().createMaterialType(vertex_shader_source, fragment_shader_source);
      if (newMaterialType != -1)
         cubenode.getMaterial(0).Type = newMaterialType;
      else
         alert('could not create shader');  //copperLicht will write the exact error line in the html
	
That's it basically. We put all this code into a function named 'main', so we only need to call it.
		}
    main();
	
And that's it, now you know how to create own materials in CopperLicht.

This is not all of course. You can specify blend modes for transparent materials for example using the further parameters of createMaterialType, and you could set shader constants during runtime, but basically this is it.
If you are interested in modifying shader variables at runtime, take a look at the CL3D.Renderer.OnChangeMaterial callback.


More Tutorials


© 2011-2018 N.Gebhardt, Ambiera
Documentation generated by JsDoc Toolkit