понедельник, 30 июля 2012 г.

Пример шейдера на Android. Metablob.

Здесь я выложу код модифицированного шейдера под названием Metablob (автор Adrian Boeing). Исходник взят от сюда Shader Toy.
Как, что и почему я тут расписывать не буду. Более детальное прикручивание шейдера к Android можно посмотреть в моем сообщении "Пример шейдера на Android. Ripple Effect.". Здесь будет интересен уже результат шейдера.

Выглядит он *примерно* так, плюс оно шевелиться всячески =):

Изменению подверглись количество и радиусы сгустков (свой! уникальный! аррргххх....=( ).
А вот и код активити:

package com.expedition107.shader.test;
import java.io.IOException;
import java.io.InputStream;

import org.andengine.engine.Engine;
import org.andengine.engine.LimitedFPSEngine;
import org.andengine.engine.camera.Camera;
import org.andengine.engine.handler.IUpdateHandler;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
import org.andengine.entity.scene.IOnAreaTouchListener;
import org.andengine.entity.scene.IOnSceneTouchListener;
import org.andengine.entity.scene.ITouchArea;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.scene.background.Background;
import org.andengine.entity.sprite.Sprite;
import org.andengine.entity.sprite.UncoloredSprite;
import org.andengine.entity.util.FPSLogger;
import org.andengine.input.touch.TouchEvent;
import org.andengine.input.touch.detector.ClickDetector;
import org.andengine.input.touch.detector.ClickDetector.IClickDetectorListener;
import org.andengine.opengl.shader.PositionTextureCoordinatesShaderProgram;
import org.andengine.opengl.shader.ShaderProgram;
import org.andengine.opengl.shader.constants.ShaderProgramConstants;
import org.andengine.opengl.shader.exception.ShaderProgramException;
import org.andengine.opengl.shader.exception.ShaderProgramLinkException;
import org.andengine.opengl.texture.ITexture;
import org.andengine.opengl.texture.PixelFormat;
import org.andengine.opengl.texture.bitmap.BitmapTexture;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.TextureRegionFactory;
import org.andengine.opengl.texture.render.RenderTexture;
import org.andengine.opengl.util.GLState;
import org.andengine.opengl.vbo.attribute.VertexBufferObjectAttributes;
import org.andengine.ui.activity.BaseGameActivity;
import org.andengine.util.adt.io.in.IInputStreamOpener;
import org.andengine.util.color.Color;

import com.expedition107.shader.test.ShockwaveTest.ShockwaveShaderProgram;

import android.opengl.GLES20;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;

public class MetablobTest extends BaseGameActivity implements IOnSceneTouchListener {
 
 public static int WIDTH = 800;
 public static int HEIGHT = 480;
 
 protected static MetablobTest Instance;
 private Camera mCamera;
 private Sprite mSprite;
        private ITexture mTexture;
        private ITextureRegion mTextureRegion;
    
 private boolean mRenderTextureInitialized = false;
 private RenderTexture mRenderTexture;
 private Sprite mRenderTextureSprite;
 
 private float mShockwaveTime = 0f;
 private float mCenterX = 0.5f;
 private float mCenterY= 0.5f;
 
 @Override
 public EngineOptions onCreateEngineOptions() {
  
  DisplayMetrics displayMetrics = new DisplayMetrics();
  WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
  wm.getDefaultDisplay().getMetrics(displayMetrics);
  wm.getDefaultDisplay().getRotation();
  WIDTH = displayMetrics.widthPixels;
  HEIGHT = displayMetrics.heightPixels;
  
  mCamera = new Camera(0, 0, WIDTH, HEIGHT);
  
  final EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(WIDTH, HEIGHT), this.mCamera);

  return engineOptions;
 }
 
 @Override
    public Engine onCreateEngine(final EngineOptions pEngineOptions) {
        return new LimitedFPSEngine(pEngineOptions, 120) {
         
         
         @Override
         public void onDrawFrame(GLState pGLState)
           throws InterruptedException {
    
     if (!mRenderTextureInitialized) {
      initRenderTexture(pGLState);
      mRenderTextureInitialized = true;
     }
     
     mRenderTexture.begin(pGLState, false, true, Color.TRANSPARENT);
     {  
      super.onDrawFrame(pGLState);
     }
     mRenderTexture.end(pGLState);
         
     pGLState.pushProjectionGLMatrix();
     pGLState.orthoProjectionGLMatrixf(0, mCamera.getSurfaceWidth(), 0, mCamera.getSurfaceHeight(), -1, 1);
     {
      mRenderTextureSprite.onDraw(pGLState, mCamera);
     }
     pGLState.popProjectionGLMatrix(); 

         }
         
   private void initRenderTexture(GLState pGLState) {
    mRenderTexture = new RenderTexture(this.getTextureManager(),mCamera.getSurfaceWidth(), mCamera.getSurfaceHeight(), PixelFormat.RGBA_8888);
    mRenderTexture.init(pGLState);
    mRenderTextureSprite = new UncoloredSprite(0f, 0f, TextureRegionFactory.extractFromTexture(mRenderTexture), getVertexBufferObjectManager());
    
    mRenderTextureSprite.setShaderProgram(ShockwaveShaderProgram.getInstance());    
   }
        };
 }

 @Override
 public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {

 Instance = this;

        try {
            this.mTexture = new BitmapTexture(this.getTextureManager(), new IInputStreamOpener() {
    
    @Override
    public InputStream open() throws IOException {
     // TODO Auto-generated method stub
     return getAssets().open("gfx/sw_12_23.jpg");
    }
   });
            
            
            this.mTextureRegion = TextureRegionFactory.extractFromTexture(mTexture);
            this.mTexture.load();
        } catch (IOException e) {
        }
        
        this.getShaderProgramManager().loadShaderProgram(ShockwaveShaderProgram.getInstance());

  pOnCreateResourcesCallback.onCreateResourcesFinished();
 }

 @Override
 public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) throws Exception {        
   this.mEngine.registerUpdateHandler(new FPSLogger());

         final Scene scene = new Scene();
         
         scene.setBackground(new Background(0.09804f, 0.6274f, 0.8784f));
         
         this.mSprite = new Sprite(0f, 0f,40,40, this.mTextureRegion, getVertexBufferObjectManager());
   scene.attachChild(mSprite);
   
   getEngine().registerUpdateHandler(new IUpdateHandler() {
    @Override
    public void reset() {
    }   
    @Override
    public void onUpdate(float pSecondsElapsed) {
     mShockwaveTime += pSecondsElapsed*0.5;
    }
   });
         scene.setOnSceneTouchListener(this);         
         pOnCreateSceneCallback.onCreateSceneFinished(scene);
 }

 @Override
 public void onPopulateScene(Scene pScene,
   OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
  pOnPopulateSceneCallback.onPopulateSceneFinished();
 }
 
 public static class ShockwaveShaderProgram extends ShaderProgram {
  
  private static ShockwaveShaderProgram instance;
  
  public static ShockwaveShaderProgram getInstance() {
   if (instance == null) instance = new ShockwaveShaderProgram();
   return instance;
  }
    
  public static final String FRAGMENTSHADER = 
  "precision highp float;\n" +
        
        "uniform float time;\n" +
        "uniform vec2 resolution;\n" +

  "void main() \n" +
  "{\n" +
  //центры сгустков
  " vec2 move1;\n" +
  " move1.x = cos(time)*0.4;\n" +
  " move1.y = sin(time*1.5)*0.4;\n" +
  " vec2 move2;\n" +  
  " move2.x = cos(time*2.0)*0.6;\n" +
  " move2.y = sin(time*3.0)*0.6;\n" +
  " vec2 move3;\n" +  
  " move3.x = sin(time*2.0)*0.8;\n" +
  " move3.y = cos(time*3.0)*0.8;\n" +
  " vec2 move4;\n" +  
  " move4.x = cos(time*1.8)*0.2;\n" +
  " move4.y = sin(time*3.0)*0.2;\n" +
  " vec2 move5;\n" +  
  " move5.x = cos(time*4.8)*1.2;\n" +
  " move5.y = sin(time*1.0)*0.5;\n" +
  " vec2 move6;\n" +  
  " move6.x = sin(time*1.8)*0.2;\n" +
  " move6.y = cos(time*1.1)*0.2;\n" +
  " vec2 move7;\n" +  
  " move7.x = cos(time*1.8)*0.2;\n" +
  " move7.y = sin(time*3.0)*0.2;\n" +
  " vec2 move8;\n" +  
  " move8.x = sin(time*0.8)*0.2;\n" +
  " move8.y = cos(time*0.3)*0.5;\n" +
  " vec2 move9;\n" +  
  " move9.x = cos(time*1.8)*0.9;\n" +
  " move9.y = sin(time)*5.4;\n" +
  //координаты
  " vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;\n" +
  //радиусы
  " float r1 =(dot(p-move1,p-move1))*16.0;\n" +
  " float r2 =(dot(p+move2,p+move2))*24.0;\n" +
  " float r3 =(dot(p+move3,p+move3))*32.0;\n" +
  " float r4 =(dot(p+move4,p+move4))*40.0;\n" +
  " float r5 =(dot(p+move5,p+move5))*42.0;\n" +
  " float r6 =(dot(p+move6,p+move6))*50.0;\n" +
  " float r7 =(dot(p+move7,p+move7))*56.0;\n" +
  " float r8 =(dot(p+move8,p+move8))*65.0;\n" +
  " float r9 =(dot(p+move9,p+move9))*70.0;\n" +
  //сумма сгустков
  " float metaball =(1.0/r1-1.0/r2+1.0/r3-1.0/r4+1.0/r5-1.0/r6+1.0/r7-1.0/r8+1.0/r9);\n" +
  //места разрыва сгустков
  " float col = pow(metaball,8.0);\n" +
  //вывод конечного цвета фрагмента
  " gl_FragColor = vec4(col+move2.x,col+move1.x,col+move4.y,0.8);\n" +
  "}\n";

  
  private ShockwaveShaderProgram() {
   super(PositionTextureCoordinatesShaderProgram.VERTEXSHADER, FRAGMENTSHADER);
  }
  
        public static int sUniformModelViewPositionMatrixLocation = ShaderProgramConstants.LOCATION_INVALID;
        public static int sUniformTexture0Location = ShaderProgramConstants.LOCATION_INVALID;
        public static int sUniformTimeLocation = ShaderProgramConstants.LOCATION_INVALID;
        public static int sUniformResolutionLocation = ShaderProgramConstants.LOCATION_INVALID;
        
        @Override
        protected void link(final GLState pGLState) throws ShaderProgramLinkException {
            GLES20.glBindAttribLocation(this.mProgramID, ShaderProgramConstants.ATTRIBUTE_POSITION_LOCATION, ShaderProgramConstants.ATTRIBUTE_POSITION);
            GLES20.glBindAttribLocation(this.mProgramID, ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES_LOCATION, ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES);

            super.link(pGLState);

            ShockwaveShaderProgram.sUniformModelViewPositionMatrixLocation = this.getUniformLocation(ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX);     
            ShockwaveShaderProgram.sUniformResolutionLocation = this.getUniformLocation("resolution");
            ShockwaveShaderProgram.sUniformTimeLocation = this.getUniformLocation("time");
        }
        
        @Override
        public void bind(final GLState pGLState, final VertexBufferObjectAttributes pVertexBufferObjectAttributes) {
            GLES20.glDisableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION);

            super.bind(pGLState, pVertexBufferObjectAttributes);
            
            GLES20.glUniformMatrix4fv(ShockwaveShaderProgram.sUniformModelViewPositionMatrixLocation, 1, false, pGLState.getModelViewProjectionGLMatrix(), 0);
            GLES20.glUniform2f(ShockwaveShaderProgram.sUniformResolutionLocation, WIDTH, HEIGHT);
            GLES20.glUniform1f(ShockwaveShaderProgram.sUniformTimeLocation, MetablobTest.Instance.mShockwaveTime);
        }

      
        @Override
        public void unbind(final GLState pGLState) throws ShaderProgramException {
            GLES20.glEnableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION);

            super.unbind(pGLState);
        }
 }


 @Override
 public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  if (pSceneTouchEvent.getAction() == TouchEvent.ACTION_DOWN){
  mCenterX = pSceneTouchEvent.getMotionEvent().getX() / this.mCamera.getSurfaceWidth();
  mCenterY = 1-pSceneTouchEvent.getMotionEvent().getY() / this.mCamera.getSurfaceHeight();
  return true;
  }
  return false;
 } 
}

Копируйте, пользуйтесь, не забывайте про Adrian-а Boeing-а! :-)


Продвинутый пример смотрите в статье "Немного о Render-to-Texture..."
Скачать исходник с апк внутри.

3 комментария:

  1. Спасибо за пример, Алексей!
    только не совсем понятно чем управляют переменные mCenterX, mCenterY - они совершенно бесполезны здесь...
    по идее их ведь куда-то в шейдере прибиндить надо чтобы управление по тачу происходило?

    ОтветитьУдалить
  2. Да действительно, эти переменные здесь не используются, они остались от моих экспериментов. Чтобы их использовать, в шейдере нужно определить униформы для них и связать. Ну и конечно, в коде шейдера эти переменные должны как-то влиять на происходящее, иначе так в них смысла и не будет).

    ОтветитьУдалить
    Ответы
    1. Раз так то понятно. Я не мог определить на что бы они могли влиять в этом шейдере и что тут по тачу вообще меняться может. А вот в предыдущем шейдере им место найдётся.
      Спасибо еще раз!

      Удалить