Partage
  • Partager sur Facebook
  • Partager sur Twitter

[SDL2] Refleter le monde

avec un scrolling...

    14 juin 2020 à 17:17:15

    Bonjour,

    Je suis sur une fonction me permettant de refléter une image "dans l'eau" et m'approcher du rendu de l'eau dans kingdom :

    Rien de bien complexe : je fais une symétrie verticale (avec échelle) et chaque pixel de destination en x est pioché au pif dans [x-1, x+1] de la source (ébauche) :

    void waterReflection(void)
    {
        SDL_Renderer* render = CEV_videoSystemGet()->render;
    
        uint32_t rmask, gmask, bmask, amask;
    
    #if SDL_BYTEORDER == SDL_BIG_ENDIAN
        rmask = 0xff000000;
        gmask = 0x00ff0000;
        bmask = 0x0000ff00;
        amask = 0x000000ff;
    #else
        rmask = 0x000000ff;
        gmask = 0x0000ff00;
        bmask = 0x00ff0000;
        amask = 0xff000000;
    #endif
    
        SDL_Surface* basePic = CEV_surfaceLoad("AngelsDemon.png"),// 320*240
                    *waterPic = SDL_CreateRGBSurface(0, 320, 120, 32, rmask, gmask, bmask, amask);
    
        SDL_Texture* dst = SDL_CreateTexture(render, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, 320, 360);    
    
        if(!(basePic && waterPic && dst))
            goto end;
    
        SDL_Rect baseBlit  = {0, 0, basePic->w, basePic->h},
                 waterBlit = {0, basePic->h, waterPic->w, waterPic->h};
    
        CEV_blitSurfaceToTexture(basePic, dst, NULL, &baseBlit);
    
        for(int i=0; i<100; i++) //100 test frames
        {
            for(int iy=0; iy<waterPic->h; iy++)
            {
                uint32_t* srcPxlLine = (uint32_t*)basePic->pixels + (iy* basePic->pitch/2),//1 on 2 as dstH = srcH/2
                        * dstPxlLine = (uint32_t*)waterPic->pixels + ((waterPic->h - iy - 1) * waterPic->pitch/4);//bottom to top
    
                for(int ix = 0; ix < basePic->w; ix++)
                {
                    int Loffset = CEV_irand(-1, 2);//[-1,2[
    
                    //not off pic
                    if(!ix && Loffset<0)
                        Loffset = 0;
                    else if ((ix == basePic->w-1) && Loffset>0)
                        Loffset = 0;
    
                    *(dstPxlLine + ix) = *(srcPxlLine + ix + Loffset);
                }
            }
            
            CEV_blitSurfaceToTexture(waterPic, dst, NULL, &waterBlit);
    
            SDL_RenderCopy(render, dst, NULL, NULL);
            SDL_RenderPresent(render);
            SDL_Delay(150);// 100~150 ok
        }
    
    end:
    
        SDL_FreeSurface(basePic);
        SDL_FreeSurface(waterPic);
        SDL_DestroyTexture(dst);
    }

    Cela fonctionne parfaitement avec une image fixe et un rendu qui me surprend moi-même vu la simplicité de l'algo. Je pourrais bien évidemment attaquer directement les pixels de la texture dst pour éviter les recopies (pixels de waterPic + CEV_blit), mais pour l'instant, je me concentre sur le rendu de l'algo, l'optimisation viendra plus tard.

    La contrainte étant de ne pas recalculer le reflet trop souvent pour ne pas tuer les épileptiques. Disons qu'entre 100 et 150 ms, ça fait à peu-près naturel.

    Pour l'incorporer à mon jeu, je me vois obligé de recalculer le reflet du monde dès que celui-ci évolue avec le scrolling, soit potentiellement toutes les 16/17 ms (60 ips) pour compléter à droite ou à gauche et refléter les joueur/pnj dans la bonne position, du coup c’est trop rapide et ça fait plus un flou qu'un reflet.

    Mon premier réflexe a été de passer par une table[32] de randomisation (le "filtre") contenant les valeurs de décalage au pif dans [-1, 1], ainsi en corrigeant l'index avec l'avance du scrolling, je peux conserver le même décalage par "colonne" et en plus modifier l'index (ou régénérer la table) toutes les 150 ms pour faire évoluer la génération du "filtre". Le problème est que comme j'utilise un index circulaire au sein d'une même génération (index = (index+1) & 31) cela créé un pattern du plus mauvais effet dans le reflet...

    Et là... C'est le drame. Je n'arrive pas à entrevoir une solution pour conserver mon reflet en face du monde en permanence sans que le "filtre" ne change trop souvent et qu'il soit fidèle au monde à tout moment... à part un filtre géant de la taille de l'image, ce qui me semble un peu lourd.

    Voilà donc... Si une astuce vous vient, je suis preneur.

    Merci.

    Salutations.

    -
    Edité par drx 14 juin 2020 à 17:19:29

    • Partager sur Facebook
    • Partager sur Twitter

    Bonhomme !! | Jeu de plateforme : Prototype.

    Anonyme
      14 juin 2020 à 22:54:04

      salut,

      un premier point que je vois serais un module à part entière pour gérer tous les problèmes de temps, par exemple une fonction qui, toutes les millisecondes, lance les fonctions qu'on lui a passées (via des pointeurs de fonction) et donc avec une fonction basique on aurait :

      // var
      static volatile int x = 0;
      
      // prototype
      void process_ms(void);
      
      // fonction
      void init(void){
         ma_fonction_ms(&process_ms);
      }
      
      void process_ms(void){
         static int ms = NBR_MS_TO_UPDATE;
      
         if(!ms){
            ms = NBR_MS_TO_UPDATE;
            // mise à jour de x
            x = rand()%3 - 1;
         }
         ms--;
      }

      c'est le best que je vois ^^

      Sinon si le nombre de frame ne bouge pas (et donc le nombre fois que tu passes dans la boucle par seconde) tu peux utiliser la méthode du filtre avec du random. Tu peux définir le nombre de coupure dans tableau que tu vas avoir, par exemple 3 sur un tableau de 30, donc de 0 à 9 sera rand()%2 -1, de 10 à 19 ... jusqu'à la fin, tu auras donc un tableau avec 3 parties remplis du même nombre random.

      Pour modifier et rendre un peu plus "vivant" tu peux encore faire du random pour déterminer la taille des parties : tu tires autant de nombres aléatoires que de parties (avec un modulo pour réduire la taille des nombres). La somme de tous ces nombres correspondra à la taille de ton tableau et par une règle de trois tu peux convertir chaque nombre à la taille d'une partie du filtre qui sera remplie avec le même le nombre.

      j'espère que j'aurais été assez claire car c'est pas très facile à explique mais en gros prend en pour référence la barre en dessous de la map :

      https://gamespot1.cbsistatic.com/uploads/scale_super/1581/15811374/3495559-apex%20legends%20snipe.jpg

      • Partager sur Facebook
      • Partager sur Twitter
        15 juin 2020 à 0:44:48

        Salut,

        Du coup, j'ai fait un grand filtre précalculé de 32 lignes par la largeur de l'image. Mais la SDL2, c'est la galère pour les accès aux pixels des textures plus l'impossibilité d'appliquer une texture sur une autre (sans ACCESS_TARGET)... C'est vachement plus simple avec les surfaces. Du coup, je suis obligé de relire le renderer...

        J'ai fait ça pour l'instant, rien n'est encore sécurisé :

        typedef struct CEV_WaterReflection
        {
            char direction,//effect direction
                *filter;   //filter array
        
            CEV_Timer timer;//filter generation tick
        
            uint32_t index;//filter row index
        
            SDL_Rect srcClip, //what part of render to reflect
                     dstBlit; //where to display (dim only)
        
            SDL_Texture *water;
            void *pixels; //to copy render
        }
        CEV_WaterReflection;
        
        CEV_WaterReflection* CEV_waterCreate(int w, int h, unsigned int refreshRate, SDL_Rect srcRect);
        
        void CEV_waterShow(CEV_WaterReflection* effect, SDL_Rect blitPos);
        
        void CEV_waterDestroy(CEV_WaterReflection* effect);
        void waterReflectionTest(void)
        {
            SDL_Renderer* render = CEV_videoSystemGet()->render;
        
            SDL_Texture* basePic = CEV_textureLoad("AngelsDemon.png");// 320*240
            SDL_Rect baseBlit = {0, 0, 320, 240},
                     waterBlit = {0, 240, 320, 120};
        
            CEV_WaterReflection* reflect = CEV_waterCreate(320, 120, 150, (SDL_Rect){0, 0, 320, 240}/*baseBlit*/);
        
            if(!(basePic && reflect))
                goto end;
            
        
            SDL_RenderCopy(render, basePic, NULL, &baseBlit);
        
            CEV_inputClear();
        
            while(!CEV_inputUpdate())
            {        
                CEV_waterShow(reflect, waterBlit);
                SDL_RenderPresent(render);
                SDL_Delay(20);
            }
        
        end:
            SDL_DestroyTexture(basePic);
            CEV_waterDestroy(reflect);
        }
        
        
        
        CEV_WaterReflection* CEV_waterCreate(int w, int h, unsigned int refreshRate, SDL_Rect srcClip)
        {
            CEV_WaterReflection* result = calloc(1, sizeof(CEV_WaterReflection));
        
            result->dstBlit = (SDL_Rect){0, 0, w, h};
            result->srcClip = srcClip;
            result->index = 0;
            result->direction = -1;
        
            result->pixels = calloc(4, srcClip.w * srcClip.h);
        
            result->filter = calloc(32, w);
        
            for(int iy=0; iy<32; iy++)
            {
                int row = iy * w;
        
                for(int ix=0; ix<w; ix++)
                {
                    result->filter[ix + row] = CEV_irand((ix>0)? -1 : 0 ,
                                                        (ix == w-1)? 1: 2);
                }
            }
            
            result->water = SDL_CreateTexture(CEV_videoSystemGet()->render,
                                                SDL_PIXELFORMAT_ABGR8888,
                                                SDL_TEXTUREACCESS_STREAMING,
                                                w, h);
        
            CEV_timerInit(&result->timer, refreshRate);
            result->timer.run = true;
        
            return result;
        }
        
        
        void CEV_waterDestroy(CEV_WaterReflection* effect)
        {
            free(effect->pixels);
            free(effect->filter);
            SDL_DestroyTexture(effect->water);
            free(effect);
        }
        
        
        void CEV_waterShow(CEV_WaterReflection* effect, SDL_Rect blitPos)
        {
            SDL_Renderer* render = CEV_videoSystemGet()->render;
        
            SDL_RenderReadPixels(render, &effect->srcClip, SDL_PIXELFORMAT_ABGR8888, effect->pixels, 4*effect->dstBlit.w);
        
            uint32_t *srcPxl = (uint32_t*)effect->pixels,
                     *dstPxl;
        
            int pitch;
        
            SDL_LockTexture(effect->water, NULL, (void**)&dstPxl, &pitch);
            pitch /= 4;
        
            for(int iy=0; iy<effect->dstBlit.h; iy++)
            {
                int clipRow = (effect->srcClip.h - (int)CEV_map(iy, 0, effect->dstBlit.h, 0, effect->srcClip.h) - 1);
        
                uint32_t* srcPxlRow = srcPxl + (clipRow * pitch),
                        * dstPxlRow = dstPxl + (iy * pitch);
        
                int filterRow = ((iy + effect->index) & 31) * effect->dstBlit.w;
        
                for(int ix = 0; ix < effect->dstBlit.w; ix++)
                {
                    int Loffset = effect->filter[ix + filterRow];
        
                    *(dstPxlRow + ix) = *(srcPxlRow + ix + Loffset);
                }
            }
        
            SDL_UnlockTexture(effect->water);
        
            SDL_RenderCopy(render, effect->water, NULL, &blitPos);
        
            if(CEV_timerRepeat(&effect->timer))
                effect->index++;
        
        }

        La doc semble dire qu'il est lent de relire le renderer, mais je n'ai pas le choix. Cela semble assez rapide pour le framerate imposé, donc pas de souci.

        ça me semble pas trop mal, je vais tester un render plus large et faire bouger l'image pour voir si ça rend correct.

        Merci.

        -
        Edité par drx 15 juin 2020 à 1:00:02

        • Partager sur Facebook
        • Partager sur Twitter

        Bonhomme !! | Jeu de plateforme : Prototype.

        Anonyme
          15 juin 2020 à 9:15:07

          ah d'accord je commence à comprendre toutes la subtilité du problème. Tu ne veux pas appliquer un [x-1, x+1] à ton image mais une partie de ton image.

          Ce que je penses à premièrement c'est de découpé ton image en plusieurs partie que tu feras bougé indépendamment. J'ai utiliser les SDL_Rect de SDL_RenderCopyEx pour pouvoir appliquer des zoom, mais tu peux aussi l'utiliser pour couper ton image sans faire de copie ou de la relire. C'est très délicat à utiliser mais voilà un code que j'ai fais en C++ qui utilise la SDL et ce principe de Rect pour appliquer des zooms et autres par rapport à une image de référence.

          https://1drv.ms/u/s!Ao4n72iAXMvig7k1aVDIYjPAFRSHRg?e=xerhnO

          Tu peux y aller en brute force avec cette méthode pour prendre pixel par pixel

          Si tu peux aussi envoyer des images du rendu car je ne vois pas très bien les problèmes que tu rencontres ^^

          • Partager sur Facebook
          • Partager sur Twitter

          [SDL2] Refleter le monde

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown