SharpGL学习笔记(十九) 摄像机漫游
所谓的摄像机漫游,就是可以在场景中来回走动。
现实中,我们通过眼睛观察东西,身体移动带动眼睛移动观察身边的事物,这也是在漫游。
在OpenGL中我们使用函数LookAt()来操作摄像机在三维场景中进行漫游。
LookAt(double eyex, double eyey, double eyez, double centerx, double centery, double centerz, double upx, double upy, double upz);
我们通过改变LookAt的参数实现漫游效果,说明如下:
- 改变视点eyeX,可以实现在场景中横向移动
- 改变视点eyeY,可以实现在场景中蹲下,跳起这样的动作
- 改变视点Z分量eyeZ,能实现在场景中前后的动作
- 对于摄像机目标点centerx,y,z 的变化,相当于观察者站着不动,但其观察方向在上下左右方向进行变化。
效果缩略图:
源代码:
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using SharpGL;
- using System.Runtime.InteropServices;
- namespace cameraRove
- {
- //原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/
- public partial class SharpGLForm : Form
- {
- SharpGL.SceneGraph.Assets.Texture texture = new SharpGL.SceneGraph.Assets.Texture();
- Camera m_Camera = new Camera();
- //光源位置
- float lx = 2f;
- float ly = 1f;
- float lz = 9f;
- float[] fLightPosition = new float[];// 光源位置
- float[] fLightAmbient = new float[] { 1f, 1f, 1f, 1.0f };// 环境光参数
- float[] fLightDiffuse = new float[] { 1f, 1f, 1f, 1f };// 漫射光参数
- public SharpGLForm()
- {
- InitializeComponent();
- }
- private void openGLControl_OpenGLDraw(object sender, PaintEventArgs e)
- {
- OpenGL gl = openGLControl.OpenGL;
- gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
- gl.LoadIdentity();
- gl.DrawText(,this.Height-, 1f, 1f, 1f, "黑体", 12f, string.Format(
- "当前位置:X={0} Y={1} Speed ={2} ", m_Camera.getView().x.ToString("0.0"),
- (-m_Camera.getView().z).ToString("0.0"), m_Camera.getSpeed()));
- m_Camera.setLook(gl);
- drawBox(gl, 1f, 1f, 0f);
- m_Camera.setViewByMouse();
- }
- void drawGrid(OpenGL gl)
- {
- //获得场景中一些状态
- byte[] lp = new byte[] { , };
- byte[] tp = new byte[] { , };
- gl.GetBooleanv(OpenGL.GL_LIGHTING, lp);
- gl.GetBooleanv(OpenGL.GL_TEXTURE_2D, tp);
- //关闭纹理和光照
- gl.Disable(OpenGL.GL_TEXTURE_2D);
- gl.Disable(OpenGL.GL_LIGHTING);
- //绘制过程
- gl.PushAttrib(OpenGL.GL_CURRENT_BIT); //保存当前属性
- gl.PushMatrix(); //压入堆栈
- gl.Translate(0f, 0f, 0f);
- gl.Color(0f, 0f, 1f);
- //在X,Z平面上绘制网格
- for (float i = -; i <= ; i += )
- {
- //绘制线
- gl.Begin(OpenGL.GL_LINES);
- //X轴方向
- gl.Vertex(-50f, 0f, i);
- gl.Vertex(50f, 0f, i);
- //Z轴方向
- gl.Vertex(i, 0f, -50f);
- gl.Vertex(i, 0f, 50f);
- gl.End();
- }
- gl.PopMatrix();
- gl.PopAttrib();
- //恢复场景状态
- if (tp[] != )
- gl.Enable(OpenGL.GL_TEXTURE_2D);
- if (lp[] != )
- gl.Enable(OpenGL.GL_LIGHTING);
- }
- void drawSphere(OpenGL gl)
- {
- //设置材质属性
- float[] mat_ambient = { 0.9f, 0.5f, 0.8f, 1.0f };
- float[] mat_diffuse = { 0.9f, 0.5f, 0.8f, 1.0f };
- float[] mat_shininess = { 100.0f };
- gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, mat_ambient);
- gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, mat_diffuse);
- gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, mat_shininess);
- //获得纹理启用状态
- byte[] tp = { , };
- gl.GetBooleanv(OpenGL.GL_TEXTURE_2D, tp);
- gl.Disable(OpenGL.GL_TEXTURE_2D); //关闭纹理
- //绘制过程
- gl.PushMatrix();
- gl.Translate(2f, 1f, 5f);
- var sphere= gl.NewQuadric();
- gl.QuadricNormals(sphere, OpenGL.GLU_OUTSIDE);
- gl.QuadricNormals(sphere, OpenGL.GLU_SMOOTH);
- gl.Sphere(sphere, 1f, , );
- gl.DeleteQuadric(sphere);
- gl.PopMatrix();
- if (tp[] != )
- gl.Enable(OpenGL.GL_TEXTURE_2D);
- }
- private void drawBox(OpenGL gl, float xPos, float yPos, float zPos)
- {
- gl.PushMatrix();
- texture.Bind(gl);
- gl.Scale(, , );
- gl.Translate(xPos, yPos, zPos);
- gl.Begin(OpenGL.GL_QUADS);
- {
- //前
- gl.TexCoord(, ); gl.Vertex(, , );
- gl.TexCoord(, ); gl.Vertex(-, , );
- gl.TexCoord(, ); gl.Vertex(-, -, );
- gl.TexCoord(, ); gl.Vertex(, -, );
- //底
- gl.TexCoord(, ); gl.Vertex(, , );
- gl.TexCoord(, ); gl.Vertex(, , -);
- gl.TexCoord(, ); gl.Vertex(-, , -);
- gl.TexCoord(, ); gl.Vertex(-, , );
- //左
- gl.TexCoord(, ); gl.Vertex(-, , );
- gl.TexCoord(, ); gl.Vertex(-, , -);
- gl.TexCoord(, ); gl.Vertex(-, -, -);
- gl.TexCoord(, ); gl.Vertex(-, -, );
- //右
- gl.TexCoord(, ); gl.Vertex(, , );
- gl.TexCoord(, ); gl.Vertex(, , -);
- gl.TexCoord(, ); gl.Vertex(, -, -);
- gl.TexCoord(, ); gl.Vertex(, -, );
- //后
- gl.TexCoord(, ); gl.Vertex(, , -);
- gl.TexCoord(, ); gl.Vertex(-, , -);
- gl.TexCoord(, ); gl.Vertex(-, -, -);
- gl.TexCoord(, ); gl.Vertex(, -, -);
- //顶
- gl.TexCoord(, ); gl.Vertex(, -, );
- gl.TexCoord(, ); gl.Vertex(, -, -);
- gl.TexCoord(, ); gl.Vertex(-, -, -);
- gl.TexCoord(, ); gl.Vertex(-, -, );
- }
- gl.End();
- gl.PopMatrix();
- drawGrid(gl);
- drawSphere(gl);
- }
- private void openGLControl_OpenGLInitialized(object sender, EventArgs e)
- {
- OpenGL gl = openGLControl.OpenGL;
- texture.Create(gl, "image.bmp");
- gl.Enable(OpenGL.GL_TEXTURE_2D);
- fLightPosition = new float[] { lx, ly, lz, 1f };
- gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, fLightAmbient);//环境光源
- gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_DIFFUSE, fLightDiffuse);//漫射光源
- gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, fLightPosition);//光源位置
- gl.Enable(OpenGL.GL_LIGHTING);//开启光照
- gl.Enable(OpenGL.GL_LIGHT0);
- gl.Enable(OpenGL.GL_NORMALIZE);
- gl.ClearColor(, , , );
- m_Camera.setCamera(0.0f, 1.5f, 6.0f, 0.0f, 1.5f, 0.0f, 0.0f, 1.0f, 0.0f);
- m_Camera.setSpeed(1f);
- }
- private void openGLControl_Resized(object sender, EventArgs e)
- {
- OpenGL gl = openGLControl.OpenGL;
- gl.MatrixMode(OpenGL.GL_PROJECTION);
- gl.LoadIdentity();
- gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);
- gl.LookAt(-, , , , , , , , );
- gl.MatrixMode(OpenGL.GL_MODELVIEW);
- }
- private void openGLControl_KeyDown(object sender, KeyEventArgs e)
- {
- switch (e.KeyCode)
- {
- case Keys.W: //上
- m_Camera.yawCamera(m_Camera.getSpeed());
- break;
- case Keys.S: //下
- m_Camera.yawCamera(-m_Camera.getSpeed());
- break;
- case Keys.A: //左
- m_Camera.moveCamera(m_Camera.getSpeed());
- break;
- case Keys.D: //右
- m_Camera.moveCamera(-m_Camera.getSpeed());
- break;
- default:
- break;
- }
- }
- }
- class Camera
- {
- private Vector3 m_Position; //位置
- private Vector3 m_View; //朝向
- private Vector3 m_UpVector; //向上向量
- private float m_Speed; //速度
- public Camera()
- {
- Vector3 zero =new Vector3(0f, 0f, 0f);
- Vector3 view =new Vector3(0f, 1f, 0.5f);
- Vector3 up =new Vector3(0f, 0f, 1f);
- m_Position = zero;
- m_View = view;
- m_UpVector = up;
- m_Speed = 0.1f;
- }
- public void setSpeed(float speed)
- {
- m_Speed = speed;
- }
- public float getSpeed()
- {
- return m_Speed;
- }
- public Vector3 getPosition()
- {
- return m_Position;
- }
- public Vector3 getView()
- {
- return m_View;
- }
- public Vector3 getUpVector()
- {
- return m_UpVector;
- }
- //设置摄像机的位置,朝向和向上向量
- public void setCamera(float positionX,float positionY,
- float positionZ, float viewX,float viewY,
- float viewZ,float upVectorX,float upVectorY,
- float upVectorZ)
- {
- //构造向量
- Vector3 Position =new Vector3(positionX, positionY, positionZ);
- Vector3 View = new Vector3(viewX, viewY, viewZ);
- Vector3 UpVector = new Vector3(upVectorX, upVectorY, upVectorZ);
- //设置摄像机
- m_Position = Position;
- m_View = View;
- m_UpVector = UpVector;
- }
- //旋转摄像机方向
- void rotateView(float angle, float x, float y, float z)
- {
- Vector3 newView=new Vector3();
- //计算方向向量
- Vector3 view = m_View - m_Position;
- //计算 sin 和cos值
- float cosTheta =(float) Math.Cos(angle);
- float sinTheta = (float)Math.Sin(angle);
- //计算旋转向量的x值
- newView.x = (cosTheta + ( - cosTheta) * x * x) * view.x;
- newView.x += (( - cosTheta) * x * y - z * sinTheta) * view.y;
- newView.x += (( - cosTheta) * x * z + y * sinTheta) * view.z;
- //计算旋转向量的y值
- newView.y = (( - cosTheta) * x * y + z * sinTheta) * view.x;
- newView.y += (cosTheta + ( - cosTheta) * y * y) * view.y;
- newView.y += (( - cosTheta) * y * z - x * sinTheta) * view.z;
- //计算旋转向量的y值
- newView.z = (( - cosTheta) * x * z - y * sinTheta) * view.x;
- newView.z += (( - cosTheta) * y * z + x * sinTheta) * view.y;
- newView.z += (cosTheta + ( - cosTheta) * z * z) * view.z;
- //更新摄像机的方向
- m_View = m_Position + newView;
- }
- [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
- public static extern int GetSystemMetrics(int which);
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- public static extern bool GetCursorPos(out Point pt);
- [DllImport("user32.dll", EntryPoint = "ShowCursor", CharSet = CharSet.Auto)]
- public extern static void ShowCursor(int status);
- [DllImport("user32.dll", EntryPoint = "SetCursorPos")]
- private static extern int SetCursorPos(int x, int y);
- float lastRotX = 0.0f; // 用于保存旋转角度
- float currentRotX = 0.0f;
- public void setViewByMouse()
- {
- const int SM_CXSCREEN = ;
- const int SM_CYSCREEN = ;
- Point mousePos; /**< 保存当前鼠标位置 */
- int middleX = GetSystemMetrics(SM_CXSCREEN) >> ; /**< 得到屏幕宽度的一半 */
- int middleY = GetSystemMetrics(SM_CYSCREEN) >> ; /**< 得到屏幕高度的一半 */
- float angleY = 0.0f; /**< 摄像机左右旋转角度 */
- float angleZ = 0.0f; /**< 摄像机上下旋转角度 */
- // 得到当前鼠标位置
- GetCursorPos(out mousePos);
- ShowCursor();
- // 如果鼠标没有移动,则不用更新
- if ((mousePos.X == middleX) && (mousePos.Y == middleY))
- return;
- // 设置鼠标位置在屏幕中心
- SetCursorPos(middleX, middleY);
- // 得到鼠标移动方向
- angleY = (float)((middleX - mousePos.X)) / 1000.0f;
- angleZ = (float)((middleY - mousePos.Y)) / 1000.0f;
- lastRotX = currentRotX;
- // 跟踪摄像机上下旋转角度
- currentRotX += angleZ;
- // 如果上下旋转弧度大于1.0,我们截取到1.0并旋转
- if (currentRotX > 1.0f)
- {
- currentRotX = 1.0f;
- // 根据保存的角度旋转方向
- if (lastRotX != 1.0f)
- {
- // 通过叉积找到与旋转方向垂直的向量
- Vector3 vAxis = m_View - m_Position;
- vAxis = vAxis.crossProduct(m_UpVector);
- vAxis = vAxis.normalize();
- ///旋转
- rotateView(1.0f - lastRotX, vAxis.x, vAxis.y, vAxis.z);
- }
- }
- // 如果旋转弧度小于-1.0,则也截取到-1.0并旋转
- else if (currentRotX < -1.0f)
- {
- currentRotX = -1.0f;
- if (lastRotX != -1.0f)
- {
- // 通过叉积找到与旋转方向垂直的向量
- Vector3 vAxis = m_View - m_Position;
- vAxis = vAxis.crossProduct(m_UpVector);
- vAxis = vAxis.normalize();
- rotateView(-1.0f - lastRotX, vAxis.x, vAxis.y, vAxis.z);
- }
- }
- // 否则就旋转angleZ度
- else
- {
- // 找到与旋转方向垂直向量
- Vector3 vAxis = m_View - m_Position;
- vAxis = vAxis.crossProduct(m_UpVector);
- vAxis = vAxis.normalize();
- rotateView(angleZ, vAxis.x, vAxis.y, vAxis.z);
- }
- // 总是左右旋转摄像机
- rotateView(angleY, , , );
- }
- public void yawCamera(float speed)
- {
- Vector3 yaw = new Vector3();
- Vector3 cross = m_View - m_Position;
- cross = cross.crossProduct(m_UpVector);
- //归一化向量
- yaw = cross.normalize();
- m_Position.x += yaw.x * speed;
- m_Position.z += yaw.z * speed;
- m_View.x += yaw.x * speed;
- m_View.z += yaw.z * speed;
- }
- public void moveCamera(float speed)
- {
- //计算方向向量
- Vector3 vector = m_View - m_Position;
- vector = vector.normalize(); //单位化
- //更新摄像机
- m_Position.x += vector.x * speed; //根据速度更新位置
- m_Position.z += vector.z * speed;
- m_Position.y += vector.y * speed;
- m_View.x += vector.x * speed; //根据速度更新方向
- m_View.y += vector.y * speed;
- m_View.z += vector.z * speed;
- }
- //设置视点
- public void setLook(OpenGL gl)
- {
- gl.LookAt(m_Position.x, m_Position.y, m_Position.z,
- m_View.x, m_View.y, m_View.z,
- m_UpVector.x, m_UpVector.y, m_UpVector.z);
- }
- }
- //向量运算类
- class Vector3
- {
- public float x, y, z;
- public Vector3()
- {
- x = ; y = ; z = ;
- }
- public Vector3(float x, float y, float z)
- {
- this.x = x;
- this.y = y;
- this.z = z;
- }
- public Vector3(Vector3 vec)
- {
- this.x = vec.x;
- this.y = vec.y;
- this.z = vec.z;
- }
- public float length()
- {
- return (float)(x * x + y * y + z * z);
- }
- public Vector3 normalize()
- {
- float len = length();
- if (len == ) len = ;
- x = x / len;
- y = y / len;
- z = z / len;
- return this;
- }
- //点积
- public float dotProduct(Vector3 vec)
- {
- return 0f;
- }
- public Vector3 crossProduct(Vector3 vec)
- {
- Vector3 v = new Vector3();
- v.x = y * vec.z - z * vec.y;
- v.y = z * vec.x - x * vec.z;
- v.z = x * vec.y - y * vec.x;
- return v;
- }
- public static Vector3 operator +(Vector3 v1,Vector3 v2)
- {
- var res = new Vector3();
- res.x=v1.x+v2.x;
- res.y=v1.y+v2.y;
- res.z=v1.z+v2.z;
- return res;
- }
- public static Vector3 operator -(Vector3 v1,Vector3 v2)
- {
- var res = new Vector3();
- res.x=v1.x-v2.x;
- res.y=v1.y-v2.y;
- res.z=v1.z-v2.z;
- return res;
- }
- public static Vector3 operator *(Vector3 v1, Vector3 v2)
- {
- var res = new Vector3();
- res.x = v1.x * v2.x;
- res.y = v1.y * v2.y;
- res.z = v1.z * v2.z;
- return res;
- }
- public static Vector3 operator /(Vector3 v1, Vector3 v2)
- {
- var res = new Vector3();
- res.x = v1.x / v2.x;
- res.y = v1.y / v2.y;
- res.z = v1.z / v2.z;
- return res;
- }
- public static Vector3 operator -(Vector3 vec)
- {
- vec.x=-*vec.x;
- vec.y=-*vec.y;
- vec.z=-*vec.z;
- return vec;
- }
- }
- }
效果如下图:
移动鼠标可以旋转摄像机,按键盘的WASD四个键可以XY方向移动摄像机。
本例子改编自徐明亮《OpenGL游戏编程》一书中“摄像机漫游” 一章节。
通过这个例子,发现了一个让笔者不解的问题。为什么我办公电脑那种垃圾配置(双核2G,集成显卡)跑这个例子比较快,但我家里的电脑(四核,显卡是geForce GTX 750Ti) 运行起来却蛮慢?
貌似根本就没有发挥强大显卡的性能嘛!
还有,VC6写的实现同样效果的程序要跑得快些哦!这又是昨回事? 这摆明让人羡慕嫉妒恨嘛!
补充一点:这个程序请按Alt+F4退出.
原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/
SharpGL学习笔记(十九) 摄像机漫游的更多相关文章
- python3.4学习笔记(十九) 同一台机器同时安装 python2.7 和 python3.4的解决方法
python3.4学习笔记(十九) 同一台机器同时安装 python2.7 和 python3.4的解决方法 同一台机器同时安装 python2.7 和 python3.4不会冲突.安装在不同目录,然 ...
- (C/C++学习笔记) 十九. 模板
十九. 模板 ● 模板的基本概念 模板(template) 函数模板:可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计. 语法: template <<模 ...
- SharpGL学习笔记(十八) 解析3ds模型并显示
笔者设想的3D仿真中的元件,是不可能都是“画”出来的.这样就玩复杂了,应该把任务分包出去,让善于制作模型的软件来制作三维模型,我们只需要解析并且显示它即可. 3dsmax制作三维模型的方便,快捷,专业 ...
- SharpGL学习笔记(十六) 多重纹理映射
多重纹理就把多张贴图隔和在一起.比如下面示例中,一个表现砖墙的纹理,配合一个表现聚光灯效果的灰度图,就形成了砖墙被一个聚光灯照亮的效果,这便是所谓的光照贴图技术. 多重纹理只在OpenGL扩展库中才提 ...
- SharpGL学习笔记(十五) 纹理映射
纹理映射非常实用,在游戏场景中已经无所不在了. 一个较少的多边形构成的模形,配合好的纹理贴图进行映射,可以得到逼真的效果.游戏中的天空,地面,墙面,和植物都是纹理贴图进行映射的. 例如最终幻想8的男女 ...
- SharpGL学习笔记(十四) 材质:十二个材质球
材质颜色 OpenGL用材料对光的红.绿.蓝三原色的反射率来近似定义材料的颜色.象光源一样,材料颜色也分成环境.漫反射和镜面反射成分,它们决定了材料对环境光.漫反射光和镜面反射光的反射程度.在进行光照 ...
- SharpGL学习笔记(十二) 光源例子:解决光源场景中的常见问题
笔者学到光源这一节,遇到的问题就比较多了,收集了一些如下所述: (1) 导入的3ds模型,如果没有材质光照效果很奇怪.如下图 (2) 导入的3ds模型,有材质,灯光效果发暗,材质偏色,效果也很奇怪. ...
- Java基础学习笔记十九 IO
File IO概述 回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下次再想使用这些数据,可是已经没有了.那怎么办呢?能不能把运算完的数据都保存下来,下次程序启动的时候,再 ...
- Java基础学习笔记十九 File
IO概述 回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下次再想使用这些数据,可是已经没有了.那怎么办呢?能不能把运算完的数据都保存下来,下次程序启动的时候,再把这些数据 ...
随机推荐
- ART:Android 摆脱卡顿的希望?
与 iOS 相比,Android 的用户体验有个相对糟糕的开始.在很长的时间里,界面一直丑小鸭,卡顿也是挥不去的痛.不过,在 Google 的全力推动,以及硬件厂商的响应下,Android 还是跨越各 ...
- 创建 iPhone/iOS8 弹出菜单(窗口)
基本步骤 添加视图:主视图与弹出视图 关联视图 配置弹出视图 编码实现:弹出菜单样式及控制器委托 override func prepareForSegue(segue: UIStoryboardSe ...
- SGU 422 Fast Typing(概率DP)
题目大意 某人在打字机上打一个字符串,给出了他打每个字符出错的概率 q[i]. 打一个字符需要单位1的时间,删除一个字符也需要单位1的时间.在任意时刻,他可以花 t 的时间检查整个打出来的字符串,并且 ...
- css压缩(一)
基于require.js的压缩,至于require.js,网上有比较权威的解说 RequireJS进阶(一) RequireJS进阶(二) RequireJS进阶(三) 目前我所做的项目是把各个模块下 ...
- JAVA多线程编程之生产者消费者模式
Java中有一个BlockingQueue可以用来充当堵塞队列,下面是一个桌面搜索的设计 package net.jcip.examples; import java.io.File; import ...
- 另类angularjs应用
回顾 上一篇文章主要讲解了创建兼容任意浏览器(主要是ie的一些奇葩问题)的angularjs web应用,但是项目开发中其实更重要的还是在功能的模块化.代码自动压缩上面,这样项目在后期维护或者功能的重 ...
- 【转载】linux tail命令的使用方法详解
本文介绍Linux下tail命令的使用方法.linux tail命令用途是依照要求将指定的文件的最后部分输出到标准设备,通常是终端,通俗讲来,就是把某个档案文件的最后几行显示到终端上,假设该档案有更新 ...
- .NET框架面向对象分层的个人想理
简单.层次清晰不要过度优化,接口这玩意儿就是个双刃剑,玩好了解藕,玩不好自找麻烦,好的代码永远都是傻瓜都能看懂的. 总结成以下几条: 公用层 代码公用并且与第三方DLL和业务逻辑无关的 独立出来 逻辑 ...
- JS魔法堂:通过marquee标签实现信息滚动效果
一.前言 有限的空间展现无限的内容,这是滚动最常用到的地方.根据信息滚动效果我们可以有很多的实现方式,但HTML自带的 marquee标签 是其中一个较简单的实现方式.下面记录一下,供日后查阅. ...
- ASP.NET MVC 5 默认模板的JS和CSS 是怎么加载的?
当创建一个默认的mvc模板后,项目如下: 运行项目后,鼠标右键查看源码,在源码里看到头部和尾部都有js和css文件被引用,他们是怎么被添加进来的呢? 首先我们先看对应的view文件index.csht ...