CSharpGL(39)GLSL光照示例:鼠标拖动太阳(光源)观察平行光的漫反射和镜面反射效果
CSharpGL(39)GLSL光照示例:鼠标拖动太阳(光源)观察平行光的漫反射和镜面反射效果
开始
一图抵千言。首先来看鼠标拖动太阳(光源)的情形。

然后是鼠标拖拽旋转模型的情形。

然后我们移动摄像机来从不同的角度看看。

现在太阳(光源)跑到比较远的位置去了,我们再移动它试试看。

本文就介绍平行光下是如何实现漫反射和镜面反射的。
本文shader核心部分来自红宝书第八版。
光照
只需记住一点,用GLSL实现光照效果时,都是根据顶点的位置、法线方向、光源位置(方向)、摄像机位置等等这些数据,根据物理学的反射规则计算出来的。当然,为了兼顾效果和效率,可能会对物理规则做一些简化处理。
Vertex shader
先看vertex shader。除了传递顶点的位置、颜色外,这里新增了传递法线(normal)的代码。顶点的变换可以通过mvp矩阵完成,而法线的变换则有所不同(说来话长,这里不要深究),所以单独提供一个normalMatrix供法线使用。
#version core uniform mat4 mvpMatrix;
uniform mat3 normalMatrix;// normal matrix is transpose(inverse(model matrix)) in vec3 inPosition;
in vec3 inColor;
in vec3 inNormal; out vec3 passNormal;
out vec3 passColor; void main()
{
passNormal = normalize(normalMatrix * inNormal);
passColor = inColor;
gl_Position = mvpMatrix * vec4(inPosition, 1.0);
}
Fragment shader
这里是计算光照的地方。详情见注释。
#version core uniform vec3 ambientLight;// 环境光
uniform vec3 directionalLightColor;
uniform vec3 directionalLightDirection;
uniform vec3 halfVector;
uniform float shininess;
uniform float strength; in vec3 passNormal;
in vec3 passColor; out vec4 outColor; void main()
{
// 根据光源方向与法线方向的夹角计算此处的漫反射的强度
float diffuse = max(0.0, dot(passNormal, directionalLightDirection));
// 计算此处的镜面反射的强度
float specular = max(0.0, dot(passNormal, halfVector)); if (diffuse == 0.0) { specular = 0.0; }// 若光源没有照射到此处,自然也不应该有镜面反射效果
else { specular = pow(specular, shininess); }// 指数式的剧烈变化,就是产生镜面高光的原理 vec3 scatteredLight = ambientLight + directionalLightColor * diffuse;// 漫反射光+环境光
vec3 reflectedLight = directionalLightColor * specular * strength;// 镜面反射光 vec3 rgb = min(passColor * scatteredLight + reflectedLight, vec3(1.0));// 最后的颜色
outColor = vec4(rgb, 1.0);// 搞定
}
渲染器
shader做好了,下面写一个渲染器。关于渲染器的详细介绍可参看(CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL)由于制作光照效果需要模型自带法线值,而我手里的模型只有这个Teapot是有法线值的,又仅仅是个例子,就写死了用Teapot了。
class DirectonalLightRenderer : PickableRenderer
{
public vec3 AmbientLightColor { get; set; }
public vec3 DirectionalLightDirection { get; set; }
public vec3 DirectionalLightColor { get; set; }
//public vec3 HalfVector { get; set; }
public float Shininess { get; set; }
public float Strength { get; set; } public static DirectonalLightRenderer Create()
{
var model = new Teapot();
var shaderCodes = new ShaderCode[];
shaderCodes[] = new ShaderCode(File.ReadAllText(@"shaders\DirectionalLight.vert"), ShaderType.VertexShader);
shaderCodes[] = new ShaderCode(File.ReadAllText(@"shaders\DirectionalLight.frag"), ShaderType.FragmentShader);
var map = new AttributeMap();
map.Add("inPosition", Teapot.strPosition);
map.Add("inColor", Teapot.strColor);
map.Add("inNormal", Teapot.strNormal); var renderer = new DirectonalLightRenderer(model, shaderCodes, map, Teapot.strPosition);
renderer.ModelSize = model.Size;
return renderer;
} private DirectonalLightRenderer(IBufferable model, ShaderCode[] shaderCodes,
AttributeMap attributeMap, string positionNameInIBufferable,
params GLState[] switches)
: base(model, shaderCodes, attributeMap, positionNameInIBufferable, switches)
{
this.AmbientLightColor = new vec3(0.2f);
this.DirectionalLightDirection = new vec3();
this.DirectionalLightColor = new vec3();
//this.HalfVector = new vec3(1);
this.Shininess = 10.0f;
this.Strength = 1.0f;
} protected override void DoRender(RenderEventArgs arg)
{
this.SetUniform("ambientLight", this.AmbientLightColor);
this.SetUniform("directionalLightColor", this.DirectionalLightColor);
this.SetUniform("directionalLightDirection", this.DirectionalLightDirection.normalize());
this.SetUniform("halfVector", this.DirectionalLightDirection.normalize());
//this.SetUniform("halfVector", this.HalfVector.normalize());
this.SetUniform("shininess", this.Shininess);
this.SetUniform("strength", this.Strength); mat4 projection = arg.Camera.GetProjectionMatrix();
mat4 view = arg.Camera.GetViewMatrix();
mat4 model = this.GetModelMatrix().Value;
this.SetUniform("mvpMatrix", projection * view * model);
this.SetUniform("normalMatrix", glm.transpose(glm.inverse(model)).to_mat3()); base.DoRender(arg);
}
}
DirectonalLightRenderer
这样其实就可以看到效果了,还可以通过属性面板控制光源的参数。

但是手动输入数值很不爽啊,没有一点点随心所欲的顺畅。于是后续的折腾就开始了。让我来画一个真正的太阳,然后通过鼠标拖动太阳,实时更新光源的位置(方向),看到本文开始的效果。
太阳(光源)
画太阳
其实太阳模型早就做过了,本质上就是利用一个noise方法模拟太阳表面的活动。外围辐射效果什么的,我先拉倒吧。

拖动太阳
把太阳放在那里很容易,如何用鼠标移动呢?
原理在(CSharpGL(20)用unProject和Project实现鼠标拖拽图元)已经整理出来了。只不过当时是单独修改模型内部的顶点位置,而现在需要整体移动模型,即修改模型的(RendererBase.WorldPosition)属性。
我的思路如下:假设有一个点在原点position = new vec3(0,0,0),我们像之前一样计算它在平移之后的位置newPosition,这是模型本身的变化,然后只需分别通过RendererBase.GetModelMatrix()的变换,就变成了在World Space里的变化,这个差别就是模型的位移。代码如下。
void IMouseHandler.canvas_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDownFlag && ((e.Button & this.lastBindingMouseButtons) != MouseButtons.None))
{
Point location = new Point(e.X, this.canvas.ClientRectangle.Height - e.Y - );
Point differenceOnScreen = new Point(location.X - this._lastPosition.X, location.Y - this._lastPosition.Y);
mat4 model = this.renderer.GetModelMatrix().Value;
mat4 view = this.camera.GetViewMatrix();
mat4 projection = this.camera.GetProjectionMatrix();
vec4 viewport;
{
int[] result = OpenGL.GetViewport();
viewport = new vec4(result[], result[], result[], result[]);
}
var position = new vec3(0.0f);// imangine we have a point at (0, 0, 0).
vec3 windowPos = glm.project(position, view * model, projection, viewport);
var newWindowPos = new vec3(windowPos.x + differenceOnScreen.X, windowPos.y + differenceOnScreen.Y, windowPos.z);
vec3 newPosition = glm.unProject(newWindowPos, view * model, projection, viewport);
var worldPosition = new vec3(model * new vec4(position, 1.0f));
var newWorldPosition = new vec3(model * new vec4(newPosition, 1.0f));
this.renderer.WorldPosition += newWorldPosition - worldPosition; this._lastPosition = location;
}
}
这样说似乎也不能彻底解释清楚,因为还需要先理解OpenGL里坐标变换的问题,这个问题可以参看(CSharpGL(27)讲讲清楚OpenGL坐标变换)
总结
整完收工。
CSharpGL(39)GLSL光照示例:鼠标拖动太阳(光源)观察平行光的漫反射和镜面反射效果的更多相关文章
- CSharpGL(13)用GLSL实现点光源(point light)和平行光源(directional light)的漫反射(diffuse reflection)
CSharpGL(13)用GLSL实现点光源(point light)和平行光源(directional light)的漫反射(diffuse reflection) 2016-08-13 由于CSh ...
- jquery 鼠标拖动排序Li或Table
1.前端页面 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="拖动排序Li或Ta ...
- jquery-11 如何实现标签的鼠标拖动效果
jquery-11 如何实现标签的鼠标拖动效果 一.总结 一句话总结:核心原理:1.标签实现绝对定位,位置的话跟着鼠标走.2.点击标签的话,给标签绑定事件,停止按住鼠标的话,解除绑定的事件. 1.事件 ...
- jQuery实现鼠标拖动改变Div高度
最近项目中需要在DashBoard页面做一个事件通知栏,该通知栏固定位于页面底部,鼠标拖动该DIV实现自动改变高度扩展内容显示区域. 以下是一个设计原型,基于jQuery实现,只实现了拖动效果,没有做 ...
- 实现鼠标拖动canvas绘制的图片
不啰嗦上代码: <html> <head> <meta http-equiv="Content-Type" content="text/ht ...
- JavaScript 实现鼠标拖动元素
一.前言 最开始实现鼠标拖动元素的目的就是在一个页面上拖动很多小圆点,用于固定定位,然后在复制HTML,粘贴在页面的开发代码中,就是这么一个功能,实现了很多遍,都没有做好,不得已采用了jQuery.f ...
- Selenium2学习-027-WebUI自动化实战实例-025-JavaScript 在 Selenium 自动化中的应用实例之三(页面滚屏,模拟鼠标拖动滚动条)
日常的 Web UI 自动化测试过程中,get 或 navigate 到指定的页面后,若想截图的元素或者指定区域范围不在浏览器的显示区域内,则通过截屏则无法获取相应的信息,反而浪费了无畏的图片服务器资 ...
- listbox鼠标拖动数据和为button注册快捷键
将listbox1中的数据用鼠标拖动至listbox2,即有左至右. 分别对应控件注册如下事件DragEnter,MouseDown,DragDrop 代码如下: //P128 DataGridVie ...
- c# 利用 两个TREEVIEW控件完成TEENODE的鼠标拖动操作
功能说明: 我们有两个TREEVIEW控件——TREEVIEW1,TREEVIEW2.Treeview1内有三个NODE,Treeview2内有三个NODE.将Treeview1内的NODE拖动到Tr ...
随机推荐
- MVVM模式和在WPF中的实现(二)数据绑定
MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
- 几个比较”有意思“的JS脚本
1.获取内网和公网真实IP地址(引用地址) <!DOCTYPE html> <html> <head> <meta http-equiv="Cont ...
- shell简介
Shell作为命令语言,它交互式地解释和执行用户输入的命令:作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支. shell使用的熟练程度反映了用户对U ...
- MVC常遇见的几个场景代码分享
本次主要分享几个场景的处理代码,有更好处理方式多多交流,相互促进进步:代码由来主要是这几天使用前端Ace框架做后台管理系统,这Ace是H5框架里面的控件效果挺多的,做兼容也很好,有点遗憾是控件效果基本 ...
- H5坦克大战之【画出坦克】
今天是个特殊的日子,圣诞节,也是周末,在这里先祝大家圣诞快乐!喜庆的日子,我们可以稍微放松一下,扯一扯昨天雷霆对战凯尔特人的比赛,这场比赛大威少又双叒叕拿下三双,而且是一个45+11+11的超级三双, ...
- C# 实时折线图,波形图
此Demo是采用VS自带的Chart图表控件,制作实时动态显示的折线图,和波形图. 涉及到知识如下: Chart 控件,功能强大,可以绘制柱状图,折线图,波形图,饼状图,大大简化了对图的开发与定制. ...
- 敏捷转型历程 - Sprint3 回顾会
我: Tech Leader 团队:团队成员分布在两个城市,我所在的城市包括我有4个成员,另外一个城市包括SM有7个成员.另外由于我们的BA离职了,我暂代IT 的PO 职位.PM和我在一个城市,但他不 ...
- Linux学习笔记(一):常用命令
经过统计Linux中能够识别的命令超过3000种,当然常用的命令就远远没有这么多了,按照我的习惯,我把已经学过的Linux常用命令做了以下几个方面的分割: 1.文件处理命令 2.文件搜索命令 3.帮助 ...
- TCP/IP之TCP_NODELAY与TCP_CORK
TCP/IP之Nagle算法与40ms延迟提到了Nagle 算法.这样虽然提高了网络吞吐量,但是实时性却降低了,在一些交互性很强的应用程序来说是不允许的,使用TCP_NODELAY选项可以禁止Nagl ...
- 邻接矩阵的深度优先遍历(java版)
这是一个有向边带权的图 顶点数组:[v0, v1, v2, v3, v4] 边数组: v0 v1 v2 v3 v4 v0 6 v1 9 3 v2 2 5 v3 1 v4 package com.dat ...