How can I render the content of a CoreGraphics context into a MTLTexture with full color and transparency? (RGBA or BGRA)
I am planning to use that MTLTexture for a material in a cross platform (macos/iOS) SceneKit project.
This example uses grayscale only:
https://medium.com/#s1ddok/combine-the-power-of-coregraphics-and-metal-by-sharing-resource-memory-eabb4c1be615
In the image the black background should be transparent. The right side (RGB) looks like misaligned. The texture only shows on iOS, doesn't work on macOS.
Update (What I attempted):
// First, we will need to know a RAM page size from the system, for this reason we will use getpagesize() function. Now we have to calculate amount of aligned bytes per row and the total allocation size. To allocate aligned memory we will use posix_memalign(_,_,_) system function.
let width = Int(256)
let height = Int(256)
#if os(macOS)
let pixelRowAlignment = Int(1024)
let bytesPerRow = alignUp(size: width, align: pixelRowAlignment)
#else
let pixelRowAlignment =
sceneRenderer.device?.minimumTextureBufferAlignment(for: .rgba8Unorm)
let bytesPerRow = alignUp(size: width, align: pixelRowAlignment!) * 4
#endif
let pagesize = Int(getpagesize())
let allocationSize = alignUp(size: bytesPerRow * height, align: pagesize)
//Allocate the memory pointed by data:
var data: UnsafeMutableRawPointer? = nil
let result = posix_memalign(&data, pagesize, allocationSize)
if result != noErr {
fatalError("Error during memory allocation")
}
// Now we are ready to create a CGContext from this memory, you probably have done this many times, so this should be pretty straightforward:
//Gray (ok!)
let context = CGContext(data: data,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceGray(),
bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue)!
//RGB (Weird)
// let context = CGContext(data: data,
// width: width,
// height: height,
// bitsPerComponent: 8,
// bytesPerRow: bytesPerRow,
// space: CGColorSpaceCreateDeviceRGB(),
// bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0, y: -CGFloat(context.height))
context.setLineJoin(.round)
context.setLineWidth(10)
context.setLineCap(.round)
context.setLineJoin(.round)
context.setStrokeColor(SCNColor(rgb: 0xFFFF00).cgColor)
context.clear(CGRect(x: 0, y: 0, width: width, height: height))
context.setFillColor(SCNColor.clear.cgColor)
context.fill(CGRect(x: 0, y: 0, width: width, height: height))
context.beginPath()
context.move(to: CGPoint(x: 0, y: 0))
context.addLine(to: CGPoint(x: 150, y: 150))
context.strokePath()
// Using the exact same memory we can now create a no-copy MTLBuffer, making it responsible for deallocating the memory. However you can of course manage this on your own by passing nil to the last argument.
#if os(macOS)
let buffer = sceneRenderer.device?
.makeBuffer(bytesNoCopy: context.data!,
length: allocationSize,
options: .storageModeManaged,
deallocator: { pointer, length in free(data) })!
#else
let buffer = sceneRenderer.device?
.makeBuffer(bytesNoCopy: context.data!,
length: allocationSize,
options: .storageModeShared,
deallocator: { pointer, length in free(data) })!
#endif
// Now, the final step: we are creating a texture from this buffer. Storage mode of the texture must be the same with the buffer.
let textureDescriptor = MTLTextureDescriptor()
textureDescriptor.pixelFormat = .r8Unorm
textureDescriptor.width = context.width
textureDescriptor.height = context.height
textureDescriptor.storageMode = (buffer?.storageMode)!
// we are only going to read from this texture on GPU side
textureDescriptor.usage = .shaderRead
let texture : MTLTexture = (buffer?.makeTexture(descriptor: textureDescriptor,
offset: 0,
bytesPerRow: context.bytesPerRow))!
// You should take into account the memory synchronization that happens between CPU and GPU. You should not create a situation where this buffer can be simultaneously accessed by CoreGraphics and Metal, especially on macOS.
let plane = SCNPlane(width: 1.0 , height: 1.0)
let material = SCNMaterial()
material.lightingModel = SCNMaterial.LightingModel.constant
material.isDoubleSided = false
//Assign the previous MTLTexture to the maaterial
material.diffuse.contents = texture
material.blendMode = .alpha
plane.firstMaterial = material
let node = SCNNode(geometry: plane)
node.simdPosition = float3(0,0,0)
scene.rootNode.addChildNode(node)
Related
We can add plane geometry with this code and position in anywhere:
var geometry = new THREE.PlaneGeometry( 5, 20, 32 );
var material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} );
var plane = new THREE.Mesh( geometry, material );
scene.add( plane );
But how can we add plane when two diagonal positions are known. Is this possible?
The closest to this so far I have is to get center of two positions and it positions the plane correctly but does not size correctly:
var dir = pointB.clone().subtract(pointA);
var length = dir.length();
dir = dir.normalize().scale(length * 50);
center_position = pointA.clone().add(dir);
Direction is not important in my case, can be set by lookat().
Find the subtraction vector of those two, its absolute values are plane size.
Average vector of those two is plane's center.
Example (r120):
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://threejs.org/build/three.module.js";
import { OrbitControls } from "https://threejs.org/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 5, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper());
let vec1 = new THREE.Vector3(0, 2, 0);
let vec2 = new THREE.Vector3(4, 0, 0);
let size = new THREE.Vector3().subVectors(vec2, vec1);
let center = new THREE.Vector3().addVectors(vec1, vec2).multiplyScalar(0.5);
let planeWidth = Math.abs(size.x);
let planeHeight = Math.abs(size.y);
let planeGeom = new THREE.PlaneBufferGeometry(planeWidth, planeHeight, planeWidth, planeHeight);
let planeMat = new THREE.MeshBasicMaterial({color: "aqua", wireframe: true});
let plane = new THREE.Mesh(planeGeom, planeMat);
plane.position.copy(center);
scene.add(plane);
renderer.setAnimationLoop(()=>{
renderer.render(scene, camera);
});
</script>
In function setup the value of app.stage.height is 0. Why? There is a text for validator(zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz)
let Application = PIXI.Application,
Container = PIXI.Container,
loader = PIXI.loader,
resources = PIXI.loader.resources,
TextureCache = PIXI.utils.TextureCache,
Sprite = PIXI.Sprite,
Rectangle = PIXI.Rectangle;
let app = new Application({
width: 512,
height: 512,
antialias: true,
transparent: false,
resolution: 1
}
);
document.body.appendChild(app.view);
loader
.add("/img/treasureHunter.json")
.load(setup);
function setup() {
console.log(app.stage.height)
let id = PIXI.loader.resources["/img/treasureHunter.json"].textures;
let dungeon = new Sprite(id["dungeon.png"])
let treasure = new Sprite(id["treasure.png"]);
let explorer = new Sprite(id["explorer.png"]);
explorer.y = app.stage.height / 2 - explorer.height / 2;
console.log(app.stage.height)
explorer.x = 68;
app.stage.addChild(dungeon);
app.stage.addChild(explorer);
app.stage.addChild(treasure);
}
The stage is a Container, whose height is always calculated from its children. Until you don't add any children to the stage, its height will be zero.
Also, the stage's height is not equal to the canvas renderer.view height.
Hi guys I been leanring WebGL and trying to make a Tetris game out of it.
I have a couple of questions I'd like to ask:
For this game I wanted to first draw the grid as the background. However I noticed that after I drew the line, if I use gl.clear(gl.COLOR_BUFFER_BIT ); after, it will clear all the lines I drew before. I understand that gl.clear(gl.COLOR_BUFFER_BIT ); is about clearing the color buffer (and you probably will ask why I would want to do that. Just bear with me. ). Then I tried use gl.uniform4f( uColor, 0, 0, 0, 1); to send the color again to the fragment shader but it doesn't help.
The snippet is like this
window.onload = function(){
getGLContext();
initShaders();
drawLines( 0, 0, 400,400 );
gl.clear(gl.COLOR_BUFFER_BIT );
gl.uniform4f( uColor, 0, 0, 0, 1);
}
For the game I need the grid as background and I need requestAnimationFrame for the game loop and will render Tetrominos inside the loop. Therefore after drawing the line I used this draw() to draw other Tetrominos. However it removes the line I drew before. And when I comment out gl.clear(gl.COLOR_BUFFER_BIT ); inside draw(), it will remove the line along with background color.
function draw() {
gl.clear(gl.COLOR_BUFFER_BIT );
gl.drawArrays(gl.TRIANGLES, 0, index*6);
requestAnimationFrame(draw);
}
Here is the demo: https://codepen.io/zhenghaohe/pen/LqxpjB
Hope you could answer these two questions. Thanks!
This is generally the way WebGL works.
WebGL is just draws into a rectangle of pixels. There is no memory of primitives. There is no structure. There is just code and the resulting canvas which is an rectangle of pixels.
Most WebGL programs/pages clear the entire canvas every frame and redraw 100% of the things they want to show every time they draw. For tetris the general code might be something like
function render() {
clear the canvas
draw the grid
draw all the stable pieces
draw the current piece
draw the next piece
draw the effects
draw the score
}
Any knowledge of primitives or other structure is entirely up to your code.
If you want the grid lines to be static then either set a static background with CSS or use another canvas
Using a background:
const gl = document.querySelector('#c').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
#c {
background-image: url(https://i.imgur.com/ZCfccZh.png);
}
<canvas id="c"></canvas>
Using 2 canvases
// this is the context for the back canvas. It could also be webgl
// using a 2D context just to make the sample simpler
const ctx = document.querySelector('#back').getContext('2d');
drawGrid(ctx);
// this is the context for the front canvas
const gl = document.querySelector('#front').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
function drawGrid(ctx) {
// draw grid
ctx.translate(-10.5, -5.5);
ctx.beginPath();
for (let i = 0; i < 330; i += 20) {
ctx.moveTo(0, i);
ctx.lineTo(330, i);
ctx.moveTo(i, 0);
ctx.lineTo(i, 300);
}
ctx.strokeStyle = "blue";
ctx.stroke();
}
#container {
position: relative; /* required so we can position child elements */
}
#front {
position: absolute;
left: 0;
top: 0;
}
<div id="container">
<canvas id="back"></canvas>
<canvas id="front"></canvas>
</div>
As for why it clears even if you didn't call clear that's because that's whqt the spec says it's supposed to do
See: Why WebGL 'clear' draw to front buffer?
My research lab is working on a webpage that displays a long scrollable list of 3d models, about 50 or so. Our first idea was to do this with separate THREE.js WebGL contexts, but it seems this isn't advisable given the architecture of WebGL, and browsers seem to limit the number of contexts on a page to about 2^4.
I don't need these contexts to do anything very impressive: the individual geometries only have a few hundred triangles, with no textures, and only one at a time ever animates when using the mouse to rotate its camera.
Can I persuade WebGL to do what I want in a way that the browser won't complain about? I thought perhaps of having a single big geometry with all my individual models lined up next to each other, and separate canvases with viewports showing just one model each. But it seems that isn't supported. (Multiple views are allowed in the same context, but that's not very useful for me.)
Thanks for any ideas!
It's not clear why you think you need multiple webgl contexts. I'm guessing because you want a list like this
1. [img] description
description
2. [img] description
description
3. [img] description
description
Or something?
Some ideas
make one canvas big enough for the screen, set its CSS so it doesn't scroll with the rest of the page. Draw the models aligned with whatever other HTML you want that does scroll.
make an offscreen webgl canvas and use canvas2d elements to display.
For each model render the model and then call
someCanvas2DContextForElementN.drawImage(webGLcanvasElement, ...);
Given there are probably only ever a few canvases visible you only need to update those ones. In fact it's probably a good idea to recycle them. In other words, rather than make 12000 canvaes or a 12000 element list make just enough to fit on the screen and update them as you scroll.
Personally I'd probably pick #1 if my page design allowed it. Seems to work, see below.
It turned out to be really easy. I just took this sample that was drawing 100 objects and made it draw one object at a time.
After clearing the screen turn on the scissor test
gl.enable(gl.SCISSOR_TEST);
Then, for each object
// get the element that is a place holder for where we want to
// draw the object
var viewElement = obj.viewElement;
// get its position relative to the page's viewport
var rect = viewElement.getBoundingClientRect();
// check if it's offscreen. If so skip it
if (rect.bottom < 0 || rect.top > gl.canvas.clientHeight ||
rect.right < 0 || rect.left > gl.canvas.clientWidth) {
return; // it's off screen
}
// set the viewport
var width = rect.right - rect.left;
var height = rect.bottom - rect.top;
var left = rect.left;
var bottom = gl.canvas.clientHeight - rect.bottom - 1;
gl.viewport(left, bottom, width, height);
gl.scissor(left, bottom, width, height);
I'm not 100% sure if I need to add 1 the width and height or not. I suppose I should look that up.
In any case I compute a new projection matrix for every rendered object just to make the code generic. The placeholder divs could be different sizes.
Update:
the solution originally posted here used position: fixed on the canvas to keep it from scrolling. The new solution uses position: absolute and updates the transform just before rendering like this
gl.canvas.style.transform = `translateY(${window.scrollY}px)`;
With the previous solution the shapes getting re-drawn in their matching positions could lag behind the scrolling. With the new solution the canvas scrolls until we get time to update it. That means shapes might be missing for a few frames if we can't draw quick enough but it looks much better than the scrolling not matching.
The sample below is the updated solution.
"use strict";
// using twgl.js because I'm lazy
twgl.setAttributePrefix("a_");
var m4 = twgl.m4;
var gl = twgl.getWebGLContext(document.getElementById("c"));
// compiles shaders, links program, looks up locations
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
// calls gl.creatBuffer, gl.bindBuffer, gl.bufferData for each shape
// for positions, normals, texcoords
var shapes = [
twgl.primitives.createCubeBufferInfo(gl, 2),
twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12),
twgl.primitives.createPlaneBufferInfo(gl, 2, 2),
twgl.primitives.createTruncatedConeBufferInfo(gl, 1, 0, 2, 24, 1),
twgl.primitives.createCresentBufferInfo(gl, 1, 1, 0.5, 0.1, 24),
twgl.primitives.createCylinderBufferInfo(gl, 1, 2, 24, 2),
twgl.primitives.createDiscBufferInfo(gl, 1, 24),
twgl.primitives.createTorusBufferInfo(gl, 1, 0.4, 24, 12),
];
function rand(min, max) {
return min + Math.random() * (max - min);
}
// Shared values
var lightWorldPosition = [1, 8, -10];
var lightColor = [1, 1, 1, 1];
var camera = m4.identity();
var view = m4.identity();
var viewProjection = m4.identity();
var tex = twgl.createTexture(gl, {
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
255, 255, 255, 255,
192, 192, 192, 255,
192, 192, 192, 255,
255, 255, 255, 255,
],
});
var randColor = function() {
var color = [Math.random(), Math.random(), Math.random(), 1];
color[Math.random() * 3 | 0] = 1; // make at least 1 bright
return color;
};
var objects = [];
var numObjects = 100;
var list = document.getElementById("list");
var listItemTemplate = document.getElementById("list-item-template").text;
for (var ii = 0; ii < numObjects; ++ii) {
var listElement = document.createElement("div");
listElement.innerHTML = listItemTemplate;
listElement.className = "list-item";
var viewElement = listElement.querySelector(".view");
var uniforms = {
u_lightWorldPos: lightWorldPosition,
u_lightColor: lightColor,
u_diffuseMult: randColor(),
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
u_viewInverse: camera,
u_world: m4.identity(),
u_worldInverseTranspose: m4.identity(),
u_worldViewProjection: m4.identity(),
};
objects.push({
ySpeed: rand(0.1, 0.3),
zSpeed: rand(0.1, 0.3),
uniforms: uniforms,
viewElement: viewElement,
programInfo: programInfo,
bufferInfo: shapes[ii % shapes.length],
});
list.appendChild(listElement);
}
var showRenderingArea = false;
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.canvas.style.transform = `translateY(${window.scrollY}px)`;
gl.enable(gl.DEPTH_TEST);
gl.disable(gl.SCISSOR_TEST);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.SCISSOR_TEST);
if (showRenderingArea) {
gl.clearColor(0, 0, 1, 1);
}
var eye = [0, 0, -8];
var target = [0, 0, 0];
var up = [0, 1, 0];
m4.lookAt(eye, target, up, camera);
m4.inverse(camera, view);
objects.forEach(function(obj, ndx) {
var viewElement = obj.viewElement;
// get viewElement's position
var rect = viewElement.getBoundingClientRect();
if (rect.bottom < 0 || rect.top > gl.canvas.clientHeight ||
rect.right < 0 || rect.left > gl.canvas.clientWidth) {
return; // it's off screen
}
var width = rect.right - rect.left;
var height = rect.bottom - rect.top;
var left = rect.left;
var bottom = gl.canvas.clientHeight - rect.bottom - 1;
gl.viewport(left, bottom, width, height);
gl.scissor(left, bottom, width, height);
if (showRenderingArea) {
gl.clear(gl.COLOR_BUFFER_BIT);
}
var projection = m4.perspective(30 * Math.PI / 180, width / height, 0.5, 100);
m4.multiply(projection, view, viewProjection);
var uni = obj.uniforms;
var world = uni.u_world;
m4.identity(world);
m4.rotateY(world, time * obj.ySpeed, world);
m4.rotateZ(world, time * obj.zSpeed, world);
m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose);
m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection);
gl.useProgram(obj.programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, obj.programInfo, obj.bufferInfo);
// calls gl.bindTexture, gl.activeTexture, gl.uniformXXX
twgl.setUniforms(obj.programInfo, uni);
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, obj.bufferInfo);
});
}
if (true) { // animated
var renderContinuously = function(time) {
render(time);
requestAnimationFrame(renderContinuously);
}
requestAnimationFrame(renderContinuously);
} else {
var requestId;
var renderRequest = function(time) {
render(time);
requestId = undefined;
}
// If animated
var queueRender = function() {
if (!requestId) {
requestId = requestAnimationFrame(renderRequest);
}
}
window.addEventListener('resize', queueRender);
window.addEventListener('scroll', queueRender);
queueRender();
}
* {
box-sizing: border-box;
-moz-box-sizing: border-box;
}
body {
font-family: monospace;
margin: 0;
}
#c {
position: absolute;
top: 0;
width: 100vw;
height: 100vh;
}
#outer {
width: 100%;
z-index: 2;
position: absolute;
top: 0px;
}
#content {
margin: auto;
padding: 2em;
}
#b {
width: 100%;
text-align: center;
}
.list-item {
border: 1px solid black;
margin: 2em;
padding: 1em;
width: 200px;
display: inline-block;
}
.list-item .view {
width: 100px;
height: 100px;
float: left;
margin: 0 1em 1em 0;
}
.list-item .description {
padding-left: 2em;
}
#media only screen and (max-width : 500px) {
#content {
width: 100%;
}
.list-item {
margin: 0.5em;
}
.list-item .description {
padding-left: 0em;
}
}
<script src="//twgljs.org/dist/4.x/twgl-full.min.js"></script>
<body>
<canvas id="c"></canvas>
<div id="outer">
<div id="content">
<div id="b">item list</div>
<div id="list"></div>
</div>
</div>
</body>
<script id="list-item-template" type="notjs">
<div class="view"></div>
<div class="description">Lorem ipsum dolor sit amet, conse ctetur adipi scing elit. </div>
</script>
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (u_worldViewProjection * a_position);
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
gl_Position = v_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_diffuseMult;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
abs(l),//max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord) * u_diffuseMult;
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
</script>
If you have a phone you can see a similar one fullscreen here.
In the code below I try render a sphere to a texture (renderTargetView), that texture is supposed to be rendered to the back buffer on a quad. I'm experimenting with it to use with glow later. The problem right now is that the sphere is rendered to the back buffer and is visible, it doesn't even matter if I comment out the lower half of the code, same result. I'm using this background color D3DXCOLOR(0.4f, 0.2f, 0.0f) here, but that is not what's on the screen, it's the color set with back buffer devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f)); that is showing.
// Set and clear render target
devcon->OMSetRenderTargets(1, &renderTargetView, zbuffer);
devcon->ClearRenderTargetView(renderTargetView, D3DXCOLOR(0.4f, 0.2f, 0.0f, 1.0f));
devcon->PSSetShaderResources(0, 1, &pTextureSun);
devcon->UpdateSubresource(pCBuffer, 0, 0, &cBuffer, 0, 0);
devcon->Draw(sizeOfVertexVector, 0);
// set the shader objects
/*
devcon->VSSetShader(pVS_glow, 0, 0);
devcon->PSSetShader(pPS_glow, 0, 0);
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, zbuffer);
devcon->IASetVertexBuffers(0, 1, &pVBufferQuad, &stride, &offset);
devcon->UpdateSubresource(pCBuffer, 0, 0, &cBuffer, 0, 0);
devcon->PSSetShaderResources(0, 1, &shaderResourceView);
devcon->Draw(6, 0);*/
Here is where the back and depth buffers set up (I have been trying with different formats such as DXGI_FORMAT_D24_UNORM_S8_UINT for depth buffer and others but no luck):
// create a struct to hold information about the swap chain
DXGI_SWAP_CHAIN_DESC scd;
// clear out the struct for use
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
// fill the swap chain description struct
scd.BufferCount = 1; // one back buffer
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // use 32-bit color
scd.BufferDesc.Width = SCREEN_WIDTH; // set the back buffer width
scd.BufferDesc.Height = SCREEN_HEIGHT; // set the back buffer height
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // how swap chain is to be used
scd.OutputWindow = hWnd; // the window to be used
scd.SampleDesc.Count = 4; // how many multisamples
scd.Windowed = TRUE; // windowed/full-screen mode
scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // allow full-screen switching
// create a device, device context and swap chain using the information in the scd struct
D3D11CreateDeviceAndSwapChain(NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
NULL,
NULL,
NULL,
D3D11_SDK_VERSION,
&scd,
&swapchain,
&dev,
NULL,
&devcon);
// create the depth buffer texture
D3D11_TEXTURE2D_DESC texd;
ZeroMemory(&texd, sizeof(texd));
texd.Width = SCREEN_WIDTH;
texd.Height = SCREEN_HEIGHT;
texd.ArraySize = 1;
texd.MipLevels = 1;
texd.SampleDesc.Count = 4;
texd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
texd.BindFlags = D3D11_BIND_DEPTH_STENCIL;
ID3D11Texture2D *pDepthBuffer;
dev->CreateTexture2D(&texd, NULL, &pDepthBuffer);
// create the depth buffer
D3D11_DEPTH_STENCIL_VIEW_DESC dsvd;
ZeroMemory(&dsvd, sizeof(dsvd));
dsvd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
dev->CreateDepthStencilView(pDepthBuffer, &dsvd, &zbuffer);
pDepthBuffer->Release();
// get the address of the back buffer
ID3D11Texture2D *pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
// use the back buffer address to create the render target
dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
pBackBuffer->Release();
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, zbuffer);
// Set the viewport
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0; // set the left to 0
viewport.TopLeftY = 0; // set the top to 0
viewport.Width = SCREEN_WIDTH; // set the width to the window's width
viewport.Height = SCREEN_HEIGHT; // set the height to the window's height
viewport.MinDepth = 0; // the closest an object can be on the depth buffer is 0.0
viewport.MaxDepth = 1; // the farthest an object can be on the depth buffer is 1.0
devcon->RSSetViewports(1, &viewport);
And the render-to-texture:
D3D11_TEXTURE2D_DESC textureDesc;
D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;
// Initialize the render target texture description.
ZeroMemory(&textureDesc, sizeof(textureDesc));
// Setup the render target texture description.
textureDesc.Width = SCREEN_WIDTH;
textureDesc.Height = SCREEN_HEIGHT;
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
textureDesc.SampleDesc.Count = 1;
textureDesc.Usage = D3D11_USAGE_DEFAULT;
textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
textureDesc.CPUAccessFlags = 0;
textureDesc.MiscFlags = 0;
dev->CreateTexture2D(&textureDesc, NULL, &renderTargetTexture);
// Setup the description of the render target view.
renderTargetViewDesc.Format = textureDesc.Format;
renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
renderTargetViewDesc.Texture2D.MipSlice = 0;
dev->CreateRenderTargetView(renderTargetTexture, &renderTargetViewDesc, &renderTargetView);
// Setup the description of the shader resource view.
shaderResourceViewDesc.Format = textureDesc.Format;
shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
shaderResourceViewDesc.Texture2D.MipLevels = 1;
// Create the shader resource view.
dev->CreateShaderResourceView(renderTargetTexture, &shaderResourceViewDesc, &shaderResourceView);
Any ideas?
UPDATE
This code is executed before rendering to texture, if I comment out it..
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, zbuffer);
cBuffer.doBlur = 0;
devcon->PSSetShaderResources(0, 1, &pTextureEarth);
devcon->UpdateSubresource(pCBuffer, 0, 0, &cBuffer, 0, 0);
devcon->Draw(sizeOfVertexVector, 0);
...this now renders to texture, currently it just shows the background color of the texture, not the sphere.
// Set and clear render target
devcon->OMSetRenderTargets(1, &renderTargetView, zbuffer);
devcon->ClearRenderTargetView(renderTargetView, D3DXCOLOR(0.4f, 0.2f, 0.0f, 0.3f));
devcon->PSSetShaderResources(0, 1, &pTextureSun);
devcon->UpdateSubresource(pCBuffer, 0, 0, &cBuffer, 0, 0);
devcon->Draw(sizeOfVertexVector, 0);
// set the shader objects
devcon->IASetInputLayout(pLayoutGlow);
devcon->VSSetShader(pVS_glow, 0, 0);
devcon->PSSetShader(pPS_glow, 0, 0);
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, zbuffer);
devcon->IASetVertexBuffers(0, 1, &pVBufferQuad, &stride, &offset);
devcon->UpdateSubresource(pCBuffer, 0, 0, &cBuffer, 0, 0);
devcon->PSSetShaderResources(0, 1, &shaderResourceView);
devcon->Draw(6, 0);
The problem was that blend wasn't enabled.
I had problems with rendet to texture before because I bound a shader resource view to the same texture as what was the render target, which is forbidden. If you use Visual Studio 2012, you should try checking your code in debug mode. Aren't you making the same mistake as me?