четверг, 24 января 2013 г.

AndEngine. Прикручиваем шейдер к конкретному спрайту. Blur-эффект.

Всем привет! В этом сообщении расскажу как прикрутить шейдер к конкретному спрайту. Мои предыдущие сообщения по теме шейдеров могли ввести в заблуждение тем, что процесс отрисовки результатов шейдера производился через специальную текстуру и UncoloredSprite. Все эти изыски изрядно сбивали с толку и меня,но время разобраться с этим вопросом появилось только сейчас (Maksim respect!)  =)
В этом примере мы напишем активити которая отображает спрайт с примененным к нему шейдером размытия (Blur-эффект). Все тривиально:

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.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.resolutionpolicy.*;
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.opengl.shader.PositionTextureCoordinatesShaderProgram;
import org.andengine.opengl.shader.ShaderProgram;
import org.andengine.opengl.shader.exception.ShaderProgramException;
import org.andengine.opengl.shader.exception.ShaderProgramLinkException;
import org.andengine.opengl.shader.constants.ShaderProgramConstants;
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 android.opengl.GLES20;

public class ShaderTestActivitySprite extends BaseGameActivity {
 
 public static final int WIDTH = 720;
 public static final int HEIGHT = 480;
 
 private Camera mCamera;
 private Sprite mSprite;
    private ITexture mTexture;
    private ITextureRegion mTextureRegion;
 
 @Override
 public EngineOptions onCreateEngineOptions() {
  this.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, 60);
 }

 @Override
 public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) throws Exception {
  
        try {
            this.mTexture = new BitmapTexture(this.getTextureManager(), new IInputStreamOpener() {
    
    @Override
    public InputStream open() throws IOException {
     return getAssets().open("gfx/sw_12_23.jpg");
    }
   });
            
            this.mTextureRegion = TextureRegionFactory.extractFromTexture(mTexture);
            this.mTexture.load();
        } catch (IOException e) {
        }
        
        this.getShaderProgramManager().loadShaderProgram(GaussianBlurPass1ShaderProgram.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));
         
         getEngine().registerUpdateHandler(new FPSLogger());
         
         pOnCreateSceneCallback.onCreateSceneFinished(scene);
 }

 @Override
 public void onPopulateScene(Scene pScene,
   OnPopulateSceneCallback pOnPopulateSceneCallback) throws Exception {
  
  this.mSprite = new Sprite(234f, 40f, this.mTextureRegion, getVertexBufferObjectManager()) {   
   @Override
   protected void onManagedUpdate(float pSecondsElapsed) {        
    this.setRotation(this.getRotation()+1);
    super.onManagedUpdate(pSecondsElapsed);
   }
  };
  
  Sprite sprite = new Sprite(0, 0, 200,200, this.mTextureRegion, getVertexBufferObjectManager());
    
  mSprite.setShaderProgram(GaussianBlurPass1ShaderProgram.getInstance());
  
  pScene.attachChild(mSprite);
  pScene.attachChild(sprite);
  
  pOnPopulateSceneCallback.onPopulateSceneFinished();
 }
 
 public static class GaussianBlurPass1ShaderProgram extends ShaderProgram {
  
  private static GaussianBlurPass1ShaderProgram instance;
  
  public static GaussianBlurPass1ShaderProgram getInstance() {
   if (instance == null) instance = new GaussianBlurPass1ShaderProgram();
   return instance;
  }
    
  public static final String FRAGMENTSHADER = 
    "precision lowp float;\n" +

             "uniform sampler2D " + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ";\n" +
             "varying mediump vec2 " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ";\n" +

    "const float blurSize = 2.0/" + (WIDTH-1) + ".0; \n" +
 
    "void main() \n" +
    "{    \n" +
    " vec4 sum = vec4(0.0); \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x - 4.0*blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.05; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x - 3.0*blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.09; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x - 2.0*blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.12; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x - blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.15; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.16; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x + blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.15; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x + 2.0*blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.12; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x + 3.0*blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.09; \n" +
    " sum += texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", vec2(" + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".x + 4.0*blurSize, " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ".y)) * 0.05; \n" +
    " gl_FragColor = sum; \n" +
    "}      \n";

  
  private GaussianBlurPass1ShaderProgram() {
   super(PositionTextureCoordinatesShaderProgram.VERTEXSHADER, FRAGMENTSHADER);
  }
  
  
  
        public static int sUniformModelViewPositionMatrixLocation = ShaderProgramConstants.LOCATION_INVALID;
        public static int sUniformTexture0Location = 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);

            GaussianBlurPass1ShaderProgram.sUniformModelViewPositionMatrixLocation = this.getUniformLocation(ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX);
            GaussianBlurPass1ShaderProgram.sUniformTexture0Location = this.getUniformLocation(ShaderProgramConstants.UNIFORM_TEXTURE_0);

        }
        
        @Override
        public void bind(final GLState pGLState, final VertexBufferObjectAttributes pVertexBufferObjectAttributes) {
            GLES20.glDisableVertexAttribArray(ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION);

            super.bind(pGLState, pVertexBufferObjectAttributes);
            
            GLES20.glUniformMatrix4fv(GaussianBlurPass1ShaderProgram.sUniformModelViewPositionMatrixLocation, 1, false, pGLState.getModelViewProjectionGLMatrix(), 0);
            GLES20.glUniform1i(GaussianBlurPass1ShaderProgram.sUniformTexture0Location, 0);
        }

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

            super.unbind(pGLState);
        }
 }
}

Ну вот, собственно и все. Подводя итог, можно сказать следующее: убрали лишнее и применили шейдер к конкретному спрайту методом: Sprite.setShaderProgram(...) в который и предаем нашу программу шейдера. Для наглядности, я добавил еще один спрайт на сцену с той же текстурой и задал вращение спрайту к которому применен шейдер. Надеюсь кому-то данная информация пригодится. Спасибо!


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