Shader Model 3 Using Vertex Texture

介绍ShaderModel3.0中顶点纹理技术的原理与应用,展示了如何在DirectX与OpenGL中实现该技术,并通过游戏案例展示其带来的画质提升。
以前只有片段着色器才使用纹理,现在3.0后,顶点着色器也可以使用纹理了

Shader Model 3 Using Vertex Texture

顶点纹理白皮书中文版

 

 

翻译者 周波 zhoubo22@hotmail.com

版权所有   Philipp Gerasimov

                  Randima (Randy) Fernando

     Simon Green

           NVIDIA Corporation

 

仅以此文赠与 Rita 19  岁生日快乐

 

 

 

Shader Model 3.0:Using Vertex Textures  SM3: 使用顶点纹理

 

      随着 GPU 可编程特性的发展, Vertex Shader  Pixel Shader 的差别越来越大。现在, Geforce6 系列 gpu  Vertex Shader  Pixel Shader 之间的通用性特征向前发展了一大步。这篇文章特别介绍了 Shader Model3 的一项技术, Vertex Shader Fetch 。它允许 Vertex Shader  Pixel Shader 一样从纹理中读取数据。

      在现代图形处理中,顶点处理的性能表现不是受制于内存带宽、 cpu 速度,就是受制于 Pixel Shader 的处理能力。但这也意味着你可以实现一个复杂的 Vertex Shader ,提高画质,而且不会有多大损失。 Vertex Shader 的制作成本比 Pixel Shader 高,所以在最新的 6800 芯片里, Vertex Shader 的数目要少于 Pixel Shader 。这样,我们就可以安心地实现一打漂亮的效果,比如流体的模拟等等。

      这篇白皮书将同时向您展示如何在 OPENGL 以及 DIRECTX 中实现 Vertex Texture 。最后,我们将用一个游戏的范例向您演示使用 Vertex Texture 的情况 

     

Specification 详解

 

      DIRECTX  OPENGL 中都可以使用 Vertex Texture 

 

       DIRECTX9

 

      MS DX9SDK 的开发文档中已经包括了 VERTEX TEXTURE 的详细说明。

      Vertex Shader3( 即使用Vertex Shader3编译器生成的Shader)支持 vertex_fetch  4 种纹理样本。 Vertex Texture ,单从名称上看就同传统的 PIXEL TEXTURE 类似,但是同 PIXEL TEXTURE 比起来有一些差别,

 

      硬件无法直接支持 Bilinear Trilinear 过滤,但是您可以手动在 Vertex Shader 中实现

      反锯齿,内容同上。

      自动 Mipmap LOD  无效

 

      D3DCAPS 成员 MaxVertexShader30InstructionSlots 标识 Vertex Shader3 中代码的上限行数。 MaxVShaderInstructionsExecuted 标识了 Vertex Shader 的上限代码行数,包括 Texture Fetch 的数目。

      DIRECTX9 支持软件 Vertex Processing 模式下使用 Vertex Texture ,这样甚至当硬件不支持 Vertex Texture 时也可以运行。

      6800 支持使用 D3DFMT_R32F and D3DFMT_A32B32G32R32F 的纹理格式实现 Vertex Texture 。

 

OPENGL

 

      顶点纹理查找通过 NV_V_PROGRAM3 扩展实现。详情请参阅 http://www.nvidia.com/dev_content/nvopenglspecs/GL_NV_vertex_program3.txt

这是标准 ARB vertex program language 的一项 Option ( 操作)。这就意味着你可以调用现有的ARB API ,载入程序,设置参数。在程序开头加入以下代码就可以了:

      OPTION NV_VERTEX_PROGRAM3

 

在程序里加入 Vertex Texture

 

      使用 Vertex Texture 的步骤如下:

           检查硬件的 Vertex Texture 支持情况

           创建 Vertex Texture 资源

           在 Vertex Shader 中加入需要的代码

      下面具体来看看怎样在 DIRECTX 以及 OPENGL 中实现。

     

DIRECTX

 

第一,检查硬件是否支持,否则将不得不用软件方式实现。调用 IDirect3D9::CheckDeviceFormat 里的 D3DUSAGE_QUERY_VERTEXTEXTURE 旗标查询 硬件支持的 Vertex Texture 格式。 Software Vertex Texture 支持所有 Vertex Texture 格式。

 

OPENGL

 

      OPENGL 里只需要检查硬件是否支持 NV_VERTEX_PROGRAM3 扩展。 GLUT 库的 glutExtensionSupported 函数可以完成这项任务。 Vertex Texture 数目的上限用下列代码获得

      glGetIntegerv( MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB, &vtex_units)

   6 系列 GPU 最大支持 4 个活动纹理( Active Texture )。你可以尽情的在 Vertex Shader 中调用它们,不过要注意Vertex Shader的代码行数。

  

创建 Vertex Texture 资源

 

       DIRECTX9

 

      库中的任何纹理创建函数都可以创建顶点纹理, IDirect3D9::CreateTexture,

IDirect3D9::CreateCubeTexture, IDirect3D9::CreateVolumeTexture 等等。

      当使用 SVP 时,顶点纹理必须创建在 D3DPOOL_SCRATCH 池中。

 

       OPENGL

      基本纹理调用操作已经包括了 Vertex Texture 的绑定,使用 GL_TEXTURE_2D 。目前只有 GL_LUMINANCE_FLOAT32_ATI 与 GL_RGBA_FLOAT32_ATI 这 2 种格式支持 Vertex Texture 。这些格式都包含了 1 个或 4 个 32bit 浮点数据通道。注意,使用其他的纹理格式,或者使用不支持的过滤方式都可以导致驱动调用 Software Vertex Processing 处理,导致性能下降。

      示例代码如下:

      GLuint vertex_texture;

glGenTextures( 1, &vertex_texture);

glBindTexture( GL_TEXTURE_2D, vertex_texture);

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_NEAREST);

glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE_FLOAT32_ATI, width, height, 0,GL_LUMINANCE, GL_FLOAT, data);

     

在 Vertex Shader 里访问 Vertex Texture

 

       DIRECTX9

 

程序调用 IDirect3DDevice9::SetTexture 设置 Vertex Texture ,样本索引为 D3DVERTEXTEXTURESAMPLER1 到 D3DVERTEXTEXTURESAMPLER3 。在 D3DPOOL_DEFAULT 里创建的 Vertex Texture 同时也可以设置成 PIXEL TEXTURE 。

      Vertex Shader 里的纹理样本必须使用 DEL_SAMPLEType 标识。

// 汇编代码

dcl_texcoord0 v0

dcl_2D s0

texldl r0, o0, s0

 

// HLSL / Cg 代码

sampler2D tex;

vDisplacement = tex2Dlod ( tex, t ); // t.w 包括 MIPMAP LOD 数据

 

OPENGL

 

      VP 的纹理查找功能通过 TEX,TXB, TXL or TXP 实现,就像在 Fragment Shader 里一样(或者在其他高等级语言中比如 CG ) 。与 Fragment Shader 的差异是 , 纹理查找功能无法自动计算 LOD 。

      LOD 的意义是确定纹理在屏幕上缩放的尺寸大小。一般根据纹理坐标象素的改变频率计算,但这里的麻烦是, Vertex Texture 由顶点访问,硬件很难计算 LOD 值。所以你不得不自己在 Vertex Processing 里计算 LOD 。

      MIPMAP 类似普通的 Pixel Shader 纹理,它可以为 Vertex Texture 在性能与画质之间折中。早期的图形处理管线中没有 Pixel-Level 这一概念,无法计算顶点纹理的 LOD 。如果需要使用LOD我们不得不人工在 Vertex Shader 里计算 mipmap 。

      示例代码如下:

      #define maxMipLevels 10.0f

Out.HPOS = mul( ModelViewProj, vPos );

float mipLevel = ( Out.HPOS.z / Out.HPOS.w ) * maxMipLevels;

float vDisplacement = tex2Dbias( tex, float4( t, mipLevel, mipLevel );

      这是根据顶点的深度计算 LOD 的算法,开销很小,精度能够让人满意。

     

#define maxMipLevels 10.0f

Out.HPOS = mul( ModelViewProj, vPos );

float mipLevel = ( Out.HPOS.z / Out.HPOS.w ) * maxMipLevels;

float mipLevelFloor = floor(mipLevel);

float mipLevelCeiling = mipLevelFloor + 1;

float mipLevelFrac = frac(mipLevel);

float vDisplacementFloor = tex2D( tex, float4( t, mipLevelFloor,mipLevelFloor );

float vDisplacementCeiling = tex2Dbias(tex,

float4( t,mipLevelCeiling,mipLevelCeiling );

float vDisplacement = vDisplacementFloor + vDisplacementCeiling

 

Filter 过滤

      Vertex Texture 允许纹理过滤,但是要根据硬件的支持情况。 6 系列只支持 NEAREST-NEIGHBOR 过滤模式。你也可以手动在 Vertex Texture 里实现过滤。

 

           Bilinear Filtering

#define textureSize 512.0f

#define texelSize 1.0f / 512.0f

float4 tex2D_bilinear( uniform sampler2D tex, float2 t )

{

float2 f = frac( t.xy * textureSize );

float4 t00 = tex2D( tex, t );

float4 t10 = tex2D( tex, t + float2( texelSize, 0.0f );

float4 tA = lerp( t00, t10, f.x );

float4 t01 = tex2D( tex, t + float2( 0.0f, texelSize ) );

float4 t11 = tex2D( tex, t + float2( texelSize, texelSize ) );

float4 tB = lerp( t01, t11, f.x );

return lerp( tA, tB, f.y );

}

           Bilinear Filtering With Mipmapping

float4 tex2D_bilinear( uniform sampler2D tex, float4 t )

{

float2 f = frac( t.xy * miplevelSize );

float4 t00 = tex2Dbias( tex, t );

float4 t10 = tex2Dbias( tex, t + float4( texelSize, 0.0f, 0.0f, 0,0f );

float4 tA = lerp( t00, t10, f.x );

float4 t01 = tex2Dbias( tex, t + float4( 0.0f, texelSize, 0.0f, 0.0f ) );

float4 t11 = tex2Dbias( tex, t + float4(texelSize, texelSize, 0.0f, 0.0f));

float4 tB = lerp( t01, t11, f.x );

return lerp( tA, tB, f.y );

}

      如果单纯站在性能的角度上考虑上述算法,还是 Bilinear 最好。 Bicubic 、 Trilinear ,以及其他的过滤算法都可以在 Vertex Shader 里实现。其中, Trilinear 过滤对性能的要求要高一点,因为 Shader 需要从不同等级的 mipmap 里访问纹理。

 

Performance Tips 性能

 

      6800 可以在一秒钟内生成 6 亿多个顶点。当然,这是在 Vertex Shader 没有任何“负载”的情况下测试的结果。如果使用 Vertex Texture Fetch 后是什么情况呢?我们的数字是每秒钟生成 3 千 3 百多万个位移顶点,计算了基本位移,使用 NEAREST方式过滤。

      3 千 3 百多万个位移顶点,意味着如果以每秒 30 帧的速度绘制画面,每一帧画面将有 100 多万个 Displacement Vertics 位移顶点。这比现在任何一款游戏在一帧画面里出现的顶点都要多,而且,并不是每个顶点都需要进行位移操作。你可以使用 6系列 gpu 的动态分支功能,对每个定点是否需要进行位移操作进行预测。比如做一次 dot(V,N) 运算,测试顶点是否靠近阴影,如果远离阴影就可以避免位移操作。这时,你就可以把节省下来的硬件资源用于处理过滤等效果上。我们推荐,如果你的 Vertex Shader 很复杂,最好在处理过程的早期就对画面或顶点进行剪裁与剔除。

           // OpenGL example

float4 vClipPos = mul( ModelViewProj, vPos );

float3 bClip = abs( vClipPos.xyz ) < ( vClipPos.www + vClipOffset );

if( all(bClip) )

{

DoLightingAndDisplacement( );

}

      还有一点非常重要,“顶点纹理不应看作连续的 RAM 。顶点纹理在提取数据时不是真正的连续读取,而是会产生等待时间。因此使用顶点纹理的最佳方法就是先进行纹理提取,然后进行逻辑算法计算,这样能在使用纹理提取前避免等待时间。顶点纹理不是用来代替大量的常量的阵列,而是用于减少顶点数据,这样每个顶点只有少量的顶点纹理需要提取数据。” —— 摘自《 GPU_Programming_Guide_Chinese From NVIDIA 》

 

<Case Study>

      目前,一些游戏已经开始使用 Vertex Texture 。比如下面要提到的这款游戏,由 Maddox GAME 开发, Ubi Software 发行的 Pacific Fighter 。

      现代游戏的设计中,飞行模拟类游戏最适合使用 dm 技术。这是因为,这些游戏的场景中包括大量的地形、河流、海洋等。 Dm 可以为这些场景提供更好的效果。让我们看一下这款使用 Displacement Mapping 的游戏。

     

IL-2 Sturmovik 系列游戏最近年来比较成功的飞行模拟类游戏,在中国武汉曾经进行过一场国际性比赛。游戏制作人员非常留心游戏业里出现的最新技术,并运用到他们的作品中。比如这款最新的 Pacific Fighter ,完全发挥了 6 系列 gpu 的性能。“Vertex Shader 里可以访问纹理是 3D 加速硬件最值得期待的技术之一。” Yuri Kryachko ,主程序员如是说。

      在这款游戏中,海水的绘制非常重要。开发人员采用了 Vertex Texture ,实现了目前游戏领域中最真实的流水效果。在没有采用 Vertex Texture 之前,开发人员一般使用凹凸贴图模拟水面,但是与采用 Vertex Texture 和几何位移算法实现的效果比起来有天壤之别。图片对比如下。

           

这款游戏的 WaterShader 非常复杂,超过 140 行,用于用物理的方式计算水面的动画,以及反射折射效果。每一个顶点的位移都是由多个 dynamic normal maps (动态向量映射)用几何方式计算出来的。而且 Shader 从多个纹理中读取数据进行过滤操作,使画面更加真实。

      Yuri Kryacko 说,“当我们在 Vertex Shader 及 Pixel Shader 中同时使用动态分支功能时,性能得到了很大的提高。我们想再优化代码,使用新的 Shader ,提高整体的画质,使我们的引擎的真实性达到一个新的高度。”

 

Downloads 下载

      想学习关于 Vertex Texture Fetch 更多的东西吗?从 NVIDIA 的站点上下载范例吧

      http://download.nvidia.com/developer/SDK/Individual_Samples/samples.html

http://download.nvidia.com/developer/SDK/Individual_Samples/effects.html

#pragma once #include "Sphere.h" class Background : public Sphere // 改为继承 Sphere { public: Background(); void initData(DataParam* param = nullptr) override; void render(glm::mat4& view, glm::mat4& projection) override; // 现在可以 override void update(float dt) override; }; #pragma once #include <glm/glm.hpp> class Camera { public: glm::vec3 Position; glm::vec3 Front = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 Up = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 Right; glm::vec3 WorldUp = glm::vec3(0.0f, 1.0f, 0.0f); float Yaw = -90.0f; float Pitch = 0.0f; float MovementSpeed = 2.5f; float MouseSensitivity = 0.1f; Camera(glm::vec3 position = glm::vec3(0.0f, 50.0f, 100.0f)); glm::mat4 GetViewMatrix(); void ProcessKeyboard(int direction, float dt); void ProcessMouseMovement(float xoffset, float yoffset); private: void updateCameraVectors(); }; #pragma once #include "Sphere.h" class Moon : public Sphere { public: Moon(); void update(float dt, const glm::mat4& earthModel); // 特殊更新函数 private: float orbitRadius = 15.0f; float orbitAngle = 0.0f; float rotationAngle = 0.0f; float orbitSpeed = 0.5f; float rotationSpeed = 0.1f; }; #pragma once #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #include <string> #include "Shader.h" typedef struct { glm::vec3 coordinate; glm::vec3 color; glm::vec2 texture; glm::vec3 normal; } TextureColorVertex; typedef struct { GLuint latSegments; GLuint longSegments; GLfloat radius; GLfloat height; } DataParam; class Object { public: Object(std::string vs, std::string fs, std::string texName = ""); virtual ~Object(); Shader* shader = nullptr; glm::mat4 model; void createShader(const char* vs, const char* fs); void setTexture(std::string texName); virtual void render(glm::mat4& view, glm::mat4& projection); virtual void initData(DataParam* param = nullptr) = 0; virtual void update(float dt) {} virtual void renderObject() = 0; virtual void updateDataBuffer(); protected: TextureColorVertex* vertices = nullptr; GLushort* indices = nullptr; GLuint indexCount = 0; GLint verticesSize = 0; GLuint indexSize = 0; GLuint texture = 0; GLuint VBO = 0, VAO = 0, EBO = 0; float rotationSpeed = 0.0f; float revolutionSpeed = 0.0f; float rotationAngle = 0.0f; float revolutionAngle = 0.0f; glm::vec3 translation = glm::vec3(0.0f); GLsizei stride = sizeof(TextureColorVertex); void createBuffer(bool createEBO = true, GLenum usage = GL_STATIC_DRAW); }; #pragma once #include "Sphere.h" class Planet : public Sphere { public: Planet(std::string texName, glm::vec3 orbitPos, float revSpeed, float rotSpeed); void update(float dt) override; // 声明 override → 必须实现 }; #pragma once #include <string> #include <glad/glad.h> class Shader { public: unsigned int ID; Shader(const char* vertexPath, const char* fragmentPath); void use(); void setInt(const std::string& name, int value); void setMat4(const std::string& name, const float* mat); void setVec3(const std::string& name, float x, float y, float z); private: void checkCompileErrors(unsigned int shader, std::string type); }; #pragma once #include "Object.h" class Sphere : public Object { public: Sphere(std::string vs, std::string fs, std::string texName = ""); ~Sphere(); void initData(DataParam* param) override; void renderObject() override; void update(float dt) override; }; #pragma once #include "Sphere.h" class Sun : public Sphere { public: Sun(); void renderObject() override; void update(float dt) override; }; #pragma once #include <glad/glad.h> #include <GLFW/glfw3.h> #include <map> #include <string> using namespace std; typedef map<string, GLuint> Textures; class TexturePool { public: virtual ~TexturePool(); private: TexturePool(); Textures textures; public: void addTexture(string name, string path); static TexturePool* getInstance(); GLuint getTexture(string name); }; // Background.cpp #include "Background.h" #include <glm/gtx/transform.hpp> Background::Background() : Sphere("shaders/background.vs", "shaders/background.fs", "background") { // 构造器初始化着色器和纹理 } void Background::initData(DataParam* param) { DataParam p = { 60, 60, 500.0f, 0 }; Sphere::initData(&p); // 调用父类生成球面数据 } void Background::render(glm::mat4& view, glm::mat4& projection) { // 移除摄像机位移,保持背景不动 glm::mat4 viewNoTrans = glm::mat4(glm::mat3(view)); Object::render(viewNoTrans, projection); // 调用 Object::render(this, ...) } void Background::update(float dt) { // 背景无需更新 } #include "Camera.h" #include <glad/glad.h> // 必须第一行 #include <GLFW/glfw3.h> // 第二行 #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #define GLM_ENABLE_EXPERIMENTAL #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> // 这个提供 glm::lookAt #include <glm/gtc/type_ptr.hpp> Camera::Camera(glm::vec3 position) : Position(position) { updateCameraVectors(); } glm::mat4 Camera::GetViewMatrix() { return glm::lookAt(Position, Position + Front, Up); } void Camera::ProcessKeyboard(int direction, float dt) { float velocity = MovementSpeed * dt; if (direction == &#39;W&#39;) Position += Front * velocity; if (direction == &#39;S&#39;) Position -= Front * velocity; if (direction == &#39;A&#39;) Position -= Right * velocity; if (direction == &#39;D&#39;) Position += Right * velocity; } void Camera::ProcessMouseMovement(float xoffset, float yoffset) { xoffset *= MouseSensitivity; yoffset *= MouseSensitivity; Yaw += xoffset; Pitch += yoffset; if (Pitch > 89.0f) Pitch = 89.0f; if (Pitch < -89.0f) Pitch = -89.0f; updateCameraVectors(); } void Camera::updateCameraVectors() { glm::vec3 front; front.x = cos(glm::radians(Pitch)) * cos(glm::radians(Yaw)); front.y = sin(glm::radians(Pitch)); front.z = cos(glm::radians(Pitch)) * sin(glm::radians(Yaw)); Front = glm::normalize(front); Right = glm::normalize(glm::cross(Front, WorldUp)); Up = glm::normalize(glm::cross(Right, Front)); } #include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> // ✅ 新增:标准库支持 #include <vector> #include <memory> // 用于 unique_ptr #include "TexturePool.h" // 必须放在 glfw/glad 之后! #include "Object.h" #include "Sphere.h" #include "Planet.h" #include "Sun.h" #include "Background.h" #include "Camera.h" #include "moon.h" #define DEBUG_SUN_POS using namespace std; const unsigned int SCR_WIDTH = 1200; const unsigned int SCR_HEIGHT = 800; Camera camera(glm::vec3(0, 50, 120)); // 从上方偏远处观察太阳系 float lastX = SCR_WIDTH / 2.0f; float lastY = SCR_HEIGHT / 2.0f; bool firstMouse = true; float deltaTime = 0.1f; float lastFrame = 0.0f; void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; return; } float xoffset = xpos - lastX; float yoffset = lastY - ypos; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } void processInput(GLFWwindow* window) { float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(&#39;W&#39;, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(&#39;S&#39;, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(&#39;A&#39;, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(&#39;D&#39;, deltaTime); } int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Solar System", NULL, NULL); if (!window) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); glEnable(GL_DEPTH_TEST); Moon moon; Planet* earth = nullptr; // 初始化纹理池 TexturePool* tp = TexturePool::getInstance(); tp->addTexture("sun", "textures/sun.jpg"); tp->addTexture("earth", "textures/earth.jpg"); tp->addTexture("mars", "textures/mars.jpg"); tp->addTexture("jupiter", "textures/jupiter.jpg"); tp->addTexture("mercury", "textures/mercury.jpg"); tp->addTexture("moon", "textures/moon.jpg"); tp->addTexture("neptune", "textures/neptune.jpg"); tp->addTexture("saturn", "textures/saturn.jpg"); tp->addTexture("uranus", "textures/uranus.jpg"); tp->addTexture("venus", "textures/venus.jpg"); tp->addTexture("background", "textures/background.jpg"); // 初始化对象 Sun sun; DataParam param{ 60, 60, 20.0f, 0 }; sun.initData(&param); Background bg; bg.initData(); std::vector<std::unique_ptr<Planet>> planets; planets.push_back(std::make_unique<Planet>("mercury", glm::vec3(0, 0, 60), 0.08f, 1.0f)); planets.push_back(std::make_unique<Planet>("venus", glm::vec3(0, 0, 70), 0.06f, 0.8f)); planets.push_back(std::make_unique<Planet>("earth", glm::vec3(0, 0, 80), 0.04f, 1.2f)); planets.push_back(std::make_unique<Planet>("mars", glm::vec3(0, 0, 90), 0.03f, 1.0f)); planets.push_back(std::make_unique<Planet>("jupiter", glm::vec3(0, 0, 100), 0.02f, 0.6f)); planets.push_back(std::make_unique<Planet>("saturn", glm::vec3(0, 0, 120), 0.015f, 0.5f)); planets.push_back(std::make_unique<Planet>("uranus", glm::vec3(0, 0, 140), 0.01f, 0.4f)); planets.push_back(std::make_unique<Planet>("neptune", glm::vec3(0, 0, 160), 0.008f, 0.3f)); for (auto& p : planets) { p->initData(&param); } earth = planets[2].get(); glfwSetCursorPosCallback(window, mouse_callback); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); while (!glfwWindowShouldClose(window)) { processInput(window); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glm::mat4 view = camera.GetViewMatrix(); glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / SCR_HEIGHT, 0.1f, 1000.0f); sun.render(view, proj); bg.render(view, proj); for (auto& p : planets) { p->shader->use(); glm::vec3 sunPos(sun.model[3][0], sun.model[3][1], sun.model[3][2]); p->shader->setVec3("lightPos", sunPos.x, sunPos.y, sunPos.z); // 设置摄像机位置(用于高光) p->shader->setVec3("viewPos", camera.Position.x, camera.Position.y, camera.Position.z); p->render(view, proj); } sun.update(deltaTime * 100); bg.update(deltaTime); for (auto& p : planets) p->update(deltaTime * 100); moon.update(deltaTime * 100, earth->model); moon.render(view, proj); #ifdef DEBUG_SUN_POS std::cout << "Sun pos: (" << sun.model[3][0] << ", " << sun.model[3][1] << ", " << sun.model[3][2] << ")\n"; #endif glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; } #include "Moon.h" Moon::Moon() : Sphere("shaders/planet.vs", "shaders/planet.fs", "moon") { } void Moon::update(float dt, const glm::mat4& earthModel) { orbitAngle += orbitSpeed * dt; rotationAngle += rotationSpeed * dt; model = glm::mat4(1.0f); // 获取地球的位置矩阵 model = earthModel; // 绕地球公转(绕Y轴) model = glm::rotate(model, glm::radians(orbitAngle), glm::vec3(0, 1, 0)); model = glm::translate(model, glm::vec3(0, 0, -orbitRadius)); // 向前飞一段 // 自转 model = glm::rotate(model, glm::radians(rotationAngle), glm::vec3(0, 1, 0)); // 缩小尺寸 model = glm::scale(model, glm::vec3(0.5f)); } #include "Object.h" #include <glad/glad.h> // 必须第一行 #include <GLFW/glfw3.h> // 第二行 #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #include "TexturePool.h" Object::Object(std::string vs, std::string fs, std::string texName) { createShader(vs.c_str(), fs.c_str()); if (!texName.empty()) { texture = TexturePool::getInstance()->getTexture(texName); if (shader) { shader->use(); shader->setInt("tex", 0); } } } Object::~Object() { glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); delete shader; } void Object::createShader(const char* vs, const char* fs) { if (shader) delete shader; shader = new Shader(vs, fs); } void Object::setTexture(std::string texName) { texture = TexturePool::getInstance()->getTexture(texName); } void Object::createBuffer(bool createEBO, GLenum usage) { glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); if (createEBO) glGenBuffers(1, &EBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, verticesSize, vertices, usage); if (createEBO && indices) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize, indices, usage); } glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(TextureColorVertex, color)); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(TextureColorVertex, texture)); glEnableVertexAttribArray(2); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(TextureColorVertex, normal)); glEnableVertexAttribArray(3); glBindVertexArray(0); } void Object::render(glm::mat4& view, glm::mat4& projection) { if (!shader || !texture) return; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindVertexArray(VAO); shader->use(); shader->setMat4("model", glm::value_ptr(model)); shader->setMat4("view", glm::value_ptr(view)); shader->setMat4("projection", glm::value_ptr(projection)); renderObject(); } void Object::updateDataBuffer() { glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferSubData(GL_ARRAY_BUFFER, 0, verticesSize, vertices); glBindBuffer(GL_ARRAY_BUFFER, 0); } #include "Planet.h" // 构造函数保持不变 Planet::Planet(std::string texName, glm::vec3 orbitPos, float revSpeed, float rotSpeed) : Sphere("shaders/planet.vs", "shaders/planet.fs", texName) { translation = orbitPos; revolutionSpeed = revSpeed; rotationSpeed = rotSpeed; } void Planet::update(float dt) { revolutionAngle += revolutionSpeed * dt; rotationAngle += rotationSpeed * dt; model = glm::mat4(1.0f); // Step 1: 绕 Y 轴旋转(实现“绕太阳公转”的角度变化) model = glm::rotate(model, glm::radians(revolutionAngle), glm::vec3(0, 1, 0)); // Step 2: 向外平移(例如 z = -80),表示轨道半径 float orbitRadius = translation.z; // 构造时传入的距离 model = glm::translate(model, glm::vec3(0, 0, -orbitRadius)); // 注意是负值! // Step 3: 绕自身轴自转 model = glm::rotate(model, glm::radians(rotationAngle), glm::vec3(0, 1, 0)); // Step 4: 缩放 model = glm::scale(model, glm::vec3(1.0f)); } #include "Shader.h" #include <fstream> #include <sstream> #include <iostream> Shader::Shader(const char* vertexPath, const char* fragmentPath) { std::string vCode, fCode; std::ifstream vFile, fFile; vFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); fFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); try { vFile.open(vertexPath); fFile.open(fragmentPath); std::stringstream vStream, fStream; vStream << vFile.rdbuf(); fStream << fFile.rdbuf(); vFile.close(); fFile.close(); vCode = vStream.str(); fCode = fStream.str(); } catch (...) { std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ" << std::endl; } const char* vShaderCode = vCode.c_str(); const char* fShaderCode = fCode.c_str(); unsigned int vShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vShader, 1, &vShaderCode, NULL); glCompileShader(vShader); checkCompileErrors(vShader, "VERTEX"); unsigned int fShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fShader, 1, &fShaderCode, NULL); glCompileShader(fShader); checkCompileErrors(fShader, "FRAGMENT"); ID = glCreateProgram(); glAttachShader(ID, vShader); glAttachShader(ID, fShader); glLinkProgram(ID); checkCompileErrors(ID, "PROGRAM"); glDeleteShader(vShader); glDeleteShader(fShader); } void Shader::use() { glUseProgram(ID); } void Shader::setInt(const std::string& name, int value) { glUniform1i(glGetUniformLocation(ID, name.c_str()), value); } void Shader::setMat4(const std::string& name, const float* mat) { glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, mat); } void Shader::setVec3(const std::string& name, float x, float y, float z) { glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z); } void Shader::checkCompileErrors(unsigned int shader, std::string type) { int success; char infoLog[1024]; if (type != "PROGRAM") { glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 1024, NULL, infoLog); std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << std::endl; } } else { glGetProgramiv(shader, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shader, 1024, NULL, infoLog); std::cout << "ERROR::PROGRAM_LINKING_ERROR\n" << infoLog << std::endl; } } } #include "Sphere.h" #include <cmath> #include <glad/glad.h> // 必须第一行 #include <GLFW/glfw3.h> // 第二行 #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> Sphere::Sphere(std::string vs, std::string fs, std::string texName) : Object(vs, fs, texName) { } Sphere::~Sphere() = default; void Sphere::initData(DataParam* param) { GLuint longSeg = param->longSegments; GLuint latSeg = param->latSegments; GLfloat r = param->radius; verticesSize = (longSeg + 1) * (latSeg + 1) * sizeof(TextureColorVertex); vertices = new TextureColorVertex[(longSeg + 1) * (latSeg + 1)]; float dPhi = glm::pi<float>() / latSeg; float dTheta = 2.0f * glm::pi<float>() / longSeg; TextureColorVertex* p = vertices; for (GLuint i = 0; i <= latSeg; ++i) { float phi = i * dPhi - glm::pi<float>() / 2.0f; for (GLuint j = 0; j <= longSeg; ++j) { float theta = j * dTheta; p->coordinate.x = r * cosf(phi) * cosf(theta); p->coordinate.y = r * sinf(phi); p->coordinate.z = r * cosf(phi) * sinf(theta); p->color = glm::vec3(1.0f); p->texture = glm::vec2((float)j / longSeg, (float)i / latSeg); p->normal = glm::normalize(p->coordinate); ++p; } } indexCount = (longSeg + 1) * 2 * (latSeg + 1); indices = new GLushort[indexCount]; GLushort* idx = indices; for (GLuint i = 0; i < latSeg; ++i) { for (GLuint j = 0; j <= longSeg; ++j) { idx[0] = i * (longSeg + 1) + j; idx[1] = (i + 1) * (longSeg + 1) + j; idx += 2; } } indexSize = indexCount * sizeof(GLushort); createBuffer(true, GL_STATIC_DRAW); delete[] vertices; vertices = nullptr; delete[] indices; indices = nullptr; rotationSpeed = 0.5f; revolutionSpeed = 0.01f; } void Sphere::renderObject() { glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_SHORT, 0); } void Sphere::update(float dt) { revolutionAngle += revolutionSpeed * dt; rotationAngle += rotationSpeed * dt; model = glm::mat4(1.0f); model = glm::translate(model, translation); model = glm::rotate(model, glm::radians(revolutionAngle), glm::vec3(0, 1, 0)); model = glm::translate(model, glm::vec3(0, 0, 5)); model = glm::rotate(model, glm::radians(rotationAngle), glm::vec3(0, 1, 0)); model = glm::scale(model, glm::vec3(1.0f)); } #include "Sun.h" Sun::Sun() : Sphere("shaders/sun.vs", "shaders/sun.fs", "sun") { rotationSpeed = 0.2f; } void Sun::update(float dt) { rotationAngle += rotationSpeed * dt; model = glm::mat4(1.0f); // 单位矩阵 到 原点 model = glm::rotate(model, glm::radians(rotationAngle), glm::vec3(0, 1, 0)); // 绕 Y 轴自转 model = glm::scale(model, glm::vec3(2.0f)); // 可以稍大一点,比如 2倍 } void Sun::renderObject() { glDepthMask(GL_FALSE); // 不写入深度缓冲,避免挡住后面行星 glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_SHORT, 0); glDepthMask(GL_TRUE); } #include "TexturePool.h" #include <SOIL2/SOIL2.h> #include <iostream> TexturePool::TexturePool() {} TexturePool::~TexturePool() { for (auto it = textures.begin(); it != textures.end(); ++it) glDeleteTextures(1, &it->second); } void TexturePool::addTexture(string name, string path) { GLuint texID = SOIL_load_OGL_texture(path.c_str(), SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y); if (texID == 0) { cout << "Failed to load texture: " << path << endl; return; } textures[name] = texID; glBindTexture(GL_TEXTURE_2D, texID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } TexturePool* TexturePool::getInstance() { static TexturePool instance; return &instance; } GLuint TexturePool::getTexture(string name) { auto it = textures.find(name); return it != textures.end() ? it->second : 0; } 你看完我就说我的问题
最新发布
11-30
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值