Tutorial Realtime raycast in Alternativa3D

Hi! In this lesson I’ll show you how to create your own realtime raycast with reflect and refract using GPU in Alternativa3D 8.17.0.

Demo 1 – Only Reflect

Demo 2 – RefractionIndex = 6 and mix-value between reflect and refract equals 0.2

Demo 3 – RefractionIndex = 1 and mix-value between reflect and refract equals 0.5


Before start, I recommend you to read my previous article Environment and Refraction in Alternativa3D.

I achived this effect by setRenderToTexture Context3D method. This method is really useful for creating:
1) Shadow maps
2) Postprocessing
3) Reflections

When you use this technique you must set optimizeForRenderToTexture property = true for draw target textures.
At first lets create CubeMap Alternativa3D resource:

package {
   import alternativa.engine3d.alternativa3d;
  import alternativa.engine3d.resources.TextureResource;
 
  import flash.display3D.Context3D;
   import flash.display3D.Context3DTextureFormat;
 
  use namespace alternativa3d;
    public class CubeMapResource extends TextureResource {
      private var _size : Number;
 
     public function CubeMapResource(size : Number) {
            _size = size;
       }
 
       override public function upload(context3D : Context3D) : void {
         if (_texture != null) _texture.dispose();
           _texture = context3D.createCubeTexture(_size, Context3DTextureFormat.BGRA, true);
       }
   }
}

Then we optimize material from previous tutorial:

package {
 import alternativa.engine3d.alternativa3d;
  import alternativa.engine3d.core.Camera3D;
  import alternativa.engine3d.core.DrawUnit;
  import alternativa.engine3d.core.Light3D;
   import alternativa.engine3d.core.Object3D;
  import alternativa.engine3d.core.RenderPriority;
    import alternativa.engine3d.core.Transform3D;
   import alternativa.engine3d.core.VertexAttributes;
  import alternativa.engine3d.materials.A3DUtils;
 import alternativa.engine3d.materials.Material;
 import alternativa.engine3d.materials.ShaderProgram;
    import alternativa.engine3d.objects.Surface;
    import alternativa.engine3d.resources.Geometry;
 import alternativa.engine3d.resources.TextureResource;
 
  import avmplus.getQualifiedClassName;
 
   import com.adobe.utils.AGALMiniAssembler;
 
   import flash.display3D.Context3DProgramType;
    import flash.display3D.VertexBuffer3D;
  import flash.utils.Dictionary;
  import flash.utils.getDefinitionByName;
 
 use namespace alternativa3d;
    public class ReflectRefractMaterial extends Material {
      alternativa3d var _cubeTexture : TextureResource;
       private var _vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
       private var _fragmentShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
     private var program : ShaderProgram;
        public var reflectionPower: Number = 0.5;
       private var refractionIndex : Number = 6;
       private var alpha : Number = 1;
     private var rotatedObject:Object3D;
     public function ReflectRefractMaterial(cubeTexture : TextureResource = null) {
          _cubeTexture = cubeTexture;
         program = new ShaderProgram(null, null);
            _vertexShaderAssembler.assemble(Context3DProgramType.VERTEX,
            "mov vt0, va1\n"+
          "m33 vt0.xyz, vt0.xyz, vc6\n"+
         "sub vt1, vc4, va0\n"+
         "m33 vt1.xyz, vt1.xyz, vc6\n"+
         "dp3 vt2, vt1, vt0\n"+
         "add vt2, vt2, vt2\n"+
         "mul vt2, vt0, vt2\n"+
         "sub vt2, vt1, vt2\n"+
         "neg vt2, vt2\n"+
          "nrm vt2.xyz, vt2.xyz\n"+
          "mov v0, vt2\n"+
           "dp3 vt3, vt1, vt0\n"+
         "mul vt3, vt3, vt0\n"+
         "sub vt3, vt3, vt1\n"+
         "mul vt3, vt3, vc5.y\n"+
           "dp3 vt4, vt1, vt0\n"+
         "mul vt4, vt4, vt4\n"+
         "sub vt4, vc5.x, vt4\n"+
           "mul vt4, vc5.z, vt4\n"+
           "sub vt4, vc5.x, vt4\n"+
           "sqt vt4, vt4\n"+
          "mul vt4, vt4, vt0\n"+
         "sub vt4, vt3, vt4\n"+
         "nrm vt4.xyz, vt4.xyz\n"+
          "mov v1, vt4\n"+
           "m44 op, va0, vc0");
 
            _fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT,
            "mov ft4.xyz, v0.yzx\n"+
           "neg ft4.z, ft4.z\n"+
          "m33 ft4.xyz, ft4.xyz, fc1\n"+
         "tex ft0,ft4.xyz,fs0 <cube,clamp,linear>\n"+
           "mov ft5.xyz, v1.yzx\n"+
           "neg ft5.z, ft5.z\n"+
          "m33 ft5.xyz, ft5.xyz, fc1\n"+
         "tex ft1,ft5.xyz,fs0 <cube,clamp,linear>\n"+
           "sub ft3, ft0, ft1\n"+
         "mul ft3, ft3, fc0\n"+
         "add ft3, ft3, ft1\n"+
         "mov ft3.w, fc0.w\n"+
          "mov oc, ft3"
           );
 
            rotatedObject = new Object3D();
         rotatedObject.rotationY = -Math.PI/2;
           rotatedObject.composeTransforms();
          Main.instance.rootContainer.addChild(rotatedObject);
        }
       alternativa3d override function fillResources(resources : Dictionary, resourceType : Class) : void {
            if (_cubeTexture != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(_cubeTexture)) as Class, resourceType)) {
                resources[_cubeTexture] = true;
         }
           program.program = Main.instance.stage.stage3Ds[0].context3D.createProgram();
            program.program.upload(_vertexShaderAssembler.agalcode, _fragmentShaderAssembler.agalcode);
     }
 
        override alternativa3d function collectDraws(camera : Camera3D, surface : Surface, geometry : Geometry, lights : Vector.<Light3D>, lightsLength : int, objectRenderPriority : int = -1) : void {
            var object : Object3D = surface.object;
         var positionBuffer : VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
          var normalsBuffer : VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
         var drawUnit : DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program);
            drawUnit.setVertexBufferAt(0, positionBuffer, 0, "float3");
         drawUnit.setVertexBufferAt(1, normalsBuffer, 5, "float3");
          drawUnit.setProjectionConstants(camera, 0, object.localToCameraTransform);
          var cameraToLocalTransform : Transform3D = object.cameraToLocalTransform;
           drawUnit.setVertexConstantsFromNumbers(4, cameraToLocalTransform.d, cameraToLocalTransform.h, cameraToLocalTransform.l);
            var refraction:Number = refractionIndex/1000;
           drawUnit.setVertexConstantsFromNumbers(5, 1, refraction, refraction*refraction, 1);
         var globalTransform : Transform3D = new Transform3D();
          globalTransform.copy(object.localToCameraTransform);
            globalTransform.append(camera.localToGlobalTransform);
          drawUnit.setVertexConstantsFromTransform(6, globalTransform);
           drawUnit.setFragmentConstantsFromNumbers(0,reflectionPower,reflectionPower,reflectionPower,alpha);
          drawUnit.setFragmentConstantsFromTransform(1, rotatedObject.inverseTransform);
          drawUnit.setTextureAt(0, _cubeTexture._texture);
            camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : RenderPriority.OPAQUE);
        }
   }
}

WARNING!!! To calculate localToGlobal transform you need to transform from localToCamera and then from cameraToGlobal, because object localToGlobal is native Transform3D inside Alternativa3D engine.

Let’s do main class of project.
Method for creating sample sphere with ReflectRefractMaterial:

private var objects : Vector.<Mesh> = new Vector.<Mesh>();
private var objectsMaterials : Vector.<ReflectRefractMaterial> = new Vector.<ReflectRefractMaterial>();
 
public function createSphere(radius : Number = 120, reflectionPower : Number = 0.5) : void {
    var sphere : GeoSphere = new GeoSphere(radius, 6);
  var sphereMaterial : ReflectRefractMaterial = new ReflectRefractMaterial(new CubeMapResource(xneg_sky.width));
  sphereMaterial.reflectionPower = reflectionPower;
   sphere.setMaterialToAllSurfaces(sphereMaterial);
    rootContainer.addChild(sphere);
 objects.push(sphere);
   objectsMaterials.push(sphereMaterial);
}

Embed images and create skybox:

[Embed(source="03.jpg")]
private var xnegJpgClass_sky : Class;
public var xneg_sky : BitmapData;
[Embed(source="06.jpg")]
private var xposJpgClass_sky : Class;
public var xpos_sky : BitmapData;
[Embed(source="01.jpg")]
private var ynegJpgClass_sky : Class;
public var yneg_sky : BitmapData;
[Embed(source="05.jpg")]
private var yposJpgClass_sky : Class;
public var ypos_sky : BitmapData;
[Embed(source="04.jpg")]
private var znegJpgClass_sky : Class;
public var zneg_sky : BitmapData;
[Embed(source="02.jpg")]
private var zposJpgClass_sky : Class;
public var zpos_sky : BitmapData;
 
private function setupScene() : void {
    xneg_sky = (new xnegJpgClass_sky() as Bitmap).bitmapData;
   xpos_sky = (new xposJpgClass_sky() as Bitmap).bitmapData;
   yneg_sky = (new ynegJpgClass_sky() as Bitmap).bitmapData;
   ypos_sky = (new yposJpgClass_sky() as Bitmap).bitmapData;
   zneg_sky = (new znegJpgClass_sky() as Bitmap).bitmapData;
   zpos_sky = (new zposJpgClass_sky() as Bitmap).bitmapData;
   createSphere(180, 0.2);
 createSphere(180, 0.2);
 createSphere(180, 0.2);
 skyBox = new SkyBox(10000, new TextureMaterial(new BitmapTextureResource(xneg_sky)), new TextureMaterial(new BitmapTextureResource(xpos_sky)), new TextureMaterial(new BitmapTextureResource(yneg_sky)), new TextureMaterial(new BitmapTextureResource(ypos_sky)), new TextureMaterial(new BitmapTextureResource(zneg_sky)), new TextureMaterial(new BitmapTextureResource(zpos_sky)));
 rootContainer.addChild(skyBox);
}

Create method which render cubeMap for every object. But here are not all simple. Let’s do that by steps:
1) Set position of skybox at object position
2) Set camera position to default pos
3) Calculate fov, scaleX, scaleY properties for camera. Thanks to Ivan Dembicki for help.
4) And then, for all 6 sides render scene from different views.

private function calculateCubeMapForSphere(sphere : Mesh, sphereMaterial : ReflectRefractMaterial) : void {
  skyBox.x = sphere.x;
    skyBox.y = sphere.y;
    skyBox.z = sphere.z;
    camera.x = sphere.x;
    camera.y = sphere.y;
    camera.z = sphere.z;
    var viewSizeX : Number = stage.stageWidth / 2;
  var viewSizeY : Number = stage.stageHeight / 2;
 camera.fov = Math.atan(Math.sqrt(viewSizeX * viewSizeX + viewSizeY * viewSizeY) / camera.focalLength) * 2;
  camera.scaleX = camera.focalLength / stage.stageWidth * 2;
  camera.scaleY = camera.focalLength / stage.stageHeight * 2;
 lookAt(camera.x - 1, camera.y, camera.z);
   stage3D.context3D.setRenderToTexture(sphereMaterial._cubeTexture._texture, false, 0, 0);
    camera.render(stage3D);
 
 lookAt(camera.x + 1, camera.y, camera.z);
   stage3D.context3D.setRenderToTexture(sphereMaterial._cubeTexture._texture, false, 0, 1);
    camera.render(stage3D);
 
 lookAt(camera.x, camera.y - 1, camera.z);
   stage3D.context3D.setRenderToTexture(sphereMaterial._cubeTexture._texture, false, 0, 4);
    camera.render(stage3D);
 
 lookAt(camera.x, camera.y + 1, camera.z);
   stage3D.context3D.setRenderToTexture(sphereMaterial._cubeTexture._texture, false, 0, 5);
    camera.render(stage3D);
 
 lookAt(camera.x, camera.y, camera.z + 1);
   camera.rotationZ = Math.PI;
 
 stage3D.context3D.setRenderToTexture(sphereMaterial._cubeTexture._texture, false, 0, 2);
    camera.render(stage3D);
 
 lookAt(camera.x, camera.y, camera.z - 1);
   camera.rotationZ = Math.PI;
 stage3D.context3D.setRenderToTexture(sphereMaterial._cubeTexture._texture, false, 0, 3);
    camera.render(stage3D);
}

lookAt method:

public function lookAt(x : Number, y : Number, z : Number) : void {
   var dx : Number = x - camera.x;
 var dy : Number = y - camera.y;
 var dz : Number = z - camera.z;
 camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - Math.PI / 2;
  camera.rotationY = 0;
   camera.rotationZ = -Math.atan2(dx, dy);
}

onEnterFrame method:

private var angle : Number = 0.01;
private var radius : Number = 400;
private var anim:Boolean = true;
private function onEnterFrame(e : Event) : void {
   //Rotate spheres
    var length : Number = objects.length;
   var step : Number = 0;
  var i : int;
    var mesh : Mesh;
    for (i = 0; i < length; i++) {
      mesh = objects[i] as Mesh;
      mesh.x = Math.cos(angle + step) * radius;
       mesh.y = Math.sin(angle + step) * radius;
       step += Math.PI * 2 / length;
   }
   if(anim) angle += 0.005;
    //Store camera propetrties
  var cameraX : Number = camera.x;
    var cameraY : Number = camera.y;
    var cameraZ : Number = camera.z;
    var cameraRotationX : Number = camera.rotationX;
    var cameraRotationY : Number = camera.rotationY;
    var cameraRotationZ : Number = camera.rotationZ;
    rootContainer.addChild(camera);
 //Render cube maps
  for (i = 0; i < length; i++) {
      mesh = objects[i] as Mesh;
      calculateCubeMapForSphere(mesh, objectsMaterials[i] as ReflectRefractMaterial);
 }
   //set render back
   stage3D.context3D.setRenderToBackBuffer();
  camera.scaleX = 1;
  camera.scaleY = 1;
  cameraContainer.addChild(camera);
   camera.x = cameraX;
 camera.y = cameraY;
 camera.z = cameraZ;
 camera.rotationX = cameraRotationX;
 camera.rotationY = cameraRotationY;
 camera.rotationZ = cameraRotationZ;
 camera.fov = 1.5707963267948966;
    camera.composeTransforms();
 skyBox.x = 0;
   skyBox.y = 0;
   skyBox.z = 0;
   //render
    camera.render(stage3D);
}

That’s all! Thanks for reading! If you have any questions please contact me.

SergeyGonchar
flash-developer
Visit Gonchar Website.

Leave a Reply

×
%d bloggers like this: