netlib.narod.ru | < Назад | Оглавление | Далее > |
Чтобы поместить шейдер, который вы только что написали, в ваш графический движок, вы делаете в основном те же самые вещи, которые FX Composer делает, чтобы показать вам шейдер. Благодаря DirectX или XNA легко загрузить файл шейдерного эффекта и установить все требуемые параметры (вы уже делали это в предыдущей главе для шейдера визуализации линий). В следующей главе вы перейдете к использованию более общего класса, который принимает много различных шейдеров и использует более оптимизированный способ установки всех необходимых шейдерам параметров, а в этой главе мы просто заставим файл SimpleShader.fx работать в вашем движке.
Как обычно, начнем с определения тестового модуля. Создайте новый файл с именем simpleShader.cs в пространстве имен Shaders и напишите следующий код:
#region Тестирование модуля public static void TestSimpleShader() { SimpleShader shader = null; Model testModel = null; Texture testTexture = null; TestGame.Start("Test SimpleShader.fx", delegate { shader = new SimpleShader(); testModel = new Model("Apple"); testTexture = new Texture("Marble"); }, delegate { // Визуализация модели shader.RenderModel(testModel.XnaModel, testTexture); }); } // TestSimpleShader #endregion
Тестовый модуль должен компилироваться, за исключением метода renderModel нового класса SimpleShader, который еще не определен. Давайте быстро определим этот метод:
public void RenderModel(XnaModel someModel, Texture texture) { } // RenderModel(someModel, texture)
Теперь тестовый модуль компилируется, и вы можете запустить его, чтобы увидеть пустой экран.
Чтобы наш маленький тестовый модуль показал на экране что-нибудь полезное, надо сперва загрузить и скомпилировать шейдер. Для этого вы используете конвейер содержимого XNA, как раньше делали для моделей и текстур. Просто перетащите файл .fx в ваш проект (поместите его в каталог содержимого вместе с остальными текстурами и моделями) и он автоматически будет скомпилирован при построении проекта. Вы даже получите сообщение об ошибке компиляции шейдера, если шейдер не компилируется. Благодаря этому вам не надо запускать вашу игру и затем перехватывать исключения, генерируемые компилятором шейдеров во время выполнения; вы уже уверены в корректности шейдеров до запуска игры. Пока вы здесь, убедитесь, что текстура marble.dds также находится в каталоге содержимого и компилируется XNA; вы используете эту текстуру в тестовом модуле.
Загрузить скомпилированный эффект так же просто, как загрузить текстуру. Просто определите переменную эффекта, а затем загрузите ее в конструкторе SimpleShader:
#region Переменные Effect effect = null; #endregion #region Конструктор public SimpleShader() { effect = BaseGame.Content.Load<Effect>( Path.Combine(Directories.ContentDirectory, "SimpleShader")); } // SimpleShader() #endregion
На платформе Windows вы также можете загружать шейдеры динамически (в этом случае они не будут заранее скомпилированы XNA Framework), что может быть полезно для тестирования и изменения шейдеров во время выполнения вашей игры. Обычно во время разработки для большинства шейдеров я использую некомпилированные файлы шейдеров, а затем помещаю их в конвейер содержимого, когда игра завершена и я больше не собираюсь изменять шейдеры. Для самостоятельной загрузки и компиляции шейдеров используется следующий код:
CompiledEffect compiledEffect = Effect.CompileEffectFromFile( Path.Combine("Shaders", shaderContentName + ".fx"), null, null, CompilerOptions.None, TargetPlatform.Windows); effect = new Effect(BaseGame.Device, compiledEffect.GetEffectCode(), CompilerOptions.None, null);
Пожалуйста, обратите внимание, что эти классы и методы доступны только на платформе Windows. Используйте директивы #if !XBOX360 #endif вокруг этих строк, если хотите, чтобы ваш код можно было компилировать для Xbox 360 (где динамическая перезагрузка шейдеров не поддерживается и вообще не имеет большого смысла).
Как вы узнали в процессе создания шейдера, мировая матрица, матрица вида и матрица проекции действительно важны для преобразования ваших трехмерных данных и получения корректного результата на экране. Для установки всех этих параметров шейдера вы просто используете метод RenderModel, который вызывается из вашего тестового модуля:
BaseGame.WorldMatrix = Matrix.CreateScale(0.25f, 0.25f, 0.25f); effect.Parameters["worldViewProj"].SetValue( BaseGame.WorldMatrix * BaseGame.ViewMatrix * BaseGame.ProjectionMatrix); effect.Parameters["world"].SetValue( BaseGame.WorldMatrix); effect.Parameters["viewInverse"].SetValue( BaseGame.InverseViewMatrix); effect.Parameters["lightDir"].SetValue( BaseGame.LightDirection); effect.Parameters["diffuseTexture"].SetValue( texture.XnaTexture);
Сначала в этом коде устанавливается мировая матрица. Это очень важно, поскольку если не устанавливать мировую матрицу, она может содержать абсолютно сумасшедшие значения от каких-нибудь предыдущих операций, и трехмерная модель будет визуализирована в каком-нибудь случайном месте, а не там, где вам надо. Поскольку модель яблока достаточно большая, вы слегка уменьшаете ее масштаб, чтобы она соответствовала размерам экрана в используемом по умолчанию классе SimpleCamera из предыдущей главы.
Затем вы вычисляете матрицу worldVievProj и также устанавливаете все другие матрицы, значение lightDir, и даже diffuseTexture, что очень важно, поскольку простая загрузка шейдерного эффекта не загружает автоматически ни одну из текстур, как это происходило в FX Composer; вы должны устанавливать эти значения самостоятельно. Если вы загружаете модель из конвейера содержимого XNA, XNA немного поможет вам и автоматически загрузит все используемые текстуры из данных модели. В вашем случае вы просто устанавливаете текстуру, которую загрузили в тестовом модуле, чтобы была показана текстура marble.dds, как и в FX Composer.
Перед тем, как визуализировать данные из вашей трехмерной модели яблока, вы должны убедиться, что ваше приложение и шейдер знают, какой формат вершин использовать. В DirectX это достигалось путем простой установки одного из предопределенных форматов вершин фиксированного конвейера функций, но эти форматы больше не существуют, и вы не можете установить их. Вместо этого вы должны определить полное объявление вершины, также как делали это в шейдере для структуры VertexInput. Поскольку мы просто используем встроенную структуру VertexPositionNormalTexture, нам не надо ничего определять самостоятельно, но в следующей главе вы узнаете, как сделать это для собственного нестандартного формата TangentVertex.
// Используем формат вершин VertexPositionNormalTexture // в SimpleShader.fx BaseGame.Device.VertexDeclaration = new VertexDeclaration(BaseGame.Device, VertexPositionNormalTexture.VertexElements);
На самом деле вам не нужно создавать новое объявление вершин каждый раз, когда вы вызываете RenderModel, но чтобы упростить материал я строю новое объявление вершин при каждом вызове. В первом параметре передается графическое устройство, а во втором — массив элементов вершин из структуры XNA VertexPositionNormalTexture. За дополнительной информацией обращайтесь к главе 7.
Чтобы теперь визуализировать яблоко с вашим шейдером необходимо сначала указать, какую технику вы хотите использовать (в любом случае, по умолчанию будет установлена первая техника, но полезно знать, как устанавливать техники). Для визуализации шейдера вы всегда будете использовать свойство CurrentTechnique класса эффекта. Затем вы проходите по технике так же, как это делает FX Composer, — вы рисуете все трехмерные данные для каждого прохода, который есть в технике (как я говорил раньше, обычно у вас будет только один проход). Визуализация самого яблока не то чтобы очень проста, поскольку единственный метод, который XNA предоставдяет вам для этого, это mesh.Draw, который надо вызывать для каждой сетки модели после установки необходимых параметров шейдеров, что вы видели в предыдущей главе, когда писали класс Model.
Другая вещь, отсутствующая в XNA Framework, это вспомогательные методы для создания сеток кубов, сфер или чайников. Вы также заметите, что большинство функциональности пространства имен Direct3DX просто не существует в XNA. Вы только можете получить доступ к некоторым методам, когда пишете собственный обработчик содержимого, но это не поможет вам при написании своего движка, или когда вы захотите проверить модели, сетки или шейдеры. Вы также не можете получить доступ к любым данным вершин или индексов загруженных моделей, потому что все буферы вершин и индексов доступны только для записи, что хорошо с точки зрения производительности, но плохо, если вы захотите что-нибудь модифицировать. В вашем небольшом фрагменте кода вы просто имитируете поведение метода mesh.Draw, только без кода эффектов, поскольку для этой цели у вас здесь используется собственный класс эффектов.
effect.CurrentTechnique = effect.Techniques["SpecularPerPixel"]; effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); // Визуализируем все сетки foreach (ModelMesh mesh in someModel.Meshes) { // Визуализируем все части сетки foreach (ModelMeshPart part in mesh.MeshParts) { // Визуализируем данные собственным способом BaseGame.Device.Vertices[0].SetSource( mesh.VertexBuffer, part.StreamOffset, part.VertexStride); BaseGame.Device.Indices = mesh.IndexBuffer; // И визуализация BaseGame.Device.DrawIndexedPrimitives( PrimitiveType.TriangleList, part.BaseVertex, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount); } // foreach } // foreach pass.End(); } // for effect.End();
Если подробнее, это означает, что вы перебираете каждый проход (снова у вас только один) и рисуете все сетки (также только одну для вашего яблока), визуализируя все части каждой сетки (представьте себе, и здесь одну), устанавливая буферы вершин и индексов с помощью свойств класса XNA Device. Затем вы вызываете DrawIndexedPrimitives для визуализации всех вершин внутри шейдера. После этого проход и шейдер закрываются и вы наконец видите свое яблоко с текстурой marble.dds на экране (рис. 6.16).
Рис. 6.16 |
Теперь все работает, но это не означает, что вы не можете немного поэкспериментировать со своим шейдером и проверить другие параметры материалов, текстуры или режимы визуализации.
Чтобы достичь эффекта проволочного каркаса, который вы видели раньше на рис. 6.5, просто измените значение FillMode перед запуском шейдера:
BaseGame.Device.RenderState.FillMode = FillMode.WireFrame;
Или вы можете немного поуправлять материалами шейдера; если хотите, можете загрузить другую текстуру или даже модель. Класс шейдера написан таким образом, что позволяет вам загружать любую модель XNA и текстуру и тестировать их. Простейшим способом модификации результата работы шейдера могло бы быть изменение значений фоновой, рассеиваемой и отражаемой компонент, чтобы получить злое инопланетное яблоко (рис. 6.17).
effect.Parameters["ambientColor"].SetValue( Color.Blue.ToVector4()); effect.Parameters["diffuseColor"].SetValue( Color.Orange.ToVector4()); effect.Parameters["specularColor"].SetValue( Color.Orchid.ToVector4());
Рис. 6.17 |
Пожалуйста, обратите внимание, что вы преобразуете цвета в Vector4, поскольку у SetValue нет перегрузки для цветовых значений (похоже, кто-то был очень ленивый). Когда вы устанавливаете любой параметр эффекта между вызовами Begin и End для шейдера, вы должны также вызвать метод Effect.CommitChanges, чтобы гарантировать, что все ваши изменения будут отправлены GPU. Если вы вызываете Begin после установки параметров, подобно тому, как это делается в классе SimpleShader, можете не беспокоиться об этом.
В следующей главе мы перейдем к более эффективному использованию параметров эффекта, поскольку установка их по имени не слишком производительна и может служить источником ошибок. Если вы допустите опечатку в имени какого-нибудь параметра, будет сгенерировано исключение, а в этом нет ничего хорошего.
netlib.narod.ru | < Назад | Оглавление | Далее > |