Преломление и окружение в Alternativa3D


Всем привет. В этом уроке я хочу поделится с вами опытом создания собственного материала окружения и преломления в Alternativa3D 8.17.0.

Кубическая текстура

В Molehill присутствует поддрежка кубических текстур. Кубическая текстура состоит из шести равных по размеру квадратных текстур, упорядоченных в кубической топологии. Они используются для описания окрестных наложений. Для того, чтобы создать кубическую текстуру необходимо вызвать метод createCubeTexture у полученного Context3D. В данный метод необходимо передать: длинну краев текстуры в текстурных пикселях, формат текстурного пикселя списка перечисления Context3DTextureFormat. Далее, в созданную кубическую текстуру с помощью метода uploadFromBitmapData, загрузите все компоненты кубического текстурирования из объекта BitmapData. Эта функция загружает один mip-уровень одной стороны карты куба. Вызывайте метод uploadFromBitmapData () по мере необходимости, чтобы загрузить каждый mip-уровень и грань карты куба.

Окружение

Для того чтобы рассчитать отражение на шейдере для объекта, необходимо знать: нормаль вершины, позицию камеры в локальном пространстве объекта, матрицу перевода из локального в глобальное пространство объекта. Отраженный луч рассчитывается по стандартной формуле:

V – View Vector = позиция камеры – позиция вершины, N – Normal

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 MyGlassMaterial extends Material {
     //кубическая текстура
     alternativa3d var _cubeTexture : TextureResource;
       //вершинный шейдер
       private var _vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
       //фрагментный шейдер
       private var _fragmentShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
     //шейдер-программа
       private var program : ShaderProgram;
 
        public function MyGlassMaterial(cubeTexture : TextureResource = null) {
         _cubeTexture = cubeTexture;
         program = new ShaderProgram(null, null);
            _vertexShaderAssembler.assemble(Context3DProgramType.VERTEX,
            //нормаль вершины
         "mov vt0, va1\n"+
          //переводим из локального в глобальное пространство
         "m33 vt0.xyz, vt0.xyz, vc5\n"+
         //в локальном пространстве рассчитываем view vec = позиция камеры - позиция вершины.
           "sub vt1, vc4, va0\n"+
         //переводим viewVec из локального в глобальное пространство
         "m33 vt1.xyz, vt1.xyz, vc5\n"+
         // r = V - 2(V.N)*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"+
           //прогоняем позицию вершины через матрицу проекции
           "m44 op, va0, vc0");
 
            _fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT,
            //получаем цвет по лучу из кубической текстуры
            "tex ft0,v0.xyz,fs0 \n"+
           //отправляем результат
           "mov oc, ft0"
           );
      }
       //переопределяем метод заливки ресурсов в видео-карту
      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., 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);
            //для вершинного шейдера загружаем буфер позиции и нормалей
           //при этом указываем их формат float3, float3 и смещения 0,5
           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 globalTransform : Transform3D = new Transform3D();
          globalTransform.copy(object.localToCameraTransform);
            globalTransform.append(camera.localToGlobalTransform);
          //передаем полученную матрицу
          drawUnit.setVertexConstantsFromTransform(5, globalTransform);
           //устанавливаем кубическую текстуру          
            drawUnit.setTextureAt(0, _cubeTexture._texture);
            //добавляем сурфейс на отрисовку
         camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority:RenderPriority.OPAQUE);
       }
   }
}

Преломление

Для того чтобы рассчитать refraction ray я использовал Закон Снелла, но немного в модифицированной его векторной форме. Исходная формула:


Итого, заменив n1/n2 на коэффициент Refraction Index мы получаем:

Выполнив данную формулу, передаем полученный вектор фрагментной программе. Так же сделаем три публичных свойства у данного материала reflectionPower, refractionIndex и alpha для удобства работы с ним.

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.Context3DBlendFactor;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.VertexBuffer3D;
  import flash.utils.Dictionary;
  import flash.utils.getDefinitionByName;
 
 use namespace alternativa3d;
    public class MyGlassMaterial 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.4;
       //коэффициент преломления
     public var refractionIndex : Number = 0.01;
     //прозрачность
      public var alpha : Number = 0.8;
 
        public function MyGlassMaterial(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"+
         //в локальном пространстве рассчитываем view vec = позиция камеры - позиция вершины.
           "sub vt1, vc4, va0\n"+
         //переводим viewVec из локального в глобальное пространство
         "m33 vt1.xyz, vt1.xyz, vc6\n"+
         // r = - (V - 2(V.N)*N) = 2(V.N)*N - V
          "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"+
           //REFR_IND*(V.N)*N-V) - SQRT(1-REFR_IND^2(1-(V.N)^2))*N
         //(V.N)
         "dp3 vt3, vt1, vt0\n"+
         //(V.N)*N
           "mul vt3, vt3, vt0\n"+
         //(V.N)*N-V
         "sub vt3, vt3, vt1\n"+
         //REFR_IND*(V.N)*N-V)
           "mul vt3, vt3, vc5.y\n"+
           //V.N
           "dp3 vt4, vt1, vt0\n"+
         //(V.N)^2
           "mul vt4, vt4, vt4\n"+
         //(1-(V.N)^2)
           "sub vt4, vc5.x, vt4\n"+
           //REFR_IND^2(1-(V.N)^2)
         "mul vt4, vc5.z, vt4\n"+
           //1-REFR_IND^2(1-(V.N)^2)
           "sub vt4, vc5.x, vt4\n"+
           //SQRT(1-REFR_IND^2(1-(V.N)^2))
         "sqt vt4, vt4\n"+
          //SQRT(1-REFR_IND^2(1-(V.N)^2))*N
           "mul vt4, vt4, vt0\n"+
         //REFR_IND*(V.N)*N-V) - SQRT(1-REFR_IND^2(1-(V.N)^2))*N
         "sub vt4, vt3, vt4\n"+
         //нормализируем результат
         "nrm vt4.xyz, vt4.xyz\n"+
          "mov v1, vt4\n"+
           //прогоняем позицию вершины через матрицу проекции
           "m44 op, va0, vc0");
 
            _fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT,
            //отражение
            "tex ft0,v0.xyz,fs0 <cube,clamp,linear>\n"+
            //преломление
            "tex ft1,v1.xyz,fs0 <cube,clamp,linear>\n"+
            //смешиваем
            "sub ft3, ft0, ft1\n"+
         "mul ft3, ft3, fc0\n"+
         "add ft3, ft3, ft1\n"+
         //alpha
         "mov ft3.w, fc0.w\n"+
          //отправляем результат
           "mov oc, ft3"
           );
      }
       //переопределяем метод заливки ресурсов в видео-карту
      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);
            //для вершинного шейдера загружаем буфер позиции и нормалей
           //при этом указываем их формат float3, float3 и смещения 0,5
           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);
            //передаем 1, refractionIndex и refractionIndex^2 
         drawUnit.setVertexConstantsFromNumbers(5, 1, refractionIndex, refractionIndex*refractionIndex, 1);
          //рассчитываем матрицу перевода из локального в глобальное пространсво
         var globalTransform : Transform3D = new Transform3D();
          globalTransform.copy(object.localToCameraTransform);
            globalTransform.append(camera.localToGlobalTransform);
          //передаем полученную матрицу
          drawUnit.setVertexConstantsFromTransform(6, globalTransform);
           //mix color
         drawUnit.setFragmentConstantsFromNumbers(0,reflectionPower,reflectionPower,reflectionPower,alpha);
          //устанавливаем кубическую текстуру
          drawUnit.setTextureAt(0, _cubeTexture._texture);
            //добавляем сурфейс на отрисовку
         camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : RenderPriority.TRANSPARENT_SORT);
          drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA;
           drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA;
        }
   }
}

SergeyGonchar
flash-developer
Visit Gonchar Website.

6 Responses to “Преломление и окружение в Alternativa3D”

  1. doctorEvil says:

    Мне вот интересно вы такой сами умный или вам Волков и КО рассказывает, а вы усердно записываете?

  2. MMMaXXX says:

    Ты как всегда супер Сергей, огромное спасибо!

  3. adzh says:

    Спасибо за урок, очень круто, как всегда!

    PS. В статье неправильная ссылка на скачивание альтернативы.

  4. Flastar says:

    Спасибо большое! Поправил.

  5. BuKT says:

    Это прекрасно!
    Нет, правда. Последние дни занимался тем, что пытался модифицировать шейдер SphereMap из урока XProger’а, получалось плохо. Экспы набрал, конечно, прилично, но как оказалось не в той области, что мне нужна

  6. BuKT says:

    В первом листинге:
    Вместо [code]override alternativa3d function collectDraws(, lights : Vector., ) : void {[/code] в первом листинге вероятно [code]override alternativa3d function collectDraws(, lights : Vector., ) : void {[/code]
    Вместо [code]if (_cubeTexture != null && A3DUtils.checkParent[/code] вероятно [code]if (_cubeTexture != null && A3DUtils.checkParent[/code]
    Вместо [code]camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority:RenderPriority.OPAQUE);[/code] вероятно [code]camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority:RenderPriority.OPAQUE);[/code]
    И, да, фрагментный шейдер не работает (даже будучи исправлен на

Leave a Reply

×
%d bloggers like this: