Нашли ошибку или опечатку? Выделите текст и нажмите

Поменять цветовую

гамму сайта?

Поменять
Обновления сайта
и новые разделы

Рекомендовать в Google +1

Текстуры

142

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

Раскрашивать пиксели можно не только путем интерполяции цветов, но и путем наложения текстуры — битовой карты, содержащей предопределенное изображение. Каждая текстура является двухмерной битовой картой и содержится в обычном графическом файле. Однако, когда вы наносите двухмерную текстуру на трехмерную фигуру, текстура растягивается и переносится на смежные грани, чтобы заполнить контур фигуры. Можете представлять себе этот процесс как укладку какого-либо предмета (трехмерного) в тонкую пластиковую обертку (двухмерную), например конфеты — в фантик или коробки в бумагу.

В рассматриваемом далее примере вы наложите текстуру на куб. Первый этап — изменение способа определения вершин. Для тонирования фигуры используется класс VertexPositionColor, а для наложения текстуры — класс VertexPositionTexture:

VertexPositionTexture[] vertices = new VertexPositionTexture[36];

Как и для класса VertexPositionColor, для класса VertexPostionTexture необходим объект Vector3, представляющий вершину. Но вместо ассоциирования с объектом Color он связывается с координатами текстуры хранящимися в объекте Vector2:

vertices[0] = new VertexPositionTexture(
   topRightFront, new Vector2(1, 0));

На первый взгляд это выглядит довольно странно. Ведь, казалось бы, класс VertexPositionTexture должен объединить вершину с объектом текстуры. Однако этого не происходит вследствие способа работы с текстурой. Один объект, такой, как например, куб, обычно имеет одну текстуру, покрывающую всю его поверхность. Поэтому ассоциировать текстуру с вершинами не имеет смысла. Мы получили бы для каждого треугольника три текстуры, что, очевидно, неприемлемо.

Как же задаются координаты текстуры? Важно отметить, что они сообщают Silverlight с какой точки текстуры нужно начать считывание графических данных. В предыдущем примере первая вершина ассоциирована с точкой (1,0) в изображении текстуры. В координатах текстуры применяется масштаб "нуль к единице", и точка (1,0) фактически представляет правый верхний угол текстуры. Поэтому первая вершина, расположенная в правом верхнем углу передней грани куба, прикреплена к верхнему правому углу текстуры:

Координаты текстуры

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

Vector2 textureTopLeft = new Vector2(0, 0);
Vector2 textureTopRight = new Vector2(1, 0);
Vector2 textureBottomLeft = new Vector2(0, 1);
Vector2 textureBottomRight = new Vector2(1, 1);

Тогда вы сможете применить эти координаты для построения VertexPositionTexture:

vertices[0] = new VertexPositionTexture(topRightFront, textureTopRight);
vertices[1] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
vertices[2] = new VertexPositionTexture(topLeftFront, textureTopLeft);
vertices[3] = new VertexPositionTexture(topRightFront, textureTopRight);
vertices[4] = new VertexPositionTexture(bottomRightFront, textureBottomRight);
vertices[5] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
...

Данный код располагает текстуру на одной грани куба. Важно отметить, что в двух смежных треугольниках используются согласованные координаты текстуры. Например, точка topRightFront включена в оба треугольника одной грани, причем в обоих случаях она ассоциируется с одной и той же координатой текстуры.

После определения всех вершин с помощью объектов VertexPositionTexture можно создать объект VertexBuffer и камеру аналогично тому, как это сделано в примере показанном в предыдущей статье.

Следующий этап — конфигурирование объекта BasicEffect. Здесь все иначе, чем в предыдущем примере. Вместо установки свойства BasicEffect.VertexColorEnabled мы зададим использование встроенных средств наложения текстуры с помощью свойства BasicEffect.TextureEnabled.

Далее нужно загрузить изображение текстуры (обычно его загружают из файла ресурсов проекта) и применить его для создания объекта Texture2D. Этот процесс немного запутанный. Сначала необходимо извлечь данные из потока и записать их в объект BitmapImage, а затем переместить их из BitmapImage в Texture2D. И наконец, нужно передать объект Texture2D объекту BasicEffect путем установки его свойства Texture.

Ниже представлен код метода PrepareDrawing() с указанными выше изменениями:

private void PrepareDrawing()
{
            // Координаты текстуры
            Vector2 textureTopLeft = new Vector2(0, 0);
            Vector2 textureTopRight = new Vector2(1, 0);
            Vector2 textureBottomLeft = new Vector2(0, 1);
            Vector2 textureBottomRight = new Vector2(1, 1);

            Vector3 topLeftFront = new Vector3(-1, 1, 1);
            Vector3 bottomLeftFront = new Vector3(-1, -1, 1);
            Vector3 topRightFront = new Vector3(1, 1, 1);
            Vector3 bottomRightFront = new Vector3(1, -1, 1);
            Vector3 topLeftBack = new Vector3(-1, 1, -1);
            Vector3 topRightBack = new Vector3(1, 1, -1);
            Vector3 bottomLeftBack = new Vector3(-1, -1, -1);
            Vector3 bottomRightBack = new Vector3(1, -1, -1);

            // Массив вершин
            VertexPositionTexture[] vertices = new VertexPositionTexture[36];

            // Задание всех вершин
            // Передняя грань
            vertices[0] = new VertexPositionTexture(topRightFront, textureTopRight);
            vertices[1] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
            vertices[2] = new VertexPositionTexture(topLeftFront, textureTopLeft);
            vertices[3] = new VertexPositionTexture(topRightFront, textureTopRight);
            vertices[4] = new VertexPositionTexture(bottomRightFront, textureBottomRight);
            vertices[5] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
            // Задняя грань
            vertices[6] = new VertexPositionTexture(bottomLeftBack, textureBottomRight);
            vertices[7] = new VertexPositionTexture(topRightBack, textureTopLeft);
            vertices[8] = new VertexPositionTexture(topLeftBack, textureTopRight);
            vertices[9] = new VertexPositionTexture(bottomRightBack, textureBottomLeft);
            vertices[10] = new VertexPositionTexture(topRightBack, textureTopLeft);
            vertices[11] = new VertexPositionTexture(bottomLeftBack, textureBottomRight);
            // Верх
            vertices[12] = new VertexPositionTexture(topLeftBack, textureTopLeft);
            vertices[13] = new VertexPositionTexture(topRightBack, textureTopRight);
            vertices[14] = new VertexPositionTexture(topLeftFront, textureBottomLeft);
            vertices[15] = new VertexPositionTexture(topRightBack, textureTopRight);
            vertices[16] = new VertexPositionTexture(topRightFront, textureBottomRight);
            vertices[17] = new VertexPositionTexture(topLeftFront, textureBottomLeft);
            // Низ
            vertices[18] = new VertexPositionTexture(bottomRightBack, textureTopLeft);
            vertices[19] = new VertexPositionTexture(bottomLeftBack, textureTopRight);
            vertices[20] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
            vertices[21] = new VertexPositionTexture(bottomRightFront, textureBottomLeft);
            vertices[22] = new VertexPositionTexture(bottomRightBack, textureTopLeft);
            vertices[23] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
            // Левая грань
            vertices[24] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
            vertices[25] = new VertexPositionTexture(bottomLeftBack, textureBottomLeft);
            vertices[26] = new VertexPositionTexture(topLeftFront, textureTopRight);
            vertices[27] = new VertexPositionTexture(topLeftFront, textureTopRight);
            vertices[28] = new VertexPositionTexture(bottomLeftBack, textureBottomLeft);
            vertices[29] = new VertexPositionTexture(topLeftBack, textureTopLeft);
            // Правая грань
            vertices[30] = new VertexPositionTexture(bottomRightBack, textureBottomRight);
            vertices[31] = new VertexPositionTexture(bottomRightFront, textureBottomLeft);
            vertices[32] = new VertexPositionTexture(topRightFront, textureTopLeft);
            vertices[33] = new VertexPositionTexture(bottomRightBack, textureBottomRight);
            vertices[34] = new VertexPositionTexture(topRightFront, textureTopLeft);
            vertices[35] = new VertexPositionTexture(topRightBack, textureTopRight);

            // Установка буфера вершин
            GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
            vertexBuffer = new VertexBuffer(device, typeof(VertexPositionTexture), vertices.Length, BufferUsage.WriteOnly);
            vertexBuffer.SetData(0, vertices, 0, vertices.Length, 0);

            // Настройка камеры
            Matrix view = Matrix.CreateLookAt(new Vector3(1, 1, 3), Vector3.Zero, Vector3.Up);
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 1.33f, 1, 100);

            // Задание эффекта
            effect = new BasicEffect(device);
            effect.World = Matrix.Identity;
            effect.View = view;
            effect.Projection = view * projection;

            // Загрузка текстуры из рисунка в объект BitmapImage
            string uri = "Silverlight 3D;component/super_man.png";
            Stream s = Application.GetResourceStream(new Uri(uri, UriKind.Relative)).Stream;
            BitmapImage bmp = new BitmapImage();
            bmp.SetSource(s);

            // Передача рисунка объекту Texture2D
            Texture2D texture;
            texture = new Texture2D(device, bmp.PixelWidth, bmp.PixelHeight);
            bmp.CopyTo(texture);

            // Установка текстуры     
            effect.TextureEnabled = true;
            effect.Texture = texture;
}

Последний этап — небольшая коррекция кода рисования путем добавления небольшой инструкции:

private void DrawingSurface_Draw_1(object sender, DrawEventArgs e)
{
            GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;

            // Автоматическое отображение текстуры
            device.SamplerStates[0] = SamplerState.LinearClamp;
            
            ...

Данная инструкция позволяет драйверу XNA графически отобразить любую текстуру. Без поля SampleState.LinearClamp для графики текстуры понадобились бы размеры в пикселях, пропорциональные степеням двойки (например были бы изображения 2x2, 4x4, 8x8, 32x32, но не 40x40). При использовании поля SampleStete.LinearClamp данное неуклюжее ограничение снимается.

Остальная часть кода остается прежней. Код по-прежнему визуализирует список треугольников с помощью метода DrawPrimiteves(). Единственное отличие состоит в том, что объект BasicEffect теперь конфигурируется на использование текстуры.

Куб с текстурой

Отображать всю текстуру не обязательно. Посмотрим, например, что произойдет, если заменить в координатах текстуры каждое вхождение единицы на 0,5:

// Координаты текстуры
Vector2 textureTopLeft = new Vector2(0, 0);
Vector2 textureTopRight = new Vector2(0.5f, 0);
Vector2 textureBottomLeft = new Vector2(0, 0.5f);
Vector2 textureBottomRight = new Vector2(0.5f, 0.5f);

Теперь куб захватит только верхнюю левую четверть изображения и применит его на каждой грани. Это объясняется тем, что точка x=0.5 представляет позицию на полпути по оси X (аналогично y=0.5):

Отображение части текстуры

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

Пройди тесты