суббота, 25 июня 2011 г.

Четыре метода использования Вершинных Текстур (Vertex Textures) Часть 9

 

Использование Вершинных Текстур в играх на платформе XNA (перевод)

Catalin Zima
Создание финальной карты смещения
Для финального этапа нашего урока нам необходим еще один файл шейдера, содержащий несколько техник, для получения карты смещения. Как уже мы это делали не раз, в папке Shaders создайте новый файл и назовите его Snow.fx. Для будущих техник нам понадобятся текстуры inputMap, randomMap, snowMap и deformationMap. Текстура inputMap будет использоваться при копировании данных из одной цели визуализации в другую, randomMap будет содержать рандомные данные, snowMap содержать данные смещения, а deformationMap, как понятно данные деформации.
    texture inputMap;
    sampler inputSampler : register(s0)  = sampler_state
    {
        Texture   = <inputMap>;
        
        MipFilter = Linear;
        MinFilter = Linear;
        MagFilter = Linear;
        
        AddressU  = Clamp;
        AddressV  = Clamp;
    };
    
    texture randomMap;
    sampler randomSampler : register(s0)= sampler_state
    {
        Texture   = <randomMap>;
        
        MipFilter = Linear;
        MinFilter = Linear;
        MagFilter = Linear;
        
        AddressU  = mirror;
        AddressV  = mirror;
    };
    
    texture snowMap;
    sampler snowSampler = sampler_state
    {
        Texture   = <snowMap>;
        
        MipFilter = Linear;
        MinFilter = Linear;
        MagFilter = Linear;
        
        AddressU  = Clamp;
        AddressV  = Clamp;
    };
 
    texture deformationMap;
    sampler deformationSampler = sampler_state
    {
        Texture   = <deformationMap>;
        
        MipFilter = Linear;
        MinFilter = Linear;
        MagFilter = Linear;
        
        AddressU  = Clamp;
        AddressV  = Clamp;
    };
 

При инициализации мы устанавливаем высоту снежного покрова в значение 32:
    float4 InitSnowPS(in float2 uv : TEXCOORD0) : COLOR
    {
        return 32;
    }
 

При деформации снежного покрова мы просто вычитаем данные деформации и текущих данных высоты:
    float4 AddDeformationPS(in float2 uv : TEXCOORD0) : COLOR
    {
        float height        = tex2D(snowSampler, uv);
        
        float deformation   = tex2D(deformationSampler, uv);
        
        return clamp(height - deformation, 0, 32);
    }

Для “восстановления” снежного покрова, мы прибавляем к данным высоты случайные значения, которые считываем из текстуры randomMap. Значение randomNumbers меняется на каждом кадре, чтобы распределить данные по всей поверхности.
    float2 randomNumbers;
    float4 AccumulateSnowPS(in float2 uv : TEXCOORD0) : COLOR
    {
            float accumulatedSnow = 0.5 * tex2D(randomSampler,
                                                uv + randomNumbers);
                                                
            float height = tex2D(snowSampler, uv);
            
            return clamp(accumulatedSnow + height, 0, 32);
    }
 

Перед тем как скомбинировать карты деформации и смещения, мы для начала немного “размоем” (blur) карту деформации, что приведет к ее сглаживанию. Этот шейдер будет использован так:
    static float2 blurOffset[8] = 
    {
        float2( 1 / 256.0f ,   1 / 256.0f), 
        float2(-1 / 256.0f ,  -1 / 256.0f), 
        float2(-1 / 256.0f ,   1 / 256.0f), 
        float2( 1 / 256.0f ,  -1 / 256.0f), 
        float2( 1 / 256.0f ,   0.0f), 
        float2(-1 / 256.0f ,   0.0f), 
        float2(0.0f ,          1 / 256.0f), 
        float2(0.0f ,         -1 / 256.0f) 
    };
    
    float4 BlurPS(in float2 uv : TEXCOORD0) : COLOR
    {
        float4 color = tex2D(deformationSampler, uv);
        
        for( int i = 0; i < 8; i++)
        {
            color += tex2D(deformationSampler, 
                           uv + blurOffset[i]);
        }
        
        return color / 9.0f;
    }

Далее приведен пиксельный шейдер для копирования текстур и техники для каждого из шейдеров.
     float4 CopyTexturePS(in float2 uv : TEXCOORD0) : COLOR
    {
        return tex2D(inputSampler, uv);
    }
    
    technique AddDeformation
    {
        pass P0
        {
            pixelShader  = compile ps_3_0 AddDeformationPS();
        }
    }
 
    technique AccumulateSnow
    {
        pass P0
        {
            pixelShader  = compile ps_3_0 AccumulateSnowPS();
        }
    }
    technique BlurDeformation
    {
        pass P0
        {
            pixelShader  = compile ps_3_0 BlurPS();
        }
    }
    technique InitSnow
    {
        pass P0
        {
            pixelShader  = compile ps_3_0 InitSnowPS();
        }
    }
    technique CopyTexture
    {
        pass P0
        {
            pixelShader  = compile ps_3_0 CopyTexturePS();
        }
    }

Возвращаемся в Game1.cs и загружаем шейдер в методе LoadGraphicsContent:
    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            [...]
            snowEffect = content.Load<Effect>("Shaders\\Snow");
        }
        [...]
    }
 

Так же как и в уроке с системой частиц нам необходим вспомогательный метод, который будет принимать на вход наименования техник и реализовывать работу с целями визуализации. Мы добавим функцию, которая берёт в качестве параметра строку и цель визуализации, в которую будет помещён результат. Сохраним её старое значение, определим временное, применим желаемую технику, и после этого скопируем результат из временного значения render target в предназначенную render target.
    private void DoSnowTechnique(string technique, 
                                 RenderTarget2D resultTarget)
    {
        RenderTarget2D oldRT = graphics.GraphicsDevice.GetRenderTarget(0) 
                                                            as RenderTarget2D;
                                                            
        DepthStencilBuffer oldDS = graphics.GraphicsDevice.DepthStencilBuffer;
 
        graphics.GraphicsDevice.DepthStencilBuffer = snowDepth;
        graphics.GraphicsDevice.SetRenderTarget(0, temporaryRT);
 
        graphics.GraphicsDevice.Clear(Color.White);
        
        spriteBatch.Begin(SpriteBlendMode.None,
                          SpriteSortMode.Immediate,
                          SaveStateMode.None);
 
        snowEffect.CurrentTechnique = snowEffect.Techniques[technique];
        snowEffect.Begin();
 
        if (isSnowReset)
        {
            snowEffect.Parameters["snowMap"].SetValue(
                                            snowRT.GetTexture());
            
            snowEffect.Parameters["deformationMap"].SetValue(
                                            deformationRT.GetTexture());
        }
        snowEffect.Parameters["randomNumbers"].SetValue(
                                            new Vector2((float)random.NextDouble(), 
                                                        (float)random.NextDouble()));
                                                
        snowEffect.CurrentTechnique.Passes[0].Begin();
        
        spriteBatch.Draw(noiseTexture, 
                         new Rectangle(0, 0, 256, 256), 
                         Color.White);
                         
        snowEffect.CurrentTechnique.Passes[0].End();
        snowEffect.End();
        spriteBatch.End();
 
        graphics.GraphicsDevice.ResolveRenderTarget(0);
 
        graphics.GraphicsDevice.SetRenderTarget(0, resultTarget);
 
        spriteBatch.Begin(SpriteBlendMode.None,
                          SpriteSortMode.Immediate,
                          SaveStateMode.None);
 
        snowEffect.CurrentTechnique = snowEffect.Techniques["CopyTexture"];
        
        snowEffect.Begin();
        snowEffect.CurrentTechnique.Passes[0].Begin();
        
        spriteBatch.Draw(temporaryRT.GetTexture(), 
                         new Rectangle(0, 0, 256, 256), 
                         Color.White);
                         
        snowEffect.CurrentTechnique.Passes[0].End();
        snowEffect.End();
        spriteBatch.End();
 
        graphics.GraphicsDevice.ResolveRenderTarget(0);
        graphics.GraphicsDevice.SetRenderTarget(0, oldRT);
        graphics.GraphicsDevice.DepthStencilBuffer = oldDS;
    }

Даная функция будет вызываться с различными параметрами. Для начала, если данные о снежном покрове должны быть сброшены, мы вызываем ее с указанием техники InitSnow. Затем первичная отрисовка карты деформации, затем ее размытие, затем “спекание” с картой смещения и, наконец, “восстановление” снежного покрова. Все эти вызовы мы помещаем в начало метода Draw(). И только после этого мы можем передать полученную текстуру в параметр displacementMap шейдера VTFSnow.
    void UpdateSnow()
    {
        if (!isSnowReset)
        {
            DoSnowTechnique("InitSnow", snowRT);
            isSnowReset = true;
        }
        
        RenderDeformationMap();
        
        DoSnowTechnique("BlurDeformation", deformationRT);
        DoSnowTechnique("AddDeformation",  snowRT);
        DoSnowTechnique("AccumulateSnow",  snowRT);
    }
 
    protected override void Draw(GameTime gameTime)
    {
        UpdateSnow();
 
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
        
        graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
 
        gridEffect.Parameters["displacementMap"].SetValue(
                                                snowRT.GetTexture());
        [...]
    }
 

Запустите приложение и Вы увидите как персонаж, несколько не напрягаясь, преодолевает снежные сугробы J

clip_image002

clip_image004

В данном разделе мы рассмотрели как можно сгенерировать карту деформации путем отрисовки сцены, используя специфичные матрицы, как скомбинировать полученную карту с картой смещения и как получить эффект оставления следов на снегу. Данный эффект можно скомбинировать с алгоритмом генерации нормалей для нормального освещения и с системой частиц для более реалистичного результата. Если Вы решите реализовать систему частиц, то можете реализовать “восстановление” снежного покрова на основе текстуры, содержащей данные о местоположении частиц, и заставить частицы “умирать”, когда они касаются снега, основываясь на данных карты смещения.

Полный код этого раздела Вы можете скачать тут: Chapter4.zip

Заключение и будущие разработки

В данном уроке мы рассмотрели четыре способа использования вершинных текстур, но мы только прикоснулись к этому большому разделу, где их можно использовать. Эффекты, рассмотренные здесь, были реализованы на скорую руку, и если немного подумать, то можно было написать более интересную и более функциональную реализацию.

Для отрисовки ландшафта можно использовать вершинную текстуру с различной плотностью сетки, более высокую плотность использовать в том месте, куда у нас в данный момент смотрит камера, и смещать ее по мере движения камеры по сцене. При таком подходе мы получим автоматическую реализацию LOD (Level Of Detail) ландшафта. Это вполне реализуемо с VTF, так как данные высоты постоянно рассчитываются в вершинном шейдере. Это использовано в технике geometric clipmaps разработанной Hugues Hoppe и Frank Losasso .

Существует большое количество способов для усовершенствования системы частиц. Не так давно поток (течение) Lord Ikon’а на форуме creators.xna.com натолкнул на идею по усовершенствованию системы частиц. Первое, это определение столкновений частиц со всеми объектами сцены. Мы все видели, как это реализовать на примере с ландшафтом, когда у нас была карта высот. Но, немного подумав, я понял, как это реализовать со всеми объектами сцены. Для этого нам надо отрисовать сцену с положения камеры “сверху вниз” и сгенерировать динамическую карту высот для всей сцены вместе со всеми объектами. Так как мы только что закончили четвертую часть урока, не так уж и трудно себе представить, как это сделать, с помощью ортогональной матрицы и специального пиксельного шейдера. Полученную карту вида сверху затем использовать в пиксельном шейдере системы частиц. Это особенно окажется действенным для частиц, которые имеют свойство падать с неба: снег, дождь. Вид камеры может быть немного подправлен, если нам нужен будет не “прямой” дождь, а идущий немного под углом. Еще одно дополнение к системе частиц, это если взглянуть на наш пример и поперемещаться внутри системы, то можно заметить разницу в плотности идущих частиц. Все было бы нормально, если бы мы держали систему частиц оцентрованной относительно положения наблюдателя. Но для этого надо решить одну проблему, надо чтобы частицы не двигались вслед за наблюдателем. Я до сих пор работаю над этим и как только найду решение, дам знать.

Существует и другие эффекты, использующие вершинные текстуры: симуляция ткани (cloth simulation), реалистичный рендеринг воды, который был рассмотрен в GPU Gems 2, и у ATI есть пример сортировки спрайтов, используя R2VB, который можно переписать с использованием вершинных текстур.

Я надеюсь, Вам понравился данный урок, и он подтолкнет Вас на дальнейшие эксперименты с вершинными текстурами и поиск новых путей их использования. Я уверен, что Вы сообщите мне, если придумает что-нибудь интересное. J

Комментариев нет:

Отправить комментарий